[Example] Overwriting troop equipment by defining the same troop id in mod xml.

Currently viewing this thread:

I wanted to change some of the default troops equipment, but I soon learned that copying the xml over to my mod and simply removing the additional equipmentSet elements did not do the trick, troops would have their original equipment plus my defined set. The solutions I found would require to reference other mods and create other files to get around that, but I don't really like to do that because I can't really change stuff as I want.

What I ended up doing was rewriting part of the Deserialize method for the BaseCharacterObject class using the project template available here in the forum. Essentially what I did was take the equipment part of the method and clear the instance equipment definition, so when a duplicate entry of the same troop gets loaded, only the lastest version will be persisted.

PS: Some of it is just TW original code copied over, they seem to love writing variables with non descriptive names, like obj1, obj2, list3, list4...

Edit: Fixed a mistake while processing horses

using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;
using TaleWorlds.Core;
using TaleWorlds.ObjectSystem;
using Extensions = TaleWorlds.Library.Extensions;

namespace Mod_1.Patches {

    [HarmonyPatch(typeof(BasicCharacterObject), "Deserialize")]
    internal class BasicCharacterObjectPatch {
        internal static MethodInfo AddEquipment = typeof(BasicCharacterObject).GetMethod("AddEquipment", BindingFlags.NonPublic | BindingFlags.Instance);
        internal static FieldInfo EquipmentsField = typeof(BasicCharacterObject).GetField("_equipments", BindingFlags.NonPublic | BindingFlags.Instance);
        internal static FieldInfo EquipmentReadOnlyField = typeof(BasicCharacterObject).GetField("_equipmentsAsReadOnlyList", BindingFlags.NonPublic | BindingFlags.Instance);
        internal static MethodInfo GetReadOnlyList = typeof(Extensions).GetMethod("GetReadOnlyList", BindingFlags.Static | BindingFlags.Public);

        public static void Postfix(ref BasicCharacterObject __instance, ref MBObjectManager objectManager, ref XmlNode node) {
           var isSoldier = node.Attributes["occupation"];

            if (isSoldier is null || isSoldier?.Value != "Soldier")

            XmlNodeList childNodes = node.ChildNodes;

            //Resets equipments for this character object.
            EquipmentsField.SetValue(__instance, new List<Equipment>());

            var equipmentSetList = node.SelectNodes("equipmentSet");
            var equipmentNodeList = node.SelectNodes("equipment");

            List<Equipment> equipments = new List<Equipment>();

            foreach (XmlNode equipmentNode in equipmentSetList) {
                bool isCivilian = equipmentNode.Attributes["civilian"] != null && Boolean.Parse(equipmentNode.Attributes["civilian"].Value);
                var equipment = new Equipment(isCivilian);
                equipment.Deserialize(objectManager, equipmentNode);

            if (equipments.Count > 0)
                AddEquipment.Invoke(__instance, new object[] { equipments });

            foreach (XmlNode equipmentNode in equipmentNodeList) {
                var currentEquipments = (List<Equipment>)EquipmentsField.GetValue(__instance);
                foreach (var equipment in currentEquipments) {
                    if (!equipment.IsCivilian)
                        equipment.DeserializeNode(objectManager, equipmentNode);

            XmlNode defaultEquipmentSet = node.Attributes["default_equipment_set"];
            if (defaultEquipmentSet != null) {
                var equipmentFieldValue = (List<Equipment>)EquipmentsField.GetValue(__instance);
                if (equipmentFieldValue[0] == null)
                    equipmentFieldValue[0] = new Equipment(false);

                equipmentFieldValue[0].FillFrom(Game.Current.GetDefaultEquipmentWithName(defaultEquipmentSet.Value), true);

                EquipmentsField.SetValue(__instance, equipmentFieldValue);

            BasicCharacterObject basicCharacterObject3 = objectManager.ReadObjectReferenceFromXml("battleTemplate", typeof(BasicCharacterObject), node) as BasicCharacterObject;
            if (basicCharacterObject3 != null) {
                List<Equipment> list4 = new List<Equipment>();
                int num2 = 0;
                foreach (Equipment equipment2 in basicCharacterObject3.AllEquipments) {
                    if (!equipment2.IsCivilian) {
                        list4.Add(new Equipment(false));
                        list4[num2].FillFrom(equipment2, false);
                AddEquipment.Invoke(__instance, new object[] { list4 });
            BasicCharacterObject basicCharacterObject4 = objectManager.ReadObjectReferenceFromXml("civilianTemplate", typeof(BasicCharacterObject), node) as BasicCharacterObject;
            if (basicCharacterObject4 != null) {
                List<Equipment> list5 = new List<Equipment>();
                int num3 = 0;
                foreach (Equipment equipment3 in basicCharacterObject4.AllEquipments) {
                    if (equipment3.IsCivilian) {
                        list5.Add(new Equipment(true));
                        list5[num3].FillFrom(equipment3, false);

                AddEquipment.Invoke(__instance, new object[] { list5 });

            EquipmentsField.SetValue(__instance, new List<Equipment>(from eq in (List<Equipment>)EquipmentsField.GetValue(__instance)
                                                                     orderby !eq.IsCivilian descending
                                                                     select eq));

            var genericMethod = GetReadOnlyList.MakeGenericMethod(typeof(Equipment));
            EquipmentReadOnlyField.SetValue(__instance, genericMethod.Invoke(null, new object[] { (List<Equipment>)EquipmentsField.GetValue(__instance) }));

Last edited:
Top Bottom