I've been toying with the idea of what it would take to be able to have mods provide custom perks, since this is one of the areas that is pretty much hardcoded into the BOS.EXE.
I was able to find the perk table in the EXE, which controls the minimum level, minimum stats/skills, and restricted/required races. This table also provides everything necessary for applying "simple" perks, which boost a single stat/skill. So modifying these aspects is not too difficult. However, I wanted to see if I could make behavioral changes to a new perk, as I had done with the fix for Team Player.
To do this, I set up a basic extension engine, using a similar idea to the FT Improver project. A small edit to the EXE allows it to load my DLL, which can then make changes to the executable from inside the same process space. This allows 1) installing any other hex patches without needing to modify the EXE, and 2) adding hooks to "interesting" locations in the code, which can then fire off custom behavior. I then tied these hooks to a Lua interpreter inside the DLL.
I then set about trying to create the following perk:
Hulk Smash!
Being radiated makes you very angry. Gain +2 Strength and +75 Radiation Resistance when in a radiated area.
Ranks: 1
Requirements: Level 16, EN 5, Doctor 50%
The result is code similar to the following:
The above adds the new entry to the perk table. This entry replaces perk #18, which has the internal name of "fortuneFinder", and is presently unused. Note that you'll want to add the appropriate strings to the localization and gui resources, so that the proper name, text, and image for the perk appear.
I then added a hook for when an actor takes radiation damage:
Note that, internally, entities have both a "permanent" and "temporary" set of attributes. The code currently doesn't use the "temporary" copy of the perks or optional traits, so I can reuse those bytes to hold information, in this case a counter to set how long the applied bonus lasts.
Finally, I need a way to turn the perk off at the right time. For this, I added a trigger to the "long" tick timer, which fires for each entity after 10 seconds (in real-time mode; turn-based mode adds 7 seconds to this timer for every complete turn):
Now, to test it:
We use Rex, from Tutorial 1, as our guinea pig. First, cheat him up to the right level. Then, check to see if the perk can be applied:
Not yet - he has high enough level and EN, but doesn't have Doctor skill of 50%. After adding to that skill and reopening the perk list:
There it is. Now select it:
And walk over to the conveniently added radiation pit. After a few seconds:
Our message appears. And checking the character stats, the bonuses are applied:
Now, to test deactivation, we walk away from the radiation and wait a minute:
And the stats:
So it works!
The engine isn't ready for actual use in a mod yet - it still needs a bit of error handling (right now, if there's an error, you're lucky if the Lua engine just fails to start; most of the time you end up with a crash). It will also need quite a few more trigger points to allow for more flexible custom perk behavior. But it's a good start as a proof-of-concept.
I've posted the code for the DLL extender, and an installer to patch the BOS.EXE, to the following Github:
https://github.com/melindil/FOTExtender
(Up-front: Feel free to take the code, use it as a baseline for another project, mod, or otherwise.)
I was able to find the perk table in the EXE, which controls the minimum level, minimum stats/skills, and restricted/required races. This table also provides everything necessary for applying "simple" perks, which boost a single stat/skill. So modifying these aspects is not too difficult. However, I wanted to see if I could make behavioral changes to a new perk, as I had done with the fix for Team Player.
To do this, I set up a basic extension engine, using a similar idea to the FT Improver project. A small edit to the EXE allows it to load my DLL, which can then make changes to the executable from inside the same process space. This allows 1) installing any other hex patches without needing to modify the EXE, and 2) adding hooks to "interesting" locations in the code, which can then fire off custom behavior. I then tied these hooks to a Lua interpreter inside the DLL.
I then set about trying to create the following perk:
Hulk Smash!
Being radiated makes you very angry. Gain +2 Strength and +75 Radiation Resistance when in a radiated area.
Ranks: 1
Requirements: Level 16, EN 5, Doctor 50%
The result is code similar to the following:
Code:
function OnStart ()
logger:log("Entered OnStart")
newperk = {name = "hulkSmash",
minlevel = 16,
maxperktaken = 1,
requiredstat1 = "doctor",
requiredamt1 = 50,
requiredEN = 5
}
hookexecutor:ReplacePerk(newperk, 18)
end
The above adds the new entry to the perk table. This entry replaces perk #18, which has the internal name of "fortuneFinder", and is presently unused. Note that you'll want to add the appropriate strings to the localization and gui resources, so that the proper name, text, and image for the perk appear.
I then added a hook for when an actor takes radiation damage:
Code:
function OnRadiated(e)
logger:log("Radiated: " .. e["name"])
if e:HasPerk("hulkSmash") then
logger:log("Radiated and has perk: " .. e["name"])
if e:GetTempPerkValue("hulkSmash") == 0 then
logger:log(e["name"] .. " gains hulkSmash")
e:DisplayMessage("<Cg>HULK SMASH!")
bonus = {strength=2, radiationResist=75}
e:ApplyTempBonus(bonus)
end
e:SetTempPerkValue("hulkSmash",6) -- # of big ticks to keep buff
end
end
Note that, internally, entities have both a "permanent" and "temporary" set of attributes. The code currently doesn't use the "temporary" copy of the perks or optional traits, so I can reuse those bytes to hold information, in this case a counter to set how long the applied bonus lasts.
Finally, I need a way to turn the perk off at the right time. For this, I added a trigger to the "long" tick timer, which fires for each entity after 10 seconds (in real-time mode; turn-based mode adds 7 seconds to this timer for every complete turn):
Code:
function OnLongTick(e)
curr = e:GetTempPerkValue("hulkSmash")
if curr > 0 then
curr = curr - 1
if curr == 0 then
logger:log(e["name"] .. " hulkSmash expired")
e:DisplayMessage("Normal")
bonus = {strength=2, radiationResist=75}
e:RemoveTempBonus(bonus)
end
e:SetTempPerkValue("hulkSmash",curr)
end
end
Now, to test it:
We use Rex, from Tutorial 1, as our guinea pig. First, cheat him up to the right level. Then, check to see if the perk can be applied:
Not yet - he has high enough level and EN, but doesn't have Doctor skill of 50%. After adding to that skill and reopening the perk list:
There it is. Now select it:
And walk over to the conveniently added radiation pit. After a few seconds:
Our message appears. And checking the character stats, the bonuses are applied:
Now, to test deactivation, we walk away from the radiation and wait a minute:
And the stats:
So it works!
The engine isn't ready for actual use in a mod yet - it still needs a bit of error handling (right now, if there's an error, you're lucky if the Lua engine just fails to start; most of the time you end up with a crash). It will also need quite a few more trigger points to allow for more flexible custom perk behavior. But it's a good start as a proof-of-concept.
I've posted the code for the DLL extender, and an installer to patch the BOS.EXE, to the following Github:
https://github.com/melindil/FOTExtender
(Up-front: Feel free to take the code, use it as a baseline for another project, mod, or otherwise.)