mipen / BannerlordTweaks

MIT License
59 stars 43 forks source link

Additional Tweak Suggestions #35

Open bgoetz opened 4 years ago

bgoetz commented 4 years ago

Just some stuff I've tinkered with over the past couple weeks. I'm not interested in going through the whole module process since it's only been for personal use, but you might find something interesting in here. If you add this stuff maybe I won't have to manually update the DLLs every time they patch.

SandBox

CreateRandomSettlementIssues() I didn't like how few quests were being generated. I feel like with all the raiding and looting and warring going on villages should be desperate for any sort of help. This makes the early game a bit easier, if you prefer to do quests to start out. I started with a x2 multiplier, but found x3 a little bit more to my liking. That's what I run with now.

desiredIssueCount *= 3;

FindSuitableCompanionsToLeadCaravan() This will search through every available party leader when generating the list of companions during the caravan creation screen. This way you can form a caravan from any city, even if the leader isn't in your party currently. The leader does teleport to the new city, but I didn't really dig into trying to make it more realistic. A side benefit is that this is more performant if you have fewer leaders / companions than troops in your party.

I ripped this from ExecuteCreateNewParty(), so you may want to periodically check that for official changes.

// You need to include this to compile
using System.Linq;

// Just straight up replace the loop in the check.
Clan clan = MobileParty.MainParty.LeaderHero.Clan;
foreach (Hero hero in (from h in clan.Heroes
    where !h.IsDisabled
    select h).Union(clan.Companions))
    {
        if (!hero.IsActive) {
            continue;
        }
        if (hero.IsChild) {
            continue;
        }
        if (hero.PartyBelongedToAsPrisoner != null) {
            continue;
        }
        if (hero.PartyBelongedTo != null && hero.PartyBelongedTo.LeaderHero == hero) {
            continue;
        }
        if (hero.PartyBelongedTo != null && hero.PartyBelongedTo.LeaderHero != Hero.MainHero) {
            continue;
        }
        if (hero.GovernorOf != null) {
            continue;
        }
        if (hero.IsOccupiedByAnEvent()) {
            continue;
        }
        if (hero.HeroState == Hero.CharacterStates.Disabled) {
            continue;
        }

        list.Add(hero.CharacterObject);
    }

TaleWorlds.CampaignSystem

MobileParty.DailyTick() Here I tweaked the daily XP gain from the CombatTips perk to increase based on both the number of non-wounded troops in a stack, and the total number of non-wounded troops in the party. It uses logarithmic logic to try and balance out the gain in large parties. I didn't do anything with RaiseTheMeek because I never take that perk

Here's what it looks like with a party of 100 troops. I adjusted CombatTips to be 75 XP / day, so keep that in mind. 'Stack XP' is how much XP is applied to a stack with the specified count, not including the total troop XP. 'Total XP' is the Stack XP but with an additional modifier based on the total number of healthy troops in the party.

# In Stack Stack XP Total XP
5 194 388
10 259 519
15 300 600
20 329 659
25 353 705
30 372 743
35 388 775
40 402 804
45 414 829
50 425 851
60 445 890
70 461 922
80 475 951
90 488 976
100 499 999
int totalRegularTroops = Math.Max(this.MemberRoster.TotalRegulars - this.MemberRoster.TotalWoundedRegulars, 1);
using (IEnumerator<CharacterObject> enumerator = this.MemberRoster.Troops.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        CharacterObject attackerTroop = enumerator.Current;
        int troopPerksXp = Campaign.Current.Models.PartyTrainingModel.GetTroopPerksXp(DefaultPerks.Leadership.CombatTips);
        float bonusMultiplier = (float)Math.Max(Math.Log10((double)totalRegularTroops), 1.0);
        int troopIndex = this.MemberRoster.FindIndexOfTroop(attackerTroop);
        if (troopIndex >= 0)
        {
            int totalTrainable = this.MemberRoster.GetElementNumber(troopIndex) - this.MemberRoster.GetElementWoundedNumber(troopIndex);
            if (totalTrainable <= 0)
            {
                bonusMultiplier = 0f;
            }
            else if (totalTrainable > 1)
            {
                bonusMultiplier *= (float)(Math.Log10((double)(totalTrainable + 1)) / Math.Log10(2.0));
            }
        }
        this.Party.MemberRoster.AddXpToTroop(MathF.Round((float)troopPerksXp * bonusMultiplier), attackerTroop);
    }
    goto IL_10F;
}

Other The only things not laid out in detail above that I think you haven't included yet are:

Bobisback commented 4 years ago

I would really love the perk xp bonus that you talk about. In what file do I put these changes? Do I have to compile a DLL or is it as easy as just changing a file?

I program for a living so you can get technical with me.

bgoetz commented 4 years ago

So this is the dirty way, where you have to update with each patch. You could create a module which theoretically is more stable, but this worked for me.

  1. Grab dnSpy: https://github.com/0xd4d/dnSpy
  2. Load all the DLLs for Bannerlord. Just do this by dragging and dropping them into the sidebar. I threw everything from the root of bin/Win64_Shipping_Client and then every DLL in Modules (there should be 11, don't include any mods). image
  3. There are three that you care about the most: StoryMode, SandBox, (both Modules) and TaleWorlds.CampaignSystem (in bin/*).
  4. The devs named things super well, so it didn't take me a lot of time to find things. You can search all open DLLs via Edit>Search Assemblies. It only decompiles when you load them. image
  5. Poke around and search for stuff. In the bottom view. It's mostly straightforward. With the Perk stuff it's faster just to search for DefaultPerks, open the instance, then search for the specific perk. image image image
  6. Here you can see I already changed the value to 75f, it defaults to 15f. RaiseTheMeek is right below it.
  7. In order to make modifications just right click inside the editor and hit either 'Edit Method' or 'Edit Class' depending on your intention. It's faster to edit methods than classes when you can, especially since in some instances you can't actually compile the whole class due to missing assemblies. I didn't care to dig into that when editing the method worked. image
  8. Here's just a random one. Editing the method opens up an editor. Hit compile at the bottom when you're done. It will auto-format and check for errors. In some cases you may need to include additional stuff that the class has but the method doesn't directly interface. The two I've seen the most often are either TaleWorlds.Core or System.Linq. Just toss that stuff at the top in the editor, if need be. image
  9. When you're done and it's compiled, save the module. This will give you a popup where you can choose where to save it. I set it up where I have a backup of the original modules (in case you do more than just static value changing and are getting crashes), and then a separate '_modded' file. image
  10. In either case, overwrite whichever DLL you're modifying and start the game. Even if you load into a game successfully, you may see a crash when moving. There are three tick functions I've seen: HourlyTick, DailyTick, and WeeklyTick. Move around a bit (I usually wait a whole day, since DailyTick is the most common). And do other minor testing according to your changes (entering towns, recruiting troops, etc.).
  11. ???
  12. Beg someone to implement all your changes in their module-based mod so you don't have to keep doing this yourself.
Bobisback commented 4 years ago

Wow awesome, thanks for the guide very helpful!

bgoetz commented 4 years ago

Because I'm lazy here's the Python script I use to run things in the background. There's another thing in there that doubles the maximum size of all bandits, for more Fun™.

https://pastebin.com/LTqwxum6

Oh. And you don't need to clear and re-add assemblies after a patch. Learned that the hard way. Just hit File>Reload All Assemblies. image

Bobisback commented 4 years ago

Ohh I like the double bandit max, that is useful. Thanks for the tips guys going to patch the dll now. So annoying how little exp you get for everything.

Bobisback commented 4 years ago
if (this.IsActive && this.HasPerk(DefaultPerks.Leadership.RaiseTheMeek))
{
    foreach (CharacterObject attackerTroop2 in from x in this.MemberRoster.Troops
    where x.Tier < 4
    select x)
    {
        int troopPerksXp2 = Campaign.Current.Models.PartyTrainingModel.GetTroopPerksXp(DefaultPerks.Leadership.RaiseTheMeek);
        float bonusMultiplier = (float)Math.Max(Math.Log10((double)this.MemberRoster.Troops.Count()), 1.0);
        int troopIndex = this.MemberRoster.FindIndexOfTroop(attackerTroop2);
        if (troopIndex >= 0)
            {
                int totalTrainable = this.MemberRoster.GetElementNumber(troopIndex) - this.MemberRoster.GetElementWoundedNumber(troopIndex);
                if (totalTrainable <= 0)
                {
                    bonusMultiplier = 0f;
                }
                else if (totalTrainable > 1)
                {
                    bonusMultiplier *= (float)(Math.Log10((double)(totalTrainable + 1)) / Math.Log10(2.0));
                }
            }
        this.Party.MemberRoster.AddXpToTroop(MathF.Round((float)troopPerksXp2 * bonusMultiplier), attackerTroop2);
    }
}

@bgoetz This look right for the raise the meek?

Edited: Found a bug lol

bgoetz commented 4 years ago

Edit: Missed what you meant. One thing I noticed is that you don't get to apply both benefits. I think its possible if you have a companion who has one and you have the other, so the fix would be to not make it a restrictive if/else.

The faster way is to edit TaleWorlds.CampaignSystem.DefaultPerks.InitializePerks(). For 150f this is what you'll get:

this.LeadershipRaiseTheMeek.Initialize("{=JGCtv8om}Raise The Meek", "{=emH1yQAs}Medium xp bonus per day to tier 1-2-3 troops.", DefaultSkills.Leadership, this.GetTierCost(1), this.LeadershipCombatTips, SkillEffect.PerkRole.PartyMember, 150f, SkillEffect.PerkRole.None, 0f, SkillEffect.EffectIncrementType.Add);

Perks can have multiple effects. In this case we are changing the primary effect from 30 to 150 (or whatever you want).

Bobisback commented 4 years ago

Ya I did that, but also wanted the stack bonus you did but for raise the meek. I got it working though :D

As long as I ignore my profession developer brain, this was a super easy and quick way to mod the game!

Just based on these dlls it looks like bannerlord is quite moddable. Mod conflicts looks like they will be a nightmare though lol

P.S Thanks a ton for the info very helpful!

bgoetz commented 4 years ago

Yeah. They did a fairly good job putting things together. I didn't do Warband modding, so I'm not sure what the official mod tools would be capable of.

To continue with the more troops = more fun idea, I decreased the calculated wage cost by half. I noticed leaders were starting to get a little too poor trying to run around with hundreds of troops.

Inside CharacterObject.TroopWage:

return MathF.Floor(0.5f * (float)num * (float)num2);

I also increased the number of spawned notables in towns to prevent a shortage of recruitable troops. This one only runs on a new campaign, but I've been doing that pretty much every day anyway.

Inside SpawnUrbanCharacters

// What we're looking at doing is increasing the amount of times `CreateHeroAtOccupation` is called.
// I only increased the ones that didn't have a fixed value, and kept the randomness as it is.

if (settlement.IsTown)
    ...
    // In this case `num` is the target value for the 'Merchant' type. I just gave it a flat +2 increase
    int num = 3 + MBRandom.RoundRandomized(valueNormalized * 2f); // Default is 1 + ...
...
else if (settlement.IsVillage)
    // Likewise `num3` is the target value for 'RuralNotable'
    int num3 = 3 + MBRandom.RandomInt(2); // Default is 1 + ...