This is the fourth and final post of my tutorial
Become a Software Engineer by modding Fallout 3.
We are about to add the last two missing features to the Extra Perk:
(a) rewarding the Player with an
additional perk immediately after he gained more than half the
experience points required to advance to the next level and
(b)
reducing the experience points gained by 50%. Currently the
Extra Perk rewards the Player immediately when he levels up and
does not reduce the experience points gained.
Like in my previous posts, I assume you know everything I explained before.
If this is not the case, I would suggest you read those posts
(Part 1,
Part 2
and Part 3)
before continuing with this one. Ready when you are!
What to expect behind the end?
How does feature (a) change the
current behavior? At the moment, when the Player
picks the Extra Perk, he won't get an additional perk for his
current player level. But each time he levels up, he is rewarded with an
additional perk, even for the maximum player level.
Implementing the new feature implies that we have to delay when to reward the
Player with an additional perk. So when he picks the Extra Perk
we have to reward him with an additional perk for the same level. But when
he hit the maximum player level, he won't gain any more experience points.
Hence we can't reward him with an additional perk for the maximum player level.
The moment when to reward the Player with an additional perk has shifted one
half player level to the left. This is an important fact to consider when
defining the criterion of when to terminate the execution of the
QWEExtraPerkQuestScript
(responsible to detect if the Player
has gained enough experience points for an additional perk).
In the middle of nowhere
The formula for how to calculate the amount of experience points required,
before gaining an additional perk is:
The function xp(playerLevel)
returns the amount of
experience points required to hit the player level playerLevel
. You may
wonder why we calculate the amount of required experience points in such a
complicated way. A much simpler formula than the one above, which returns
the same result in a perfect world, is:
(xp(playerLevel) + xp(playerLevel + 1)) / 2
.
The reason why we choose the more complicated variant is to prevent a potential
arithmetic overflow.
Most data types
define a minimum and maximum value they are capable to represent. If we add
some value greater than zero to the maximum value or subtract it from the
minimum value, we get confronted with either an
arithmetic overflow
or
arithmetic underflow
exception or the calculation
results in a value we did not expect. Hence, ensuring every calculation
produces only values that are inside the boundaries defined by the
data type of the result is very important.
The second formula adds two values and divides the result by 2.
Adding two values may result in an arithmetic overflow if both values
are positive and large enough or in an arithmetic underflow if both
values are negative and small enough.
In contrast to the second formula, the first formula starts with a
subtraction:
r1 = xp(playerLevel + 1) - xp(playerLevel)
whereas the first value to subtract from is a larger positive value than the
second value to be subtracted. This calculation always results in a positive
value smaller than the first value of the subtraction. Therefore the result
r1
is inside the boundaries defined by the
data type.
The first formula continues with a division:
r2 = r1 / 2 < r1
Dividing a positive value by a positive value greater than or equal to 1
results in either zero (due to truncation) or a positive value not larger
than the value that got divided. This implies r2
as
well is a value that is inside the boundaries, defined by the
data type.
The last calculation performed by the first formula adds the result
r2
to the second value of the first calculation:
r3 = xp(playerLevel) + r2 < xp(playerLevel) + r1 = xp(playerLevel + 1)
The value of r2
is smaller than twice its value, which
is equal to r1
, and if added instead results in the
first value of the first calculation. And because this first value already is
inside the boundaries defined by the data type, the value
calculated by the first formula is always inside those boundaries as well.
The more complicated calculation has an advantage over the simpler calculation:
it is fail-safe.
A shift to the left
We are almost ready to add the feature defined by
(a) to the Extra Perk. But
there is one last thing to decide first: when do we have to calculate the
amount of experience points required by the Player, before he gets rewarded
with an additional perk?
The first time we have to calculate that amount is when the Player picks the
Extra Perk (which means in the
initializer
of the QWEExtraPerkQuest
,
the quest stage with an index of 0). And afterwards we have to
calculate that amount every time when the Player was rewarded with an
additional perk (in the function rewardXP
defined by the QWEExtraPerkQuest
, the quest stage
with an index of 100). But we must not calculate the next amount when
the Player got rewarded for the second last possible player level. This amount
would be greater than the amount required to hit the last possible player
level, which could cause an arithmetic overflow, right? So beware
not to!
The formula
for calculating the amount of experience points required to advance to a given
player level is based on two game setting values:
iXPBumpBase
and iXPBase
. Hence, when calculating the
amount of experience points required we have to obtain those game settings
using the G.E.C.K function
getGameSetting
 <variable name>
.
Let's add a quest stage with an index of 102 to the
QWEExtraPerkQuest
, acting as a function. I will refer
to it by the name definePerkXP
.
This function will calculate the amount of experience points required by the Player, before
he gets rewarded with an additional perk. The result of the calculation will be stored in the variable
perkXP
, which we have to add to the
QWEExtraPerkQuestScript
. Make it look like the image to the left.
To address the changed criterion about when to terminate the
QWEExtraPerkQuestScript
, we have to move the conditional block
responsible (demarcated by the lines
09:
if
and
11:
elseif
) to
the top. This ensures that no calculation happens that is based on values
not properly set. The conditional block can be interpreted as: if the Player
has to be rewarded for the maximum player level or above, terminate the
script.
Further the statement: 14:
setStage
QWEExtraPerkQuest
102
was added to the second conditional block (demarcated
by the lines 11:
elseif
and 15:
endif
),
which updates the value of the variable perkXP
whenever the Player got rewarded with an additional perk. The function
definePerkXP
will not calculate a value for
the variable perkXP
if the player
level is either lesser than one or if it is equal to or greater than the
maximum player level.
Faster than light
Let's develop the quest stage function definePerkXP
.
It mainly relies on the function xp(plvl)
,
which calculates the experience points required to reach the player level
plvl
. The function xp(plvl)
is defined as:
xp(plvl) = (plvl - 1) * ((plvl - 2) * iXPBumpBase / 2 + iXPBase)
The new value we are about to calculate and assign to the variable
perkXP
(as described above)
depends on two different player levels: the player level plvl
for which to reward the Player with an additional perk and the next player
level plvl + 1
the Player may reach.
Because of the fact, the G.E.C.K. does not allow defining the formula
as a real function and because we do not want to introduce too many
variables acting only as a sharing mechanism for temporary values across
different scripts, we embed the formula into the function
definePerkXP
.
The new value to calculate is:
perkXP
= xp(plvl) + (xp(plvl + 1) - xp(plvl)) / 2
The value of each function call is:
xp(plvl) = (plvl - 1) * ((plvl - 2) * iXPBumpBase / 2 + iXPBase)
xp(plvl + 1) = plvl * ((plvl - 1) * iXPBumpBase / 2 + iXPBase)
Performing this computation straight forward requires 10 additions or
subtractions, 10 multiplications or divisions and 6 function calls. To avoid
unnecessary computations we will optimize the calculation. The expression
iXPBumpBase / 2
occur in both function calls. Hence we
will compute the expression only once and use the result instead:
bump
= 0.5 *
getGameSetting
iXPBumpBase
xp(plvl) = (plvl - 1) * ((plvl - 2) * bump
+ iXPBase)
xp(plvl + 1) = plvl * ((plvl - 2) * bump
+ iXPBase + bump
)
The next expression that occurs in both function calls is
(plvl - 2) * bump
+ iXPBase
.
Computing this expression as well only once will further optimize the calculation:
base
= (plvl
- 2) *
bump
+
getGameSetting
iXPBase
xp(plvl) = (plvl - 1) * base
xp(plvl + 1) = (plvl - 1) * base
+ base
+ plvl * bump
The last optimization left is to calculate the expression
(plvl - 1) * base
only once:
xp
= (plvl
- 1) *
base
xp(plvl) = xp
xp(plvl + 1) = xp
+ base
+ plvl * bump
Replacing the function calls of the formula with the optimized
calculations results in the following final formula:
perkXP
= xp(plvl) + (xp(plvl + 1) - xp(plvl)) / 2
perkXP
= xp
+ (xp
+ base
+ plvl * bump
- xp
) / 2
perkXP
= xp
+ (base
+ plvl * bump
) / 2
We reduced the number of required additions or subtractions from 10 to 5, the
number of required multiplications or divisions from 10 to 5 and the number of
required function calls from 6 to 2.
Just do it
With these optimizations in place, we are ready to implement the quest stage
function definePerkXP
. Open the
G.E.C.K., select the quest stage 102 and make the
associated script look like the image below.
The conditional block demarcated by the lines
07:
if
and
09:
endif
prevent the script from computing a value if the player level is outside of
the boundaries defined by the function xp(plvl)
.
The Player starts with level 1 and can't pick the Extra Perk
before he reaches player level 2. Apart from that the Player can't be rewarded
any further with additional perks after he got rewarded for the second last
player level. Lines 10:
-
13:
compute the new value for the variable
perkXP
.
What's left is to initialize the newly introduced variable
perkXP
when the Player picks the
Extra Perk. Modify the initializer
(the quest stage with an index of 0) and make it look like the
image below.
Save
all the changes in the G.E.C.K. and start Fallout 3.
Perform the following actions to verify that things work as intended:
- Load a save-game where the Player does not already possess the
Extra Perk.
- Level the Player up by issuing the command
advLevel
using the in-game console.
- Pick the Extra Perk.
- Reward the Player with an amount of experience points, close to but
lesser than the amount needed to hit the next player level, by
issuing the command
rewardXP <amount>
using the in-game console.
The Player should be rewarded with an additional perk although he did not
hit the next player level.
We should verify once more that the QWEExtraPerkQuest
is
terminated immediately after the Player has been rewarded with an additional
perk for the second last player level:
- Load a save-game where the Player does not already possess the
Extra Perk.
- Set the level of the Player to the third last possible player level
(which is either 18 without Broken Steel or 28 with
Broken Steel installed) by issuing the command
player.setLevel <level>
using the in-game console.
- Execute the actions 2 - 4 defined above once again.
- Issue the command
showQuestVars QWEExtraPerkQuest
using the in-game console.
You should observe that the QWEExtraPerkQuest
is not running anymore.
Keep the World in balance
The final missing feature is the
reduction of experience points by 50%. It is an attempt to balance the benefit
of one additional perk per level by doubling the time required to hit the next
player level.
This can be achieved by intercepting the game engine mechanism which rewards
the Player with experience points. A perk is capable of intercepting this
mechanism besides many others. The interception is defined in terms of a
Perk Entry which in our case reduces the experience points to
reward by multiplying it with 0.5
.
Start the G.E.C.K. one last time and select the
QWEExtraPerk
. Add a new Perk Entry defining an
Entry Point which adjusts the experience points rewarded by
multiplying it with 0.5
. Make it look like the image below and
save
all changes.
Start Fallout 3 and perform the following actions to verify that the
experience points gained are half the experience points rewarded:
- Load a save-game where the Player does not already possess the
Extra Perk.
- Level the Player up by issuing the command
advLevel
using the in-game console.
- Pick the Extra Perk.
- Open the pipboy and remember the amount of experience points the
Player currently has.
- Reward the Player with experience points by issuing the command
rewardXP 10
using the in-game console.
Open the pipboy once again and verify that the amount of experience points the
Player has is by 5
greater than the amount of experience
points the Player had in step 4.
That's it. Congratulations! You implemented every feature of the
Extra Perk.
Conclusion
Finally we implemented all the requirements as stated in
Part 1
of this tutorial. When computing new values we took care not to cause
arithmetic underflows or overflows by respecting the
boundaries as defined by the data type the computation was based
on. Further we protected our scripts from computing new values when certain
pre-conditions were
not satisfied. To avoid wasting valuable resources, we optimized the
computation of values. We used the interception mechanism provided by perks to
modify the experience points before they were rewarded to the Player.
I hope you enjoyed this final post and all my previous ones. Feel free to
contact me either by e-mail or leave
a comment on the
Fallout 3 Nexus Site.
If you made it this far and liked my tutorial, please consider to
endorse it.
The End
Logged Off: Harmlezz