Experiments on custom perks

Melindil

It Wandered In From the Wastes
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:

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:

fbos0008.jpg

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:

fbos0009.jpg

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:

fbos0010.jpg

There it is. Now select it:

fbos0011.jpg


And walk over to the conveniently added radiation pit. After a few seconds:

fbos0012.jpg


Our message appears. And checking the character stats, the bonuses are applied:
fbos0013.jpg

Now, to test deactivation, we walk away from the radiation and wait a minute:

fbos0014.jpg

And the stats:

fbos0015.jpg


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.)
 
What? What? What? You are MY CHAMPION! :D I'll stay tuned for more updates about this prowess and will be one of your tester when I'll get back on the working table next month. Fantastic job here!
 
He I'm back to work on my mod! So I can't wait to see and try your Extender, and I'll try the Hex hacks in the next days. I have another question regarding perks : It is possible to modify or add perks, splendid. But could there be (eventually) a way, with the Extender, to control perks from the engine code? I mean, under certain circumstances, give a perk to the main player? They do that a lot in all the other Fallout (childkiller, grave digger, vault city inoculations, etc.), as easter eggs mostly, and this is a fantastic way to reward the player... Thanks!
 
He I'm back to work on my mod! So I can't wait to see and try your Extender, and I'll try the Hex hacks in the next days. I have another question regarding perks : It is possible to modify or add perks, splendid. But could there be (eventually) a way, with the Extender, to control perks from the engine code? I mean, under certain circumstances, give a perk to the main player? They do that a lot in all the other Fallout (childkiller, grave digger, vault city inoculations, etc.), as easter eggs mostly, and this is a fantastic way to reward the player... Thanks!

Yes, this will be possible. With the current code, it's almost possible to do a limited form of this already - just need to add one extra function for modifying permanent attributes (almost identical to adding temporary modifiers, just needs one offset change), and a trigger/hook for the condition which should apply the bonus perk. The one trigger that I haven't added yet, that's probably key, is to fire based on a mission or campaign variable being set. This would give at least one connection between the existing FoT script behaviors and the new extender behaviors. Being able to add new trigger conditions and actions is another thing I'm considering, but I don't know yet how difficult that would actually be, so I can't promise anything there yet.

Recently, I've been focusing on getting the internal data structures figured out (primarily the Actor class). This would allow the scripting engine to see things like equipped weapons/armor, inventory, and so forth. One case I considered where this might be useful is in scripting an "intimidation" factor - e.g. have NPCs react differently based on how well-armed your character/squad is. (Might need a more thorough dialog system to make use of that - and that is on my to-do list to look into. :) )
 
Oh yeah, well done, and what a great to-do list you made for yourself. Happy to know that we will be able to connect scripts and triggers from variables. Almost everything works from them anyways. It seems an easy and pactical mean to walk this trough. Great job. I'll follow that in every step, thanks again!
 
Back
Top