Adding Companions/Wanderers - Research and Development

Users who are viewing this thread

mr_mouflon

Recruit
Howdy all,

I've been fiddling around trying to add new wanderers via xml. I'm not quite there yet but I figured I ought to share what I've found and see if anyone knows how to get me over the line. It seems like it should be possible only with XMLs, at least I'm hoping so since that's about as far as my script kiddy skills will take me...

So! I copied and modified entries from the Sandbox Module's spspecialcharacters.xml, wanderer_strings.xml and SandBoxCore's spnpccharactertemplates.xml (which may have had a point to it? I don't understand the difference between sandbox and sandboxcore, if I'm honest).

Plugged everything in, hey presto after a few dozen attempts the new wanderers do appear in the encyclopedia, and seem to exist to the given specification. But whenever I go to the town they're shown as being in, there's nothing there. Other wanderers will appear in the tavern as usual, but not the ones I added. Any clues? I'm guessing there's some loose end NPCCharacter entry somewhere that the mobs need to fully initialize so they can appear in the flesh?

In the meantime, I've poked around the dlls with dnSpy and discovered how the game populates itself with wanderers. For those interested!

At the start of a new game, it grabs all the valid NPC templates with the occupation 'Wanderer' (there are tons of unfinished/unused ones in the files, deactivated by commenting out "isTemplate=true")

Once it has them all, it counts them up then does this maths:

Code:
int count = this._companionTemplates.Count;
float num5 = MathF.Clamp(25f / (float)count, 0.33f, 1f);

foreach (CharacterObject companionTemplate in this._companionTemplates)
            {
                if (MBRandom.RandomFloat < num5)
                {
                    this.CreateCompanion(companionTemplate);
                }

So it divides 25 by the number of templates and clamps the result between 0.33 and 1, then goes through every template and spawns them based on that probability. I infer: if there are 25 or fewer templates, all of them will spawn; if there is an arbitrarily large number of templates, one third of them will spawn. Presumably there are other barriers in place to stop it getting too silly.

There's also a system to spawn new companions as the campaign progresses. There are some hard-coded numbers here:

Code:
private float _randomCompanionSpawnFrequencyInWeeks = 6f;
private float _companionSpawnCooldownForSettlementInWeeks = 6f; 
private const float TargetCompanionNumber = 25f;

Although that TargetCompanionNumber I can't find referenced anywhere else, not sure where that leads.

Incidentally - when you look at binaries with dnSpy, that's not how the code was actually written, is it? Some of this stuff is impenetrable, I can only imagine a mind that works with this many layers of efficiency.

Anyway, that's all for now. I'll keep chewing at it and post what I find. Any insights from the more educated would be most appreciated.!
 
Thanks for the writeup! That's incredibly helpful to me personally, I'll let you know if I discover anything as well.

Would we be able to re implement the cut static NPCs in a similar manner to the cut random ones? I noticed the dialogue and stats for them aren't actually commented out.
 
COMPANION SPAWNING EXPLANATION

Hey so I am not a programmer or know anything C# but I have managed to crack the number limit of companions. Here is the explanation of how it works. Apologies for bad coding or explaining things wrong.



AT THE START IT CREATES A NEW LIST
so here, it creates companion templates list and selects all the NPC character that have is template = "true" and occupation= "wanderer"

C#:
// Token: 0x060025A2 RID: 9634
        public void OnNewGameCreated(CampaignGameStarter campaignGameStarter)
        {
            this._companionTemplates = new List<CharacterObject>(from x in CharacterObject.Templates
            where x.Occupation == Occupation.Wanderer
            select x);
            this._nextRandomCompanionSpawnDate = CampaignTime.WeeksFromNow(this._randomCompanionSpawnFrequencyInWeeks);
            this.SpawnUrbanCharacters();
        }


SPAWNING YOUR COMPANIONS

So here it spawns the companions and essentially a clamp randomiser varying to the size of your list. It essentially gives you around 25 (+/-5) companions.


C#:
// Token: 0x060025B2 RID: 9650
        private void SpawnUrbanCharacters()
        {

           int count = this._companionTemplates.Count;
            float num5 = MathF.Clamp(25f / (float)count, 0.33f, 1f);
            foreach (CharacterObject companionTemplate in this._companionTemplates)
            {
                if (MBRandom.RandomFloat < num5)
                {
                    this.CreateCompanion(companionTemplate);
                }
            }
            this._companions.Shuffle<Hero>();
        }

SETTING COMPANION LIMIT
so I changed the code into a loop and used another method of creating a companion from the "weeklytick" So here I can have as much as I want with the exact number. AWESOME!


C#:
// Token: 0x060025B2 RID: 9650
        private void SpawnUrbanCharacters()
        {
int num5 = 200;
            for (int n = 0; n < num5; n++)
            {
                CharacterObject randomElement = (from x in this._companionTemplates
                where !this._companions.Contains(x.HeroObject)
                select x).GetRandomElement<CharacterObject>();
                this.CreateCompanion(randomElement ?? this._companionTemplates.GetRandomElement<CharacterObject>());
            }
            this._companions.Shuffle<Hero>();
        }


BUT I CAN'T FIND MY COMPANION, THEY ARE NOT THERE

The reason why you can find your companions is they are not activated yet. They don't exist yet. What is happening is an encyclopedia bug. On the create companion method, they have a part where they randomise a settlement to each new companion created.

After a short while of running. The encyclopedia updates show these locations which can also be castles. Just to give information out I guess.


C#:
// Token: 0x06003365 RID: 13157
        private void CreateCompanion(CharacterObject companionTemplate)
        {

settlement3 = ((list.Any<Settlement>() ? list.GetRandomElement<Settlement>().Village.Bound : settlement3) ?? Settlement.All.GetRandomElement<Settlement>());

}


HOW THEY ARE ACTUALLY ACTIVATED

Companions are actually activated when you enter a town and nothing else using this code. Essentially the IF is if the main party is in a town and the companionsettlement list show there are no companions here but they are companion still need to be activated, spawn a new companion from the list.

The chose of a companion of again randomised but chosen to fit closest the settlement you in by the culture and settlement they were assigned. So unfortunately until they are actually activated you can not look in your encyclopedia at the beginning choose the one you want. Only once you have gone into a town and they are randomly chosen will you be able to get them.

C#:
// TaleWorlds.CampaignSystem.SandBox.CampaignBehaviors.UrbanCharactersCampaignBehavior
// Token: 0x060025AE RID: 9646
public void OnSettlementEntered(MobileParty mobileParty, Settlement settlement, Hero hero)
{
    if (mobileParty == MobileParty.MainParty && settlement.IsTown && !this._companionSettlements.ContainsKey(settlement) && this._companions.Count > 0)
    {
        int index = 0;
        MBRandom.ChooseWeighted<Hero>(this._companions, (Hero x) => (float)((x.Culture == settlement.Culture) ? 5 : 1), out index);
        Hero hero2 = this._companions[index];
        hero2.ChangeState(Hero.CharacterStates.Active);
        EnterSettlementAction.ApplyForCharacterOnly(hero2, settlement);
        this._companionSettlements.Add(settlement, CampaignTime.Now);
        this._companions.Remove(hero2);
    }
}


THE PROBLEM
So the problem with this code is that what is happening is after you active the COMPANION it puts in a _companionSettlements.list . assigning that town to be full. Even if you recruit that companion. So after you enter a town for the first it won't spawn another companion until the next _nextRandomCompanionSpawnDate. whish is as default 6 weeks. This is the idea to make look like the companions are moving around but in reality what the code does is. It removes the _companionSettlements.list and deactivated the companions that are in the town and start all over again. So essentially it will be like you never been in that town before.

So essentially the only way to get all your companions is you visit the very single town once will spawn a new companion until your companion number is finished.

THE FIX

So simple fix for this is to simply remove this part of the code and it just stops the activation adding the settlement onto the list. The IF query still thinks it's empty and will spawn another one. So going into a town, leaving and going back in again does the trick and brings out your companions!! ( banner lord essential mod is great here as allows you to click to leave. )



C#:
 this._companionSettlements.Add(settlement, CampaignTime.Now);

AND THERE YOU HAVE IT!!!! 200 companions filling the towns!!

unknown.png




NEED YOUR HELP

now I actually need your help. So I have altered the code and everything but no idea how to make it into a standalone mod. I managed to create a project but no idea how to override the code or why it won't reference the method that is in the same class. Ive put the project in github but trust me it probably horrible wrong. I also tried to use harmony by copying clantweaker mod but yeah its probably all wrong.


https://github.com/haybie/CompanionHoarder

I have also made a mod here with the campaignsystem.dll so you can all see it as well.

https://www.nexusmods.com/mountandblade2bannerlord/mods/170

KNOWN ISSUES

So the only main issue I have tested is that the tavern has only 5 slots available. If you keep going in and out of the same town then they start to populate the actual main town where merchants and gang leaders are. Which again is fine but of course there are only a set amount of character paths so after a while they all do start bunching together.

unknown.png


Once however you get to page 10 of the characters so around 50 visits...... the GUI breaks and puts all the characters into one slot.


unknown.png




The real question is WHY THE HELL YOU VISITING THE SAME TOWN MORE THAN 60 times in one go.
LAST THING
Oh last thing I did as well. When the 6 weeks happened there also code to spawn a new companion every time so I just turned this over. But the loop you can spawn as many as you want every 6 weeks, I tried 25- 50 everytime. But you might want to set a limit.
C#:
if (this._nextRandomCompanionSpawnDate.IsPast)
            {
                int num = 0;
                for (int i = 0; i < num; i++)
                {
                    CharacterObject randomElement = (from x in this._companionTemplates
                    where !this._companions.Contains(x.HeroObject)
                    select x).GetRandomElement<CharacterObject>();
                    this.CreateCompanion(randomElement ?? this._companionTemplates.GetRandomElement<CharacterObject>());
                    this._nextRandomCompanionSpawnDate = CampaignTime.WeeksFromNow(this._randomCompanionSpawnFrequencyInWeeks);
                }
            }
FURTHER TESTING NEEDED

I haven't actually tried to hire 200 companions yet so I don't actually if this breaks the game.

I haven't tried going into battle with 200 companions so again do not know if this will break the game.

FIXES I HAVE TRIED


A couple of different things I have already tried and it crashed.

Increase the number of companions spawned in a tavern at a time
I tried adding a simple loop around the spawning part and no matter what I do it just crashes as soon as you enter the town.
I also tried just copying the entire block so its just repeat process but same effect.


C#:
// Token: 0x06003360 RID: 13152 RVA: 0x000D56C8 File Offset: 0x000D38C8
        public void OnSettlementEntered(MobileParty mobileParty, Settlement settlement, Hero hero)
        {
            if (mobileParty == MobileParty.MainParty && settlement.IsTown && !this._companionSettlements.ContainsKey(settlement) && this._companions.Count > 0)
            {
              int num5 = 2;
              for (int n = 0; n < num; n++)
            {
                 int index = 0;
                MBRandom.ChooseWeighted<Hero>(this._companions, (Hero x) => (float)((x.Culture == settlement.Culture) ? 5 : 1), out index);
                Hero hero2 = this._companions[index];
                hero2.ChangeState(Hero.CharacterStates.Active);
                EnterSettlementAction.ApplyForCharacterOnly(hero2, settlement);
                this._companions.Remove(hero2);
}
            }
        }


ACTIVATE COMPANIONS WHEN THEY ARE CREATED
I tried moving the activation process when the companion is created which work great and even allowed companions to spawn on the castles they were assigned and the encyclopedia was correct as soon as the game started.

Unforntately , this only works for a maximum of 15-20 companions. Assuming the coding is too linear and a lot to process as campaign starts, It crashes when you start new games and loads very slow when you try with 10 companions but just about works.

C#:
// Token: 0x060025B3 RID: 9651
        private void CreateCompanion(CharacterObject companionTemplate)

            int index = 0;
            hero = this._companions[index];
            hero.ChangeState(Hero.CharacterStates.Active);
            EnterSettlementAction.ApplyForCharacterOnly(hero, settlement3);
            this._companionSettlements.Add(settlement3, CampaignTime.Now);
            this._companions.Remove(hero);

}        {


WELL, I HOPE THIS HELPS!!
I don't mind people making their own limit, I just ask to be credited for this haha. Glad to help people understand this part more.
 
Last edited:
Would we be able to re implement the cut static NPCs in a similar manner to the cut random ones? I noticed the dialogue and stats for them aren't actually commented out.

Seems like it should be possible to add them into the pool to be generated just by changing their occupation from 'Companion' to 'Wanderer'. Although it's probably harder than that, as the templates seem to be designed differently - e.g. companions have fixed names, while wanderers just have titles. Skills are setup differently as well - companions have direct levels set, e.g. 'One-Handed=100', whereas the all the active templates use Traits, e.g. 'Fighter=1' and then there's a load of code to derive skill levels from those traits.


COMPANION SPAWNING EXPLANATION

Wow, that's a lot of work! It will take some time to get my head around it. I was hoping to avoid delving into c# if possible but it might be inevitable.

As for making a standalone mod, I'd check the documentation and Glorified's video series. I really don't know much about code, but just from looking at the github, it seems to be missing a dll? And then you put the dll path into SubModule.xml. Beyond that, I have no idea, sorry!

So my problem getting wanderers to spawn is just native behaviour, if I understand you correctly? The game can generate multiple companions linked to the same settlement, but will only spawn one at a time, minimum 6 weeks apart. You don't notice normally because you're not checking the encyclopedia for every wanderer before you enter a town, and within a few months they're all out there anyway. I'll test a bit more and see if I can confirm.

I suppose the best answer lies somewhere in that OnSettlementEntered part, making it activate all wanderers at that location. It must be possible, since the encyclopedia knows where they are. How to do it, I'm sure I don't know.

Also, pardon me if this is dumb, but this loop you added:

C#:
              int num5 = 2;
              for (int n = 0; n < num; n++)

Shouldn't num5 and num be the same variable? Wouldn't it be nice if the answer was that simple.
 
Seems like it should be possible to add them into the pool to be generated just by changing their occupation from 'Companion' to 'Wanderer'. Although it's probably harder than that, as the templates seem to be designed differently - e.g. companions have fixed names, while wanderers just have titles. Skills are setup differently as well - companions have direct levels set, e.g. 'One-Handed=100', whereas the all the active templates use Traits, e.g. 'Fighter=1' and then there's a load of code to derive skill levels from those traits.




Wow, that's a lot of work! It will take some time to get my head around it. I was hoping to avoid delving into c# if possible but it might be inevitable.

As for making a standalone mod, I'd check the documentation and Glorified's video series. I really don't know much about code, but just from looking at the github, it seems to be missing a dll? And then you put the dll path into SubModule.xml. Beyond that, I have no idea, sorry!

So my problem getting wanderers to spawn is just native behaviour, if I understand you correctly? The game can generate multiple companions linked to the same settlement, but will only spawn one at a time, minimum 6 weeks apart. You don't notice normally because you're not checking the encyclopedia for every wanderer before you enter a town, and within a few months they're all out there anyway. I'll test a bit more and see if I can confirm.

I suppose the best answer lies somewhere in that OnSettlementEntered part, making it activate all wanderers at that location. It must be possible, since the encyclopedia knows where they are. How to do it, I'm sure I don't know.

Also, pardon me if this is dumb, but this loop you added:

C#:
              int num5 = 2;
              for (int n = 0; n < num; n++)

Shouldn't num5 and num be the same variable? Wouldn't it be nice if the answer was that simple.



So native behaviour, yes so normally as you normally get 25 ish companions. You enter all 25 new towns than all 25 companions will be activated and on the world map. The companion settlement they were assigned to is just overwritten and a random one is chosen as you enter the town.


so either I figure how to activate a companion when they are created will make them show as soon as they are made. Or when you enter a settlement have it instead check which companions has that settlement too and for each spawn each character.


And yeah the num should be num5 shah, good spot :wink:
 
Progress! I can now get new wanderers to spawn reasonably easily, and have been messing around with their stats and skills to see what can be done.

Firstly, here's a list of all the currently active wanderers (isTemplate="true"):

Code:
EMPIRE - 5

id="spc_wanderer_empire_4" name="{=t9e34kgH}the Robber"
id="spc_wanderer_empire_7" name="{=c5Kjcxsa}the Wanderer"
id="spc_wanderer_empire_8" name="{=KTObbG71}the Wronged"
id="spc_wanderer_empire_9" name="{=7apnGFz7}the Black"
id="spc_wanderer_empire_11" name="{=KTObbG71}the Wronged"

STURGIA - 10

id="spc_wanderer_sturgia_0" name="{=yr5pa0hs}Willowbark"
id="spc_wanderer_sturgia_1" name="{=W7aa22Yl}Breakskull"
id="spc_wanderer_sturgia_2" name="{=aE8abvsQ}the Lucky"
id="spc_wanderer_sturgia_3" name="{=LmlL8fVu}Bloodaxe"
id="spc_wanderer_sturgia_4" name="{=WU5GnkvR}the Fish"
id="spc_wanderer_sturgia_5" name="{=NSaetzNJ}Coalbiter"
id="spc_wanderer_sturgia_6" name="{=zXjdb1zT}Frostbeard"
id="spc_wanderer_sturgia_7" name="{=QyQPJJ9B}the Accursed"
id="spc_wanderer_sturgia_8" name="{=rtsnnL2J}the Shieldmaiden"
id="spc_wanderer_sturgia_9" name="{=akALDUs0}Longknife"

BATTANIA - 2

id="spc_wanderer_battania_0" name="{=G8mawcBV}the Healer"
id="spc_wanderer_battania_1" name="{=qslA0bVb}the Red"

TOTAL - 17

To test the OnSettlementEntered spawning mechanic, I assigned my new wanderers to the cultures that currently have none (Khuzait, Aserai, Vlandia), with the idea that they would always be first preference for that culture's settlements and ought to spawn first. And it works! Sort of. They will spawn where the encyclopedia says they are roughly 1/3 of the time, and (almost) always in the first ~3 settlements of that culture that you visit.

The issue I think is this part of the OnSettlementEntered call:

C#:
                MBRandom.ChooseWeighted<Hero>(this._companions, (Hero x) => (float)((x.Culture == settlement.Culture) ? 5 : 1), out index);
                Hero hero2 = this._companions[index];

I don't fully understand that, but my guess is it's choosing a companion from the whole list, but with 5x weight on any companion with a culture that matches the settlement. So, when you have 20 to choose from and only one with the same culture, it'll still end up choosing a non-matching culture more often than not. As more companions spawn, the list gets smaller, so the odds of getting the 'right' one increase.

Which seems like kind of a silly way to do it, to be honest. But I suppose once the list of templates is balanced between all cultures the behaviour will make more sense.

Anyway! Once I had them spawning reliably, I got to work fiddling with their stats and skills and what have you.

Here's an example from one of my xmls:

XML:
<NPCCharacter id="spc_wanderer_untested_1" name="{=yr5pa0hs}the Urchin" voice="softspoken" is_template="true" age="20" is_female="true" default_group="Infantry" is_hero="false" level="5" culture="Culture.aserai" battleTemplate="NPCCharacter.npc_companion_equipment_template_aserai" civilianTemplate="NPCCharacter.npc_companion_equipment_template_aserai" occupation="Wanderer">
    <face>
      <face_key_template value="NPCCharacter.townswoman_aserai" />
    </face>
    <Traits>
      <Trait id="WandererEquipment" value="1" />
      <Trait id="SteppeHair" value="1" />
      <Trait id="LightBuild" value="1" />
      <!-- <Trait id="BalancedFightingSkills" value="1" />
      <Trait id="WoodsScoutSkills" value="1" />
      <Trait id="Surgeon" value="3" /> -->
      <Trait id="Calculating" value="1" />
      <Trait id="Mercy" value="2" />
    </Traits>
  
    <skills>
      <skill id="OneHanded" value="12" />
      <skill id="TwoHanded" value="12" />
      <skill id="Polearm" value="12" />
    
      <skill id="Bow" value="15" />
      <skill id="Crossbow" value="12" />
      <skill id="Throwing" value="15" />

      <skill id="Riding" value="20" />
      <skill id="Athletics" value="16" />
      <skill id="Crafting" value="0" />

      <skill id="Scouting" value="0" />
      <skill id="Tactics" value="0" />
      <skill id="Roguery" value="36" />

      <skill id="Charm" value="20" />
      <skill id="Leadership" value="0" />
      <skill id="Trade" value="18" />
    
      <skill id="Steward" value="18" />
      <skill id="Medicine" value="18" />
      <skill id="Engineering" value="0" />
    </skills>

  </NPCCharacter>

Age - seems to do nothing? The age calculation I found in the code is: "HeroComesOfAge + 5 + MBRandom.RandomInt(27)) ", where HeroComesOfAge is set in the campaign AgeModel (18 by default). So, I guess it's always random.

Voice - options are Curt, Earnest, Ironic and Softspoken

Level - this does nothing. Level is inferred from skillpoints (more on that later)

Traits (Equipment) - options are WandererEquipment and GentryEquipment

Traits (Hair) - options are RomanHair, FrankishHair, CelticHair, ArabianHair and SteppeHair

Traits (Build) - options are MuscularBuild LightBuild HeavyBuild OutOfShapeBuild

Traits (Skills) - there are loads of these, used to generate skill levels in the default templates. You can find the full list by using dnSpy to snoop inside Taleworlds.CampaignSystem.DefaultTraits.

Traits (Character) - options are Mercy, Valor, Honor, Generosity, Caculating. These can have negative values (e.g. Valor="-1" results in 'Cautious') or values higher than 1 (e.g. Mercy="2" gives 'Compassionate').

Skills - these are not used in any of the active templates (that I've found), but they do indeed work, and set the skill level to that specific number. When combined with skill traits, only the higher result is used, they do not add or otherwise interact. E.g., Surgeon 3 normally results in a Medicine skill of 60; if you then set the Medicine skill to 20, the resulting character will still have Medicine 60. If you set Medicine to 100, the resulting character will have Medicine 100.

ATTRIBUTES AND LEVELS

Here's where it starts to get fiddly. There is no way (that I've found) to set attributes or levels directly. Instead, both are derived from skills.

A level 1 character has 210 skill points (the same as a freshly rolled player character). Any less than that results in a broken character with negative SP and 0 in all attributes (you may have seen these in bugged quests that leave characters in your party). 5 SP takes you from level 1 to 2, 10 SP from 2 to 3, 15 from 3 to 4 and so on. So, if you set each skill individually as I've done, you can control the level of the character with precision.

Attributes - the code is a bit beyond me here, but it seems like first it sets the attributes to the minimum required to support the skill levels, then spends any spare points using some inscrutible formula, preferring to put points where the linked skills are highest. This can lead to some very wonky builds, especially at low levels, where small discrepancies in skill distribution lead to huge imbalances in attributes (the attribute with 20 total skillpoints gets pumped to 10; the attribute with 10 total skillpoints is dumped to 0). The only sure-fire way to get at least 1 point in each attribute is to have their skillpoint totals match exactly.

Focus points, I haven't even looked into yet.

HIRING COST

Depends on equipment and (possibly) level. The default range for a level 1 character with wanderer equipment seems to be between ~800 and ~1200. Gentry equipment immediately adds ~2k to the cost. Higher level characters seem to cost more, but not a great deal - it may just be coincidence. Different cultures' equipment sets may also cause variance - I note that Battanian wanderer equipment seems particularly pricey. There doesn't seem to be any way to modify the hiring cost arbitrarily.

DIALOGUE

Here's the final hurdle before being able to put out a simple, but complete, companion mod. I can't figure out how to link the new wanderer templates to the dialogue strings for the tavern conversation. It doesn't crash or anything, the conversation is just made of 'ERROR: xyz not found'.

If I make the template id the same as an existing wanderer, it will pick up the relevant dialogue from wanderer_strings.xml as you'd expect. But so far I haven't found a way to make it pick up new strings, however I try to arrange it. That will be my next task, I suppose.
 
Last edited:
Nice work,

I am still working on trying to get this to a mod but its proofing actually a lot harder than expected.

First things first if you want more variety of companions I simply put the is a template to the XML list and made into a mod here.

https://www.nexusmods.com/mountandblade2bannerlord/mods/76


the Onentersettlment method you are correct. It is a random choice but is weighted so it close to someone with a similar culture to that settlement.
Eventually, if you want to spawn when you are on a certain settlement we can change that to say its matching then spawn them.

The skills I am not sure about. There are not on the system and definitely tested them on the system and everything up to the scout skill are not done individually. The first 10 skills are altered by there preset skills. From 1-5.

this.TraitKnightFightingSkills,
this.TraitCavalryFightingSkills,
this.TraitHorseArcherFightingSkills,
this.TraitHuscarlFightingSkills,
this.TraitHopliteFightingSkills,
this.TraitArcherFIghtingSkills,
this.TraitCrossbowmanStyle,
this.TraitPeltastFightingSkills,


The other skills are individual are as follows.

this.TraitPolitician, - Charm
this.TraitManager, - Leadership and Steward
this.TraitSurgery, - Medicine
this.TraitTracking, -Tracking
this.TraitRogueSkills, - Rougery
this.TraitEngineerSkills, - Engineer
this.TraitDesertScoutSkills, - Scout (forest, hill and desert)


Yeah haven't touch attributes
The price cost yes seems like just equipment is the main cost of pricing of two equipment. The level is just * 10 so that like either 10 denar or 50 denar not much.

The dialogue is put not via the same way as the npccharacters XML files.
Its placed on every game load here.
C#:
// TaleWorlds.CampaignSystem.SandBox.SandBoxManager

// Token: 0x060022F0 RID: 8944 RVA: 0x00088DB4 File Offset: 0x00086FB4

private void LoadXmlFiles(CampaignGameStarter gameInitializer)

{

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/minor_faction_conversations.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/world_lore_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/companion_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/wanderer_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/comment_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/comment_on_action_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/trait_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/voice_strings.xml");

    gameInitializer.LoadGameTexts(BasePath.Name + "Modules/SandBox/ModuleData/action_strings.xml");

}
WHich is then action on gameload and onnewgamecreated here.


C#:
    // Token: 0x060022ED RID: 8941 RVA: 0x00088D14 File Offset: 0x00086F14
        public void OnGameLoaded(object gameInitializer)
        {
            CampaignGameStarter gameInitializer2 = (CampaignGameStarter)gameInitializer;
            this.LoadXmlFiles(gameInitializer2);
            this.AddDialogs(gameInitializer2);
        }

        // Token: 0x060022EE RID: 8942 RVA: 0x00088D14 File Offset: 0x00086F14
        public void OnNewGameCreated(object gameInitializer)
        {
            CampaignGameStarter gameInitializer2 = (CampaignGameStarter)gameInitializer;
            this.LoadXmlFiles(gameInitializer2);
            this.AddDialogs(gameInitializer2);
        }

So what we would do is create your own separate action that does it in our own module and create a path to it.
C#:
//  protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
       // {
        //    if (!(game.GameType is Campaign))
                //return;
        //    CampaignGameStarter gameInitializer = (CampaignGameStarter)gameStarterObject;

         //       gameInitializer.LoadGameTexts(BasePath.Name + "Modules/Your Module's Name/ModuleData/wanderer_strings.xml");
       // }
    }

}

Hope this helps again. :smile: Great work with the research though
 
Hah, yeah I found that part with LoadGameTexts just as you were posting this. That's a bummer, I guess it means we can't introduce new dialogue just with XML. I think (hope!) that will change as Taleworlds keeps working on the game.

As for skills, I can definitely confirm that using the <skills> tag does work, even though I guess it shouldn't. The log even throws an error:

Code:
Error: The element 'NPCCharacter' has invalid child element 'skills'. List of possible elements expected: 'Traits, feats, equipmentSet, equipment, upgrade_targets'.[CODE]

But regardless, the skills come out with exactly what you set them to (unless DeriveSkillsFromTraits overrides it with a higher number). This is really the only way to get balanced characters below level 10, as the traits tend to give skills in multiples of 20, and those skillspoints add up to levels very quickly.

I suppose what we'd need to do is make a clever mini-mod that replaces the logic of that LoadGameTexts so that it matches the one used for NPCCharacters. That way it could load dialogue xmls from all installed mods the way it currently loads npccharacter xmls, without needing each one to be individually coded in. But that's a bit beyond me. Instead it's probably best to wait a bit, see if TaleWorlds says anything about making the game more mod-friendly.
 
Hi;
I messed a littly bit with the wanderer xml and found a way to not "randomnize" the face key and add your own equipment. Also i think you can "stop" the name randomnizer by creating your own culture and only add one name (difn't try it yet so not sure). I hope it helps a little.

XML:
  <NPCCharacter id="spc_wanderer_aela" name="the Huntress" voice="curt" age="28" default_group="Infantry" is_female="true" is_template="true" is_hero="false" culture="Culture.battania" civilianTemplate="NPCCharacter.npc_companion_equipment_template_battania" occupation="Wanderer">
    <face>
      <BodyProperties version="4" age="28" weight="0.5" build="0.5" key="001C600701FC24087887966C673048B04755A4B5958C7214765BBCE576BB734301777B1307C73B83000000000000000000000000000000000000000000F84000"  />
      <BodyPropertiesMax version="4" age="28" weight="0.5" build="0.5" key="001C600701FC24087887966C673048B04755A4B5958C7214765BBCE576BB734301777B1307C73B83000000000000000000000000000000000000000000F84000" />
      <!-- if I understand correctly the game wil randomnize between the value's from BodyPropeties and BodyPropertiesMax -->
      <!--- Age dont work -->
      <!-- You can get/inport your face key by clicking ctrl + c in your character creation menu and ctrl + v will exprort a face key from your clipboard to your character -->
    </face>
    <skills>
      <skill id="OneHanded" value="150" />
      <skill id="TwoHanded" value="20" />
      <skill id="Polearm" value="20" />
      <skill id="Bow" value="200" />
      <skill id="Crossbow" value="5" />
      <skill id="Throwing" value="5" />
      <skill id="Riding" value="20" />
      <skill id="Athletics" value="125" />
      <skill id="Crafting" value="5" />
      <skill id="Tactics" value="5" />
      <skill id="Scouting" value="5" />
      <skill id="Roguery" value="50" />
      <skill id="Leadership" value="5" />
      <skill id="Charm" value="75" />
      <skill id="Trade" value="5" />
      <skill id="Steward" value="5" />
      <skill id="Medicine" value="10" />
      <skill id="Engineering" value="10" />
      <!-- If using skill you cant use skill traits (not sure do) -->
    </skills>
      <equipmentSet>
      <equipment slot="Item0" id="Item.vlandia_sword_2_t3" />
      <equipment slot="Item1" id="Item.mountain_hunting_bow" />
      <equipment slot="Item2" id="Item.bodkin_arrows_a" />
      <equipment slot="Body" id="Item.battania_civil_b" />
      <equipment slot="Leg" id="Item.leather_shoes" />
      <equipment slot="Cape" id="Item.battania_civil_cloak" />
      <!-- If using equipmentSet you need to delete the battletemplate (battleTemplate="NPCCharacter.npc_companion_equipment_template_aserai") from your NPCCharter header and also don't use de equipment trait (again not 100% sure) -->
      </equipmentSet>
      <!-- 
      === Different method to give skills ===
      <Trait id="BalancedFightingSkills" value="2" />
      <Trait id="HorseArcherFightingSkills" value="2" />
      <Trait id="KnightFightingSkills" value="5" />
      <Trait id="ArcherFIghtingSkills" value="3" />
      <Trait id="HuscarlFightingSkills" value="2" />
      <Trait id="HopliteFightingSkills" value="3" />
      <Trait id="HillScoutSkills" value="2" />
      <Trait id="Blacksmith" value="1" />
      <Trait id="ArcherFIghtingSkills" value="1" />
      
      === Different method to gife equimpment ===
      <Trait id="WandererEquipment" value="1" />
      -->
  </NPCCharacter>
 
Hey, good stuff! I did see those <BodyProperties> tags in the XMLSchema, but I couldn't get them to work, I think I had the formatting wrong. It's a shame that age still does nothing though. You'd have to patch the base code to set ages from xml, I think - at the moment it just generates the age at creation with HeroComesOfAge + 5 + Ran(27), where the default HeroComesOfAge is set to 18.

Also I found in my testing that skills and traits can co-exist in a template - the game just picks whichever results in a higher skill level for the character and goes with that.

Preset equipment, that's handy - might be helpful for tweaking the hiring price, since it's the equipment value that seems to make the biggest difference.
 
Yeah, if you only use the <BodyPropeties> it doesn't work, it took me a while.

Yup price goes up. You will need to pay 6k to get the wander from the example code.

And if you add civilian="true" it will change the civilian equipment not the battle one.
XML:
<equipmentSet civilian="true">
      <equipment slot="Item0" id="Item.khuzait_mace_3_t4" />
      <equipment slot="Body" id="Item.bandit_envelope_dress_v1" />
      <equipment slot="Leg" id="Item.wrapped_shoes" />
</equipmentSet>
 
Well here's the sum of my efforts so far:

UNTESTED COMPANIONS MOD
Nexus | Github

The trickiest part is getting the game to pick up the strings for the tavern dialogue. There's no way to do it without C#, but luckily it can be done very simply. If you follow the exact steps from this guide to create a new mod and replace the example script with this (modified to your mod and filenames) it should work pretty much right away.

Also anyone interested might want to look at this project - Bleinz's Wanderer Library - which is getting into managing how the game handles wanderers in general, seems interesting.
 
Just continuing the thread as it's sort of relevant... I've managed to get a companion mod working but the thing that's really niggling me is the inability to have a single name character. Even if you just have a single name like "Bloggs" without the {=garbage}, the game still inserts a random first name.

Hero units are all single named which makes me wonder if it's actually going to be an issue creating single name wanderer units. I was kind of hoping something like {=!!} might force a "blank" but no joy.

Am I missing something really obvious?
(Thanks in advance!)
 
@Harlequin565 - there's no way to do it just with xml, but yes it's definitely possible. The game has hundreds of lines of code to generate names when it creates its characters, but ultimately it's just two strings on each hero object, Hero.FirstName and Hero.Name, and you can overwrite them. The trick would be getting your code to catch the right heroes at the right time; just the templates you've added and just after they're created.
 
@Harlequin565 - there's no way to do it just with xml, but yes it's definitely possible. The game has hundreds of lines of code to generate names when it creates its characters, but ultimately it's just two strings on each hero object, Hero.FirstName and Hero.Name, and you can overwrite them. The trick would be getting your code to catch the right heroes at the right time; just the templates you've added and just after they're created.
Thanks. I think it's simpler to just be abit more inventive with the names. The detailed NPC Creation mod allows renaming to whatever you like, so it'd be a load of work to get nowhere. Still thanks for the reply.
 
Had the "great" idea of editing the already existing wanderers stats instead of making new ones.

My first project was buffing the "Battanian Wanderer" girl as she's unquestionably underwhelming compared to the "Aserai of the Wastes" guy.
My first problem was: If you give her the same traits as the Aserai guy, she's still gonna use her crappy 1H skill over his insane 2H skill.

Code:
<NPCCharacter id="spc_wanderer_aserai_6" name="{=muC2SGlM}of the Wastes" voice="curt" age="31" default_group="Infantry" is_template="true" is_hero="false" culture="Culture.aserai" battleTemplate="NPCCharacter.npc_companion_equipment_template_aserai" civilianTemplate="NPCCharacter.npc_companion_equipment_template_aserai" occupation="Wanderer">
    <face>
      <face_key_template value="NPCCharacter.villager_aserai" />
    </face>
    <!--<Hero id="spc_wanderer_aserai_6" faction="Kingdom.aserai" />-->
    <Traits>
      <!--<Trait id="IsTemplate" value="1" />-->
      <Trait id="WandererEquipment" value="1" />
      <Trait id="ArcherFIghtingSkills" value="5" />
      <Trait id="DesertScoutSkills" value="7" />
      <Trait id="Honor" value="-1" />
      <Trait id="Generosity" value="-1" />
    </Traits>
  </NPCCharacter>

Code:
  <NPCCharacter id="spc_wanderer_battania_7" name="{=c5Kjcxsa}the Wanderer" voice="ironic" age="21" is_female="true" default_group="Infantry" is_template="true" is_hero="false" culture="Culture.battania" battleTemplate="NPCCharacter.npc_companion_equipment_template_battania" civilianTemplate="NPCCharacter.npc_companion_equipment_template_battania" occupation="Wanderer">
    <face>
      <face_key_template value="NPCCharacter.townswoman_battania" />
    </face>
    <!--<Hero id="spc_wanderer_battania_7" faction="Kingdom.battania" />-->
    <Traits>
      <!--<Trait id="IsTemplate" value="1" />-->
      <Trait id="WandererEquipment" value="1" />
      <Trait id="HopliteFightingSkills" value="3" />
      <Trait id="HillScoutSkills" value="3" />
      <Trait id="Valor" value="1" />
      <Trait id="Calculating" value="1" />
      <!--<Trait id="RuralClanIsNemesis" value="1" />-->
    </Traits>
  </NPCCharacter>

And finally underneath you can see what she looks like now.
I can't get the skills to work at all, even when I remove the traits the skills are ignored.
And no matter what I do she stills has 1H skills over 2H skills?

Code:
  <NPCCharacter id="spc_wanderer_battania_7" name="{=c5Kjcxsa}the Wanderer" voice="ironic" age="21" is_female="true" default_group="Infantry" is_template="true" is_hero="false" culture="Culture.battania" battleTemplate="NPCCharacter.npc_companion_equipment_template_battania" civilianTemplate="NPCCharacter.npc_companion_equipment_template_battania" occupation="Wanderer">
    <face>
      <face_key_template value="NPCCharacter.townswoman_battania" />
    </face>
    <!--<Hero id="spc_wanderer_battania_7" faction="Kingdom.battania" />-->
    <Traits>
      <!--<Trait id="IsTemplate" value="1" />-->
      <Trait id="WandererEquipment" value="1" />
      <Trait id="ArcherFIghtingSkills" value="5" />
      <Trait id="DesertScoutSkills" value="7" />
      <Trait id="Valor" value="1" />
      <Trait id="Calculating" value="1" />
      <!--<Trait id="RuralClanIsNemesis" value="1" />-->
    </Traits>
  </NPCCharacter>

Code:
<skills>
      <skill id="OneHanded" value="0" />
      <skill id="TwoHanded" value="150" />
      <skill id="Polearm" value="0" />

      <skill id="Bow" value="185" />
      <skill id="Crossbow" value="0" />
      <skill id="Throwing" value="0" />

      <skill id="Riding" value="0" />
      <skill id="Athletics" value="110" />
      <skill id="Crafting" value="0" />

      <skill id="Scouting" value="140" />
      <skill id="Tactics" value="0" />
      <skill id="Roguery" value="0" />

      <skill id="Charm" value="0" />
      <skill id="Leadership" value="0" />
      <skill id="Trade" value="0" />

      <skill id="Steward" value="0" />
      <skill id="Medicine" value="0" />
      <skill id="Engineering" value="0" />
    </skills>

Basically what I'm wondering is; where's the 1H and 2H skill coming from?
 
Last edited:
Back
Top Bottom