Settlement Modding - Research and Development

Users who are viewing this thread

Hey y'all, I made this doc on the modding Discord and me and some others have been tinkering with it:


Namely I am trying to figure out the limits of settlement modding right now.

As far as I can tell we've made some main discoveries:

1. You can only add settlements in a new module -- we haven't tested it yet but the theory is sound, as that's how the campaign adds the training camp

2. You can not remove or edit vanilla settlements (and until we figure out how to disable modules or replace xmls in them from another then that's going to be one of the main roadblocks to settlement editing)

3. You can not change a settlements location. At least not through the XML, I've tried and it just isn't a thing that works.

If anyone has any other insight or wants to add to the doc please let me know! We have dialogue modding down, UI modding down, texture swaps are happening, and then we just need model, settlement and scene modding and we'll be ready for some conversions! Any help is appreciated.
 
This is a good start.

Is there any update for this?

I'd like to add, that it's possible to change the location through posX, posY. The settlement does change it's location, however, the map icon indicating the village does not move. So the village will be invisible at the new location. The AI knows where the new settlement is and use it's location. Just the player needs to press on the icon which will then make the player travel to the hidden settlements location.

So the real question is, where do the settlement icons receive their location from?
 
You can remove models and behavior from the engine but it requires more testing. I do not want to say things that prove then to be wrong but as i'm working on a mod that deal with caravans (and so to settlements to some extends) i have been able to replace some of the basic model classes from the very game. Yet it worked for me twice and other time it resulted to a simple crash at loading ... So i'll have to do more testing and then will provide my solution with a bit of documentation to what to do or not.

I want to make it clean cause it would prevent people from using harmony for everything and anything cause i guess it will soon be the cancer of M&B2 modding ...
 
Through Taleworlds.campaignsystem.dll under DefaultVillageTypes you can change village type outputs, for example silver mines;

this.AddProductions(this.VillageTypeSilverMine, new ValueTuple<string, float>[]
{
new ValueTuple<string, float>("silver", 3f)
});

Just simple copy and paste other outputs to more or less create a "super Village", also figured out that market tiered items are based off of quantity of raw resources. Making all the villages around a town produce twice as much iron as normal will result in much greater chance for top tier armor and weapons for trade, creating a smith workshop increases this as well.

Another note is that making "super villages" will create an explosion in prosperity for their tied town and massive tax benefits.
 
Tonight's testing.

By applying a custom culture to a village it didn't crash, only a town. I've noticed there are no notable villagers at that village. I'm not completely sure where noteable villagers are defined (are they in specialnpcs?) or why they aren't spawning. Going to continue down this path of experimentation tomorrow. I believe the reason Towns are crashing is possibly because they are reliant on those notables possibly existing.

Update: Confirming. Missing Special Character Notables (Outlaws, etc.) from your custom culture WILL CRASH TOWNS. You need to define the various hooligans to populate towns or else it won't function.
 
Last edited:
maxresdefault.jpg


You guys are on a modding discord?
 
Speaking of notables I was able to change which notable class gives elite recruits and what power level needed to spawn them.
TaleWorlds.CampaignSystem Helpers.Herohelpers

Code:
        // Token: 0x06000037 RID: 55
        public static bool HeroShouldGiveEliteTroop(Hero sellerHero)
        {
            return sellerHero.IsMerchant && sellerHero.Power >= 100;
        }

With this you and the AI are almost guaranteed to have at least one elite recruit in any settlement with a merchant. I was tired of trying to find the one village that had elite recruits. Also discovered that notable power is partially derived from their settlement prosperity which explained why 1-3 villages that use to have elite's no longer did due to raiding as time went on.

Would anyone here know how to add another line to include multiple notables? I would like to keep IsRuralNotable (landowner) able to give elites as well.
You guys are on a modding discord?
Never used discord before, hell I have a hard time using forums as is.
 
Speaking of notables I was able to change which notable class gives elite recruits and what power level needed to spawn them.
TaleWorlds.CampaignSystem Helpers.Herohelpers

Code:
        // Token: 0x06000037 RID: 55
        public static bool HeroShouldGiveEliteTroop(Hero sellerHero)
        {
            return sellerHero.IsMerchant && sellerHero.Power >= 100;
        }

With this you and the AI are almost guaranteed to have at least one elite recruit in any settlement with a merchant. I was tired of trying to find the one village that had elite recruits. Also discovered that notable power is partially derived from their settlement prosperity which explained why 1-3 villages that use to have elite's no longer did due to raiding as time went on.

Would anyone here know how to add another line to include multiple notables? I would like to keep IsRuralNotable (landowner) able to give elites as well.

Never used discord before, hell I have a hard time using forums as is.

This is amazing! Does this mean we could effectively create new notable types that are determined by the offerer? IE: Bandit elite units offered by an powerful outlaw, etc. as well as the normal elites offered by traditional notables? or even tier the notables offered by the power of the offerer?
 
Does this mean we could effectively create new notable types that are determined by the offerer? IE: Bandit elite units offered by an powerful outlaw

It appears that HeroShouldGiveEliteTroop is based off of spcultures.xml with the line elite_basic_troop="NPCCharacter.vlandian_squire" for example. From memory there were a lot of references to culture for recruiting from notables, so for bandits I believe you would need to create a "bandit culture" for your notables which in turn is based off of settlement culture.

or even tier the notables offered by the power of the offerer?
Not sure what you mean by this, do you refer to a listing priority in the recruitment UI?

Few days ago is my first time delving into anything more complex than notepad++ so take my words with a grain of salt. I have no doubt that there are coding wizards out there who can make it happen.
 
Also for fun I found a reference for notable power rank;
C#:
        // Token: 0x04000BFF RID: 3071
        private DefaultNotablePowerModel.NotablePowerRank[] NotablePowerRanks = new DefaultNotablePowerModel.NotablePowerRank[]
        {
            new DefaultNotablePowerModel.NotablePowerRank(new TextObject("{=aTeuX4L0}Regular", null), 0, 0.2f),
            new DefaultNotablePowerModel.NotablePowerRank(new TextObject("{=nTETQEmy}Influential", null), 100, 0.4f),
            new DefaultNotablePowerModel.NotablePowerRank(new TextObject("{=UCpyo9hw}Powerful", null), 200, 0.6f)
        };

        // Token: 0x04000C00 RID: 3072
        private TextObject _currentRankEffect = new TextObject("{=7j9uHxLM}Current Rank Effect", null);

        // Token: 0x04000C01 RID: 3073
        private TextObject _militiaEffect = new TextObject("{=R1MaIgOb}Militia Effect", null);

        // Token: 0x04000C02 RID: 3074
        private TextObject _rulerClanEffect = new TextObject("{=JE3RTqx5}Ruler Clan Effect", null);

        // Token: 0x04000C03 RID: 3075
        private TextObject _propertyEffect = new TextObject("{=yDomN9L2}Property Effect", null);

        // Token: 0x04000C04 RID: 3076
        private TextObject _issueText = GameTexts.FindText("str_issues", null);

This would explain why all my settlements had nearly all high level recruits when I tested with a max level character and level 6 clan.
 
I moreso meant, for example could we add our own lines to reference similar to the Elite_troop in cultures.xml and add, let's say for the sake of this example Outlaw_Troop. Then the code would reference the culture's outlaw troop and only assign those troop types to the Outlaw character in a city?
 
A few more notes:

posX, posY determine position of the settlement.
gate_posX, gate_posY determine position of settlements mapicon.
Settlement and settlement map icon do not have to be in the same location.

Working:
change posX, posY to move settlement to location
change gate_posX, gate_posY for training field. For Training field it's possible to move both it's location and it's map icon.

Not working
change gate_posX, gate_posY. It gets ignored and map_icon is shown at previous location. Exception is the training field, this can be moved with its mapicon to a new location.

Best way to test: edit story_mode_settlements.xml and change either pos or gate_pos and notice how either the map icon moves, or the map icon stays at the same location, but when clicking on it, the player will travel to some different location to enter the training field.
 
+1 to this thread, very useful thank you! I was just scouring through the files to see if I could track down custom campaign settings, and you've eliminated most of that. I suppose it is just too early to change the campaign map and settlements which is a shame because I've got a map ready to go. Love this community :smile:
 
@Shadowclaimer Unfortunately I cannot find out how HeroShouldGiveEliteTroop refers to EliteBasicTroop, which in turn is directed to read from the xml's Elite_Basic_Troop. If I can find that reference I should be able to add HeroShouldGiveOutlawTroop for IsGangLeader and add references in the xml.

I've dug through all dll's and xml's and cannot for the life of me find that bloody reference.
 
I've found it within TaleWorlds.CampaignSysytem.SandBox.CampaignBehaviours.RecruitCampaignBehaviour

C#:
if (hero.VolunteerTypes[i] == basicTroop && HeroHelper.HeroShouldGiveEliteTroop(hero))
{
    hero.VolunteerTypes[i] = cultureObject.EliteBasicTroop;
    flag = true;
}

By changing EliteBasicTroop into MeleeEliteMilitiaTroop and changing the notable in TaleWorlds.CampaignSysytem.Helpers.HeroHelper to IsGangLeader I was able to have gang leaders in towns have veteran militia troops for recruitment.

I just need to figure out how to add and/or line into the code so as to have multiple branches of recruitment like gang leaders giving bandits and merchants elite recruits.
 
Done! Was able to successfully add multiple branches of which you can recruit from notables in town, below is what I've done.

First add a new notable and troop to TaleWorlds.CampaignSysytem.Helpers.HeroHelper
C#:
        public static bool HeroShouldGiveGangleaderBodyguard(Hero sellerHero)
        {
            return sellerHero.IsGangLeader && sellerHero.Power >= 2;
        }
    }
}

Second is add this to TaleWorlds.CampaignSysytem.SandBox.CampaignBehaviours.RecruitCampaignBehaviour
C#:
        // Token: 0x060024B8 RID: 9400
        private static void UpdateVolunteersOfNotables(bool initialRunning = false)
        {
            foreach (Settlement settlement in Campaign.Current.Settlements)
            {
                if ((settlement.IsTown && !settlement.Town.IsRebeling) || (settlement.IsVillage && !settlement.Village.Bound.Town.IsRebeling))
                {
                    foreach (Hero hero in settlement.Notables)
                    {
                        if (hero.CanHaveRecruits)
                        {
                            bool flag = false;
                            CultureObject cultureObject = (hero.CurrentSettlement != null) ? hero.CurrentSettlement.Culture : hero.Clan.Culture;
                            CharacterObject basicTroop = cultureObject.BasicTroop;
                            double num = (hero.IsRuralNotable && hero.Power >= 200) ? 1.0 : 0.5;
                            float num2 = 200f;
                            for (int i = 0; i < 6; i++)
                            {
                                if (MBRandom.RandomFloat < Campaign.Current.Models.VolunteerProductionModel.GetDailyVolunteerProductionProbability(hero, i, settlement))
                                {
                                    if (!RecruitCampaignBehavior.IsBitSet(hero, i))
                                    {
                                        hero.VolunteerTypes[i] = basicTroop;
                                        flag = true;
                                    }
                                    else
                                    {
                                        float num3 = num2 * num2 / (Math.Max(50f, (float)hero.Power) * Math.Max(50f, (float)hero.Power));
                                        int level = hero.VolunteerTypes[i].Level;
                                        if (MBRandom.RandomInt((int)Math.Max(2.0, (double)((float)level * num3) * num * 1.5)) == 0 && hero.VolunteerTypes[i].UpgradeTargets != null && hero.VolunteerTypes[i].Level < 20)
                                        {
                                            if (hero.VolunteerTypes[i] == basicTroop && HeroHelper.HeroShouldGiveEliteTroop(hero))
                                            {
                                                hero.VolunteerTypes[i] = cultureObject.EliteBasicTroop;
                                            }
                                            if (hero.VolunteerTypes[i] == basicTroop && HeroHelper.HeroShouldGiveGangleaderBodyguard(hero))
                                            {
                                                hero.VolunteerTypes[i] = cultureObject.GangleaderBodyguard;
                                                flag = true;
                                            }
                                            else
                                            {
                                                hero.VolunteerTypes[i] = hero.VolunteerTypes[i].UpgradeTargets[MBRandom.RandomInt(hero.VolunteerTypes[i].UpgradeTargets.Length)];
                                                flag = true;
                                            }
                                        }
                                    }
                                }
                            }
                            if (flag)
                            {
                                for (int j = 0; j < 6; j++)
                                {
                                    for (int k = 0; k < 6; k++)
                                    {
                                        if (hero.VolunteerTypes[k] != null)
                                        {
                                            int l = k + 1;
                                            while (l < 6)
                                            {
                                                if (hero.VolunteerTypes[l] != null)
                                                {
                                                    if ((float)hero.VolunteerTypes[k].Level + (hero.VolunteerTypes[k].IsMounted ? 0.5f : 0f) > (float)hero.VolunteerTypes[l].Level + (hero.VolunteerTypes[l].IsMounted ? 0.5f : 0f))
                                                    {
                                                        CharacterObject characterObject = hero.VolunteerTypes[k];
                                                        hero.VolunteerTypes[k] = hero.VolunteerTypes[l];
                                                        hero.VolunteerTypes[l] = characterObject;
                                                        break;
                                                    }
                                                    break;
                                                }
                                                else
                                                {
                                                    l++;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

With this all you need to do is change gangleader_bodyguard in spcultures.xml to whatever bandit flavor. Vlandia>Mountain bandit/ Aserai>Desert bandit and so on. Only problem I had was the vanilla code in TaleWorlds.CampaignSysytem.CultureObject contained errors and cannot compile, so I repurposed gangleader_bodyguard.
 
Done! Was able to successfully add multiple branches of which you can recruit from notables in town, below is what I've done.

First add a new notable and troop to TaleWorlds.CampaignSysytem.Helpers.HeroHelper
C#:
        public static bool HeroShouldGiveGangleaderBodyguard(Hero sellerHero)
        {
            return sellerHero.IsGangLeader && sellerHero.Power >= 2;
        }
    }
}

Second is add this to TaleWorlds.CampaignSysytem.SandBox.CampaignBehaviours.RecruitCampaignBehaviour
C#:
        // Token: 0x060024B8 RID: 9400
        private static void UpdateVolunteersOfNotables(bool initialRunning = false)
        {
            foreach (Settlement settlement in Campaign.Current.Settlements)
            {
                if ((settlement.IsTown && !settlement.Town.IsRebeling) || (settlement.IsVillage && !settlement.Village.Bound.Town.IsRebeling))
                {
                    foreach (Hero hero in settlement.Notables)
                    {
                        if (hero.CanHaveRecruits)
                        {
                            bool flag = false;
                            CultureObject cultureObject = (hero.CurrentSettlement != null) ? hero.CurrentSettlement.Culture : hero.Clan.Culture;
                            CharacterObject basicTroop = cultureObject.BasicTroop;
                            double num = (hero.IsRuralNotable && hero.Power >= 200) ? 1.0 : 0.5;
                            float num2 = 200f;
                            for (int i = 0; i < 6; i++)
                            {
                                if (MBRandom.RandomFloat < Campaign.Current.Models.VolunteerProductionModel.GetDailyVolunteerProductionProbability(hero, i, settlement))
                                {
                                    if (!RecruitCampaignBehavior.IsBitSet(hero, i))
                                    {
                                        hero.VolunteerTypes[i] = basicTroop;
                                        flag = true;
                                    }
                                    else
                                    {
                                        float num3 = num2 * num2 / (Math.Max(50f, (float)hero.Power) * Math.Max(50f, (float)hero.Power));
                                        int level = hero.VolunteerTypes[i].Level;
                                        if (MBRandom.RandomInt((int)Math.Max(2.0, (double)((float)level * num3) * num * 1.5)) == 0 && hero.VolunteerTypes[i].UpgradeTargets != null && hero.VolunteerTypes[i].Level < 20)
                                        {
                                            if (hero.VolunteerTypes[i] == basicTroop && HeroHelper.HeroShouldGiveEliteTroop(hero))
                                            {
                                                hero.VolunteerTypes[i] = cultureObject.EliteBasicTroop;
                                            }
                                            if (hero.VolunteerTypes[i] == basicTroop && HeroHelper.HeroShouldGiveGangleaderBodyguard(hero))
                                            {
                                                hero.VolunteerTypes[i] = cultureObject.GangleaderBodyguard;
                                                flag = true;
                                            }
                                            else
                                            {
                                                hero.VolunteerTypes[i] = hero.VolunteerTypes[i].UpgradeTargets[MBRandom.RandomInt(hero.VolunteerTypes[i].UpgradeTargets.Length)];
                                                flag = true;
                                            }
                                        }
                                    }
                                }
                            }
                            if (flag)
                            {
                                for (int j = 0; j < 6; j++)
                                {
                                    for (int k = 0; k < 6; k++)
                                    {
                                        if (hero.VolunteerTypes[k] != null)
                                        {
                                            int l = k + 1;
                                            while (l < 6)
                                            {
                                                if (hero.VolunteerTypes[l] != null)
                                                {
                                                    if ((float)hero.VolunteerTypes[k].Level + (hero.VolunteerTypes[k].IsMounted ? 0.5f : 0f) > (float)hero.VolunteerTypes[l].Level + (hero.VolunteerTypes[l].IsMounted ? 0.5f : 0f))
                                                    {
                                                        CharacterObject characterObject = hero.VolunteerTypes[k];
                                                        hero.VolunteerTypes[k] = hero.VolunteerTypes[l];
                                                        hero.VolunteerTypes[l] = characterObject;
                                                        break;
                                                    }
                                                    break;
                                                }
                                                else
                                                {
                                                    l++;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

With this all you need to do is change gangleader_bodyguard in spcultures.xml to whatever bandit flavor. Vlandia>Mountain bandit/ Aserai>Desert bandit and so on. Only problem I had was the vanilla code in TaleWorlds.CampaignSysytem.CultureObject contained errors and cannot compile, so I repurposed gangleader_bodyguard.

This is amazing! Thank you so much! This opens up so many possibilities!
 
This is amazing! Thank you so much! This opens up so many possibilities!
No worries, your inquiry started out as a innocent curiosity only to turn into a raged induced challenge.

Next I'll see about adding artisan's with militia, merchants with elites and gang leaders with bandits. If they implement religion that's stated in the code along with IsPreacher notables I'll add religious units to them.
 
Back
Top Bottom