Difference between revisions of "Fallout3 beginner's scripting tutorial"

From Nexus Mods Wiki
Jump to: navigation, search
(Copied from article)
 
m (Format changes)
 
(One intermediate revision by one other user not shown)
Line 1: Line 1:
Posted by Cipscis on 00:01, 9 April 2009
+
This article is best viewed on [http://www.cipscis.com/ cipscis.com] - [http://www.cipscis.com/fallout/tutorials/beginners.aspx Scripting for Beginners]
From http://www.fallout3nexus.com/articles/article.php?id=82
+
 
Introduction
+
== 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:
 
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.
 
- 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.
 
- 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.
 
- 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, shall we? The first thing you need to know about scripting in Fallout 3 is that every script (except for Result scripts, which will be explained in a later tutorial) must start with a ScriptName declaration, which declares the EditorID of the script. For example:
+
Now, let's get started, shall we? The first thing you need to know about scripting in Fallout 3 is that every script (except for Result scripts, which will be explained in a later tutorial) must start with a ScriptName declaration, which declares the EditorID of the script.  
 +
 
 +
===Example===
 +
 
 +
 
 +
ScriptName MyFirstScript
  
ScriptName MyFirstScript
 
  
 
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:
 
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 MyFirstScript
+
 
 +
scn MyFirstScript
 +
 
  
 
Congratulations, you've written your first script! Of course, it's not particularly exciting - it doesn't actually do anything at all at the moment. Why don't we 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.
 
Congratulations, you've written your first script! Of course, it's not particularly exciting - it doesn't actually do anything at all at the moment. Why don't we 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, but before we can put this in the script we need to tell it when we want it to run, which is done by using Begin/End blocks (again, except for in Result scripts).
+
 
 +
The function that we're going to use to display this message is called [http://geck.bethsoft.com/index.php/ShowMessage ShowMessage], but before we can put this in the script we need to tell it when we want it to run, which is done by using Begin/End blocks (again, except for in Result scripts).
  
 
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. As you can see, they are essentially highly specific conditional statements.
 
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. As you can see, they are essentially highly specific conditional statements.
  
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:
+
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 [http://geck.bethsoft.com/index.php/OnAdd OnAdd] blocktype, and use player as a parameter:
  
ScriptName MyFirstScript
 
  
Begin OnAdd player
+
ScriptName MyFirstScript
 +
 
 +
Begin OnAdd player
 +
 
 +
End
 +
 
  
End
 
  
 
In order to attach our script to the bobby pin form, we have to specify which type of script it is. With the exception of Result scripts, all scripts are divided up into three types:
 
In order to attach our script to the bobby pin form, we have to specify which type of script it is. With the exception of Result scripts, all scripts are divided up into three types:
  
 
- Object
 
- Object
 +
 
- Quest
 
- Quest
 +
 
- Effect
 
- Effect
  
 
Because we want to attach this to the bobby pin, which is a Misc Item form, it should be specified as an object script.
 
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, before we can use this function, we need to create a message form to use as a parameter. 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".
+
As I mentioned before, the function that we're going to use in this script is [http://geck.bethsoft.com/index.php/ShowMessage ShowMessage]. However, before we can use this function, we need to create a message form to use as a parameter. 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 this message:
 
Now that we've set up a message to use, we can use ShowMessage in our script in order to display this message:
  
ScriptName MyFirstScript
 
  
Begin OnAdd player
+
 
ShowMessage MyMessage
+
ScriptName MyFirstScript
End
+
 
 +
Begin OnAdd player
 +
ShowMessage MyMessage
 +
End
 +
 
  
 
Now, 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.
 
Now, 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
+
 
 +
 
 +
==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!"
 
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.
+
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,[http://geck.bethsoft.com/index.php/GetContainer 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.
 
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.
Line 58: Line 80:
 
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:
 
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 MyFirstScript
+
 +
ScriptName MyFirstScript
 +
 
 +
Begin OnAdd
 +
if GetContainer == player
 +
ShowMessage MyMessage
 +
endif
 +
End
 +
 
  
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 that one. First,  [http://geck.bethsoft.com/index.php/GetContainer 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 visualise the condition like this:
  
Now, a lot of people misunderstand just how the conditions of "if" statements work, so let's work through that one. 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 visualise the condition like this:
 
  
if player == player
+
if player == player
  
  
 
The second part of the condition, "==" is an operator. The "==" 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:
 
The second part of the condition, "==" is an operator. The "==" 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
+
if 1
  
  
Line 81: Line 106:
 
Now, as you can see, our script has pretty much exactly the same functionality as it had previously, although it is slightly less efficient. What we are going to do now is add an alternate 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:
 
Now, as you can see, our script has pretty much exactly the same functionality as it had previously, although it is slightly less efficient. What we are going to do now is add an alternate 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
+
if GetContainer != player
 +
 
 +
 
  
  
Line 88: Line 115:
 
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:
 
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 MyFirstScript
 
  
Begin OnAdd
+
ScriptName MyFirstScript
if GetContainer == player
+
 
ShowMessage MyMessage
+
Begin OnAdd
else
+
if GetContainer == player
ShowMessage MyOtherMessage
+
ShowMessage MyMessage
endif
+
else
End
+
ShowMessage MyOtherMessage
 +
endif
 +
End
  
  
Line 103: Line 131:
 
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. For example, what if we wanted to play a different message again (let's call it "MyDadMessage" and have it say "Hello Dad!") if a bobby pin is added to the player's father'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:
 
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. For example, what if we wanted to play a different message again (let's call it "MyDadMessage" and have it say "Hello Dad!") if a bobby pin is added to the player's father'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 MyFirstScript
+
 +
ScriptName MyFirstScript
  
Begin OnAdd
+
Begin OnAdd
if GetContainer == player
+
if GetContainer == player
ShowMessage MyMessage
+
ShowMessage MyMessage
else
+
else
if GetContainer == MQDadRef
+
if GetContainer == MQDadRef
ShowMessage MyDadMessage
+
ShowMessage MyDadMessage
else
+
else
ShowMessage MyOtherMessage
+
ShowMessage MyOtherMessage
endif
+
endif
endif
+
endif
End
+
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 tool 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 tool is known as an "elseif" statement, and can be used like this:
 
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 tool 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 tool is known as an "elseif" statement, and can be used like this:
  
ScriptName MyFirstScript
+
 +
ScriptName MyFirstScript
  
Begin OnAdd
+
Begin OnAdd
if GetContainer == player
+
if GetContainer == player
ShowMessage MyMessage
+
ShowMessage MyMessage
elseif GetContainer == MQDadRef
+
elseif GetContainer == MQDadRef
ShowMessage MyDadMessage
+
ShowMessage MyDadMessage
else
+
else
ShowMessage MyOtherMessage
+
ShowMessage MyOtherMessage
endif
+
endif
End
+
End
  
  
Line 137: Line 167:
 
One of the most common general questions that I see asked about the scripting language used in Fallout 3 is "can we use switch/case?". If you haven't done programming before, then this won't really mean anything to you. "Switch/case" statements can be used in certain situations instead of "if" statements. While they do not provide any exra functionality whatsoever, they can make code much easier to read. 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" and "elseif" statements.
 
One of the most common general questions that I see asked about the scripting language used in Fallout 3 is "can we use switch/case?". If you haven't done programming before, then this won't really mean anything to you. "Switch/case" statements can be used in certain situations instead of "if" statements. While they do not provide any exra functionality whatsoever, they can make code much easier to read. 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" and "elseif" statements.
  
Variables
+
 
 +
==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:
 
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
 
- int / short / long
 +
 
- float
 
- float
 +
 
- ref / reference
 
- ref / reference
  
Line 149: Line 182:
 
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 (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:
 
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 (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
+
 +
ScriptName MyScript
  
ref rContainer
+
ref rContainer
  
Begin OnAdd
+
Begin OnAdd
...
+
...
End
+
End
  
  
Line 162: Line 196:
 
Once we've declared our variable, 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, if we want to set our "rContainer" variable to the return value of GetContainer, we would do it like this:
 
Once we've declared our variable, 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, if we want to set our "rContainer" variable to the return value of GetContainer, we would do it like this:
  
set rContainer to GetContainer
+
 +
set rContainer to GetContainer
  
  
 
As you can see, the "set" command is initiated with the keyword "set", which is followed by the name of the variable, which is followed by the "to" command, which is followed by an expression. The value of the variable will then be set to the result of that expression.
 
As you can see, the "set" command is initiated with the keyword "set", which is followed by the name of the variable, which is followed by the "to" command, which is followed by an expression. The value of the variable will then be set to the result of that 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.
+
==Reference Functions==
 +
 
 +
 
 +
The first function that we used in this tutorial,[http://geck.bethsoft.com/index.php/ShowMessage 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 scripted reference. Because of this, reference functions can only be called with implicit syntax in reference scripts. Reference functions called with implicit reference syntax use the exact same syntax as non-reference functions, and can be called on inventory items as well as references.
 
- When a reference function is called with implicit reference syntax, they are called on the scripted reference. Because of this, reference functions can only be called with implicit syntax in reference scripts. Reference functions called with implicit reference syntax use the exact 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:
 
- 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 storing the reference's RefID.
+
*via EditorRefID. Only persistent references can be referred to in this way.
 +
 
 +
*via a local "ref" variable storing the reference's RefID.
 +
 
 
- Reference functions called with explicit reference syntax specify a reference by prefixing the function name with either the reference's EditorRefID, or the name of a "ref" variable, and separating this from the function with a period - just like the one at the end of this sentence.
 
- Reference functions called with explicit reference syntax specify a reference by prefixing the function name with either the reference's EditorRefID, or the name of a "ref" variable, 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:
 
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
+
 +
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:
 
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
+
ref rContainer
set rContainer to GetContainer
+
set rContainer to GetContainer
rContainer.AddItem Caps001 10
+
rContainer.AddItem Caps001 10
 +
 
  
 
In our script, we have already called a reference function - GetContainer. Because GetContainer only really works on inventory items, it should always be called with implicit reference syntax, just like we have done.
 
In our script, we have already called a reference function - GetContainer. Because GetContainer only really works on inventory items, it should always be called with implicit reference syntax, just like we have done.
  
Anyway, where were we? Ah, that's right, we'd just declared a "ref" variable, called rContainer, and had 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, what if we wanted to 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 could do this by using explicit reference syntax to call AddItem on the reference, like so:
+
Anyway, where were we? Ah, that's right, we'd just declared a "ref" variable, called rContainer, and had 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, what if we wanted to 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 could do this by using explicit reference syntax to call [http://geck.bethsoft.com/index.php/AddItem AddItem] on the reference, like so:
 +
 
 +
 
 +
ScriptName MyFirstScript
  
ScriptName MyFirstScript
+
ref rContainer
  
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
  
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 (both RefIDs stored in "ref" variables and those represented by EditorRefIDs), the GetIsReference function should be used instead of using the "==" operator, so our script should look like this:
+
One more thing that I should bring up here is that, when comparing RefIDs (both RefIDs stored in "ref" variables and those represented by EditorRefIDs), the [http://geck.bethsoft.com/index.php/GetIsReference GetIsReference] function should be used instead of using the "==" operator, so our script should look like this:
  
ScriptName MyFirstScript
+
ScriptName MyFirstScript
  
ref rContainer
+
ref rContainer
  
Begin OnAdd
+
Begin OnAdd
set rContainer to GetContainer
+
set rContainer to GetContainer
if rContainer.GetIsReference player
+
if rContainer.GetIsReference player
ShowMessage MyMessage
+
ShowMessage MyMessage
elseif rContainer.GetIsReference MQDadRef
+
elseif rContainer.GetIsReference MQDadRef
ShowMessage MyDadMessage
+
ShowMessage MyDadMessage
else
+
else
rContainer.AddItem Caps001 10
+
rContainer.AddItem Caps001 10
endif
+
endif
End
+
End
  
  
 
As you can see, 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 (e.g. "player.GetIsReference rContainer) and it would work just fine.
 
As you can see, 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 (e.g. "player.GetIsReference rContainer) and it would work just fine.
  
Comments
+
 
 +
 
 +
 
 +
==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 - it has no effect whatsoever on how a script works.
 
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 - it has no effect whatsoever on how a script works.
Line 233: Line 282:
 
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 specifying character to the end of the line. Many other computer languages also allow a type of comment known as a block comment, where another specifying character or string is used to signify the end of a comment, but Fallout 3 scripts do not allow this.
 
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 specifying character to the end of the line. Many other computer languages also allow a type of comment known as a block comment, where another specifying character or string is used to signify the end of a comment, 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:if GetOpenState == 3 ; Closed
+
One particularly useful way to utilise comments is to explain what a certain value represents. For example, the function [http://geck.bethsoft.com/index.php/GetOpenState 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.  
...
+
 
elseif GetOpenState == 4 ; Closing
+
For example:
...
+
 
endif
+
 +
if GetOpenState == 3 ; Closed
 +
...
 +
elseif GetOpenState == 4 ; Closing
 +
...
 +
endif
 +
 
 +
 
 
As you can see, the comments here help 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.
 
As you can see, the comments here help 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.
  
Line 244: Line 300:
 
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.
 
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.
  
Glossary
 
  
- Alias
+
 
 +
==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:
 
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
+
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 several categories:
 
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 several categories:
  
- Effect
 
  
---- ScriptEffectFinish
+
'''Effect'''
---- ScriptEffectStart
+
 
---- ScriptEffectUpdate
+
 
 +
[http://geck.bethsoft.com/index.php/ScriptEffectFinish ScriptEffectFinish]
 +
 
 +
[http://geck.bethsoft.com/index.php/ScriptEffectStart ScriptEffectStart]
 +
 
 +
[http://geck.bethsoft.com/index.php/ScriptEffectUpdate ScriptEffectUpdate]
 +
 
 +
 
 +
'''Reference-specific'''
 +
 
 +
 
 +
[http://geck.bethsoft.com/index.php/OnActivate OnActivate]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnActorEquip OnActorEquip]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnActorUnequip OnActorUnequip]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnAdd OnAdd]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnCombatEnd OnCombatEnd]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnDeath OnDeath]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnDestructionStageChange OnDestructionStageChange]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnDrop OnDrop]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnEquip OnEquip]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnHit OnHit]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnHitWith OnHitWith]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnLoad OnLoad]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnMagicEffectHit OnMagicEffectHit]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnMurder OnMurder]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnPackageChange OnPackageChange]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnPackageDone OnPackageDone]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnPackageStart OnPackageStart]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnRelease OnRelease]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnSell OnSell]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnStartCombat OnStartCombat]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnTrigger OnTrigger]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnTriggerEnter OnTriggerEnter]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnTriggerLeave OnTriggerLeave]
 +
 
 +
[http://geck.bethsoft.com/index.php/OnUnequip OnUnequip]
 +
 
 +
[http://geck.bethsoft.com/index.php/SayToDone SayToDone]
 +
 
 +
 
 +
'''General'''
  
- Reference-specific
 
  
---- OnActivate
+
[http://geck.bethsoft.com/index.php/GameMode GameMode]
---- OnActorEquip
 
---- OnActorUnequip
 
---- OnAdd
 
---- OnCombatEnd
 
---- OnDeath
 
---- OnDestructionStageChange
 
---- OnDrop
 
---- OnEquip
 
---- OnHit
 
---- OnHitWith
 
---- OnLoad
 
---- OnMagicEffectHit
 
---- OnMurder
 
---- OnPackageChange
 
---- OnPackageDone
 
---- OnPackageStart
 
---- OnRelease
 
---- OnSell
 
---- OnStartCombat
 
---- OnTrigger
 
---- OnTriggerEnter
 
---- OnTriggerLeave
 
---- OnUnequip
 
---- SayToDone
 
  
- General
+
[http://geck.bethsoft.com/index.php/MenuMode MenuMode]
  
---- GameMode
 
---- MenuMode
 
  
 
As you can see, the vast majority of blocktypes are reference-specific. Most of these blocktypes can only be used in 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.
 
As you can see, the vast majority of blocktypes are reference-specific. Most of these blocktypes can only be used in 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
+
===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.
 
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.
  
- EditorRefID
+
===EditorRefID===
 +
 
 +
 
 
The EditorID of a reference.
 
The EditorID of a reference.
  
- Effect Script
+
===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:
 
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:
  
- ScriptEffectStart
 
- ScriptEffectUpdate
 
- ScriptEffectFinish
 
  
- False
+
[http://geck.bethsoft.com/index.php/ScriptEffectStart ScriptEffectStart]
 +
 
 +
[http://geck.bethsoft.com/index.php/ScriptEffectUpdate ScriptEffectUpdate]
 +
 
 +
[http://geck.bethsoft.com/index.php/ScriptEffectFinish ScriptEffectFinish]
 +
 
 +
===False===
 +
 
 +
 
 
Zero
 
Zero
  
- Form
+
===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, Script forms store information about scripts, whereas Weapon forms store information about weapons. Different forms can be identified by their unique EditorID and FormID.
 
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, Script forms store information about scripts, whereas Weapon forms store information about weapons. Different forms can be identified by their unique EditorID and FormID.
  
- FormID
+
===FormID===
 +
 
 +
 
 
A six-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.)
 
A six-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
+
===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.
 
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 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 executed every frame in which the scripted object (or its container, in the case of inventory objects) is loaded.
 
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 executed every frame in which the scripted object (or its container, in the case of inventory objects) is loaded.
  
- Operator
+
===Operator===
 +
 
 +
 
 
Operators are used to manipulate values, and are split into three categories:
 
Operators are used to manipulate values, and are split into three categories:
- Arithmetic operators:
+
 
 +
 
 +
===Arithmetic operators===
 +
 
  
 
+ Addition
 
+ Addition
 +
 
- Subtraction
 
- Subtraction
* Multiplication
+
 
 +
<nowiki>*</nowiki> Multiplication
 +
 
 
/ Division
 
/ Division
 +
 
% Modulo
 
% Modulo
  
 
Comparison operators:
 
Comparison operators:
 +
 
== Equal to
 
== Equal to
 +
 
!= Not equal to
 
!= Not equal to
 +
 
> Greater than
 
> Greater than
 +
 
< Less than
 
< Less than
 +
 
>= Greater than or equal to
 
>= Greater than or equal to
 +
 
<= Less than or equal to
 
<= Less than or equal to
  
 
Logical operators:
 
Logical operators:
 +
 
&& AND
 
&& AND
 +
 
|| OR
 
|| 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.
 
- 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.
Line 350: Line 483:
 
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:
 
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
+
A B A&&B A||B
false true false true
+
false false false false
true false false true
+
false true false true
true true true 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||B)-(A&&B)" in the place of "A XOR B".
 
- 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||B)-(A&&B)" in the place of "A XOR B".
Line 360: Line 495:
 
Keep in mind that when using complicated expressions it is helpful to include parentheses so that you and anyone reading your code can be certain of what order your operators are evaluated in.
 
Keep in mind that when using complicated expressions it is helpful to include parentheses so that you and anyone reading your code can be certain of what order your operators are evaluated in.
  
- Parameter
+
===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:
 
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
+
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 be given a default value. For example, the following two lines are equivalent:
 
- 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 be given a default value. For example, the following two lines are equivalent:
  
player.AddItem Caps001 10 0
 
player.AddItem Caps001 10
 
  
- Player
+
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" reference. 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 the EditorRefID of the "player" reference, no matter which data files are or aren't loaded.
 
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" reference. 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 the EditorRefID of the "player" reference, no matter which data files are or aren't loaded.
  
- Quest Script
+
===Quest Script===
 +
 
 +
 
 
Quest scripts can be attached to quest forms, and are not reference scripts. They cannot use reference-specific 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 can be attached to quest forms, and are not reference scripts. They cannot use reference-specific 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.
  
- Reference
+
===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:
 
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
+
*Reference-specific information
---- Information about the base object (the form that a reference is based on)
+
*Information about the base object (the form that a reference is based on)
 +
 
 +
===Reference Script===
 +
 
  
- 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.
 
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
+
===RefID===
 +
 
 
The FormID of a reference.
 
The FormID of a reference.
  
- True
+
===True===
 +
 
 
Non-zero
 
Non-zero
  
- Variable
+
===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:
 
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
 
- int / short / long
 +
 
Stores an integer (a whole number). Has a range from -2,147,483,648 to 2,147,483,647.
 
Stores an integer (a whole number). Has a range from -2,147,483,648 to 2,147,483,647.
 +
 
- float
 
- float
 +
 
Stores a floating point number (a number with a decimal point). Has a range including -3.402823x10^38 to -1.175494x10^-38, 0 and 1.175494x10^-38 to 3.402823x10^38
 
Stores a floating point number (a number with a decimal point). Has a range including -3.402823x10^38 to -1.175494x10^-38, 0 and 1.175494x10^-38 to 3.402823x10^38
 +
 
- ref / reference
 
- ref / reference
 
Stores a FormID. Usually used to store a RefID, hence the keyword ref/reference.  
 
Stores a FormID. Usually used to store a RefID, hence the keyword ref/reference.  
 +
 +
 +
 +
  
 
[[Category:Fallout 3]]
 
[[Category:Fallout 3]]
 
[[Category:GECK]]
 
[[Category:GECK]]
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]

Latest revision as of 22:18, 21 June 2011

This article is best viewed on cipscis.com - 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, shall we? The first thing you need to know about scripting in Fallout 3 is that every script (except for Result scripts, which will be explained in a later tutorial) must start with a ScriptName declaration, which declares the EditorID of the script.

Example

ScriptName MyFirstScript


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 MyFirstScript


Congratulations, you've written your first script! Of course, it's not particularly exciting - it doesn't actually do anything at all at the moment. Why don't we 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, but before we can put this in the script we need to tell it when we want it to run, which is done by using Begin/End blocks (again, except for in Result scripts).

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. As you can see, they are essentially highly specific conditional statements.

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 MyFirstScript
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. With the exception of Result scripts, all scripts are divided up into three types:

- Object

- Quest

- Effect

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, before we can use this function, we need to create a message form to use as a parameter. 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 this message:


ScriptName MyFirstScript
Begin OnAdd player
ShowMessage MyMessage
End


Now, 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 MyFirstScript
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 that one. 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 visualise the condition like this:


if player == player


The second part of the condition, "==" is an operator. The "==" 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, the code within the "if" statement will not run.

Now, as you can see, our script has pretty much exactly the same functionality as it had previously, although it is slightly less efficient. What we are going to do now is add an alternate 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



As you can see, 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 MyFirstScript
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 "elseif" 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 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. For example, what if we wanted to play a different message again (let's call it "MyDadMessage" and have it say "Hello Dad!") if a bobby pin is added to the player's father'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 MyFirstScript
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 tool 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 tool is known as an "elseif" statement, and can be used like this:


ScriptName MyFirstScript
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, as you can see, 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.

One of the most common general questions that I see asked about the scripting language used in Fallout 3 is "can we use switch/case?". If you haven't done programming before, then this won't really mean anything to you. "Switch/case" statements can be used in certain situations instead of "if" statements. While they do not provide any exra functionality whatsoever, they can make code much easier to read. 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" and "elseif" 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, 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 (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


As you can see, 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 are all named "Variable1", "Variable2" etc. then your script will probably be very difficult to understand.

Once we've declared our variable, 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, if we want to set our "rContainer" variable to the return value of GetContainer, we would do it like this:


set rContainer to GetContainer


As you can see, the "set" command is initiated with the keyword "set", which is followed by the name of the variable, which is followed by the "to" command, which is followed by an expression. The value of the variable will then be set to the result of that 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 scripted reference. Because of this, reference functions can only be called with implicit syntax in reference scripts. Reference functions called with implicit reference syntax use the exact 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 storing the reference's RefID.

- Reference functions called with explicit reference syntax specify a reference by prefixing the function name with either the reference's EditorRefID, or the name of a "ref" variable, 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 our script, we have already called a reference function - GetContainer. Because GetContainer only really works on inventory items, it should always be called with implicit reference syntax, just like we have done.

Anyway, where were we? Ah, that's right, we'd just declared a "ref" variable, called rContainer, and had 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, what if we wanted to 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 could do this by using explicit reference syntax to call AddItem on the reference, like so:


ScriptName MyFirstScript
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 (both RefIDs stored in "ref" variables and those represented by EditorRefIDs), the GetIsReference function should be used instead of using the "==" operator, so our script should look like this:

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


As you can see, 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 (e.g. "player.GetIsReference rContainer) and it would work just fine.



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 - it 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 specifying character to the end of the line. Many other computer languages also allow a type of comment known as a block comment, where another specifying character or string is used to signify the end of a comment, 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:


if GetOpenState == 3 ; Closed
...
elseif GetOpenState == 4 ; Closing
...
endif


As you can see, the comments here help 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 thread. That way, anyone reading the thread will only have to read this comment, 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.


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 several categories:


Effect


ScriptEffectFinish

ScriptEffectStart

ScriptEffectUpdate


Reference-specific


OnActivate

OnActorEquip

OnActorUnequip

OnAdd

OnCombatEnd

OnDeath

OnDestructionStageChange

OnDrop

OnEquip

OnHit

OnHitWith

OnLoad

OnMagicEffectHit

OnMurder

OnPackageChange

OnPackageDone

OnPackageStart

OnRelease

OnSell

OnStartCombat

OnTrigger

OnTriggerEnter

OnTriggerLeave

OnUnequip

SayToDone


General


GameMode

MenuMode


As you can see, the vast majority of blocktypes are reference-specific. Most of these blocktypes can only be used in 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.

EditorRefID

The EditorID of a reference.

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:


ScriptEffectStart

ScriptEffectUpdate

ScriptEffectFinish

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, Script forms store information about scripts, whereas Weapon forms store information about weapons. Different forms can be identified by their unique EditorID and FormID.

FormID

A six-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 executed every frame in which 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

+ 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||B)-(A&&B)" in the place of "A XOR B".

Keep in mind that when using complicated expressions it is helpful to include parentheses so that you and anyone reading your code can be certain of what order your operators are evaluated in.

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 be given a default value. For example, the following two lines are equivalent:


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" reference. 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 the EditorRefID of the "player" reference, no matter which data files are or aren't loaded.

Quest Script

Quest scripts can be attached to quest forms, and are not reference scripts. They cannot use reference-specific 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.

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

The FormID of a reference.

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.402823x10^38 to -1.175494x10^-38, 0 and 1.175494x10^-38 to 3.402823x10^38

- ref / reference Stores a FormID. Usually used to store a RefID, hence the keyword ref/reference.