You are viewing our Forum Archives. To view or take place in current topics click here.
Scripting Basics, Grammar & Syntax
Posted:
Scripting Basics, Grammar & SyntaxPosted:
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
This section will include several things you need to know before you start scripting.
All scripts are contained within simple text files that contain no formatting, so programs such as Word are not to be used. The file format used for scripts within the Call of Duty series is 'GSC' (.gsc). It is recommended you use a simple but effective editor for programming, such programs include Crimson Editor, Programmers Notepad and Editplus.
A few things you need to know before reading any further are a few common words used within scripting.
Variables: variables are data[code]for(i = 0; i < 10; i++)
{
wait 1;
thread function(); for(i = 0; i < 10; i++)
{
wait 1;
thread function();
}
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) }
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) [/code
storage locations which are assigned a name. For example...
integerthing = 1; //by the way, comments like this can be made using //, and are ignored by the game.
floatthing = 0.5;
stringthing = "Text and symbols 1234567890";
booleanthing = true; //another thing, almost every line must have a ; at the end or the script won't work. More on that later.
The variables are declared on the left, and assigned a value such as an integer (whole number), a float (a number containing a decimal), a string (text and symbols) or a boolean (true/false).
Entities: 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.
Functions: an action or procedure that can be called or threaded and can return a value. For example...
Arguements: These are key piece of information that are passed along to functions, and can be any type of object (entity, string, boolean etc). The function that was passed the arguments can then reference them.
For example, if a function is shown as
function(arg1, arg2)
The function is asking for two arguements to be sent to the function. An example of this in use...
Calling/Threading: these are used with functions. Functions can be threaded or called sequentially.
If a function is threaded, then that function is performed while the script continues, whereas if a function is called sequentially, the script waits until the function is completed before it continues. Examples...
function(); //the script will stop at this line and carry out function() before going down to...
thread function(); //this will start function() and carry on to execute 'ent moveZ'
ent moveZ(350, 5);
Self: If you call a function on an entity e.g 'unnamedent thread dostuff()', then within the function dostuff(), you can refer to unnamedent as 'self'.
For example...
Variables can be used in several ways, but in all cases they are used to store some data for the duration of the game. Variables come in different forms: integers, floats, entities, strings, arrays and booleans, there are also several different ways variables can be stored.
A simple variable is simply declared using
variable = data;
This variable can be used in the current function and any function that passes it as an argument, or is called on it (so it'd be used as 'self').
Variables can be global (which can be used in all threads without needing to be called) by using the
level.variable = data;
or they can be assigned to entities individually
entity.variable = data;
for things like player.health (integer, already built-in, but can be modified) and level.teamBased (boolean).
Maths & Operators
Maths is used throughout scripting to get many different values, be it the distance between two objects or to calculate an equation.
For example, a variable can be given data from an equation.
variable = 5 + 1;
Although the above example is pretty pointless, as it adds one constant to another to make a constant, and you can just calculate it yourself and use the answer i.e.
variable = 6;
But variables can be calculated using other variables, for example...
varAnswer = var1 + var2;
varAnswer will be equal to the value of var1 plus the value of var2.
There are several operators that can be used in maths...
+ :: Addition
- :: Subtraction
* :: Multiplication
/ :: Division
% :: Modulus
= :: Equals
There are also some more little-known operators such as...
++ :: Increment (+1)
-- :: Decrement (-1)
+= :: Incrementation (requires number)
-= :: Decrementation (requires number)
Example of these in use...
var++; //Set var to var + 1
var--; //Set var to var - 1
var += int; //Set var to var + int
var -= int; //Set var to var - int
IFs, Loops & Logic
This section will go into a little more detail of how to use 'if' statements and loops.
An 'if' statement is used to verify whether some data satisfies certain conditions, and then to execute code depending on the outcome.
To understand this section, you must first know the operators used to compare data:
== :: Equal To
!= :: Not Equal To
! :: Negation (Not equal to)
< :: Less than
> :: Greater than
<= :: Less or Equal to
>= :: Greater or Equal to
&& :: And
|| :: Or
Ok, now that we have some operators, lets get started on the statement.
An 'if' statement, requires a minimum of one arguement and usually one operator.
Here are some examples...notice the lack of semicolons at the end of the statements.
if(variable) //If variable is true
if(!variable) //If variable is not true
if(variable1 == variable2) //If variable1 is equal to variable2
if(variable1 != variable2) //If variable1 is not equal to variable2
if(integer1 < integer2) //If integer1 is less than integer2
if(integer1 > integer2) //If integer1 is greater than integer2
if(integer1 <= integer2) //If integer1 is less than or equal to integer2
if(integer1 >= integer2) //If integer1 is greater or equal to integer2
if((var1 == var2) && (var3 != var4)) //If var1 is equal to var2 AND var3 is not equal to var4
if((int1 > int2) || (var1 == var2)) //If int1 is greater than int2 OR var1 is equal to var2
To use an if statement to determine the movement of the script, you need to use the arguements to move the script in certain directions...
if(var1 == var2)
{
//if var1 equals var2, do this code
//everything inside these curly braces
}
//then do this code regardless of the result
If the statement above is true, the code inside the curly brackets is processed, if it is not true, the code inside the brackets are skipped.
Whether or not the statement is true, the code outside of the brackets is going to be processed. If this is not what you want, you need to use "Else" after the statement, for example...
if(var1 == var2)
{
//if above arguement is true
}
else if(!var1 && var3)
{
//if var1 is false but var3 is true
}
else
{
//if all other if statements were false
}
Thats the basics of if's, so let move on to loops.
Loops come in different forms...
While :: A while loop is a loop that keeps looping WHILE the arguement is true. For :: A for loop is a loop that loops a set amount of times
To use a while loop, a condition/some conditions must be stated: "while(conditions)" Often, this loop is used for infinite loops, which last forever unless specifically stopped. This is done using the arguement of 1 or true (1 is the integer of true)
while(1)
while(true)
A while loop can also be used as a normal loop that loops while the arguement is true, when the arguement becomes false the loop exits automatically (or rather, doesn't begin executing the commands in the loop again but just finishes the loop in progress).
int = 0;
while(int < 10)
{
wait 1;
int++;
}
The above code will loop while 'int' is less than 10. The loop waits 1 second, and then the loop increments 'int'. Once 'int' is not less than 10, the loop breaks.
The same applies for FOR loops.
A FOR loop requires 3 arguements.
for(declare; while; do)
Declare is the section which declares a variable for the loop to use.
While is what determines when the loop breaks
Do is what the loop should do after each loop.
A common FOR loop looks like this...
for(i = 0; i < int; i++)
The above code means: (i is 0, while i is smaller than int, add one to i).
Let's replace int with an actual number:
[code]for(i = 0; i < 10; i++)
{
wait 1;
thread function();
}
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) [/code]The FOR loop can also be used as an "infinite loop" using the "forever loop"
for(;
The above will simply keep repeating the code until manually terminated.
The problem with infinite loops is that they will give you an error if you don't allow them to pause at any point - so all infinite loops require a wait statement somewhere in the executed code.
To finish off, we need to know how to manually exit these loops. A common way to exit an infinite loop is to use an IF statement to determine when to 'break' (break is the keyword used to exit a loop) - here is an example of an IF statement breaking an infinite loop...
for(;
{
wait 1;
if(var1 == var2)
{
break;
}
}
The above sequence simply goes...
- wait 1 second - check if statement... - if var1 is equal to var2, break out of the loop - else continue the loop
Creating Functions
A custom function is a good way to use repeat sections of code more efficiently. For example, if you often use the same sequence of code, you can template them into a custom function. Imagine this is your code...
{
wait 1;
brush1 moveZ(320, 5);
brush1 waittill("movedone");
wait 1;
brush2 moveZ(540, 3);
brush2 waittill("movedone");
}
This can be simplified using a custom function, lets call this function "_moveEnt" (it is common practice to use an underscore as the first character of a function)
_moveEnt(ent, dist, time)
As the above shows, we are going to need 3 arguements, ent (entity), dist (distance) and time.
Now lets look at the full code with custom function in use...
{
_moveEnt(brush1, 320, 5);
_moveEnt(brush2, 540, 3);
}
_moveEnt(ent, dist, time)
{
wait 1;
ent moveZ(dist, time);
ent waittill("movedone");
}
As the above code shows, the custom function can simply be called using the required arguements, which are passed along and used in the new function.
Once the custom function has finished, the script returns to the original location from where it was called.
Functions can also return values to the original script, or even entities.
A simple function to calculate volume from the width, height and depth:
{
area = _areaEquation(2, 4, 6);
}
_areaEquation(x, y, z)
{
answer = x * y * z;
return answer;
}
Once the code calls the function '_areaEquation' the function works out the 'answer', 'answer' is the returned. This declares a new variable (area). The variable area, is the answer that is returned by the function.
The two lines:
answer = x * y * z;
return answer;
could be replace by
return x * y * z;
for the same effect.
Arrays
Arrays are objects that store several variables. These can be integers, strings, entities or even other arrays.
Arrays are extremely helpful when it comes to doing the same thing to several different objects, such as moving a few platforms by a certain amount in a map, or storing lots of related variables.
To create an array we simply type...
arrayName = [];
Now, to add an object/variable to this array, we can simply use:
array(array.size) = "a string variable";
Whenever you see array.size, the .size does not mean it's dimensions, but the amount of items in the array. So if an array contains 1 piece of data, the array.size is 1. To retrieve an items from an array, you can use array[0] for the first object/var stored in it, and then array[1] and so on for successive items.
Going back to the example above, we could retrieve the data by using array[0]. The first item in an array is listed as 0, the second as 1, and so on. This means that by using array(array.size) = "blah";, we can add new items to it because of the offset of 1.
A common use for an array is to call a function on all of the players in the game. This can be done by using an array with a FOR loop. To start with we must get our array of players, and instead of using the above method of defining an array and adding custom data, we can use a builtin function.
players = getEntArray("player", "classname");
So, our array has been defined as "players", or more accurately, "players[]".
Inside "players[]" is every player on the server, and now all we need to do is use the array to thread a function to each player. So, here we have a for loop to do such a thing:
for(i = 0; i < players.size; i++)
{
players[i] thread function();
}
That's our loop to go through every player and call function(); on them.
Remember, 'i' is a variable not a letter, so 'i' is substitued with the number of the loop. In the first loop, 'i' equals 0, so:
players[0] thread function();
is executed.
Arrays are a complicated part of scripting, but once you have them understood, they are one of the most useful things in the game.
Switch
Switch is often used instead of multiple if statements, as it is more efficient and doesn't require you to type as much.
How to use "Switch".
Switch can be quite hard to understand at first glance, but after using it, it will become easier.
Here is an example of how it is used. This is taken from the menu scripts, which handles the ingame menus, I have cut the code and also added the commented lines myself.
self waittill("menuresponse", menu, response);
switch(response)
{
case "changeweapon":
self closeMenu();
self closeInGameMenu();
if(self.pers["team"] == "allies")
self openMenu(game["menu_weapon_allies"]);
else if(self.pers["team"] == "axis")
self openMenu(game["menu_weapon_axis"]);
break;
case "changeteam":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
break;
case "muteplayer":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_muteplayer"]);
break;
case "callvote":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_callvote"]);
break;
default:
//add default action here
break;
}
The first part of the code is where the variable is defined. The game waits until a menu has been activated. The variables recieved are "menu" (the name of the menu activated) and "response" (what option was chosen from the menu). "Response" is the key variable for the 'Switch'.
After the variables have been defined, the Switch function is called. It then checks every "Case" (case is the term used for the possible outcomes of the variable) to find a match, if no match is found, the "Default" case is used. (if you do not have a default case, the script will crash - you can just add an empty one.)
If a match is found, then the function will do ALL the events from that point onwards, which is why you MUST add "break;" at the end of every case, if the break is not existent, then all other case functions will run also.
To use the above example, I shall input my own values to show how the example works...
When I open the menu ingame, I choose the option "changeteam". The code kicks in and the variable "response" becomes equal to "changeteam". The switch statement will now look at every case for a positive match.
Once the match is found, everything after the case will happen:
self closeMenu(); //current menu closes
self closeInGameMenu(); //close any other ingame menus
self openMenu(game["menu_team"]); //will open the team menu
break; //the rest of the switch statement is ended and the code continues
You can also make the script perform the same actions in multiple cases. For example:
self waittill("menuresponse", menu, response);
switch(response)
{
case "changeweapon":
case "changeteam":
case "muteplayer":
case "callvote":
function();
break;
default:
//add default action here
break;
}
This means that if the response variable is: 'changeweapon', 'changeteam', 'muteplayer' or 'callvote', function(); will be called.
Notify / Endon / Waittill
These 3 functions allow you to make a script wait for specific events and then trigger those events in different parts of the scripts.
This one ends the function it is inside when 'thread_restart' is triggered on 'level':
level endon ("thread_restart");
and this triggers the 'killed_player' notification on 'self' (which is a player in this case):
self notify("killed_player");
If you use 'player waittill("x")' or 'player endon("x")', then using a 'level notify("x")' will not trigger either of them - level is not the same entity as player, and all entities' triggers are independant.
Using the functions is easy. Firstly, decide which entity you want to wait for the trigger on. This is a player most of the time, but if you want a global trigger then use 'level'.
Then you must decide which trigger to use. You can choose either "endon" or "waittill" - they are both self explanatory, one will end the function it is running in when triggered, and the other will 'wait' until the specified trigger.
Next you decide on a unique name for the trigger. For example...
level endon("a_unique_call");
And to activate this trigger, you use notify on the same entity (level):
level notify("a_unique_call");
You can use as many waittill and endon functions as you want on an entity, in different functions. They will all be triggered by a notify.
Here is a quick example of it in use in multiple threads in the DM gametype...
spawnPlayer()
{
self notify("spawned");
/*... Code snipped ... this is another type of
comment that can span multiple lines. */
}
Callback_PlayerKilled(attacker, some other arguments)
{
self endon("spawned"); //this makes callback_playerkilled() terminate when "spawned" is triggered in spawnplayer().
}
Scripting hints
A few useful commands:
To print a large message on the screen:
iprintlnbold(stringMessage);or to a specific player: player iprintlnbold(stringMessage);
To print a small message on the bottom left of the screen:
iprintln(stringMessage); same as iprintlnbold() to a specific player
To check whether a variable or entity exists:
if(isdefined(timetowait))
wait (timetowait);
You can also use functions in if statements, by the returned value:
if(checksomething())
//code here
checksomething()
{
if(var == "hi there!") //remember to use == in if statements to check equality, but var1 = var2 to set equality outside them.
return true;
else
return false;
}
Ok, so I hope you understand a bit more about coding. I will be doing another tutorial on Menu Scripting, Menu Modding Basic's and Scripting Syntax and Grammar.
Credit to Drofder2004 for the references
What Is GSC?
GSC is the IWEngine's custom scripting language. It is very quickly compiled at runtime by the game executable during the level load stage. Conveniently, it is also reloaded and recompiled during a "map_restart", which only takes a second or two, allowing for very quick iteration. It also supports an in-game script debugger which functions relatively similarly to debugging in DevStudio, including such features as step by step debugging, watch window, and breakpoints.
Where Do GSC Files Reside?
GSC files are found in the \raw\maps folder and subfolders. In broad terms there are two types of gsc files, which are separates primarily by filenaming and organizational conventions.
The first are the utility modules, whose filenames are prefixed by an '_'. Some, like the _ambientpackage.gsc, are designed to be a nearly autonomous module which can run on its own following one or more lines of initialization. Others, such as the aptly named _utility.gsc, are more of a convenience, housing lots of commonly used functions that are implemented in script.
The second is the level module, which may be made up of one or more files prefixed by the name of the level they are associated with. levelname.gsc is expected by the game to exist and to contain a function called main(). Depending how the level script has been organized, there may be other level files referenced by levelname.gsc. For example, levelname_amb.gsc is the convention which denotes the ambient sound scripting for a given level. This allows the sound department to setup their ambient scripts without interfering with the level scripter, and vice versa. For example, the main function for a map named amb_pkg_test which tests the _ambientpackage might look like this:
#include maps\_utility;
main()
{
maps\scriptgen\amb_pkg_test_scriptgen::main();
level thread maps\amb_pkg_test_amb::main();
}
Notice the second line. The first token, "level", declares the thing which will be acted upon, the second, "thread", declares that a new thread is desired (threads will be discussed in more detail later on), and the third token acts as input, telling the script the name of the function to be used as the basis of the new thread. This third token is in fact made up of two pieces separated by the pair of colons. The first, "maps\amb_pkg_test_amb", tells which file to look in relative to the raw directory, and the second, "main()", identifies the name of the function to be found within that file. If we wished to reference a function in the same file, we would not need the pathing declaration, or the double colons.
Comments
A comment is one or more lines of a script file that are not compiled into the script at run time. They allow us to put notes or explanations about the script into the script file without affecting the script:
// this is a single line comment
/* this
is a multi line
comment
*/
x = 10; // this is a single line comment placed after a statement which will execute
The "//" declares that script should ignore any text (including it) on the remainder of the line it is found on. As we can see above, that means that such a comment can even be placed following a statement that is meant to be executed at runtime. The other comment type is the multi line comment, which is opened with "/*" and is closed with "*/". Anything in between those special characters will be ignored.
Variables, Arrays and Structs
Variables are not formally declared, instead, you simply name a new variable and assign a value to it:
var_int = 7; // declare an integer variable
var_float = 4.2; // declare a float variable
var_string = "test_string"; // declare a string variable
GSC is loosely typed, i.e. a variable receives a type (int, float, bool, string, etc.) when it is created based on the value or variable assigned to it. Functions, particularly builtin functions (functions defined in code instead of other script files), will sometimes perform validation on input parameters to ensure that the expected data type is being passed in.
There are some types which can be silently interchanged, for example:
// 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;
In the above example var_int is automatically treated as a string for the scope of the expression, simplifying the creation of compound strings.
Finally, there is a special "undefined" data type, which is returned by variables that do not yet exist or haven't been initialized. This case can be tested for through the "isDefined" function:
// the ! operator inverts the value of a boolean statement, ie. true becomes false, and false becomes true
if ( !isDefined( var_does_not_exist ) )
{
var_does_not_exist = false;
}
// see whether var_does_not_exist is a variable that exists
if ( isDefined( var_does_not_exist ) )
{
return var_does_not_exist;
}
The above example returns the value that varDoesNotExist has been set to, false, because it doesn't previously exist, causing it to be initialized, at which point it is found to be defined, allowing the return statement to be executed.
Arrays
Arrays are initialized by assigning "[]". Arrays are zero-indexed and automatically have a "size" member which can be accessed through the dot operator:
level._ambient_packages[package].elements = []; // declare this variable to be an array
index = level._ambient_packages[package].elements.size; //store the count of elements in the index variable
level._ambient_packages[package].elements[index] = spawnStruct(); // add a struct to the end of the array
level._ambient_packages[package].elements[index].alias = alias; // a new variable w/ the value alias
Here we have created an array in the variable "level._ambient_packages[package].elements", retrieved its size, 0, to determine which index to append our new data to, and finally used the bracket operator on it to assign our new data. If we were to repeat the last three lines, size would have automatically been updated to 1, reflecting the previously added element, and we would be adding elements at the 1th index.
Arrays can also be indexed by strings:
level._ambient_packages["outdoors"] = "outdoor_package"; //index into the array by string instead of number
level._ambient_packages["indoors"] = "indoor_package";
level._ambient_packages["both"] = "all";
packageArray = getArrayKeys( level._ambient_packages ); // get an array of the strings used to index
for ( i = 0; i < packageArray.size; i++ )
{
if ( level._ambient_packages[packageArray[i]] == "indoor_package" )
{
return i;
}
}
The above will return 1 as the value of i.
Structs
There is one other variable type of note, the struct, which allows for combing multiple pieces of related data together into one variable:
timer = spawnStruct(); // declare a struct
timer.start = 10; // add the variable start to it, with the value 10
timer.current = timer.start; // add the variable current to it and set its value to that of start
timer.end = 150; // add the variable end to it, with the value 150
In the example we have declared a timer struct using the spawnStruct() function. Once we have a struct, we can declare new variables as members of it using the dot operator, including variables that contain arrays, or even other structs. Not only is it convenient to group related data together like this, but it also simplifies passing this data around to other functions, or as well see later, using it as input to a thread. For example:
// declare a new function named "is_timer_finished"
// which takes 1 variable as input, which will be referred in the function as timer
is_timer_finished( timer )
{
if ( timer.current < timer.end ) // if current time is less than end time
{
return false;
}
else // otherwise end time must be greater than or equal to current time
{
return true;
}
}
Functions
Functions allow you to write script once, and then reference multiple times, possibly changing its results by using different input. The syntax for a function is very simple:
foo_bar( param_1, param_2, optional_param ) // decalre the function foo_bar, with 3 inputs
{
opt_var = 0; // intialize opt_var to 0
if ( isDefined( optional_param ) )
{
opt_var = optional_param; // only set opt_var to optional_param if optional_param was passed in
}
return param_1 * param_2 + opt_var; // multiply param_1 by param_2, then add opt_var to the result
}
foo_bar( 2, 6, 5 ) // results equal 17
foo_bar( 2, 6 ) // results equal 12
maps\this_file_name::foo_bar( 2, 6, 3 ) // results equal 15
Here we have created a new function, which tests to see if a value was passed into optional_param, and then performs arithmetic on those values which were passed in. We can also see that by simply defining the function once here, we have a number of different ways to reference it. If it were defined in a different file, we could reference it through the double colon syntax, as seen in the last line. Otherwise, we can simply call the function and supply the parameters as needed. Note that calling a function with not enough parameters will generally result in a script error about using an undefined variable.
Execution Flow
There are numerous methods for directing the flow of execution in script, which will be covered briefly here. Threads are complicated enough to warrant their own section, and thus will be discussed later on.
wait
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
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 the setting of c to the absolute value of the difference of 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 statment becomes true. An interesting note is that if a is not defined, then whter 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 "&&":
[code]// 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;
}[/code]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, whether b is defined will not be checked as the expression is already known to be false.
switch
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:
[code]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;
}[/code]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 statments, otherwise execution would continue on past the next case statement and execute the code there as well.
for
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 (to intialize variables as needed), the continuation check (an expression which if true will cause the script within the for loop's braces to execute again, otherwise the for loop will be left, 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):
[code]// 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
}[/code]This for loop will start by setting idx to 0, check if that value is less than the size of the weaponsList and if so run the code in the braces, then increase the value of idx by 1 and check against the size again, 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
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:
[code]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 [/code]
// results:
// 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.
Threads
Threads allow mutiple 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:
[code]// declare the function foo that is intended for use as a thread
foo()
{
for ( ; ; ) // this for loop continues forever
{
do_stuff();
wait 5;
}
} [/code]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
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:
[code]// 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;
}
}[/code]
// 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
notifies, waittills And endOns
While a thread can be made to execute until completion and then end, the most typical use of a thread is for processing that you want to occur repeatedly throughout the level or until some event occurs. In the examples we've seen so far, the threads use an infinite for loop and run until the level is complete. Now we'll see how we can use Waittills and EndOns to control when threads execute and stop. Each of these statements take a string as their parameter, are run on some variable (be it level, player, an entity, or a struct), and are triggered by a corresponding call to notify run on that same variable with the same string as its parameter. Let's see how we could use these statements to cause an elevator to rise 100 units at the player's command:
elevator notify( "rise" );
elevator waittill( "rise done" );
This rise() thread waits until something notifies the elevator it was attached to, causing it to wake up and start executing. Once it has completed moving the elevator, it sends a notify back through the elevator that it is done. The thread begins with an endOn("death") statement, which will cause the thread to cease if the elevator is ever destroyed.
All scripts are contained within simple text files that contain no formatting, so programs such as Word are not to be used. The file format used for scripts within the Call of Duty series is 'GSC' (.gsc). It is recommended you use a simple but effective editor for programming, such programs include Crimson Editor, Programmers Notepad and Editplus.
A few things you need to know before reading any further are a few common words used within scripting.
Variables: variables are data[code]for(i = 0; i < 10; i++)
{
wait 1;
thread function(); for(i = 0; i < 10; i++)
{
wait 1;
thread function();
}
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) }
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) [/code
storage locations which are assigned a name. For example...
integerthing = 1; //by the way, comments like this can be made using //, and are ignored by the game.
floatthing = 0.5;
stringthing = "Text and symbols 1234567890";
booleanthing = true; //another thing, almost every line must have a ; at the end or the script won't work. More on that later.
The variables are declared on the left, and assigned a value such as an integer (whole number), a float (a number containing a decimal), a string (text and symbols) or a boolean (true/false).
Entities: 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.
Functions: an action or procedure that can be called or threaded and can return a value. For example...
funcMove()The main function in the above code is funcMove(), and it will perform the actions inside the curly braces when it runs. There is also another function, moveY, which is a built-in function inside COD4, and moves an entity on the Y axis.
{
self moveY(320, 3);
}
Arguements: These are key piece of information that are passed along to functions, and can be any type of object (entity, string, boolean etc). The function that was passed the arguments can then reference them.
For example, if a function is shown as
function(arg1, arg2)
The function is asking for two arguements to be sent to the function. An example of this in use...
someotherstuff()As you can see, function() is called on both 'ent' and 'thing', and the two arguments '320' and '5' are passed to the new function as 'distance' and 'time'. Then moveZ is called on the entities.
{
ent function(320, 5);
thing function(320, 5);
}
function(distance, time)
{
ent moveZ(distance, time);
}
Calling/Threading: these are used with functions. Functions can be threaded or called sequentially.
If a function is threaded, then that function is performed while the script continues, whereas if a function is called sequentially, the script waits until the function is completed before it continues. Examples...
function(); //the script will stop at this line and carry out function() before going down to...
thread function(); //this will start function() and carry on to execute 'ent moveZ'
ent moveZ(350, 5);
Self: If you call a function on an entity e.g 'unnamedent thread dostuff()', then within the function dostuff(), you can refer to unnamedent as 'self'.
For example...
something()Using Variables
{
ent = getent("ent","targetname");
ent function();
}
function()
{
self moveZ(150, 5);
}
Variables can be used in several ways, but in all cases they are used to store some data for the duration of the game. Variables come in different forms: integers, floats, entities, strings, arrays and booleans, there are also several different ways variables can be stored.
A simple variable is simply declared using
variable = data;
This variable can be used in the current function and any function that passes it as an argument, or is called on it (so it'd be used as 'self').
Variables can be global (which can be used in all threads without needing to be called) by using the
level.variable = data;
or they can be assigned to entities individually
entity.variable = data;
for things like player.health (integer, already built-in, but can be modified) and level.teamBased (boolean).
Maths & Operators
Maths is used throughout scripting to get many different values, be it the distance between two objects or to calculate an equation.
For example, a variable can be given data from an equation.
variable = 5 + 1;
Although the above example is pretty pointless, as it adds one constant to another to make a constant, and you can just calculate it yourself and use the answer i.e.
variable = 6;
But variables can be calculated using other variables, for example...
varAnswer = var1 + var2;
varAnswer will be equal to the value of var1 plus the value of var2.
There are several operators that can be used in maths...
+ :: Addition
- :: Subtraction
* :: Multiplication
/ :: Division
% :: Modulus
= :: Equals
There are also some more little-known operators such as...
++ :: Increment (+1)
-- :: Decrement (-1)
+= :: Incrementation (requires number)
-= :: Decrementation (requires number)
Example of these in use...
var++; //Set var to var + 1
var--; //Set var to var - 1
var += int; //Set var to var + int
var -= int; //Set var to var - int
IFs, Loops & Logic
This section will go into a little more detail of how to use 'if' statements and loops.
An 'if' statement is used to verify whether some data satisfies certain conditions, and then to execute code depending on the outcome.
To understand this section, you must first know the operators used to compare data:
== :: Equal To
!= :: Not Equal To
! :: Negation (Not equal to)
< :: Less than
> :: Greater than
<= :: Less or Equal to
>= :: Greater or Equal to
&& :: And
|| :: Or
Ok, now that we have some operators, lets get started on the statement.
An 'if' statement, requires a minimum of one arguement and usually one operator.
Here are some examples...notice the lack of semicolons at the end of the statements.
if(variable) //If variable is true
if(!variable) //If variable is not true
if(variable1 == variable2) //If variable1 is equal to variable2
if(variable1 != variable2) //If variable1 is not equal to variable2
if(integer1 < integer2) //If integer1 is less than integer2
if(integer1 > integer2) //If integer1 is greater than integer2
if(integer1 <= integer2) //If integer1 is less than or equal to integer2
if(integer1 >= integer2) //If integer1 is greater or equal to integer2
if((var1 == var2) && (var3 != var4)) //If var1 is equal to var2 AND var3 is not equal to var4
if((int1 > int2) || (var1 == var2)) //If int1 is greater than int2 OR var1 is equal to var2
To use an if statement to determine the movement of the script, you need to use the arguements to move the script in certain directions...
if(var1 == var2)
{
//if var1 equals var2, do this code
//everything inside these curly braces
}
//then do this code regardless of the result
If the statement above is true, the code inside the curly brackets is processed, if it is not true, the code inside the brackets are skipped.
Whether or not the statement is true, the code outside of the brackets is going to be processed. If this is not what you want, you need to use "Else" after the statement, for example...
if(var1 == var2)You can also use an "else if" in the statement. This is used in a scenario where you want to check multiple comparisons.
{
//if it's true then do this
}
else
{
//if it's false then do this
}
if(var1 == var2)
{
//if above arguement is true
}
else if(!var1 && var3)
{
//if var1 is false but var3 is true
}
else
{
//if all other if statements were false
}
Thats the basics of if's, so let move on to loops.
Loops come in different forms...
While :: A while loop is a loop that keeps looping WHILE the arguement is true. For :: A for loop is a loop that loops a set amount of times
To use a while loop, a condition/some conditions must be stated: "while(conditions)" Often, this loop is used for infinite loops, which last forever unless specifically stopped. This is done using the arguement of 1 or true (1 is the integer of true)
while(1)
while(true)
A while loop can also be used as a normal loop that loops while the arguement is true, when the arguement becomes false the loop exits automatically (or rather, doesn't begin executing the commands in the loop again but just finishes the loop in progress).
int = 0;
while(int < 10)
{
wait 1;
int++;
}
The above code will loop while 'int' is less than 10. The loop waits 1 second, and then the loop increments 'int'. Once 'int' is not less than 10, the loop breaks.
The same applies for FOR loops.
A FOR loop requires 3 arguements.
for(declare; while; do)
Declare is the section which declares a variable for the loop to use.
While is what determines when the loop breaks
Do is what the loop should do after each loop.
A common FOR loop looks like this...
for(i = 0; i < int; i++)
The above code means: (i is 0, while i is smaller than int, add one to i).
Let's replace int with an actual number:
[code]for(i = 0; i < 10; i++)
{
wait 1;
thread function();
}
This is the sequence of events...
- 'i' = 0 - while i is less than 10, continue the loop - perform code (wait 1; and function()) - increment 'i' (i++) [/code]The FOR loop can also be used as an "infinite loop" using the "forever loop"
for(;
The above will simply keep repeating the code until manually terminated.
The problem with infinite loops is that they will give you an error if you don't allow them to pause at any point - so all infinite loops require a wait statement somewhere in the executed code.
To finish off, we need to know how to manually exit these loops. A common way to exit an infinite loop is to use an IF statement to determine when to 'break' (break is the keyword used to exit a loop) - here is an example of an IF statement breaking an infinite loop...
for(;
{
wait 1;
if(var1 == var2)
{
break;
}
}
The above sequence simply goes...
- wait 1 second - check if statement... - if var1 is equal to var2, break out of the loop - else continue the loop
Creating Functions
A custom function is a good way to use repeat sections of code more efficiently. For example, if you often use the same sequence of code, you can template them into a custom function. Imagine this is your code...
{
wait 1;
brush1 moveZ(320, 5);
brush1 waittill("movedone");
wait 1;
brush2 moveZ(540, 3);
brush2 waittill("movedone");
}
This can be simplified using a custom function, lets call this function "_moveEnt" (it is common practice to use an underscore as the first character of a function)
_moveEnt(ent, dist, time)
As the above shows, we are going to need 3 arguements, ent (entity), dist (distance) and time.
Now lets look at the full code with custom function in use...
{
_moveEnt(brush1, 320, 5);
_moveEnt(brush2, 540, 3);
}
_moveEnt(ent, dist, time)
{
wait 1;
ent moveZ(dist, time);
ent waittill("movedone");
}
As the above code shows, the custom function can simply be called using the required arguements, which are passed along and used in the new function.
Once the custom function has finished, the script returns to the original location from where it was called.
Functions can also return values to the original script, or even entities.
A simple function to calculate volume from the width, height and depth:
{
area = _areaEquation(2, 4, 6);
}
_areaEquation(x, y, z)
{
answer = x * y * z;
return answer;
}
Once the code calls the function '_areaEquation' the function works out the 'answer', 'answer' is the returned. This declares a new variable (area). The variable area, is the answer that is returned by the function.
The two lines:
answer = x * y * z;
return answer;
could be replace by
return x * y * z;
for the same effect.
Arrays
Arrays are objects that store several variables. These can be integers, strings, entities or even other arrays.
Arrays are extremely helpful when it comes to doing the same thing to several different objects, such as moving a few platforms by a certain amount in a map, or storing lots of related variables.
To create an array we simply type...
arrayName = [];
Now, to add an object/variable to this array, we can simply use:
array(array.size) = "a string variable";
Whenever you see array.size, the .size does not mean it's dimensions, but the amount of items in the array. So if an array contains 1 piece of data, the array.size is 1. To retrieve an items from an array, you can use array[0] for the first object/var stored in it, and then array[1] and so on for successive items.
Going back to the example above, we could retrieve the data by using array[0]. The first item in an array is listed as 0, the second as 1, and so on. This means that by using array(array.size) = "blah";, we can add new items to it because of the offset of 1.
A common use for an array is to call a function on all of the players in the game. This can be done by using an array with a FOR loop. To start with we must get our array of players, and instead of using the above method of defining an array and adding custom data, we can use a builtin function.
players = getEntArray("player", "classname");
So, our array has been defined as "players", or more accurately, "players[]".
Inside "players[]" is every player on the server, and now all we need to do is use the array to thread a function to each player. So, here we have a for loop to do such a thing:
for(i = 0; i < players.size; i++)
{
players[i] thread function();
}
That's our loop to go through every player and call function(); on them.
Remember, 'i' is a variable not a letter, so 'i' is substitued with the number of the loop. In the first loop, 'i' equals 0, so:
players[0] thread function();
is executed.
Arrays are a complicated part of scripting, but once you have them understood, they are one of the most useful things in the game.
Switch
Switch is often used instead of multiple if statements, as it is more efficient and doesn't require you to type as much.
How to use "Switch".
Switch can be quite hard to understand at first glance, but after using it, it will become easier.
Here is an example of how it is used. This is taken from the menu scripts, which handles the ingame menus, I have cut the code and also added the commented lines myself.
self waittill("menuresponse", menu, response);
switch(response)
{
case "changeweapon":
self closeMenu();
self closeInGameMenu();
if(self.pers["team"] == "allies")
self openMenu(game["menu_weapon_allies"]);
else if(self.pers["team"] == "axis")
self openMenu(game["menu_weapon_axis"]);
break;
case "changeteam":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
break;
case "muteplayer":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_muteplayer"]);
break;
case "callvote":
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_callvote"]);
break;
default:
//add default action here
break;
}
The first part of the code is where the variable is defined. The game waits until a menu has been activated. The variables recieved are "menu" (the name of the menu activated) and "response" (what option was chosen from the menu). "Response" is the key variable for the 'Switch'.
After the variables have been defined, the Switch function is called. It then checks every "Case" (case is the term used for the possible outcomes of the variable) to find a match, if no match is found, the "Default" case is used. (if you do not have a default case, the script will crash - you can just add an empty one.)
If a match is found, then the function will do ALL the events from that point onwards, which is why you MUST add "break;" at the end of every case, if the break is not existent, then all other case functions will run also.
To use the above example, I shall input my own values to show how the example works...
When I open the menu ingame, I choose the option "changeteam". The code kicks in and the variable "response" becomes equal to "changeteam". The switch statement will now look at every case for a positive match.
Once the match is found, everything after the case will happen:
self closeMenu(); //current menu closes
self closeInGameMenu(); //close any other ingame menus
self openMenu(game["menu_team"]); //will open the team menu
break; //the rest of the switch statement is ended and the code continues
You can also make the script perform the same actions in multiple cases. For example:
self waittill("menuresponse", menu, response);
switch(response)
{
case "changeweapon":
case "changeteam":
case "muteplayer":
case "callvote":
function();
break;
default:
//add default action here
break;
}
This means that if the response variable is: 'changeweapon', 'changeteam', 'muteplayer' or 'callvote', function(); will be called.
Notify / Endon / Waittill
These 3 functions allow you to make a script wait for specific events and then trigger those events in different parts of the scripts.
This one ends the function it is inside when 'thread_restart' is triggered on 'level':
level endon ("thread_restart");
and this triggers the 'killed_player' notification on 'self' (which is a player in this case):
self notify("killed_player");
If you use 'player waittill("x")' or 'player endon("x")', then using a 'level notify("x")' will not trigger either of them - level is not the same entity as player, and all entities' triggers are independant.
Using the functions is easy. Firstly, decide which entity you want to wait for the trigger on. This is a player most of the time, but if you want a global trigger then use 'level'.
Then you must decide which trigger to use. You can choose either "endon" or "waittill" - they are both self explanatory, one will end the function it is running in when triggered, and the other will 'wait' until the specified trigger.
Next you decide on a unique name for the trigger. For example...
level endon("a_unique_call");
And to activate this trigger, you use notify on the same entity (level):
level notify("a_unique_call");
You can use as many waittill and endon functions as you want on an entity, in different functions. They will all be triggered by a notify.
Here is a quick example of it in use in multiple threads in the DM gametype...
spawnPlayer()
{
self notify("spawned");
/*... Code snipped ... this is another type of
comment that can span multiple lines. */
}
Callback_PlayerKilled(attacker, some other arguments)
{
self endon("spawned"); //this makes callback_playerkilled() terminate when "spawned" is triggered in spawnplayer().
}
Scripting hints
A few useful commands:
To print a large message on the screen:
iprintlnbold(stringMessage);or to a specific player: player iprintlnbold(stringMessage);
To print a small message on the bottom left of the screen:
iprintln(stringMessage); same as iprintlnbold() to a specific player
To check whether a variable or entity exists:
if(isdefined(timetowait))
wait (timetowait);
You can also use functions in if statements, by the returned value:
if(checksomething())
//code here
checksomething()
{
if(var == "hi there!") //remember to use == in if statements to check equality, but var1 = var2 to set equality outside them.
return true;
else
return false;
}
Ok, so I hope you understand a bit more about coding. I will be doing another tutorial on Menu Scripting, Menu Modding Basic's and Scripting Syntax and Grammar.
Credit to Drofder2004 for the references
What Is GSC?
GSC is the IWEngine's custom scripting language. It is very quickly compiled at runtime by the game executable during the level load stage. Conveniently, it is also reloaded and recompiled during a "map_restart", which only takes a second or two, allowing for very quick iteration. It also supports an in-game script debugger which functions relatively similarly to debugging in DevStudio, including such features as step by step debugging, watch window, and breakpoints.
Where Do GSC Files Reside?
GSC files are found in the \raw\maps folder and subfolders. In broad terms there are two types of gsc files, which are separates primarily by filenaming and organizational conventions.
The first are the utility modules, whose filenames are prefixed by an '_'. Some, like the _ambientpackage.gsc, are designed to be a nearly autonomous module which can run on its own following one or more lines of initialization. Others, such as the aptly named _utility.gsc, are more of a convenience, housing lots of commonly used functions that are implemented in script.
The second is the level module, which may be made up of one or more files prefixed by the name of the level they are associated with. levelname.gsc is expected by the game to exist and to contain a function called main(). Depending how the level script has been organized, there may be other level files referenced by levelname.gsc. For example, levelname_amb.gsc is the convention which denotes the ambient sound scripting for a given level. This allows the sound department to setup their ambient scripts without interfering with the level scripter, and vice versa. For example, the main function for a map named amb_pkg_test which tests the _ambientpackage might look like this:
#include maps\_utility;
main()
{
maps\scriptgen\amb_pkg_test_scriptgen::main();
level thread maps\amb_pkg_test_amb::main();
}
Notice the second line. The first token, "level", declares the thing which will be acted upon, the second, "thread", declares that a new thread is desired (threads will be discussed in more detail later on), and the third token acts as input, telling the script the name of the function to be used as the basis of the new thread. This third token is in fact made up of two pieces separated by the pair of colons. The first, "maps\amb_pkg_test_amb", tells which file to look in relative to the raw directory, and the second, "main()", identifies the name of the function to be found within that file. If we wished to reference a function in the same file, we would not need the pathing declaration, or the double colons.
Comments
A comment is one or more lines of a script file that are not compiled into the script at run time. They allow us to put notes or explanations about the script into the script file without affecting the script:
// this is a single line comment
/* this
is a multi line
comment
*/
x = 10; // this is a single line comment placed after a statement which will execute
The "//" declares that script should ignore any text (including it) on the remainder of the line it is found on. As we can see above, that means that such a comment can even be placed following a statement that is meant to be executed at runtime. The other comment type is the multi line comment, which is opened with "/*" and is closed with "*/". Anything in between those special characters will be ignored.
Variables, Arrays and Structs
Variables are not formally declared, instead, you simply name a new variable and assign a value to it:
var_int = 7; // declare an integer variable
var_float = 4.2; // declare a float variable
var_string = "test_string"; // declare a string variable
GSC is loosely typed, i.e. a variable receives a type (int, float, bool, string, etc.) when it is created based on the value or variable assigned to it. Functions, particularly builtin functions (functions defined in code instead of other script files), will sometimes perform validation on input parameters to ensure that the expected data type is being passed in.
There are some types which can be silently interchanged, for example:
// 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;
In the above example var_int is automatically treated as a string for the scope of the expression, simplifying the creation of compound strings.
Finally, there is a special "undefined" data type, which is returned by variables that do not yet exist or haven't been initialized. This case can be tested for through the "isDefined" function:
// the ! operator inverts the value of a boolean statement, ie. true becomes false, and false becomes true
if ( !isDefined( var_does_not_exist ) )
{
var_does_not_exist = false;
}
// see whether var_does_not_exist is a variable that exists
if ( isDefined( var_does_not_exist ) )
{
return var_does_not_exist;
}
The above example returns the value that varDoesNotExist has been set to, false, because it doesn't previously exist, causing it to be initialized, at which point it is found to be defined, allowing the return statement to be executed.
Arrays
Arrays are initialized by assigning "[]". Arrays are zero-indexed and automatically have a "size" member which can be accessed through the dot operator:
level._ambient_packages[package].elements = []; // declare this variable to be an array
index = level._ambient_packages[package].elements.size; //store the count of elements in the index variable
level._ambient_packages[package].elements[index] = spawnStruct(); // add a struct to the end of the array
level._ambient_packages[package].elements[index].alias = alias; // a new variable w/ the value alias
Here we have created an array in the variable "level._ambient_packages[package].elements", retrieved its size, 0, to determine which index to append our new data to, and finally used the bracket operator on it to assign our new data. If we were to repeat the last three lines, size would have automatically been updated to 1, reflecting the previously added element, and we would be adding elements at the 1th index.
Arrays can also be indexed by strings:
level._ambient_packages["outdoors"] = "outdoor_package"; //index into the array by string instead of number
level._ambient_packages["indoors"] = "indoor_package";
level._ambient_packages["both"] = "all";
packageArray = getArrayKeys( level._ambient_packages ); // get an array of the strings used to index
for ( i = 0; i < packageArray.size; i++ )
{
if ( level._ambient_packages[packageArray[i]] == "indoor_package" )
{
return i;
}
}
The above will return 1 as the value of i.
Structs
There is one other variable type of note, the struct, which allows for combing multiple pieces of related data together into one variable:
timer = spawnStruct(); // declare a struct
timer.start = 10; // add the variable start to it, with the value 10
timer.current = timer.start; // add the variable current to it and set its value to that of start
timer.end = 150; // add the variable end to it, with the value 150
In the example we have declared a timer struct using the spawnStruct() function. Once we have a struct, we can declare new variables as members of it using the dot operator, including variables that contain arrays, or even other structs. Not only is it convenient to group related data together like this, but it also simplifies passing this data around to other functions, or as well see later, using it as input to a thread. For example:
// declare a new function named "is_timer_finished"
// which takes 1 variable as input, which will be referred in the function as timer
is_timer_finished( timer )
{
if ( timer.current < timer.end ) // if current time is less than end time
{
return false;
}
else // otherwise end time must be greater than or equal to current time
{
return true;
}
}
Functions
Functions allow you to write script once, and then reference multiple times, possibly changing its results by using different input. The syntax for a function is very simple:
foo_bar( param_1, param_2, optional_param ) // decalre the function foo_bar, with 3 inputs
{
opt_var = 0; // intialize opt_var to 0
if ( isDefined( optional_param ) )
{
opt_var = optional_param; // only set opt_var to optional_param if optional_param was passed in
}
return param_1 * param_2 + opt_var; // multiply param_1 by param_2, then add opt_var to the result
}
foo_bar( 2, 6, 5 ) // results equal 17
foo_bar( 2, 6 ) // results equal 12
maps\this_file_name::foo_bar( 2, 6, 3 ) // results equal 15
Here we have created a new function, which tests to see if a value was passed into optional_param, and then performs arithmetic on those values which were passed in. We can also see that by simply defining the function once here, we have a number of different ways to reference it. If it were defined in a different file, we could reference it through the double colon syntax, as seen in the last line. Otherwise, we can simply call the function and supply the parameters as needed. Note that calling a function with not enough parameters will generally result in a script error about using an undefined variable.
Execution Flow
There are numerous methods for directing the flow of execution in script, which will be covered briefly here. Threads are complicated enough to warrant their own section, and thus will be discussed later on.
wait
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
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 the setting of c to the absolute value of the difference of 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 statment becomes true. An interesting note is that if a is not defined, then whter 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 "&&":
[code]// 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;
}[/code]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, whether b is defined will not be checked as the expression is already known to be false.
switch
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:
[code]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;
}[/code]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 statments, otherwise execution would continue on past the next case statement and execute the code there as well.
for
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 (to intialize variables as needed), the continuation check (an expression which if true will cause the script within the for loop's braces to execute again, otherwise the for loop will be left, 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):
[code]// 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
}[/code]This for loop will start by setting idx to 0, check if that value is less than the size of the weaponsList and if so run the code in the braces, then increase the value of idx by 1 and check against the size again, 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
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:
[code]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 [/code]
// results:
// 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.
Threads
Threads allow mutiple 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:
[code]// declare the function foo that is intended for use as a thread
foo()
{
for ( ; ; ) // this for loop continues forever
{
do_stuff();
wait 5;
}
} [/code]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
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:
[code]// 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;
}
}[/code]
// 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
notifies, waittills And endOns
While a thread can be made to execute until completion and then end, the most typical use of a thread is for processing that you want to occur repeatedly throughout the level or until some event occurs. In the examples we've seen so far, the threads use an infinite for loop and run until the level is complete. Now we'll see how we can use Waittills and EndOns to control when threads execute and stop. Each of these statements take a string as their parameter, are run on some variable (be it level, player, an entity, or a struct), and are triggered by a corresponding call to notify run on that same variable with the same string as its parameter. Let's see how we could use these statements to cause an elevator to rise 100 units at the player's command:
rise()// later on the player presses the up button:
{
// declare that this thread should end if the notify "death" is sent on self
self endOn( "death" );
for ( ; ; )
{
self waittill( "rise" ); // wait until the notify "rise is sent on self
for ( count = 20; count; count-- )
{
self moveto(self.origin + (0, 0, 5), .05);
wait 0.05;
}
self notify( "rise done" ); // send the notify "rise done" on self
}
}
elevator thread rise();
elevator notify( "rise" );
elevator waittill( "rise done" );
This rise() thread waits until something notifies the elevator it was attached to, causing it to wake up and start executing. Once it has completed moving the elevator, it sends a notify back through the elevator that it is done. The thread begins with an endOn("death") statement, which will cause the thread to cease if the elevator is ever destroyed.
#2. Posted:
Status: Offline
Joined: Apr 04, 201014Year Member
Posts: 6,113
Reputation Power: 178
Status: Offline
Joined: Apr 04, 201014Year Member
Posts: 6,113
Reputation Power: 178
NO way did you write all this yourself :L
- 0useful
- 0not useful
#3. Posted:
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
-FanBoy- wrote NO way did you write all this yourself :LI DID ON A NOTE PAD THEN I COPY AND PASTE 8)
- 0useful
- 0not useful
#4. Posted:
Status: Offline
Joined: Oct 23, 201014Year Member
Posts: 2,323
Reputation Power: 133
Status: Offline
Joined: Oct 23, 201014Year Member
Posts: 2,323
Reputation Power: 133
nice copy and paste bro ;)
- 0useful
- 0not useful
#5. Posted:
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
XxVeNoMxMoDzxX wrote nice copy and paste bro ;)I DID ON A NOTE PAD THEN I COPY AND PASTE 8)
- 0useful
- 0not useful
#6. Posted:
Status: Offline
Joined: Feb 06, 201014Year Member
Posts: 996
Reputation Power: 64
Status: Offline
Joined: Feb 06, 201014Year Member
Posts: 996
Reputation Power: 64
This has already been posted before
- 0useful
- 0not useful
#7. Posted:
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
TTGXMODsX wrote This has already been posted beforeI GOT HAVE YOUR BUYABLE GUN MENU :arrow:
Last edited by TIGERxMoDz ; edited 1 time in total
- 0useful
- 0not useful
#8. Posted:
Status: Offline
Joined: Aug 15, 201014Year Member
Posts: 949
Reputation Power: 46
Status: Offline
Joined: Aug 15, 201014Year Member
Posts: 949
Reputation Power: 46
Either You Wasted Your Time Rewriting Or You copy and pasted from ii Slap Zombiezz
- 0useful
- 0not useful
#9. Posted:
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Status: Offline
Joined: Apr 05, 201113Year Member
Posts: 21
Reputation Power: 0
Resistance wrote Either You Wasted Your Time Rewriting Or You copy and pasted from ii Slap ZombiezzI DID ON A NOTE PAD THEN I COPY AND PASTE
- 0useful
- 0not useful
You are viewing our Forum Archives. To view or take place in current topics click here.