13 October 2012

Become a Software Engineer by modding Fallout 3 - Tutorial Part 2

This is the second part of my tutorial Become a Software Engineer by modding Fallout 3. If you missed to read the first part I would like to suggest that you read it before continuing, because this post expects you know already what I explained there.

In this second post I am about to explain how to add behavior to the Extra Perk mostly by developing scripts. On top you will get an introduction on how to install and use a Source Code Management System, which is capable of maintaining a persistent history of important states our mod was in. It enables us to restore the mod to any of those states later on. This tool eliminates the fear of breaking our mod when altering it while exploring new functionality or debugging strange behavior. Doesn't this sound like a comfortable position to be in?

If you intend to develop the Extra Perk (which I use as an example throughout this tutorial) you should set up a development environment as described in the first part and implement the features of the yet-do-nothing-perk to catch up to the point where this tutorial continues.

Let's recap what to expect from the perk

The requirements can be stated as follows: starting from the moment the Player picks the Extra Perk, he has to be rewarded with an additional perk once per player level, as soon as he gained at least half of the experience points needed to reach the next level. At the same time all experience points he gains have to be reduced by 50%.

Sticking to the Top-Down approach we have to think about the next smallest feature of value we have a good idea of how to implement it. Most often picking a major feature and reducing it repeatedly produces a small feature candidate. Let's try:

  1. Player is rewarded with an additional perk once per level after half of the experience points required for next level are gained.
  2. Player is rewarded with an additional perk once per level after experience points are gained.
  3. Player is rewarded with an additional perk once per level.

The last statement seems much less complicated than the first statement while still being part of the original feature. Another possible outcome as the last statement would have been:

  • Player is rewarded with an additional perk after experience points are gained.

Both statements are equally valid but the one above would be a little bit annoying (gaining so many additional perks). Therefore we pick the requirements of statement 3. as our next feature set to be added to the Extra Perk.

What does the blueprint look like?

In theory we would like to register some callback to the main loop (run by the Fallout 3 game engine) at the moment when the Player picks the Extra Perk, to be called on the event when the Player levels up. The functionality of the callback would be to call a function (provided by the G.E.C.K. API) showing up the Perk-Selection-Dialogue which is part of the level-up dialogues we all are familiar with.

But sadly the world is not shaped as we might wish. Using the mechanisms provided by the G.E.C.K. makes things a bit more complicated. Take a brief look at the diagram below:


What you see is the collaboration structure of four objects (belonging to three different object types) involved in detecting when the Player levels up. Let's have a closer look at each object and its responsibility.

The rectangle labeled Extra Perk is the perk object we created in Part 1 of this tutorial. It has a single responsibility: to start the script Extra Perk Quest Script which repeatedly checks if the Player has leveled up. But a perk object can't start a script by itself so we have to find another way to accomplish this.

Scripts are always part of a quest, an item or an effect object. Because of the fact, the Extra Perk has to start tracking the current level of the Player as soon as the Player picks the Extra Perk and stop tracking when the Player has reached the maximum player level, a quest is the proper object to attach the Extra Perk Quest Script to.

The rectangle labeled Extra Perk Quest is an object of type quest. Quests can be started either explicitly by calling the function startQuest or implicitly by setting the quest to one of its stages. When a quest gets started, the script attached to it will be run repeatedly until the quest is stopped. Knowing this, we can rephrase the responsibility of the Extra Perk object as: to start the Extra Perk Quest. This can be achieved by adding a perk entry which will set the quest to a stage, the one acting as the quest initializer, when the Player picks the Extra Perk.

A quest may define many stages to track the Player's progress. The Extra Perk has no need to track any progress but instead it needs to remember the level of the Player at the moment when he picked the Extra Perk. Because this has to be done only once, we will add a stage to the quest acting as an initializer, setting the variable, defined by the Extra Perk Quest Script, to the current level of the Player. The functionality of the initializer is defined by another script, embedded into the quest and associated with the initializer stage.

Perhaps you are now overwhelmed by my explanations, but be assured that things are not as scary as they might sound. I will guide you through everything in detail now, so: don't panic!

Starting the quest when picking the perk

To enable the Extra Perk object to start the Extra Perk Quest we have to create a quest object first. Start up the G.E.C.K. and mark the Extra Perk.esp as the Active File. Hit the button which will load the Extra Perk.esp file as well as the Fallout3.esm file (which is the Parent Master of the former).

Once all objects defined by both files are loaded, type QWE into the Filter field of the Object Window to filter out all objects not part of our Extra Perk mod. Select the object category Quest, which is a child of the object category Actor Data, right-click on the right panel and choose New from the context menu.

First we have to give the new quest object an Editor ID. To do that select the Quest Data tab and type QWEExtraPerkQuest into the field labeled ID. The Editor ID will be used later on by the Extra Perk object as a handle to refer to the Extra Perk Quest object.

Than we have to add a stage to the quest which for the moment will only start the Extra Perk Quest when set by the Extra Perk object. To achieve this select the Quest Stages tab, right-click on the left panel and choose New from the context menu. Leave the index number at 0 and hit the button.

The last thing we have to do is triggering the start of the Extra Perk Quest on the event when the Player picked the Extra Perk. This can be achieved by adding a quest perk entry to the Extra Perk object. Select the object category Perk which should show only one perk object on the right panel (the QWEExtraPerk) due to the QWE filter applied. Double-click the QWEExtraPerk to open the perk dialogue. Place the mouse inside the Perk Entries panel and right-click. Select New from the context menu.

Select the radio button labeled Quest and select from the drop-down menu the QWEExtraPerkQuest. Make sure the Rank is set to 1 and the Stage to 0. Hit the button. The Perk Entries panel should show one entry of type Quest (make sure your entry looks like the image displayed below).

 
Hit the button once again and save the mod. We are ready to load and test the mod. Start Fallout 3 loading the Extra Perk.esp mod as described in Part 1 of this tutorial. Load a proper save-game where the Player has not already picked the Extra Perk.

Open the in-game console and submit the following command: showQuestVars QWEExtraPerkQuest. The console should print the current state of the quest, indicating that the Extra Perk Quest is not running.

Submit the following command next: advLevel. This should level you up. Distribute your skill points and pick the Extra Perk.

Submit the command which shows the quest variables once again (as described above). Now the console should print the current state of the quest, indicating that the Extra Perk Quest is running.

We achieved our first goal, starting the quest when the Player picked the Extra Perk. Let's proceed with adding the script which repeatedly checks if the Player leveled up.

Running a script repeatedly

We managed to start the Extra Perk Quest. But without attaching a script to it no tracking will happen. Running a script repeatedly allocates a portion of available CPU time. We should always think about when to release a critical resource before even acquiring it. Consuming CPU time when none is required is as bad as allocating too much of it.

As soon as the Player reaches the maximum possible level (20 for vanilla Fallout 3 and 30 with Broken Steel installed) we should stop the Extra Perk Quest which implies not running the Extra Perk Quest Script anymore.

Next we have to answer the question: how long may we delay rewarding the Player with his additional perk? The answer will define the amount of seconds we wait between two checks, detecting if the Player leveled up.

In my opinion 10 seconds should be fine, because while in combat nobody wants to get disturbed by a Level-Up menu displayed. And outside of combat nobody should really care about getting his additional perk 10 seconds later if it is unlikely that meanwhile a fight might happen. And gaining experience points from fighting is the main source to consider.

Having found two important answers, we are now ready to implement tracking the Player's current level. Start the G.E.C.K. as always and ease your development by using QWE as the infix to filter objects displayed in the Object Window. Select the object category Script, which is a child of the object category Miscellaneous. Place your mouse inside the right panel, right-click and choose New from the context menu.

An empty scripting editor should open up. To define the Editor ID for a script the first line must start with the command ScriptName followed by one space and the Editor ID we want the script to be identified by. To be able to attach the script to a quest object, we have to change the Script Type to Quest, using the drop-down list located in the middle of the scripting editor's toolbar. Hit the save button located on the left side of the scripting editor, which will compile the script but does not save it! Leave the scripting editor and hit the save button of the G.E.C.K. to save the newly created script object to the Extra Perk.esp file. We just created the yet-do-nothing Extra Perk Quest Script.

Open the Extra Perk Quest object by double-clicking it. Located at the top-middle of the dialogue window you will find a drop-down list labeled Script. Select the entry named QWEExtraPerkQuestScript to attach the Extra Perk Quest Script to the Extra Perk Quest. Just below the drop-down list you will find a check-box labeled Script Processing Delay. If checked, uncheck it and enter the number 10 into the field labeled Default to the right. Hit the button and save the changes to the Extra Perk.esp file.

By ticking the check-box, the script attached to the quest will run every 5 seconds (the initial default value of fQuestScriptDelayTime defined by the file FALLOUT.INI). In my opinion it does not make much sense to rely on a default value that might change. When you have to decide if the default value is appropriate, you need to know to which value it is currently set. If changed later on, the criterion we used to make our decision is of no value anymore. Why would we ever want to use the unreliable default value? I do not know.

Taking care of critical resources

The Extra Perk Quest Script will run every 10 seconds after the Extra Perk Quest was started, infinitely! To change this we have to add an execution path to the script, to stop the Extra Perk Quest as soon as the Player reaches the maximum player level. Let's discuss the script in detail.

Line 1: int declares a variable of type integer, which can be referenced by the identifier maxPlayerLevel (within the same script). A variable is a storage location able of holding information of some type, which can be read and modified. In our case the information is an integer value. When declared, the value of an integer variable is 0. Later on we will assign the maximum player level to the maxPlayerLevel variable, immediately before starting the Extra Perk Quest.

Line 3: begin and 7: end demarcates a block of statements to be executed sequentially from top to bottom. A script may be composed of more than one block, each defining a different block type. The block type acts as a condition, expressing the expectation about a context to exist, when the statements of the block are executed.

The block type GameMode defines the context when (for example) the Player is exploring the world by moving around or is fighting enemies. It seems that GameMode is the proper block type for our script, because it is the same context Fallout 3 chooses to display the Level-Up dialogue. Rewarding the Player with an additional perk will use that dialogue as well.

Line 4: if and 6: endif demarcates a conditional block, to be executed only if the boolean expression (the condition) evaluates to true. In our case the boolean expression is player.getLevel >= maxPlayerLevel.

The identifier player is globally available in all scripts. It is a variable of type reference, holding the memory address the player object is stored at. The dot separates the identifier from the function to be called, using the reference as a parameter. Literally player.getLevel means: get the current level of the Player. And more general R.F means: call the function F and pass the reference R as an input parameter to F.

The operator >= checks if the term to its left is either greater than or equal to the term to its right. Therefore the statements inside the if - endif block are executed only if the level of the Player is greater than or equal to the value stored in the variable maxPlayerLevel (which we will initialize to the maximum possible player level later on).

You may ask why we use the operator >= instead of the operator ==, which checks if the term to its left is equal to the term to its right, when the player level can't be greater than maxPlayerLevel? This is the result of defensive programming. If the program (Fallout 3 and all the plugins installed) has a bug, and the player level is raised above the maximum player level (which some plugins do), we could miss the moment when to stop the script. The world is not perfect, so better be safe than sorry.

When the Player reaches the maximum player level, we stop the quest. We don't like to reward the Player with additional perks. Stopping the quest implies stopping the script, which means we stop consuming valuable CPU time. The statement stopQuest QWEExtraPerkQuest does exactly that. The function stopQuest expects one input parameter, a handle to the quest to be stopped.

Add all lines to the Extra Perk Quest Script. If you can't remember how to, read the previous chapter once again. Don't forget to save the script (which only compiles it) and save the mod.

Initializing the quest script

What's left is initializing the quest script. It won't work correctly as long as the variable maxPlayerLevel is not set to the maximum player level.

There are two common approaches to implement a do-once-task: embedding the initialization code into the script defining the variable, guarded by a conditional block that ensures the initialization takes place exactly once, or defining a stage, acting as the script initializer, triggered when the quest gets started.

The first approach increases readability by placing the initialization code close to the code where the variable is defined. But it requires to define an additional variable (acting as a flag) only to track if the initialization took place or not. It also introduces a conditional block which gets evaluated every time the script is run.

The second approach increases complexity while distributing the initialization code and the definition of the variable to two different places. But if the execution stack is easily remembered,

  1. Obtaining a new perk rank activates perk entry
  2. Perk entry does set quest to initializer stage
  3. Initializer stage runs associated stage script
  4. Stage script initializes quest script variable

each object participating in the collaboration has a well-defined responsibility. The do-once-task gets executed once without the need to add additional code to make this happen. Less code means fewer opportunities to add bugs as well as better maintainability. Reuse of mature code is less likely to have bugs than code freshly introduced.

We already added the initializer stage (the one with an index of 0) to the Extra Perk Quest, triggering the start of the quest. Now we will associate a script with the stage, getting executed when the quest is set to the stage.

The script consists of only one statement (as shown above) which initializes one variable. The pair of commands set and to assigns a value to a variable.

The variable we want to assign a value to is defined in a different script, so we have to refer to it using a fully qualified name. A script of type Quest is treated as belonging to the quest it is assigned to (each quest gets its own instances of the variables defined by the script). Therefore we have to qualify the variable identifier maxPlayerLevel by the Editor ID QWEExtraPerkQuest, being the handle of the quest, the script belongs to.

The value we want to assign to the variable is a value defined by the variable iMaxCharacterLevel, part of the game settings of Fallout 3. Those variables can be accessed using the function getGameSetting expecting one input parameter, the identifier of the variable for which to return its value. So identifiers of variables part of the game settings do not fetch their values, but are used as handles passed to the function getGameSetting which fetches their values instead.

It is questionable if caching the value of the game setting variable iMaxCharacterLevel is the right thing to do. If the value would be changed (for example by installing the DLC Broken Steel), the value stored in the variable QWEExtraPerkQuest.maxPlayerLevel would remain unchanged. I leave it to you to remove the definition of the variable maxPlayerLevel from the QWEExtraPerkQuestScript and replace all its occurrences with the function call getGameSetting iMaxCharacterLevel as a practice. The tutorial will continue using the variable maxPlayerLevel to explain how scripts associated with stages work.

Open the QWEExtraPerkQuest object in the G.E.C.K. by double-clicking it and switch to the stage tab by clicking the button at the top of the window. Select the stage having an index of 0 (which acts as the initializer), place the mouse inside the Quest Stage Items panel to the right, right click and select New from the context menu showing up. Make sure the newly created Log Entry it is selected and open the associated script by clicking the button located to the right of a panel labeled Result Script.


Add the single statement to the embedded stage script and hit the button. Close the quest object dialogue and save the mod.

Start Fallout 3 loading the Extra Perk.esp as well. Load a proper save-game and enter the in-game console once again. Submit the command: advLevel and pick the Extra Perk after distributing all skill points. Now submit the command: sqv QWEExtraPerkQuest which should display the value of the maxPlayerLevel variable together with the running status of the quest. The variable was initialized by the script associated with the quest stage having an index of 0.

Now submit the command: player.setLevel 30 (or 20 if Broken Steel is not installed). Leave the console and wait about 10 seconds. Enter the in-game console again and check the quest variables one more time. You should be able to inspect that the quest is not running anymore. If this is the case, the statements defined by the conditional block of the QWEExtraPerkQuestScript were executed, as intended. Congratulations!

Conclusion

We established the collaboration structure composed of four objects, required to repeatedly run a script, started when the Player picked the Extra Perk and stopped when the Player reached the maximum player level. We took care to acquire not too much CPU time and stopped consuming CPU time as soon as the perk completed his task.

I promised to explain more in this post than I have. But the post has already grown to a size much larger than anticipated by me (although I skipped some topics about software engineering I would have liked to explain). The other topics will not be skipped but postponed to Part 3 of this tutorial.

I hope you enjoyed this post nevertheless. Feel free to contact me either by e-mail or leave a comment at the Fallout 3 Nexus Site. If you think this tutorial is worth to be recommended, please consider to endorse it.

Logged Off: Harmlezz