22nd August 2010

Loops

Fallout 3's scripting language, unlike most programming and scripting languages, lacks many usual "flow control" features. However, the FOSE team has made two very useful functions available that allow us to better control the flow of our scripts. These functions are Label and Goto, and one of their most important uses is in the creation of loops.

This page contains templates for three common loops, and examples of specific uses of loops:

Before we go over the specific syntax of each loop, let's have a look at the syntax of Label and Goto. Both of these functions are non-reference functions, and take a single parameter, referred to as "LabelID" in the FOSE Documentation.

Label is used, as the name implies, to label a specific point in a script so that it can be targeted with Goto. Once a point in the script has been labelled, Goto can be used to redirect the script to the labelled point, as specified by its LabelID.

While

"While" loops are used to continually execute a section of code for as long as a specified condition is true. Here is some typical code for a "while" loop, in C++ syntax:

while (<condition>)
{
	<code>;
}

The condition specified in the "while" loop works in exactly the same way as in a conditional statement - if it is true then the code contained within it is executed. The main difference between a "while" loop and a conditional statement is that, once the code has been executed, the loop repeats itself, starting with re-checking the condition. Because of this, the code contained within the loop will be executed over and over again until the condition is no longer true.

Because the code contained within the loop will be executed over and over again until the condition is no longer true, it is very important that the code within the loop can cause the condition to become false, otherwise the loop will never cease and Fallout 3 will freeze. Keeping this all in mind, a "while" loop can be constructed using FOSE's Label and Goto functions like so:

Label 1
if <condition>
	<code>
	Goto 1
endif

Do while

A "do while" loop is very similar to a "while" loop. The only difference is that the code is executed before the condition is checked. In other words, the condition controls whether or not to run the loop again, as opposed to whether or not to execute the code contained within it. As a result of this difference, the code within a "do while" loop will always be executed at least once.

Here is some typical code for a "do while" loop, in C++ syntax:

do
{
	<code>;
} while (<condition>);

So, thinking again about the structure of a "do while" loop, here is how one could be written with Label and Goto:

Label 1
<code>
if <condition>
	Goto 1
endif

For

"For" loops are different to "while" and "do while" loops in that their duration is (normally) not determined by the code that they contain, but instead by the value of a controlling variable. Instead of just a condition, the start of a "for" loop consists of three parts:

  • Initialisation
  • Condition
  • Increment

The initialisation step occurs only once, when the loop first begins, and sets the controlling variable to an initial value, which may be the result of an expression. Then comes the condition, which is essentially the same as for a "while" or "do while" loop - if the condition is true, then the code runs, otherwise the loop is terminated. The increment step occurs after the code contained within the "for" loop has been run, and changes the value of the variable slightly.

Like with "while" and "do while" loops, you should take care to ensure that your "for" loops cannot become infinite loops. The combination of the initialisation and increment steps (and possibly the code within the loop) should always be able to render the condition false.

Here is an example "for" loop, in C++ syntax:

for (i = 0; i < 10; i++)
{
	<code>;
}

As you can see, provided that the code contained within the "for" loop does alter the value of the controlling variable, "i", it will run 10 times. On the 11th time, the value of "i" will be 10, rendering the condition false and terminating the loop.

So, now that we understand how a "for" loop works, here is how one can be written using Label and Goto:

<initialisation>
Label 1
if <condition>
	<code>
	<increment>
	Goto 1
endif

Examples

Here are some examples of useful ways to use loops in scripts. I've placed each example in a GameMode block, but if you want to use them in an actual script you'll almost certainly want to either use a different blocktype or some extra code to control when the loop is executed, as opposed to running it every time the script is executed.

Reference Walking

Using FOSE's reference walking functions, GetFirstRef/GetFirstRefInCell and GetNextRef, it is possible to loop through all references of a certain type within a cell or set of adjacent cells by using a for loop:

ref rCurrentRef

Begin GameMode

	set rCurrentRef to GetFirstRef 200 1 0 ; Actors

	Label 10
	if rCurrentRef
		; Do stuff with rCurrentRef

		set rCurrentRef to Pencil01 ; Prevent apple bug
		set rCurrentRef to GetNextRef
		Goto 10
	endif

End

One potential bug that you should be aware of when using a reference walking loop is the "apple" bug. In some cases, GetNextRef will not correctly update the variable that you are using to store the current reference. This can be fixed by setting this variable to an arbitrary value before calling GetNextRef.

In Oblivion, where the bug was first encountered, this arbitrary value was the "Apple" form, so the bug was called the "apple bug". In Fallout 3, I always use "Pencil01" as the arbitrary value, but I still call the bug the "apple bug" so that anyone familiar with it from Oblivion scripting will know what I'm referring to.

Inventories

As of FOSE v1.2, it is possible to loop through the objects in a container or an actor's inventory. The functions used to do this, which are not documented as of the 12th of July 2010, are GetNumItems and GetInventoryObject. Here is an example of how you could walk through the player's inventory with a for loop:

ref rCurrentItem
int iIndex

Begin GameMode

	set iIndex to player.GetNumItems - 1

	Label 10
	set rCurrentItem to player.GetInventoryObject iIndex
	if rCurrentItem
		; Do stuff

		set iIndex to iIndex - 1
		Goto 10
	endif

End

Form Lists

Form lists allow you to store arrays of forms, which can be incredibly useful. Some functions, like GetItemCount and RemoveItem can take a form list as a parameter in order to perform their function on multiple forms at once, and form lists can be used to store an array of references that have had a certain operation performed on them.

One of the most useful ways of using form lists is to create a list of items which have been affected by a script so that they can later be reverted. For example, this for loop will add all weapons within a 1 cell radius to a form list, and apply a shader effect to them:

ref rCurrentRef

Begin GameMode

	set rCurrentRef to GetFirstRef 40 1 0 ; Weapon

	Label 10
	if rCurrentRef
		rCurrentRef.PlayMagicShaderVisuals MyShader
		rCurrentRef.ListAddReference MyFormList

		set rCurrentRef to Pencil01
		set rCurrentRef to GetNextRef
		Goto 10
	endif

End

This while loop will take the information stored in "MyFormList", and use it to remove the shader effect from all affected weapons, as well as removing each reference from the form list once the shader effect has been removed:

ref rCurrentRef

Begin GameMode

	Label 10
	if ListGetCount MyFormList
		set rCurrentRef to ListRemoveNthForm MyFormList 0
		rCurrentRef.StopMagicShaderVisuals MyShader

		Goto 10
	endif

End

If you have any questions about the content of this article, or if you notice any errors, don't hesitate to contact me.