How To Edit Perks With Harmony? (Solved)

Users who are viewing this thread

Hi! I wanted to edit a perk, let's say Wrapped Handles. Perks are Initialized in DefaultPerks in TaleWorlds.CampaignSystem.CharacterDevelopment.
Here is the code:
C#:
private void InitializeAll()
{
    this._oneHandedWrappedHandles.Initialize("{=looKU9Gl}Wrapped Handles", DefaultSkills.OneHanded, this.GetTierCost(1),
    this._oneHandedBasher, "{=dY3GOmTN}{VALUE}% handling to one handed weapons.", SkillEffect.PerkRole.Personal, 0.2f,
    SkillEffect.EffectIncrementType.AddFactor, "{=0mBHB7mA}{VALUE} one handed skill to infantry troops in your formation.",
    SkillEffect.PerkRole.Captain, 30f, SkillEffect.EffectIncrementType.Add, TroopUsageFlags.Undefined, TroopUsageFlags.OneHandedUser);

Now how do I replace primaryDescription({=dY3GOmTN}{VALUE}% handling to one handed weapons) and primaryBonus (0.2f) using Harmony? Is there a way to replace the Initialize arguments with a Prefix? Or maybe you could re-Initialize the perk at the end of InitiateAll with a Postfix? . A lot of mods change perks so there should be an easy way but I can't figure it out


[UPDATE]
I was able to do it with a Transpiler, so this is a very simple and probably not very efficient code to change Perk's name and primaryBonus, obviously you can change any argument in the Initialize method, you just have to figure out what opcode you want to change and add to index i a corresponding int.
C#:
namespace PerkReBalance
{
    [HarmonyPatch(typeof(DefaultPerks), "InitializeAll")]

    public class Perks
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> codes)
        {
            var instructions = new List<CodeInstruction>(codes);
            for (var i = 0; i < instructions.Count; i++)
            {
                if (instructions[i].opcode == OpCodes.Ldstr && (string)instructions[i].operand == "{=looKU9Gl}Wrapped Handles")
                {
                    instructions[i].operand = "{=looKU9Gl}Pizza Margherita";
                    instructions[i+9].operand = 0.7f;

                    break;
                }
            }
            for (var i = 0; i < instructions.Count; i++)
            {
                if (instructions[i].opcode == OpCodes.Ldstr && (string)instructions[i].operand == "{=eJIbMa8P}Frugal")
                {
                    instructions[i].operand = "{=looKU9Gl}Pizza Napoli";
                    instructions[i+9].operand = -0.90f;

                    break;
                }
            }
            return instructions.AsEnumerable();
        }
 
    }
 
}

You can easily check ingame that Frugal/Pizza Napoli correctly decreases your party wages by 90%.
I wasn't able to figure out how to access this._oneHandedWrappedHandles.Initialize and how to replace the parameters called by value with a prefix, so I resorted to a transpiler, which scared me a lot in the beginning because I didn't know anything about IL. So this is how the code works: since each perk has a unique name the if looks for a lstr with operand "{something} the name of the perk" this way you find the perk you want to edit inside the InitialeAll() method and you manipulate the index i so that you move to the opcode you want to change, for example pimaryBonus is 9 indices after name.

I found this tutorial really instructive https://research.checkpoint.com/2024/net-hooking-harmonizing-managed-territory/ even tho you have to adapt it to bannerlord.

as another example, less meme, I want to change the Spring Of Gold perk, the new cap is going to be 5000 gold, 0.002 interest rate, the new name of the perk is going to be Banking, I'm also going to change the description of the perk and the part where it's implemented (DefaultClanFinanceModel):
C#:
namespace PerkReBalance
{
    [HarmonyPatch(typeof(DefaultPerks), "InitializeAll")]

    public class Perks
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> codes)
        {
            var instructions = new List<CodeInstruction>(codes);
            for (var i = 0; i < instructions.Count; i++)
            {
                if (instructions[i].opcode == OpCodes.Ldstr && (string)instructions[i].operand == "{=K0SRwH6E}Spring of Gold")
                {
                    instructions[i].operand = "{=looKU9Gl}Banking"; //change name
                    instructions[i + 7].operand = "{=gu7EN92A}{VALUE}% denars of interest income per day based on your current denars up to 5000 denars.";   //change primaryDescription
                    instructions[i + 9].operand = 0.002f; //change primaryBonus
                    break;
                }
            }
            return instructions.AsEnumerable();
        }

    }

    [HarmonyPatch(typeof(DefaultClanFinanceModel), "CalculateClanIncomeInternal")]
    public class SpringOfGold
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> codes)
        {
            var instructions = new List<CodeInstruction>(codes);
            for (var i = 0; i < instructions.Count; i++)
            {
                if (instructions[i].opcode == OpCodes.Ldc_I4 && (int)instructions[i].operand == 1000)
                {
                    instructions[i].operand = 5000; //change value from 1000
                    break;
                }
            }
            return instructions.AsEnumerable();

        }


    }
}





I have to say Transpiler is very powerful and flexible I'm kinda amazed by what it can do.
 
Last edited:
Thanks, I watched the video and now I understand that a transpiler would be the best option. Still I don't quite understand how to change primaryBonus. When I open the IL editor I see the operand 0.2 stored in the opcode ldc.i4. But how do I replace it?

C#:
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var found = false;
    foreach (var instruction in instructions)
    {
        yield return instruction;
        if (instruction.opcode==OpCodes.Ldc_R4 && instruction.operand == (object)0.2)
        {
            yield return new CodeInstruction(OpCodes.Ldc_R4, 0.7);

            found = true;
        }

    }
                
}
Something like this definitely wouldn't work. Also how do I tell harmony that the method I want to edit is just this._oneHandedWrappedHandles.Initialize and not InitializeAll? I'm not sure that [HarmonyPatch(typeof(DefaultPerks ), "this._oneHandedWrappedHandles.Initialize")] would work since _oneHandedWrappedHandles is a private field.
 
Thanks, I watched the video and now I understand that a transpiler would be the best option. Still I don't quite understand how to change primaryBonus. When I open the IL editor I see the operand 0.2 stored in the opcode ldc.i4. But how do I replace it?

C#:
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var found = false;
    foreach (var instruction in instructions)
    {
        yield return instruction;
        if (instruction.opcode==OpCodes.Ldc_R4 && instruction.operand == (object)0.2)
        {
            yield return new CodeInstruction(OpCodes.Ldc_R4, 0.7);

            found = true;
        }

    }
               
}
Something like this definitely wouldn't work. Also how do I tell harmony that the method I want to edit is just this._oneHandedWrappedHandles.Initialize and not InitializeAll? I'm not sure that [HarmonyPatch(typeof(DefaultPerks ), "this._oneHandedWrappedHandles.Initialize")] would work since _oneHandedWrappedHandles is a private field.
For specific answers you need to ask in the bl-coding channel of the Mount & Blade Modding discord where modding coders will see your question.
 
Oh ok thank you! But I was actually able to do it on my own using a transpiler. If anybody is interested I could post the code once I refine it. Once I do that I plan to make a full trade perk rework mod
 
Back
Top Bottom