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:
- Player is rewarded with an additional perk once per level after half of the experience points required for next level are gained.
- Player is rewarded with an additional perk once per level after experience points are gained.
- 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,
- Obtaining a new perk rank activates
perk entry
Perk entry
does setquest
to initializerstage
- Initializer
stage
runs associated stagescript
- Stage
script
initializes quest scriptvariable
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