XP gain rate in arena

Users who are viewing this thread

Hello,
does anyone here know where exactly I can modify the XP gain rate in the arena (both for training and tournaments)? I would like to set it to 100% to make the game a bit less grindy (I'm a casual player).

So far I couldn't find anything in the module files and being a noob at modding I don't really know how to search for it in binary files.

Thanks!
 
Solution
DefaultCombatXpModel in TaleWorlds.CampaignSystem.DLL.
Though i'm pretty sure a mod already exists that increases xp gain in the arena.
DefaultCombatXpModel in TaleWorlds.CampaignSystem.DLL.
Though i'm pretty sure a mod already exists that increases xp gain in the arena.
 
Upvote 0
Solution
Thanks a lot Marek15!

There is the True Arena Experience mod but it hasn't been updated for 1.6.4 yet.

EDIT: This is weird... When I try to add TaleWorlds.CampaignSystem.DLL to my mod using Visual Studio it does not appear in the reference manager window. Or maybe that's not the way to modify it?
 
Last edited:
Upvote 0
The proper way of modding is by patching existing code on runtime using harmony.
I suggest looking around the forums for documentation and tutorials on the subject.
If that fails you can always peek at working code from another mod to figure out how to use harmony. My mod "Leveling Rebalance" has the source code included with the mod, so feel free to use that as an example.
Edit: And you also need a C# decompiler such as dotPeek or ILSpy to look at the game's code.
 
Upvote 0
So I had a look to the source code of your mod as well as some of the documentation and both have been quite helpful so far. Currently, the code for my mod looks as follows:

C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.SandBox.GameComponents.Map;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.MountAndBlade;

namespace ArenaXP
{
    // Token: 0x02000002 RID: 2
    public class SubModule : MBSubModuleBase
    {
        // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
        protected override void OnBeforeInitialModuleScreenSetAsRoot()
        {
            base.OnBeforeInitialModuleScreenSetAsRoot();
            InformationManager.DisplayMessage(new InformationMessage("Arena XP"));
        }
        protected override void OnSubModuleLoad()
        {
            base.OnSubModuleLoad();
            var harmony = new Harmony("arenaxp");
            harmony.PatchAll();
        }

    }

    // Token: 0x02000003 RID: 3
    [HarmonyPatch(typeof(DefaultCombatXpModel), "GetXpFromHit")]
    internal class ArenaXPOverride
    {
        // Token: 0x06002E0C RID: 11788 RVA: 0x000BAFD4 File Offset: 0x000B91D4
        public static bool Prefix(CharacterObject attackerTroop, CharacterObject captain, CharacterObject attackedTroop, PartyBase party, int damage, bool isFatal, CombatXpModel.MissionTypeEnum missionType, out int xpAmount)
        {
            int num = attackedTroop.MaxHitPoints();
            float troopPowerBasedOnContext;
            if (((party != null) ? party.MapEvent : null) != null)
            {
                troopPowerBasedOnContext = Campaign.Current.Models.MilitaryPowerModel.GetTroopPowerBasedOnContext(attackerTroop, party.MapEvent.EventType, party.Side, missionType == CombatXpModel.MissionTypeEnum.SimulationBattle);
            }
            else
            {
                troopPowerBasedOnContext = Campaign.Current.Models.MilitaryPowerModel.GetTroopPowerBasedOnContext(attackerTroop, MapEvent.BattleTypes.None, BattleSideEnum.None, false);
            }
            float num2 = 0.4f * ((troopPowerBasedOnContext + 0.5f) * (float)(Math.Min(damage, num) + (isFatal ? num : 0)));
            num2 *= ((missionType == CombatXpModel.MissionTypeEnum.NoXp) ? 0f : ((missionType == CombatXpModel.MissionTypeEnum.PracticeFight) ? 1f : ((missionType == CombatXpModel.MissionTypeEnum.Tournament) ? 1f : ((missionType == CombatXpModel.MissionTypeEnum.SimulationBattle) ? 1f : ((missionType == CombatXpModel.MissionTypeEnum.Battle) ? 1f : 1f)))));
            ExplainedNumber explainedNumber = new ExplainedNumber(num2, false, null);
            if (party != null)
            {
                this.GetBattleXpBonusFromPerks(party, ref explainedNumber, attackerTroop);
            }
            if (captain != null && captain.IsHero && captain.GetPerkValue(DefaultPerks.Leadership.InspiringLeader))
            {
                explainedNumber.AddFactor(DefaultPerks.Leadership.InspiringLeader.SecondaryBonus, DefaultPerks.Leadership.InspiringLeader.Name);
            }
            xpAmount = MathF.Round(explainedNumber.ResultNumber);
            return false;
        }
    }
}

What seems to get in the way now is the "this" keyword here referring to another method ("GetBattleXpBonusFromPerks"). The thing however is that I have no interest in patching that particular method, only the "GetXPFromHit" one. Any idea how I could circumvent patching that other method to get my code to work?
 
Upvote 0
Since your goal is to just alter the xp values, i would recommend using the transpiler to snipe the specific values you want to change.
Link to transpiler documentation.

In your case it should look like this:
C#:
    [HarmonyPatch(typeof(DefaultCombatXpModel))]
    [HarmonyPatch("GetXpFromHit")]
    public static class ArenaXPOverride
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            var codes = new List<CodeInstruction>(instructions);
            int num = 0;
            for (var i = 0; i < codes.Count; i++)
            {
                if (num == 0 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.9f)
                {
                    codes[i].operand = 1f;
                    num++;
                    i++;
                }
                if (num == 1 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.33f)
                {
                    codes[i].operand = 1f;
                    num++;
                    i++;
                }
                if (num == 2 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.0625f)
                {
                    codes[i].operand = 1f;
                    break;
                }
            }
            return codes.AsEnumerable();
        }
    }
Hope this helps.
 
Last edited:
Upvote 0
Wow. I slightly understand what this is doing after looking at it after some time and checking the documentation, but that's above what I learned in Java class. I also see why it is convenient to have it that way using Harmony. There is significantly less chances of conflict between mods with that.

The code now looks like this:
C#:
using System.Linq;
using System.Collections.Generic;
using System.Reflection.Emit;
using HarmonyLib;
using TaleWorlds.CampaignSystem.SandBox.GameComponents.Map;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;


namespace ArenaXP
{
    // Token: 0x02000002 RID: 2
    public class SubModule : MBSubModuleBase
    {
        // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
        protected override void OnBeforeInitialModuleScreenSetAsRoot()
        {
            base.OnBeforeInitialModuleScreenSetAsRoot();
            InformationManager.DisplayMessage(new InformationMessage("Arena XP"));
        }
        protected override void OnSubModuleLoad()
        {
            base.OnSubModuleLoad();
            var harmony = new Harmony("arenaxp");
            harmony.PatchAll();
        }

    }

    [HarmonyPatch(typeof(DefaultCombatXpModel))]
    [HarmonyPatch("GetXpFromHit")]
    public static class ArenaXPOverride
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            var codes = new List<CodeInstruction>(instructions);
            int num = 0;
            for (var i = 0; i < codes.Count; i++)
            {
                if (num == 0 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.9f)
                {
                    codes[i].operand = 1f;
                    num++;
                    i++;
                }
                if (num == 1 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.33f)
                {
                    codes[i].operand = 1f;
                    num++;
                    i++;
                }
                if (num == 2 && codes[i].opcode == OpCodes.Ldc_R4 && (float)codes[i].operand == 0.0625f)
                {
                    codes[i].operand = 1f;
                    break;
                }
            }
            return codes.AsEnumerable();
        }
    }
}

However it still doesn't work.

When I hover over the Transpiler function I get the following pop-up:
IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)

Private member 'ArenaXPOverride.Transpiler' is unused.

And when debugging I get an "Exception Unhandled" window saying:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

I don't get it. Am I missing some reference here? So far I included 0Harmony and all the TaleWorlds binaries (just to make sure that nothing is missing at first) and rebuilt the solution.

In any case, thanks a lot for your time. Even if right now it doesn't work I am (slowly) learning what modding is about and this alone is fascinating.
 
Upvote 0
Strange, it builds just fine on my end. Even copy pasted the code from your post.
Guessing there must be some configuration that is causing the issue. Best advice i can give is to explore the settings and maybe you will stumble on cause of this error.

Or you could upload the entire project folder and then i could take a closer look at it.
 
Upvote 0
I found it. I had just forgotten to change the SubModule file - and now it's working!

Thanks a lot for your help. I would like to upload the mod on Nexus as a temporary replacement for the outdated True Arena Experience mod. Considering that this is mostly your code, is it okay if I do so? And under what name should I put you in the credits?
 
Upvote 0
Back
Top Bottom