Modding in C# feels incredibly boring and tedious.

Currently Viewing (Users: 0, Guests: 1)

Best answers
0
I don't think you are supposed to be able to mod at this timeline of the game anyway. Dotpeek3 is not just practical. Really wait on modding tools before judging. I mean you are reverse-engineering DLLs then complain about modding practicability, and you are doing it on an early access game. Of course, it is unoptimized.

Also, warband *API* was such a headache. (spent 8 years on there bruh) I mean I am glad I started the coding that way, but after switching to C#, damn that is the way it goes. It is just uncomparable in terms of the scope, performance and accessibility.
 

FutureHendrix

Recruit
Best answers
2
I don't think you are supposed to be able to mod at this timeline of the game anyway. Dotpeek3 is not just practical. Really wait on modding tools before judging. I mean you are reverse-engineering DLLs then complain about modding practicability, and you are doing it on an early access game. Of course, it is unoptimized.
Right
 

Midnightknight

Regular
WBWF&SVC
Best answers
4
I don't think you are supposed to be able to mod at this timeline of the game anyway. Dotpeek3 is not just practical. Really wait on modding tools before judging. I mean you are reverse-engineering DLLs then complain about modding practicability, and you are doing it on an early access game. Of course, it is unoptimized.
Yeah exactly that. Modding a bit on the game now and i see classes and methods that are not properly declared so far, put in the wrong DLL or simply place holders for later. They are still heavily working on the code and moving things in and out. Not only modding this early access game is a pain but the mods might break from a patch to the next. Adding Harmony to the deal like many does because it's easier that way makes all even more hazardous. I'm not trying to blame anybody, but when you want to mod a game in this state (like i do) you do not complain about how frustrating it is. You signed for it, so it's no need to complain again and again about it.
 

seedelek

Recruit
Best answers
1
Must I use Visual Studio for C# programming or can I use Visual Studio Code? I have used C# in VSC for other purposes previously (web development). Can I do the same? What is really the difference? I have never used VS I just heard it is a big slow beast and VSC is nicely lighter.
 

Abelardo

Squire
WBWF&SNWVC
Best answers
0
Must I use Visual Studio for C# programming or can I use Visual Studio Code? I have used C# in VSC for other purposes previously (web development). Can I do the same? What is really the difference? I have never used VS I just heard it is a big slow beast and VSC is nicely lighter.
I never used VSC to actually compile any projects, but I think it's possible, though it does require extra steps to set it up for that, it's more an editor than an IDE.

You should really give VS a go though, it's not as cumbersome as it once was, you can install just what you need, for instance, just for C# development the full size of the whole package should be under 3 to 4 GB I think, plus it's free, and it comes already set up with everything you need to start making stuff.
 

Hutchy

Recruit
Best answers
0
Must I use Visual Studio for C# programming or can I use Visual Studio Code? I have used C# in VSC for other purposes previously (web development). Can I do the same? What is really the difference? I have never used VS I just heard it is a big slow beast and VSC is nicely lighter.
I use VSCode a lot professionally but I only really use it for dotnet core console apps to quickly make a project in a morning that has a specific purpose. If you are going to make a larger more long term project i'd use VS, it has more tools at the click of a button instead of finding a hacky extension or using the command line.
 

g4borg

Recruit
Best answers
0
The modding communities for Morrowind and Oblivion vs Skyrim and Fallout 4 demonstrate that: http://wryemusings.com/Cathedral vs. Parlor.html
This was an epic read, thank you. Coming from an even older background of Ultima Online shard scripting, I can only testify, the parlor behaviour basicly destroyed that so old game's potential back then (not that it was not immensely successful anyway, with some freeshards running until this day, but it could have been more). Every shard after all wanted to be exclusive, so every secret, every script, picture, technique, simply any knowledge was held like some sort of holy mythos. The few who finally opened up, came way too late.

In this I also hope Bannerlord will create a rich community with extensive libraries which borrow from each other, instead of self portrayal. However I also think, this is a question of size of the community as well. Very successful games which are easy to mod usually develop a better culture.

I think it is important to give credit, therefore. Honor someone's work, if he did all the research and enabled a later generation to build on his painstaking work of figuring out how to do things, even if years later almost everything disappears.

Python is slow as all hell and a janky mess.
I could not agree less to that one tho. Python is of course slower as a dynamic typed language - but it can be quite fast for most needs. Probably not for an integrated language in a game however, as its runtime is just big. Otherwise, python code is almost perfect to become a clean coder, or to prototype.
It is no wonder, it inspired a lot of new languages to become more readable, and it attracts usually very good programmers, compared to minecraft javaists, young sweaty hot shot rubians or hipster javascript-kiddies.

Note, that I was all of this, including a Pascal/Delphian :wink:
And tbh. even with now a few decades under my belt, I still understand, why someone can miss dynamic dictionaries and lists in c# in the age of json.

On the topic of C# I think TaleWorlds can benefit a lot from the knowledge that Unity produced the last few years. I am quite happy, that in the MS C# world by now the cathedral view is also more prevalent, than the parlor view. Let's hope for the best.
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
33
I could not agree less to that one tho. Python is of course slower as a dynamic typed language
well considering we dont have Python in M&B for anything besides a parser (converts MBScript to the .txt files), it doesnt really matter how fast or slow it is. Anyone that complains about Python performance while doing mods just doesnt understand they are not using Python at all.
 

DigitalSauce

Recruit
Best answers
0
C# is for professional work, if you have never programmed in it before then you should expect it to feel pretty boring and tedious even if the mod tools were already released. To put it simply, the reason you can define so much is for a greater level of control when it comes to execution. If you have never programmed before then you should learn some basics first since you will be writing basic code anyway. I got my feet wet by creating a text adventure in Python and then studied C++ tutorials for a while which let me focus on learning fundamentals about programming. Switching to C# after that was honestly easy as it's mostly the same concepts but this is time consuming.

Once the mod tools are out there should be a lot of guides written as well so it will be easier to learn purely through Bannerlord in the future.
 

EmreBugday

Recruit
Best answers
1
Okay bare with me on this one. Yes old warband system was simple. That is the whole point here. IT WAS SIMPLE. It made the learning process easier except the fact that you had to learn a language which you will NEVER EVER USE ANYWHERE ELSE IN YOUR LIFETIME. With a simple language you can not create complex application. There are lots of limitations due to the extend of the language. With C# that is not the case at all. First and foremost you have native C# support. Are you aware of what that means? It means you can do the heck ever you want. It means if are crazy enough you can go nuts and use the .net networking library to add multiplayer functionality to the campaign mod. Our ability to create complex mods had been improved indefinitely. This is a huge step here. Our only limitation is the engine it self.

Secondly;
Yes. It is boring and inefficient to mod for bannerlord right now because we have to decompile the each and every dll and see what they do, how they do, where they do etc. We are literally looking for a needle in a haystack. Once the official documentation is created all of this issue is going to be resolved.

Devs made a great choice with creating native c# support for modders. The only drawback is security. With great powers comes great responsibilities.
 

SonKidd

Knight
Best answers
0
Seems like many people have made good points already (the system is not ready to be judged, a lot of reverse engineering...).

For those who don’t like the verbosity or complexity of object oriented C#, given the flexibility of C#, I’m sure someone will come up with a scripting language interface for it eventually (some people even came up with a lua or something modding alternative for warband...). Also actually even with Warband people did ddl hooks into the engine to add modding functionality iirc (WSE), now it’s just possible without super low level programming knowledge.

And one more point for those who think coding C# is boring.. I found hunting bugs and the development process in warband boring.. No one enjoys using 100x on screen print statements to check if your variables are correct. Also developing anything more complex (some amazing person made a dynamic array allocator and a spartial tree data structure back the day) is downright horrible. With the new module system (I must admit that these two points are not unique to C#), a lot of this boring and tedious work would be much easier.

80% of this threads arguments honestly doesn’t make much sense... This has nothing to do with OOP or scripting, dynamically typed or whatever. Python (not saying Warband actually used Python, just saying if a scripting language was chosen) has OOP and procedural, adding optional typing, C# has OOP and procedural, LINQ (semi functional), dynamic typing... whatever you name. Those who are saying that these complexity arrises from the module system not being ready is spot on, and it mostly isnt related to the language choice.

For those who want to do simple stuff, wait a bit, I’m willing to bet that the new flexibility will allow serious modders to come up with tools and methods that let you do all the stuff you want much easier than in Warband.

If you don’t wait... Then you have to learn proper programming.
 

seedelek

Recruit
Best answers
1
C# is for professional work, if you have never programmed in it before then you should expect it to feel pretty boring and tedious
Tip: use LINQ a lot, that is fun! First thing is to replace every kind of loop with it.

Normal boring programming be like:

var bestStudents = new List<Student>();
foreach (var s in students)
{
if (s.Grade > 9)
{
bestStudents.Add(s);
}
}

LINQ be like:
var bestStudents = students.Where(s => s.Grade > 9).ToList();
 

highstreet

Recruit
Best answers
0
I wrote small example / proof-of-concept of using Python (via IronPython) for modding Bannerlord. It uses a C# hook to launch the IronPython runtime (included in the Mono distribution found in the game's files) and executes a python file named main.py found in the bin/Win64../ folder.

Maybe this can help some people getting started. You still have to use the .NET API tough and my example is rather barebones (IPythonModule stuff is rather obnoxious, it is something that I have used before in another project). If you have feedback or suggestions create an issue at the GitHub repository and contributions are also welcome!

To explore the .NET API via Python look at the interactive.py.

Link to the GitHub repo: https://github.com/highstreeto/TestIronPythonMod

How the Python code looks like (run in IronPython):
Python:
from TaleWorlds.MountAndBlade import MBSubModuleBase
from TaleWorlds.Core import InformationManager, InformationMessage
from TaleWorlds.Library import Colors

from TestIronPythonMod import IPythonModule

class Module(IPythonModule):
    def OnBeforeInitialModuleScreenSetAsRoot(self):
        msg = InformationMessage("Hello from Python!")
        msg.Color = Colors.Green
        InformationManager.DisplayMessage(msg)
 

Odelaly

Sergeant
Best answers
0
This has nothing to do with OOP or scripting, dynamically typed or whatever. Python (not saying Warband actually used Python, just saying if a scripting language was chosen) has OOP and procedural, adding optional typing, C# has OOP and procedural, LINQ (semi functional), dynamic typing... whatever you name. Those who are saying that these complexity arrises from the module system not being ready is spot on, and it mostly isnt related to the language choice.
To be fair this actually makes a fair bit of difference in the sense that if they went for a LUA or Python, we could not hope for the degree of modding freedom that this type of architecture allows, it's closer to a programming level than a 'scripting' one: the scripting language being C# automaticly requires fewer layers of abstraction to make a proper interface between the Core API and the Modding one (the core being what, a mix of C++ and C#?). So yes, it has actually to do with language choice. If you have LUA over a C or C++ core (which is common), the versatility of your API is only dictated by your efforts to properly interface between those, which is tedious, and chances are you will probably leave it with more limitations.
 

azakhi

Internal Tools Dev
Developer
M&BWBWF&S
Best answers
2
Tip: use LINQ a lot, that is fun! First thing is to replace every kind of loop with it.

Normal boring programming be like:

var bestStudents = new List<Student>();
foreach (var s in students)
{
if (s.Grade > 9)
{
bestStudents.Add(s);
}
}

LINQ be like:
var bestStudents = students.Where(s => s.Grade > 9).ToList();
It is also source of the performance issues :smile: Maybe not exactly for that example but LINQ can hurt performance a lot compared to a loop alternative. This is usually the case only for games though.
 

Dcvd2005

Recruit
Best answers
1
It is also source of the performance issues :smile: Maybe not exactly for that example but LINQ can hurt performance a lot compared to a loop alternative. This is usually the case only for games though.
To add more detail here:

LINQ is a performance issue because it tends to create a lot of short-lived objects that need to be garbage collected. In a tight loop where the loop needs to run as quickly as possible on the CPU, the work done collecting that garbage can create more work than the time spent on the loop itself. Unfortunately for games, the game loop itself is a tight loop!

I use ReSharper's Heap Allocation Viewer plugin when I'm writing C# code to keep track of stuff like that and make sure I'm only using LINQ in code that doesn't run every frame, e.g. initialization or in response to loading a new resource at run-time for debugging.

I do understand the replies stating that it is a far better programming langage overall, and I'm not against that.

The problem that I see is that it seems very easy to add tweaks, but it would be extremely hard for example to modify the individual combat AI, the spawn of a party, the properties of the hero class, the formula to output damages when in warband it was extremely simple.

I am worried for many reasons, but also because they designed some systems to be incredibly black box and difficult to tweak such as the melee damage output in the code. If you fiddle with it, you will see a bunch of goto functions, of variables defined one after the other and it doesn't seem that it can be modified easily without using harmony. Seriously??

public static float CalculateStrikeMagnitudeForSwing(
float swingSpeed,
float impactPointAsPercent,
float weaponWeight,
float weaponLength,
float weaponInertia,
float weaponCoM,
float extraLinearSpeed)
{
float num1 = weaponLength * impactPointAsPercent - weaponCoM;
float num2 = swingSpeed * (0.5f + weaponCoM) + extraLinearSpeed;
double num3 = 0.5 * (double) weaponWeight * (double) num2 * (double) num2;
float num4 = swingSpeed;
double num5 = (double) (0.5f * weaponInertia * num4 * num4);
double num6 = num3 + num5;
float num7 = (float) (((double) num2 + (double) num4 * (double) num1) / (1.0 / (double) weaponWeight + (double) num1 * (double) num1 / (double) weaponInertia));
float num8 = num2 - num7 / weaponWeight;
float num9 = num4 - num7 * num1 / weaponInertia;
float num10 = 0.5f * weaponWeight * num8 * num8 + 0.5f * weaponInertia * num9 * num9;
double num11 = (double) num10;
double num12 = num6 - num11;
float num13 = num10 * 0.5f;
return 0.067f * (float) (num12 + 0.5);
public static float CalculateStrikeMagnitudeForThrust(
float thrustWeaponSpeed,
float weaponWeight,
float extraLinearSpeed,
bool isThrown)
{
float num = thrustWeaponSpeed + extraLinearSpeed;
if (!isThrown)
weaponWeight += 2.5f;
return 0.125f * (0.5f * weaponWeight * num * num);
}
public static float ComputeRawDamageNew(
DamageTypes damageType,
float magnitude,
float armorEffectiveness,
float absorbedDamageRatio)
{
float num1 = 0.0f;
float factorByDamageType = CombatStatCalculator.GetBluntDamageFactorByDamageType(damageType);
float num2 = magnitude * factorByDamageType;
float num3 = (float) (100.0 / (100.0 + (double) armorEffectiveness));
float num4 = num1 + num2 * num3;
float num5;
switch (damageType)
{
case DamageTypes.Cut:
num5 = Math.Max(0.0f, (float) ((double) magnitude * (double) num3 - (double) armorEffectiveness * 0.5));
break;
case DamageTypes.Pierce:
num5 = Math.Max(0.0f, (float) ((double) magnitude * (double) num3 - (double) armorEffectiveness * 0.330000013113022));
break;
case DamageTypes.Blunt:
label_5:
return num4 * absorbedDamageRatio;
default:
return 0.0f;
}
num4 += num5 * (1f - factorByDamageType);
goto label_5;
}
private static float GetBluntDamageFactorByDamageType(DamageTypes damageType)
{
float num = 0.0f;
switch (damageType)
{
case DamageTypes.Cut:
num = 0.1f;
break;
case DamageTypes.Pierce:
num = 0.25f;
break;
case DamageTypes.Blunt:
num = 1f;
break;
}
return num;
}

That code you're looking at is decompiled, meaning that an algorithm has turned something that isn't human readable into what you're seeing. It's not necessarily what the original code looked like.

To use another example, consider

C#:
public override float GetScoreOfKingdomToGetClan(Kingdom kingdom, Clan clan)
{
    float num1 = Math.Min(2f, Math.Max(0.33f, (float) (1.0 + 0.0199999995529652 * (double) FactionManager.GetRelationBetweenClans(kingdom.RulingClan, clan))));
    float num2 = (float) (1.0 + (kingdom.Culture == clan.Culture ? 1.0 : 0.0));
    int num3 = clan.CommanderHeroes != null ? clan.CommanderHeroes.Count<Hero>() : 0;
    float num4 = (float) (((double) clan.TotalStrength + 150.0 * (double) num3) * 10.0);
    float powerRatioToEnemies = FactionHelper.GetPowerRatioToEnemies(kingdom);
    float reliabilityConstant = HeroHelper.CalculateReliabilityConstant(clan.Leader, 1f);
    float num5 = 1f / Math.Max(0.4f, Math.Min(2.5f, (float) Math.Sqrt((double) powerRatioToEnemies)));
    float num6 = num4 * num5;
    return (clan.CalculateSettlementValue(kingdom) * 0.1f + num6) * num1 * num2 * reliabilityConstant;
}
It's possible that this code is fairly close to what was in the game's original code.

It's also possible that it actually looked something more like:

C#:
public override float GetScoreOfKingdomToGetClan(Kingdom kingdom, Clan clan)
{
    var numClanHeroes = clan.CommanderHeroes.Count;
    var existingHeroesInKingdom = GetNumberOfHeroesInKingdom(kingdom);

    if ((numClanHeroes == 0) ||
        (existingHeroesInKingdom == 0))
    {
        return -1E+08f;
    }

    // Start with a basis of 0.
    var valueProposition = 0.0f;

    // Add value for the settlements the clan will bring to the kingdom, and the adjusted clan strength.
    // Adjusted clan strength is the clan strength ~ how badly the kingdom needs allies.
    var clanStrength = GetClanStrength(clan, numClanHeroes);
    var powerRatioToEnemies = FactionHelper.GetPowerRatioToEnemies(kingdom);
    var howBadlyDoesThisKingdomNeedSupport = 1f / Clamp(powerRatioToEnemies > 1 ? (float)Math.Sqrt(powerRatioToEnemies) : powerRatioToEnemies, 0.1f, 2.5f);
    var adjustedClanStrength = clanStrength * howBadlyDoesThisKingdomNeedSupport;
    valueProposition += clan.CalculateSettlementValue(kingdom) * 0.1f + adjustedClanStrength;

    // Increase the value for happier relations.
    var relationMult = Clamp(1.0f + 0.02f * FactionManager.GetRelationBetweenClans(kingdom.RulingClan, clan), 0.33f, 2f);
    valueProposition *= relationMult;

    // Increase the value for the same culture.
    var sameCultureMult = (float)(1.0 + (kingdom.Culture == clan.Culture ? 1.0 : 0.0));
    valueProposition *= sameCultureMult;

    // Increase the value if the lord is known to be honorable.
    var reliabilityConstant = HeroHelper.CalculateReliabilityConstant(clan.Leader);
    valueProposition *= reliabilityConstant;

    return valueProposition;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Clamp(float value, float min, float max)
{
    return Math.Min(max, Math.Max(min, value));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetNumberOfHeroesInKingdom(Kingdom kingdom)
{
    var numHeroes = 0;
    foreach (var clan in kingdom.Clans)
    {
        if (clan.IsMinorFaction && clan != Clan.PlayerClan)
        {
            continue;
        }

        numHeroes += clan.CommanderHeroes.Count;
    }
    return numHeroes;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float GetClanStrength(Clan clan, int numClanHeroes)
{
    return (clan.TotalStrength + 150.0f * numClanHeroes) * 10.0f;
}
The decompiled code could have lost variable names, or added new intermediate values. There may be function calls missing because they were inlined. There won't be any comments. The spacing and separation won't look like a human did it.

Well this looks incredibly bad to me, on top of the fact that it is not easily modifiable. And it is just a small part of the code for damage output calculation in melee. And it is also not balanced, because cutting damage will deal natively less damages than piercing damages because of the wrong formulae they implemented, spears will be overpowered when swung from a horse and so on and so forth.
This function CalculateStrikeMagnitudeForSwing that you're looking at is actually used by a class called DefaultStrikeMagnitudeModel. While the function isn't immediately modifiable, the class can be entirely replaced. Once you do so, everything else in the game will use your class instead. That is incredibly powerful. It's as easy as creating a new class that inherits from DefaultStrikeMagnitudeModel and overriding whatever methods you want. You could replace the swing calculation, or the thrust calculation, or anything.

So they decided to "fix" the problem by implementing a multiplier of damages of every blade of the game that you can build in the forge (you can see that in the xml files). If you know how to code, this is INCREDIBLY bad design. I'm really afraid that most of the code is difficult to read, difficult to modify and not balanced at all.
It is common in game development to write your code in one language (e.g. C++, Java, or C#) and then support reading from a data file at run-time (XML, JSON, other custom text formats) so that you can quickly tweak or iterate on specific numbers, including multipliers.

It is really good to try to simulate the physical properties of the real world, the economy etc etc but please do that when you have some knowledge in that, not just when you like fiddling around with wrong numbers and terrible simulations... And C# doesn't help by being the most difficult language to read for many reasons, with a really convoluted syntax for many things.
C# has a syntax much like all other typed languages. It's comparable to Java, C, C++, TypeScript, and others. If you've only used a dynamic language like Python or Lua I can understand why it might be surprising to you.

This game is not horribly bad, but it looks like they ported the code of warband to a better architecture but they understood that their architecture was actually really bad to make further progress (for example, dialogs with characters are boring and rare, and it is because their system to implement them looks just so wrong...). I must admit that having all the dialogs in one file in warband was much more convenient.
The architecture of the game engine seems fine to me. I've only made two mods so far but in both cases I was pleasantly surprised by the organization of the code and thought given to decoupling components from each other. It's been very easy to reach in and make changes by swapping out a model or an XML file and the game's care given to the mod-focused architecture is what makes that possible, e.g. the loop that finds game models by searching backwards through the list of all the models grabbed from modules during init, or the use of reflection for the data-binding between the UI schema in XML and the view models in the game.

I think what's happening here is that for many people this is their first experience with a large, complex codebase. With a dedicated sandbox for game scripting, that complexity is hidden from you. Unfortunately, the only way to hide that complexity is by making assumptions about what modders will want or need for you. One way to avoid that is to "dog-food" your system, meaning to use the system you expose for making mods to make your game. That's what TaleWorlds has done here. The core game is a module loader. The native game is a module. You'll get truly incredible mods from this design.

It'll get easier to learn as you see more mods demonstrate how to replace or modify different parts of the code.
 

seedelek

Recruit
Best answers
1
It's possible that this code is fairly close to what was in the game's original code.
I bloody well hope it is not close. These numbers like that 150 are data, belong into databases or configuration files, not into the source code, so that you can tweak the game balance without changing the source code. Even for internal development purposes, it would not be efficient to recompile the code every time they play around with such values. And indeed TaleWorlds has promised that all these things will be in XML configuration files and you can tweak a lot of things by just tweaking those files. That is how it should be, so I hope the promise is kept.