13th July 2009
Scripting For Beginners
Introduction
This tutorial is aimed at people who want to learn the basics of scripting for Fallout 3, but have no prior programming experience. If this sounds like you, then hopefully you'll find this tutorial helpful. Before we start, there are a couple of things that we should go over:
- The scripting language used in Fallout 3 is not case-sensitive. While this means that you can have a perfectly good script with inconsistent capitalisation, it is a good idea to try to standardise your capitalisation, as it can help to make your scripts easier to read.
- This tutorial assumes that you are competent when it comes to using the GECK, or some other tool capable of creating and editing data files for Fallout 3. If this is not the case, then I recommend that you look at some of the official tutorials for the GECK to familiarise yourself with it before you attempt to follow this tutorial.
- If you don't understand any of the terminology that I've used in this tutorial, you can check the Glossary of Terms at the bottom of this page.
Now, let's get started. The first thing you need to know about scripting in Fallout 3 is that every script (except for result scripts, which are very different to regular scripts - I'll only be talking about regular scripts in this tutorial) must start with a ScriptName declaration, which declares the editorID of the script. For example:
ScriptName MyScript
If you look through some of the scripts that exist in Fallout3.esm, you'll probably notice that many of them use scn instead of ScriptName. This is perfectly alright, as scn is an alias of ScriptName. That means that we could just as easily use this for our ScriptName declaration:
scn MyScript
Congratulations, you've written a script! Of course, it's not particularly exciting - it doesn't actually do anything at all at the moment. Let's do something about that; let's edit that script so that it causes the text "Hello World!" to display on screen when you pick up a bobby pin.
The function that we're going to use to display this message is called ShowMessage. Before we can use this in the script we need to specify when we want it to run, which is done by using Begin/End blocks.
For every frame in which a script runs, the conditions of each Begin/End block are checked sequentially from top to bottom. If the specified condition is true, then the code contained within the Begin/End block will run, otherwise it will not run for this frame. They are essentially a type of conditional statement.
Because we want our code to run whenever a bobby pin is added to the player's inventory, we will be attaching the script to the bobby pin form (editorID Lockpick), and our Begin/End block will use the OnAdd blocktype, and use player as a parameter:
ScriptName HelloWorld Begin OnAdd player End
In order to attach our script to the bobby pin form, we have to specify which type of script it is. All scripts are divided up into three types, based on the types of forms that they can be attached to:
Because we want to attach this to the bobby pin, which is a Misc Item form, it should be specified as an object script.
As I mentioned before, the function that we're going to use in this script is ShowMessage. However, this function works by displaying information contained within a message form, so we need to create this message form first. This message form should have the Message Box checkbox unticked, and should have "Hello World!" written in the Message Text box. Let's give it the ID "MyMessage".
Now that we've set up a message to use, we can use ShowMessage in our script in order to display it:
ScriptName HelloWorld Begin OnAdd player ShowMessage MyMessage End
If this script is attached to the bobby pin form, "Hello World!" will be displayed in the upper-left hand corner of the screen whenever a bobby pin is added the player's inventory.
Conditional Statements
At the moment, the code contained within our script will only run if a bobby pin is added to the player's inventory. This is all well and good, but what if we wanted to display a different message if it was added to the inventory of any actor other than the player, and display the "Hello World!" message if it is added to the player's inventory? Let's call this other message "MyOtherMessage", and have it say "Goodbye World!"
Of course, we could achieve this by having multiple OnAdd blocks, but then we would end up having duplicate code within our script, which is something that we should always try to avoid. Instead, we will use another function, GetContainer, to determine which actor has the scripted item in their inventory.
Just calling GetContainer won't be enough, we're going to have to check its return value and use that check to determine what sections of our script should run. In order to do this, we are going to use an "if" statement.
An "if" statement is very similar to a Begin/End block. It begins with the keyword "if", followed by the condition which should be checked, and it ends with the keyword "endif". "If" statements are different to Begin/End blocks in that the condition is completely defined by you, as opposed to being chosen from a list of usable conditions. Here is how we will change our script so that the code within the OnAdd block will run when the bobby pin is added to the inventory of any actor, but the message will only be shown if it is added to the player's inventory:
ScriptName MyScript Begin OnAdd if GetContainer == player ShowMessage MyMessage endif End
Now, a lot of people misunderstand just how the conditions of "if" statements work, so let's work through this one step by step.
First, GetContainer is called. GetContainer returns the RefID of the reference that has the scripted item in its inventory, so once it is called you can visualise its return value as replacing it. For example, if the scripted item is in the player's inventory, then GetContainer will return the player's RefID, so you can imagine the condition becoming like this:
if player == player
The second part of the condition to be evaluated is "==", which is the "equal to" operator. The "equal to" operator returns 1 if both arguments are equal, and 0 if they are not. Therefore, because "player" and "player" are equal, the condition evaluates to 1:
if 1
Because the condition evaluates to a true value, the code contained within the "if" statement will run. If, however, GetContainer returns the RefID of a reference other than the player, then the condition would evaluate to 0, which is a false value, so the code within the "if" statement would not run.
Now our script has pretty much exactly the same functionality as it had previously, although it is slightly less efficient because the code will run when the scripted item is added to any inventory instead of just the player's. What we are going to do next is add a section of code that will only run if the scripted item is added to the inventory of a reference other than the player. The most obvious way in which we could do this would be to create a second "if" statement, that checks a different condition:
if GetContainer != player
This new condition is the exact opposite of the condition that we used earlier. Because the return value of GetContainer will be the same in both conditions, we can be sure that one and only one of these conditions will always evaluate to true. This means that, instead of creating an entirely new "if" statement, we can use an "else" statement.
An "else" statement can only be used in between an "if" statement and its corresponding "endif" statement, and basically means "if all prior conditions returned false". Let's update our script again, this time to display "MyOtherMessage" if a bobby pin is added to the inventory of a reference other than the player:
ScriptName MyScript Begin OnAdd if GetContainer == player ShowMessage MyMessage else ShowMessage MyOtherMessage endif End
Now, when the OnAdd block runs, the "if GetContainer == player" condition is checked first. If it evaluates to true, then the "ShowMessage MyMessage" line will run, and the script will then skip to the corresponding "endif" statement - the "else" condition and the code it contains will be completely skipped. However, if the first condition evaluates to false, then the second condition, "else", is checked. As I mentioned before, "else" statements basically mean "if all prior conditions returned false" - if an "else" statement is ever checked, the code within it will run and all following conditions (there shouldn't be any) will be skipped.
Now our script has two possible outputs - if a bobby pin is added to the player's inventory, "MyMessage" will be shown, and if a bobby pin is added to the inventory of a container other than the player, "MyOtherMessage" will be shown. This is cool, but what if we want to have more possible outputs associated with our script.
Let's play a different message again (call it "MyDadMessage" and have it say "Hello Dad!") if a bobby pin is added to the player's dad's inventory (his RefID is MQDadRef). Perhaps the most obvious approach to this would be to place a new "if" statement within the "else" statement like so:
ScriptName MyScript Begin OnAdd if GetContainer == player ShowMessage MyMessage else if GetContainer == MQDadRef ShowMessage MyDadMessage else ShowMessage MyOtherMessage endif endif End
As you can see, if a bobby pin is added to the inventory of a reference other than the player, the script will then check if the bobby pin has been added to the inventory of the MQDadRef reference. While this will work perfectly, the scripting language used in Fallout 3 includes a nifty keyword that allows us to use a lot of different conditions together like this without having to nest all of our "if" statements within one another. This keyword is the "elseif" statement, and can be used like this:
ScriptName MyScript Begin OnAdd if GetContainer == player ShowMessage MyMessage elseif GetContainer == MQDadRef ShowMessage MyDadMessage else ShowMessage MyOtherMessage endif End
This code will work in exactly the same way as the previous code, except it is much easier to read and you only have to include one "endif" statement because you are only using one "if" statement. Always remember - "elseif" and "else" statements don't need their own "endif" statements, only "if" statements need their own "endif" statements.
A common question that I see asked about the scripting language used in Fallout 3 is "can we use switch/case?". If you haven't done any programming before, then this won't really mean anything to you. "Switch/case" statements can be used in certain situations instead of "if" statements, where you want to check the value of something against a few possible values without having to evaluate it more than once. While they do not provide any exra functionality whatsoever, they can make code more efficient and easier to read. Unfortunately, the answer to this question is no - "switch/case" statements cannot be used in Fallout 3 scripts, so we'll just have to stick with "if", "elseif" and "else" statements.
Variables
Often, you'll find that you need to somehow store information in a script. For this purpose, we have three types of variables available for us to use. These three variable types, including their aliases are:
- int / short / long
- float
- ref / reference
Each of these variables types can be used to store a specific type of value. Basically, "int" variables store integer values (whole numbers, which can be positive or negative), "float" variables store floating point values (numbers with decimal places, which can also be positive or negative), and "ref" variables store formIDs (they are most commonly used to store refIDs, which are a specific type of formID, hence the name).
In order to create a variable for us to use, we have to declare it. Just like how we had to use the "ScriptName" keyword when we declared the editorID of our script, when we declare a variable we have to use an appropriate keyword to determine what type of variable it is.
We can choose the name of our variable, but no two variables in the same script can have the same name, even if they are of different types, and a variable can't share its name with any form or function. For example, I couldn't give a variable the name "Lockpick", as this name is already used as the editorID of the bobby pin Form.
Let's declare a "ref" variable, so that we can store the return value of GetContainer for later use:
ScriptName MyScript ref rContainer Begin OnAdd ... End
The declaration of our new variable is located at the top of the script, outside of our Begin/End block. All of your variable declarations must be placed here, just like the ScriptName declaration. Notice as well how I have named my variable - I have prefixed it with the letter "r" so that I know that it is a "ref" variable, and I have named it according to its function, which is to store the refID of the scripted item's container.
How you name your variables and what conventions you use is completely up to you, but it is important that they are named according to their function so that your script is easy to follow. If your variables have names like "Variable1", "Variable2" etc. then your script will be very difficult to follow.
Once a variable has been declared, it is automatically given a value of 0. In order to change the value of a variable, we must use a "set" command, which consists of the use of two keywords - "set" and "to". For example, to set our "rContainer" variable to the return value of GetContainer, we would use code like this:
set rContainer to GetContainer
The "set" command is made up of four sequential parts:
- "set"
- The name of the variable
- "to"
- An expression
The value of the variable will be set to the result of the expression.
Reference Functions
The first function that we used in this tutorial, ShowMessage, is what's known as a non-reference function, as it does not act on a specific reference. Most functions, however, are reference functions, and perform an action on a specific reference.
Because reference functions act on a specific reference, when calling them the reference on which they should act must be specified. There are two ways in which a reference function may be called - with implicit reference syntax and with explicit reference syntax.
-
When a reference function is called with implicit reference syntax, they are called on the reference that the script is running on. Because of this, reference functions can only be called with implicit syntax in reference scripts.
Reference functions called with implicit reference syntax use the same syntax as non-reference functions, and can be called on inventory items as well as references.
-
When a reference function is called with explicit reference syntax, they are called on a specified reference. This reference can be specified in two ways:
- via editorRefID. Only persistent references can be referred to in this way.
- via a local "ref" variable that stores the reference's refID.
Reference functions called with explicit reference syntax specify a reference by prefixing the function name with one of the above methods of specifying the reference, and separating this from the function with a period - just like the one at the end of this sentence.
It is important to note that functions cannot be used directly to call reference functions with explicit reference syntax, nor can they be used directly as parameters in other functions. Instead, the return value of a function must be stored in a variable, and that variable should then be used when calling the function. For example, the following code will not compile:
GetContainer.AddItem Caps001 10
Instead, the return value of GetContainer must be stored in a local "ref" variable, and that local variable should be used to call GetContainer:
ref rContainer ... set rContainer to GetContainer rContainer.AddItem Caps001 10
In the script we wrote earlier, we have already called a reference function - GetContainer. Because GetContainer only really works on inventory items (it will always return 0 if called on a reference not in a container), it should always be called with implicit reference syntax.
Anyway, where were we? Ah, that's right, we'd just declared a "ref" variable, which we called rContainer, and set it to the return value of GetContainer. Now that we have the refID of the reference that has the scripted bobby pin in its inventory stored in a "ref" variable, we can call reference functions on it with explicit reference syntax.
Now, let's change our script so that if a bobby pin is added to the inventory of a reference other than the player and MQDadRef, 10 caps are added to that reference's inventory instead of showing a message. We can do this by using explicit reference syntax to call the function AddItem on the reference, like so:
ScriptName MyScript ref rContainer Begin OnAdd set rContainer to GetContainer if rContainer == player ShowMessage MyMessage elseif rContainer == MQDadRef ShowMessage MyDadMessage else rContainer.AddItem Caps001 10 endif End
One more thing that I should bring up here is that, when comparing refIDs, no matter if they are refIDs stored in "ref" variables or represented by editorRefIDs, the GetIsReference function should be used instead of using the "equal to" operator. Using the "equal to" operator will work perfectly fine in most cases, but sometimes, particularly when working with the "player" reference, it will cause issues.
ScriptName MyScript ref rContainer Begin OnAdd set rContainer to GetContainer if rContainer.GetIsReference player ShowMessage MyMessage elseif rContainer.GetIsReference MQDadRef ShowMessage MyDadMessage else rContainer.AddItem Caps001 10 endif End
GetIsReference is called by the reference that has its refID stored in our rContainer variable, and uses the refID that it is being compared against as a parameter. For this function, we could swap these two around (i.e. "player.GetIsReference rContainer") and it would work just as well.
Glossary
-
Alias
Many functions and keywords have "aliases". These are alternate names or keywords that can be used in the place of the main function name or keyword. For example, the function StopCombatAlarmOnActor has an alias - SCAOnActor. This means that the following two lines of code are essentially identical:
StopCombatAlarmOnActor SCAOnActor
-
Blocktype
There are a limited number of conditions that can be used for Begin/End blocks, such as OnAdd and OnDeath. Each of these conditions is known as a blocktype. Different types of scripts have different blocktypes available to them, so it can help to think of blocktyps as being split into three categories:
- Effect
- Reference-specific
- General
As you can see, the vast majority of blocktypes are reference-specific. Most of these blocktypes can only be used on certain types of forms. For example, OnDeath blocks can only be used on actors (NPCs and creatures), and OnAdd blocks can only be used on carryable forms, such as weapons.
-
EditorID
All non-reference forms, as well as certain references, have editorIDs. These are basically aliases for their formIDs, and are used in scripts to refer to specific forms. Note the editorIDs should never begin with numbers, as the compiler will wrongly recognise them as formIDs and be unable to find them.
-
EditorRefID
-
Effect Script
Effect scripts can be attached to base effect forms, which can be used in object effect, actor effect and ingestible forms. Effect scripts are reference scripts, although they are run on the target of the effect as opposed to the scripted form, which would be the effect itself. Because of this, variables declared in effect scripts cannot be accessed remotely. Effect scripts are limited to three usable blocktypes:
-
False
Zero
-
Form
Pretty much every object stored in a data file is a type of form. There are many different types of form, each which stores information about a different type of object. For example, "SCPT" forms store information about scripts, whereas "QUST" forms store information about quests. Each form can be identified by its unique editorID and formID.
-
FormID
An eight-digit hexadecimal number that specifies a form. The first two digits of a formID correspond with the position of the data file from which the form originates in your load order. This allows for up to 255 data files to be loaded simultaneously, including Fallout3.esm. The "ff" prefix is reserved for forms that are stored in the save file (most of these will be references.)
-
Function
Aside from the manipulation of variables, everything in scripting is done by functions. Certain functions can be used to determine information about specific forms or the current game state, and others can be used to perform actions - affecting specific forms or the state of the game in some way. Functions which act on specific references are known as reference functions, whereas functions that do not act on specific forms are known as non-reference functions.
-
Object Script
Object scripts can be attached to forms that can have references, such as actors, as well as carryable forms, such as weapons. They are reference scripts, and can use reference-specific blocktypes. Object scripts are run while the scripted object (or its container, in the case of inventory objects) is loaded.
-
Operator
Operators are used to manipulate values, and are split into three categories:
-
Arithmetic Operators + Positive - Negative + Addition - Subtraction * Multiplication / Division % Modulo -
Comparison Operators == Equal to != Not equal to > Greater than < less than >= Greater than or equal to <= Less than or equal to -
Logical Operators && AND || OR
The comparison operators will return 1 if they evaluate to true, and 0 if they evaluate to false. For example, "3 < 5" will return 1 because the statement "3 is less than 5" is true, whereas "3 == 5" will return 0 because the statement "3 is equal to 5" is false. These operators cannot return any values other than 1 and 0.
Like the comparison operators, the logical operators will also return 1 if they evaluate to true, and 0 if they evaluate to false. Here is a truth table that show the possible outputs of the logical operators:
A B A&&B A||B false false false false false true false true true false false true true true true true Although the NOT and XOR logical operators are not available, you can still achieve the same output by using "A == 0" in the place of "NOT A" and "(A != 0) != (B != 0)" in the place of "A XOR B". Note that, for boolean conditions, only "A != B" is required - "!= 0" can be used to transform any number into a boolean number (0 for false, 1 for true).
For more information on operators in Fallout 3's scripting language, see my Operators tutorial.
-
-
Parameter
Many functions, especially those that perform actions rather than gather information, require more information in order to work correctly. This information is passed to functions in the form of parameters, which are separated from the function name and each other by white space, and/or an optional comma. For example, the following two lines are equivalent:
player.AddItem Caps001 10 player.AddItem,Caps001,10
Some functions have optional parameters, which can be omitted from the function call. If they are not included in the function call, then they will either be given a default value or ignored. For example, the following two lines are equivalent because the default value of AddItem is 0:
player.AddItem Caps001 10 0 player.AddItem Caps001 10
-
Player
Several forms are hard-coded into the game. Most of these needn't concern you, but a very important and useful one is the "player". Basically, the fact that this reference is hard-coded means that you will always be able to refer to the "player" reference by the keyword "player", which is treated as the editorRefID of the "player" reference, no matter which data files are or aren't loaded.
Technically, "player" is the editorID of the player base object, and "playerref" is the editorRefID of the player reference, but the GECK will treat "player" in the same way as "playerref" when used in scripts.
-
Quest Script
Quest scripts can be attached to quest forms, and are not reference scripts. They can only use general blocktypes. Quest scripts have the unique ability to have a script delay time, which defines how often the script will be executed. This is defined in the quest form that the script is attached to, and can be changed via script with the SetQuestDelay function. In order for a quest script to run every frame, the script delay time should be set to a very low value like 0.001.
Quest scripts will run while the quest that they are attached to is enabled.
-
Reference
A reference is a special type of form. References are the forms that you can see and interact with in the game world, and basically acts as pointers to forms. As such, they contain two sets of information:
- Reference-specific information
- Information about the base object (the form that a reference is based on)
-
Reference Script
Scripts attached to forms that can have references are called reference scripts. Each reference to one of these forms will run its own instance of the script.
-
RefID
-
True
Non-zero
-
Variable
A variable is something that allows you to store information in a script for later use. There are three types of variable avaliable for use in Fallout 3 scripting, each which stores a different type of information:
-
int / short / long
Stores an integer (a whole number). Has a range from -2,147,483,648 to 2,147,483,647.
-
float
Stores a floating point number (a number with a decimal point). Has a range including -3.402823x1038 to -1.175494x10-38, 0 and 1.175494x10-38 to 3.402823x1038
-
ref / reference
Stores a formID. Usually used to store a refID, hence the keyword ref/reference.
-
int / short / long
Comments
As your scripts become longer and more complex, they may become difficult to follow. In order to help make scripts easy to understand, it is important to annotate them using comments. Comments are basically text that is part of the script's source code, but is completely ignored by the compiler and therefore has no effect whatsoever on how a script works.
Comments in Fallout 3 scripts are specified by the use of a semicolon, ";". All text on a line that comes after a semicolon is a comment, and will be ignored by the compiler. This type of comment is known as a line comment, as the comment extends from the semicolon to the end of the line.
Many other computer languages also allow a type of comment known as a block comment, where another character or set of characters is used to signify the end of a comment instead of a newline, but Fallout 3 scripts do not allow this.
One particularly useful way to utilise comments is to explain what a certain value represents. For example, the function GetOpenState uses different values to represent different "open states" of a door. On their own, they do not particularly make sense, so it is usually a good idea to use a comment to explain the meaning of the value. For example:
As you can see, the comments here are used to explain the true meaning of the conditions. If the comments were absent, then someone reading the script would need to look up the documentation of GetOpenState in order to understand what the conditions really mean. The same concept applies to variables - if you use a variable in which different values represent different states, it is a good idea to use comments when you declare the variable in order to explain what each value represents.
Another good way to use comments is to describe the function of a script. For example, if you have a script that uses some complicated calculations to place a reference in front of the player, then it would be a good idea to explain this in a comment at the top of the script. That way, anyone reading the script will only have to read this comment in order to understand what the script is supposed to do, instead of trying to understand your calculations in order to determine how they work.
Whenever you write scripts, you should always annotate them wherever you think it is necessary. If you think that any part of your script would benefit from a little explanation, don't hesitate to add a comment nearby. Even if it's only one or two words - a little clarification can go a long way, and scripts that don't contain any comments can sometimes take a long time to fully understand.