Variables
1. Introduction
Variables are invaluable tools and play a major role in SinScript because a lot of tasks simply wouldn't be possible without variables: calculations, conditional execution of commands, complex animations, sophisticated mechanical models and interactive consoles are only a few examples. In fact, all programming languages use variables so there's got to be something essential about them right? Fortunately, they are simple and easy to use once you understand the basics.
2. Basics
The syntax structure used for all variables in SinScript is:
<variable_type>.<variable_name>
There are 4 variables types in SinScript: game, level, local and parm.
A game variable, once created, is available to any script in the entire game. Its name and value are saved along with a game.
A level variable, once created, is available to all the threads in a script.
A local variable is available only to the thread it was created in.
A parm variable is used to pass a local variable from the thread in which it was created to another thread that needs to use it. Once a parm variable is created, it can be used by any thread in the script.
There are 2 kinds of variable names: user defined and reserved.
The name says it all: the user chooses the variable's name. The name can be anything you want except implicit variable names.
These are pre-defined variable names that have a special meaning. They are created by SinScript during the execution of the script threads. Never use reserved names for user defined variables.
Finally there are a few rules to observe with user defined variables:
-
Before a variable can be used for conditional testing, you must assign a value to it.
-
You must always refer to a variable by the <variable_type>.<variable_name>
syntax in a script. This is true for implicit variables as well.
-
Variable names are case sensitive: local.freddy and local.Freddy
are 2 entirely different variables. Beware of typos.
-
The value of a string variable (the actual text string you assign to it) is case sensitive.
Beware of typos when using conditional test commands on string variables.
3. Assigning values to variables
Now the next step in defining variables is to assign a value to them. But what value do we assign to a variable? There are 3 kinds of value that you can assign to a variable: numbers, character strings and vectors. The kind you choose will determine what syntax to use.
First, let's define a variable and assign a number to it:
Next, let's define another variable and assign a character string to it:
local.Kruger string "Elm_Street"
Next, let's define another variable and assign a vector to it:
local.arrow vector "(11 24 35.5)"
That's what the difference is. For numbers, you must use the = sign between the variable name and its value. For character strings you must use the word string as a operator between the variable name and its value. In this example, we gave the value of 6.66 to local.freddy. For number variables, both integer and floating point numbers are permitted but not letters. For string variables, you can assign any ASCII character but you must always enclose the value between double quotes ("). For vector variables, both integer and floating point numbers are permitted but not letters. You must enclose the 3 numerical values either between double quotes OR parentheses and double-quotes and the numbers must be separated by spaces.
Vectors, for those of you not familiar with vectorial algebra or physics, are straight lines characterized by 3 parameters: length, orientation and direction. They are used to determine positional and angular data in 3D space.
Vector variables in SinScript have a wider meaning and range of uses than a vector in the strict mathematical sense of the word. Simply put, they can be used whenever you need to store a set of 3 numerical values. These 3 values can be used to define an XYZ position in your map, a set of angles (pitch, yaw, roll) or even an RGB color if you want.
But, as we will see later, the vector variable operators treat a vector variable exactly like a genuine mathematical vector for specialized operations such as normalizing, dot product and cross product. In the definition of those particular operators, I included a formula for each that reduces them to their simplest mathematical expression. I dug out the formulas from my old math books so you wouldn't have to :)
So the bottom line about vector variables is: even if you have no notion of what a vector is in the mathematical sense, vector variables can still be very useful for you when it comes to storing numerical triplets for positions, angles, colors, etc. But you might not want to bother with the more complex vectorial operators though since those are likely to be useful only to people who are familiar with vectorial algebra and its practical applications.
Now, back to string variables: It's important to note that if you assign a variable the following value:
local.jason string "20.00"
This is perfectly legal but remember, to the script, 20.00 is a character string and not a number. And that's all the difference in the world because of the kind of operators you can or can't use with those variables later.
4. Modifying the value of number variables
After you assign a value to a variable, it's then possible to modify it. And like in the value assignment process, the appropriate syntax required to do this is determined by the kind of value you have to modify.
Let's start with number variables. The simplest way to modify a variable is to assign a new value to it:
From now on, the value of local.freddy is no longer 6.66 but 71.
You can also modify a number variable's value by assigning the value of another previously defined number variable to it. Let's say we define:
Next, we can assign the value of local.norman to local.freddy:
local.freddy = local.norman
From now on, the value of local.freddy is no longer 71 but 4 and the value of local_norman is unchanged. Be careful with syntax though because if you type this instead:
local.norman = local.freddy
The value of local.freddy will be assigned to local_norman instead and both variables will have a value of 71. When using this method, it's very important to make sure that the variable local.norman is a number variable and not a string variable otherwise you'll end up with a bad case of illegal syntax.
You can also modify the value of a number variable with arithmetic operators:
Addition operator: this will add 10 to the present value of local.freddy and assign the result back to local.freddy. Its value is now 14.
Subtraction operator: this will subtract 2 to the present value of local.freddy and assign the result back to local.freddy. Its value is now 12.
Multiplication operator: this will multiply the present value of local.freddy by 3 and assign the result back to local.freddy. Its value is now 72.
Floating point division operator: it keeps both the integer and decimal portions of the result. This will divide the present value of local.freddy by 5 and assign the result back to local.freddy. Its value is now 14.4 (72 divided by 5).
Integer division operator: it keeps only the integer portion of the result. This will divide the present value of local.freddy by 2 and assign the result to local.freddy. Its value is now 7 (14.4 divided by 2 = 7.2 then remove the .2 portion = 7).
Modulo operator: a division operator that keeps only the remainder portion of the result. This will divide the present value of local.freddy by 3 and assign the result to local.freddy. Its value is now 1 (7 divided by 3 = 2 with a remainder of 1).
local.freddy randomint 16
Random integer assignment operator: this will assign an integer between 1 and 16 to local.freddy on a random basis.
local.freddy randomfloat 9
Random floating point assignment operator: this will assign an floating point number between 0 and 9 to local.freddy on a random basis.
local.freddy getcvar skill
Console variable assignment operator: this will assign the current value of the console variable skill to local.freddy. Any regular console variable name can be used.
All the arithmetic operators can also be used with a predefined variable instead of a direct value, for example:
local.freddy += local.norman
This will add the value of local.norman (4) to the present value of local.freddy (1) and assign the result (5) to local.freddy and local.norman is unchanged.
5. Modifying the value of string variables
Now let's take a look at string variables. As with number variables, the simplest way to modify a variable is to assign a new value to it:
local.Kruger string "scissors"
The value of local.Kruger is no longer Elm_Street but scissors.
You can also modify a string variable's value by assigning the value of another previously defined string variable to it. Let's say we define:
local.Bates string "knife"
Next, we can assign the value of local.Bates to local.Kruger:
local.Kruger string local.Bates
From now on, the value of local.Kruger is no longer scissors but knife and the value of local.Bates is unchanged. As with number variables, be careful with the syntax because swapping the position of the variable names in the command will produce the opposite effect.
You can also modify the value of a string variable with the append operator:
local.Kruger append "_hands"
This will append the text string _hands at the end of the present value of local.Kruger and assign the new value to local.Kruger. It now has a value of knife_hands.
A particular thing about string variables is that it's possible to append a number at the end of the string with the appendint and appendfloat operators:
local.Kruger appendint local.norman
This will append the integer 4 at the end of the present value of local.Kruger and assign the new value to local.Kruger (local.norman is of course unchanged). It now has a value of knife_hands4. It's important to mention here that local.Kruger remains a string variable and that you cannot do the opposite operation which is to append a string variable to a number variable. The append operator is illegal with number variables the same way arithmetic operators are illegal with string variables.
local.Kruger appendfloat 1.25
This will append the floating point value 1.25 at the end of the present value of local.Kruger and assign the new value to local.Kruger. It now has a value of knife_hands1.25. As stated above, local.Kruger remains a string variable.
6. Modifying the value of vector variables
Now let's take a look at vector variables. As with any other variables, the simplest way to modify a variable is to assign a new value to it:
local.arrow vector "(35.5 -24 -11)"
The value of local.arrow is no longer (11 24 35.5) but (35.5 -24 -11).
You can also modify a vector variable's value by assigning the value of another previously defined vector variable to it. Let's say we define:
local.director vector "(-2 6 -47.4)"
Next, we can assign the value of local.director to local.arrow
local.arrow vector local.director
From now on, the value of local.arrow is no longer (35.5 -24 -11) but (-2 6 -47.4) and the value of local.director is unchanged. As with any other variables, be careful with the syntax because swapping the position of the variable names in the command will produce the opposite effect.
The value of vector variables can be be modified with basic vector variable operators:
local.arrow vector_add "(5 -8 36.4)"
This will add the vector (5 -8 36.4) to the present value of local.arrow which is (-2 6 -47.4) and assign the new value to local.arrow. It now has a value of (3 -2 -11).
Math performed by operator:
Assuming Vector1 = ( X1 Y1 Z1 ) and Vector2 = ( X2 Y2 Z2 ) , then:
Vector1 + Vector2 = ( (X1 + X2) (Y1 + Y2) (Z1 + Z2) )
local.arrow vector_subtract "(12 -7 3)"
This will subtract the vector (12 -7 3) from the present value of local.arrow which is (3 -2 -11) and assign the new value to local.arrow. It now has a value of (-9 5 -14).
Math performed by operator:
Assuming V1 = ( X1 Y1 Z1 ) and V2 = ( X2 Y2 Z2 ) , then:
V1 - V2 = ( (X1 - X2) (Y1 - Y2) (Z1 - Z2) )
local.arrow vector_scale 0.5
This will multiply the scale factor to the present value of local.arrow which is (-9 5 -14) and assign the new value to local.arrow. It now has a value of (-4.5 2.5 -7). You can also use a number variable as argument for this command instead of a direct numerical value.
Math performed by operator:
Assuming V1 = ( X1 Y1 Z1 ) and a scale factor S , then:
S * V1 = ( (S * X1) (S * Y1) (S * Z1) )
Some vector variable operators can perform operations between vector variables and number variables. These are simple vector variable cross-operators:
local.Xcomponent vector_x local.vector1
This will read the X value from the vector variable local.vector1 and assign it to the number variable local.Xcomponent.
local.Ycomponent vector_y local.vector1
This will read the Y value from the vector variable local.vector1 and assign it to the number variable local.Ycomponent.
local.Zcomponent vector_z local.vector1
This will read the Z value from the vector variable local.vector1 and assign it to the number variable local.Zcomponent.
local.vector1 vector_setx local.Xcomponent
This will read the value of the number variable local.Xcomponent and assign it to the X value of vector variable local.vector1.
local.vector1 vector_sety local.Ycomponent
This will read the value of the number variable local.Ycomponent and assign it to the Y value of vector variable local.vector1.
local.vector1 vector_setz local.Zcomponent
This will read the value of the number variable local.Zcomponent and assign it to the Z value of vector variable local.vector1.
These last 4 vector variable operators are the ones I was refering to earlier in the introduction to variable value types: they won't mean anything to you if you don't really know what a vector is in the mathematical sense. These are advanced vectorial operators:
local.length vector_length local.vector1
This will calculate the length of the vector variable local.vector1 and assign the result to the number variable local.length.
Math performed by operator:
Assuming V1 = ( X Y Z ) and the length of V1 as | V1 | , then:
| V1 | = Square root of ( X2 + Y2 + Z2 )
local.arrow vector_normalize
This will normalize the present value of local.arrow and assign the new value to local.arrow. Normalizing a vector is to scale it with the appropriate factor such that it will retain its orientation and direction but will have a length of 1. This is done by scaling the vector by a factor of (1 / length).
Math performed by operator:
Assuming V1 = ( X Y Z ) , L1 as its length and Vn as the resulting normalized vector, then:
Vn = ( (X / L1) (Y / L1) (Z / L1) )
local.dotp vector_dot local.vector1 local.vector2
This will perform the dot product of the vector variables local.vector1 with local.vector2 and assign the result to the number variable local.dotp. Note that for this operator, swapping the order of local.vector1 and local.vector2 in the command makes no difference.
Math performed by operator:
Assuming V1 = ( X1 Y1 Z1 ) and V2 = ( X2 Y2 Z2 ) and Pd as the dot product of these vectors, then:
Pd = ( (X1 * X2) + (Y1 * Y2) + (Z1 * Z2) )
local.normal vector_cross local.director1 local.director2
This will do the cross product of the vector variables local.director1 with local.director2 and assign the result to the vector variable local.normal. Note that for this operator, swapping the order of local.director1 and local.director2 in the command will result in a vector of the same length pointing in the opposite direction (the sign of the individual XYZ values will be inverted).
Math performed by operator:
Assuming V1 = ( X1 Y1 Z1 ) and V2 = ( X2 Y2 Z2 ) and Vn as the resulting normal vector, then:
Vn = ( [ (Y1 * Z2) - (Y2 * Z1) ] -[ (X1 * Z2) - (X2 * Z1) ] [ (X1 * Y2) - (X2 * Y1) ] )
7. Using variables for decision making
If all you could do with variables would be the things I described above, they would still be useful but that usefulness would be extremely limited. Their true role in SinScript as in any programming language is to provide a way to make the execution of certain commands conditional to certain events in the game. For example: is this door open or closed? How many times has it been opened? Has it finished closing yet? Etc.. Decision making based on the value of one or several variables is what unleashes their true power.
The process consists essentially of comparing the value of a variable with an explicit value or the value of another variable of the same kind. The result of that comparison will decide whether or not a specific action will be taken. This action can be any one of the following 3: go to a certain place in the same thread, execute a command or call another thread.
When comparing variables, you can only compare numbers to numbers, strings to strings and vectors to vectors. And the same way as when you assign values to variables or modify them, there is a set of conditional operators for number variables, another for string variables and yet another for vector variables.
The command syntax when using variables conditional operators always follow either one of these 2 variants:
<variable_type>.<variable_name> <conditional_operator> <value> <command>
<var_type>.<var_name> <conditional_operator> <var_type>.<var_name> <command>
And the <command> at the end of the line is executed if the condition is true.
8. Number variables conditional operators
What follows is a list of all the conditional operators applicable to number variables. Each one is set in a typical example of the basic syntax. Even though the thread call command was used in these examples, any valid SinScript command could be used after the comparison test (except another variable comparison test, of course).
local.freddy ifless 10 thread halloween_thread
If the value of local.freddy is less than 10, call the thread halloween_thread. If it's not, don't call the thread and go to the next command line in the script.
local.freddy iflessequal 10 thread halloween_thread
If the value of local.freddy is less than or equal to 10, call the thread haloween_thread. If it's not, don't call the thread and go to the next command line in the script.
local.freddy ifequal 10 thread halloween_thread
If the value of local.freddy is equal to 10, call the thread halloween_thread. If it's not, don't call the thread and go to the next command line in the script.
local.freddy ifnotequal 10 thread halloween_thread
If the value of local.freddy is not equal to 10, call the thread halloween_thread. If it is, don't call the thread and go to the next command line in the script.
local.freddy ifgreaterequal 10 thread halloween_thread
If the value of local.freddy is greater than or equal to 10, call the thread haloween_thread. If it's not, don't call the thread and go to the next command line in the script.
local.freddy ifgreater 10 thread halloween_thread
If the value of local.freddy is greater than 10, call the thread halloween_thread. If it's not, don't call the thread and go to the next line in the script.
All the conditional operators can also be used with a predefined variable instead of a direct value, for example:
local.freddy ifless local.norman thread halloween_thread
If the value of local.freddy is less than the value of local.norman, call the thread haloween_thread. If it's not, don't call the thread and go to the next line in the script.
9. String variables conditional operators
As far as string variables are concerned, this is very straightforward since there are only 2 conditional operators that apply (either the string is the same or it's not). Here's a typical example of the basic syntax. Even though the thread call command was used in this example, any valid SinScript command could be used after the comparison test (except another variable comparison test, of course).
local.Kruger ifstrequal "knife_hands" thread halloween_thread
If the value of the string in local.Kruger is a perfect match (case-sensitive) with the string knife_hands, call the thread halloween_thread. If it's not, don't call the thread and go to the next line in the script.
local.Kruger ifstrnotequal "knife_hands" thread halloween_thread
If the value of the string in local.Kruger is not a perfect match (case-sensitive) with the string knife_hands, call the thread halloween_thread. If it is, don't call the thread and go to the next line in the script.
The conditional operator can also be used with a predefined variable instead of a direct value, for example:
local.Kruger ifstrequal local.Bates thread halloween_thread
If the value of the string in local.Kruger is identical (case-sensitive) to the value of the string in local.Bates, call the thread halloween_thread. If it's not, don't call the thread and go to the next line in the script.
10. Vector variables conditional operators
For vector variables, it's just as simple as for string variables: only 2 conditional operators apply. Here's a typical example of the basic syntax. Even though the thread call command was used in this example, any valid SinScript command could be used after the comparison test (except another variable comparison test, of course).
local.vector1 vector_ifequal "(7 -4.4 -9)" thread trajectory_thread
If the value of the vector in local.vector1 is the same as (7 -4.4 -9), call the thread trajectory_thread. If it's not, don't call the thread and go to the next line in the script.
local.vector1 vector_ifnotequal "(7 -4.4 -9)" thread trajectory_thread
If the value of the vector in local.vector1 is not the same as (7 -4.4 -9), call the thread trajectory_thread. If it is, don't call the thread and go to the next line in the script.
The conditional operator can also be used with a predefined variable instead of a direct value, for example:
local.vector1 vector_ifequal local.vector2 thread trajectory_thread
If the value of the vector in local.vector1 is the same as the value of the vector in local.vector2, call the thread trajectory_thread. If it's not, don't call the thread and go to the next line in the script.
11. Reserved variables
Reserved variables are variables which have reserved names and are pre-defined by the script itself. You don't create those or assign values to them. Never attempt to modify them or use the same names for your own variables. They each have their own specific purpose. Here is a partial list of those I could find:
This is a string variable to which is automatically assigned the value of the named entity which calls that particular instance of the thread. Since local.self is a local type variable, it can only be used by the instance of the thread called by the entity (local.self applies to actors and func_scriptdoors only).
Let's look at an example from intro_start.scr to illustrate this. First, here's a snippet from the thread initialize_thread:
initialize_thread:
|
|
$hc1 thread ignore_thread
$hc2 thread ignore_thread
$hc3 thread ignore_thread
|
|
end
You will notice that the named entities $hc1, $hc2, $hc3 each call their separate instance of the thread ignore_thread. Each of these 3 entities is the owner of its respective instance of that thread. When the named entity $hc1 calls its instance of the thread, a string variable named local.self is automatically created and the text string hc1 is assigned as its value. Of course, the variable local.self that's just been created is available only to that specific instance of ignore_thread (IOW, to the other threads, it simply doesn't exist because it's a local variable). The same process is repeated for the other 2 instances of ignore_thread called by $hc2 and $hc3 and each instance creates its own variable named local.self. Now let's take a look at the code of the thread ignore_thread:
ignore_thread:
local.self ignoreall
pause
goto ignore_thread
end
See how the thread ignore_thread uses the variable local.self ? Even though each instance of the thread executes the same commands, the action taken in each instance of that thread appplies to a different entity or owner because local.self has a different value in each of those instances of the thread ignore_thread.
In the first instance of ignore_thread, local.self ignoreall effectively means $hc1 ignoreall.
In the second instance of ignore_thread, local.self ignoreall effectively means $hc2 ignoreall.
In the third instance of ignore_thread, local.self ignoreall effectively means $hc3 ignoreall.
So the main purpose of the local.self variable is to make it possible to accomplish the same task for many different named entities using only one thread definition (in this case, ignore_thread). IOW, it saves you from having to write a lot of redundant code. Was it not for the existence of local.self, the above task would have required 3 separate thread definitions (one for each of the named entities) instead of just one.
This is a number variable which is automatically created when a thread is called from another thread. A number which monitors the status of the called thread is assigned to its value. At first, the number reports an open status and once the called thread terminates, the number reports a closed status. This value is updated as soon as the called thread ends.
This variable is used in situations where you need a called thread to complete and close before proceeding in the main thread. Here's a typical example from bank.scr of how this variable is used in scripts:
bank_init:
|
|
thread dialog::jc_clear_bank_out
local.waitforthread = parm.previousthread
waitForThread local.waitforthread
|
|
end
What this does basically is to wait until the dialog thread jc_clear_bank_out has completed before executing the next commands in the main thread bank_init.
Please note that the value of parm.previousthread is automatically lost if the called thread is paused either by a "pause" or "wait" command. This is why it's considered good practice to always pass the value of parm.previousthread to a local variable to make sure it will be preserved.
12. Passing variables from thread to thread
Variable passing is done through the use of the parm variable type. This, of course, applies strictly to local variables since level type variables can be used directly by any thread in a script and game variables can be used by any thread in any script in the entire game so those types of variables don't need to be passed.
Here's a typical example of passing the value of a local variable in a thread to a local variable in a another thread using the parm type:
First, local.freddy is defined is the first thread:
tight_end_thread:
local.freddy = 55
parm.da_Ball = local.freddy
end
Next, the value of local.freddy is passed to local.freddy in the second thread using parm:
wide_receiver_thread:
local.freddy = parm.da_Ball
end
In the previous example, I used the same variable name local.freddy in both threads to make it perfectly clear (again) that local.freddy in the first thread and local.freddy in the second thread are 2 entirely separate, different variables even if they have the same name and value after the variable passing command. They are local variables that "reside" in 2 different threads and are "invisible" to all the threads in the script except the one they "live" in. The closest analogy I can think of for this is for example, 2 files with identical names and content that are stored in 2 different folders on your hard disk: even though they're identical, they are nevertheless 2 separate distinct files.
Note:
If local.freddy had been a string variable, the parm variable passing command would, of course, have required the use of the string operator instead of the = operator.
13. Conclusion
I tried to cover the basics of variables as thoroughly as possible in this document. There are many ways to use variables and there's a virtually infinite number of tasks that can be accomplished with them. The only limit is your imagination and creativity. With time and practice, you can find many original ways to use the power of variables.
|