[Code] I wrote this C# script to fix the wrong default formations in spnpccharacters.xml. Now I just run it after each game patch.

Users who are viewing this thread

Waybook

Sergeant
Basically, it checks if a troop has ammo for crossbow or bow and if it has anything in the horse slot. I've used this for several patches now and it works great.

This is the class, which replaces the formation:

C#:
using System;
using System.Xml;

namespace BannerlordTroopEditor {
    class DefaultFormationFixer {

        static readonly string npcCharacterNode = "/NPCCharacters/NPCCharacter";

        static bool checkIfSkip(string troopName) {
            string[] skipList = { "tournament_template_", "player_char_", "weapon_", "dummy_", "brother_char_creation_", "main_hero_for_perf", "prison_guard_" };
            foreach (string troopToSkip in skipList)
                if (troopName.StartsWith(troopToSkip))
                    return true;
            return false;
        }

        static string findCorrectFormation(bool ranged, bool cavalry) {
            if (ranged && cavalry)
                return "HorseArcher";
            else if (ranged)
                return "Ranged";
            else if (cavalry)
                return "Cavalry";
            else
                return "Infantry";
        }

        static bool checkIfHorseNode(XmlNode node) {
            return node.Attributes["slot"] != null && node.Attributes["slot"].Value.Equals("Horse");
        }

        public static XmlDocument changeFormations(XmlDocument doc) {
            foreach (XmlNode node in doc.SelectNodes(npcCharacterNode)) {
                string troopName = node.Attributes["id"].Value;
                if (checkIfSkip(troopName))
                    continue;

                string originalFormation = node.Attributes["default_group"].Value;

                if (!string.IsNullOrEmpty(troopName) && !string.IsNullOrEmpty(originalFormation)) {
                    bool ranged = false;
                    bool cavalry = false;

                    XmlNodeList characterNodes = node.ChildNodes;
                    XmlNodeList itemNodes = null;
                    string itemId = null;

                    foreach (XmlNode characterNode in characterNodes) {
                        if (characterNode.Name.Equals("equipmentSet")) {
                            itemNodes = characterNode.ChildNodes;
                            foreach (XmlNode itemNode in itemNodes) {
                                if (itemNode.Attributes == null) // To deal with comment lines in .XML
                                    continue;
                                if (itemNode.Attributes["id"] != null) {
                                    itemId = itemNode.Attributes["id"].Value;
                                    if (itemId.StartsWith("Item.bolt") || itemId.Contains("_arrows"))
                                        ranged = true;
                                    if (checkIfHorseNode(itemNode))
                                        cavalry = true;
                                }
                            }
                        } else if (characterNode.Name.Equals("equipment"))
                            if (checkIfHorseNode(characterNode))
                                cavalry = true;
                    }

                    string correctFormation = findCorrectFormation(ranged, cavalry);

                    if (!originalFormation.Equals(correctFormation)) {
                        node.Attributes["default_group"].Value = correctFormation;
                        Console.WriteLine(troopName + " old formation: " + originalFormation + ", new: " + correctFormation);
                    }

                }
            }
            return doc;
        }

    }
}

And then in your main method use something like:

C#:
            XmlDocument troopDoc = new XmlDocument();
            troopDoc.Load(path + "spnpccharacters.xml");
            troopDoc = DefaultFormationFixer.changeFormations(troopDoc);
            troopDoc.Save(path + "spnpccharacters2.xml");

(I actually do it differently, because my program does other stuff with the doc before fixing the formations.)
 
Last edited:
Back
Top Bottom