So you want to write a mod

This guide will assume that you’ve read these excellent start-up instructions and can print ‘Hello World’ on the MelonLoader terminal. But, once you’ve done this, how to you actually make a mod do something? How do you even figure out what bit of code needs modifying?

This general guide is recycled from this older post, but with some worked through examples later. It also assumes you know a little codding including the most basic elements of Object Oriented Programming but specialist knowledge is not required (not least of all because I lack it myself). For those unfamiliar with C# you can think of it as a modern Java or strongly typed Python. It’s fairly friendly, especially with modern IDEs that teach you the syntax as you go. For the sake of brevity, this guide won’t cover that, but will keep jargon to a minimum.


Discovering what to change

The first thing you need to do is to read the code that you want to modify. Note that we don’t have access to any of the data files (.csv, .json, xml, etc…) that specify numerical parameters for buildings, units, recipes, etc… So even if your mod is just about changing the value of a static variable, you have to do that through code. That code is in the Assemble-CSharp.dll library that you added as a reference while setting up VS (Visual Studio) for modding. You can use the ‘object viewer’ in VS to peruse it, but I prefer dnSpy. The ability to search for classes by name and/or inheritance structure is great, as is the “analyze” function which will find all the cases where a particular method is called.

Sometimes just reading code is not enough to understand it, and seeing what the code is doing at run time is helpful. Especially because some variables are set by assets we don’t have access to being loaded at run time. For this Unity Explorer is priceless. It’s functionally a mod that works for all Unity mono games. Especially nice is the ability to click on an in-game object (on the map or in the UI), and inspect it. Seeing the values of various fields (and edit them), the tree like structure of GameObjects, and run any pre-existing method live is something you can hardly live without - both to understand what the vanilla game does, and to verify that your mod is doing what you want it to.

Doing all of this, you might come across some Unity idiosyncrasies. There’s a lot of material and tutorials on Unity out there, which can get bewildering. I’d say that it’s good to have a passing familiarity with what MonoBehaviour , GameObject , Transform , Prefab , and Components are, just so you’re not bewildered when they come up. But beyond that it’s unlikely you’ll need a deep knowledge of how they work.


How to make the change

The above should let you figure out what part of the code you need to change to achieve your desired result. But how do you make a mod actually do it? There are, fundamentally, two approaches.

The first one, is to use one of the callbacks provided by MelonLoader. These are methods that get called after specific events, your mod can override these and any code you write there will get executed at the appropriate time. Some of these hooks get called after a scene is loaded or even every frame, the existing list is available here . That entire page is well worth a read. These call backs are very easy to use and require only light scripting, but the call backs available are common to all Unity games, so they’re completely generic.

The second option is to use HarmonyPatch. This uses C# reflection black magic to let you write a function which gets called just before (or just after, or even replace) any specific method in the entire library of code that makes up the game. This is very powerful and at the other end of the generic-specific spectrum than the MelonLoader hooks. However, it can be fiddly for various technical reasons and potentially comes with computational overheads. That link is a concise overview of what it can do and how how to use it; the whole ‘Patching’ section of notes are well worth a read. The MelonLoader article linked above explains how the two are integrated.

3 Likes

Theory is all well and good, but a worked example can be a more practical way to learn. In this section, I’ll show 3 different ways of implementing something very similar to my most popular mod, Free Ranging Foragers to show the different options available to modders.

What we want to do is increase the radius of the working circle of forager shacks. From looking at the source code we learnt that what we want to change is the attribute foragingRadius in the ForagerShack class (a child of Building through a few intermediaries). From playing around with Unity Explorer we’ve decided that we want to set that range to 150 and, luckily, changing the working radius also changes the circle drawn in-game (circleRadius) and the villager AI / logistics system automatically adjusts to it. Always good to double check these things rather than taking them for granted!


Option 1 - Getter Replacement

The first way to mod this is the most direct. We use HarmonyPatch such that, every time the game tries to access the foragingRadius of an instance of ForagerShack, we intercept the call and replace it with our own value.

    public class  GetterReplacementMod : MelonMod
    {
    }

    [HarmonyPatch(typeof(ForagerShack), "get_foragingRadius")]
    public class PatchForagerShackRadius
    {
        public static void Postfix(ref float __result)
        {
            __result = 150;
        }
    }

And that’s it. Out MelonMod child does nothing explicitly, but will automatically apply all the HarmonyPatches we declare below it. PatchForagerShackRadius follows the syntax of HarmonyPatch linked above. One thing to note is that the method name is "get_foragingRadius" because we’re modifying the getter of a property rather than a typical method. We could also have used [HarmonyPatch(typeof(ForagerShack), "foragingRadius", MethodType.Getter)] for exactly the same effect. We could have also used a Prefix that returned false instead of a Postfix.


Option 2 - Initialisation Injection

In some ways, intercepting every single call to ForagerShack.foragingRadius is a little wasteful. We could just set that variable once when an instance of forager shack is created, and then let the mod sit back. The creation of GameObjects and Components in Unity is complicated and mostly happens “behind the scenes”. What matters is that, once something is almost ready to be used by the game, its Awake() and Start() methods get called, which basically handle the initialisation of the parts of the code that we’re dealing with. So we can use a HarmonyPatch here:

    public class  InitialisationInjectionMod : MelonMod
    {
    }

    [HarmonyPatch(typeof(ForagerShack), "Awake")]
    public class PatchForagerShackRadius
    {
        public static void Postfix(ForagerShack __instance)
        {
            __instance.foragingRadius = 150;
        }
    }

Very similar to the above, but now we set the value of foragingRadius once for an instance of ForagerShack at the end of its Awake() method and then don’t interfere with it.


Option 3 - Assets Alteration

Can we take the idea of minimising the number of things the mod does even further? Rather than setting the forager radius for each instance of ForagerShack can we just do it once by changing what the game considers the default value? The answer is yes and, best of all, we can do it without HarmonyPatch and instead just use MelonLoader:

public class AssetsAlterationMod : MelonMod
{
    public override void OnSceneWasInitialized(int buildIndex, string sceneName)
    {
        if (sceneName == "Frontier")
        {
            foreach (BuildingData bData in GlobalAssets.buildingSetupData.buildingData)
            {
                if (bData.identifier == "ForagerShack")
                {
                    foreach (var conPrefab in bData.prefabEntries)
                    {
                        var foragerShack = conPrefab.prefab.GetComponent<ForagerShack>();
                        if (foragerShack != null)
                        {
                            foragerShack.foragingRadius = 150;
                        }
                    }
                }
            }
        }
    }
}

The method we are overriding is a MelonMod hook that is called after a scene is loaded. A “Scene” is a Unity term for a chunk of the game that belongs together. It’s typically used to group together everything that belongs to the same level in a first or third person game. In FF there are a few scenes: one for the main menu, one for when you’re on the map, and a couple that gets called during the loading screen. As far as I can tell, “Frontier” is the scene that loads the assets from the game files, before it starts building the world from a save file (or for a new game). So just after its been loaded is a good place to make our changes.

GlobalAssets is a static class that holds a lot of the fixed game data - things that don’t change while playing or from one run to another. BuildingSetUpData is that part of the data used to set up buildings. So we go through the list and pick out the one with the name we want (UnityExplorer is very helpful for finding out what this name is - it’s not always what you think it should be). BuildingSetUpData has the variables that make sense for every building, eg, desirability and construction cost. But foragingRadius is specific to one building, so we have to dig deeper into the prefabs. Prefabs are a type of Unity GameObjects that serve as a template for others that are created (possibly in multiple copies) while playing the game. Most buildings only have 1 prefab but some, especially houses, have multiple each with unique graphics. So we go through all the prefabs (only one in this case) and pick the Component that has the class we want to change. One last null check (if we had the wrong name and had selected, eg, a bakery, this component would be missing), and set the foraging radius to what we want it to be.

And that’s it! This will happen once during the loading scene, and then the mod does nothing at all while playing the game. In fact, the way it’s done here, the change will persist until the game is closed and relaunched, so we only really need to do it the first time a map is created / loaded per play session.

This example is more specific than the others, but it can easily be adapted to changing other things. There are other lists in GlobalAssets than buildingSetupData. PrefabAssetMap might be the most generic. For some cases ObjectDataStore.GetAllDataRecords<>() is a very powerful tool to get all data of a particular type (eg, if you want to modify some / all crops). The singleton GameManager and the various sub-managers it contains is also a great way to get at game data, although they don’t all initialise early enough. Lastly, Unity comes with an inbuilt tool to search for all instances of a particular class: Resources.FindObjectsOfTypeAll(typeof())

Common Pitfalls

A word of caution about all of these is to make sure the changes are applied the expected number of times. Here we’re setting a hard value, so doing it multiple times doesn’t matter. But if we were, eg, doubling a value it would be a problem if we did it too many times. This is not a problem for Option 1. But for option 2, when we save the game, forager shacks will get saved with the bigger radius. When we later load that save, Awake() will be called once again, and we’ll double out already doubled value! Option 3 gets called on every loading scene, so if you reload a save without quitting, you’ll have doubled it twice. In that case it’s easy to set up a Boolean flag to only apply the correction once, but in general these kind of traps are something to be careful about and are definitely something you should be testing.


Which one should I use?

Which ever works! Mods are hard enough, if you have something which does what you want it to do, and you’ve tested that it works, well done! In this case we had an abundance of choice, but for some other projects this might not be the case.

If you do have the choice, then it comes down to personal preference. Personally, I usually like Option 3 the most and Option 1 the least. Despite the code looking a little more complicated, the fact that it’s not using HarmonyPatch makes it easier to debug. The mod also ‘does less’ as it only intervenes once during loading. This means that it doesn’t harm performance (not that any of those examples would in any appreciable way), and also that it’s less likely to conflict with other mods or with future patches. Or, at least, those conflicts will be easier to identify and resolve. That said, when I’m changing the behaviour of a function rather than the value of an attribute, something closer to Option 1 is often the only viable way to go.

3 Likes

I’ll close with a couple of general quality-of-life tips.

Workflow

Making a mod, like all codding, invariably involves trying things, seeing the effect, and then trying something else. In this loop it’s easy to forget to compile your changes in VS and, especially, forget to copy the new .dll from your working directory to the FF/Mono/Mod folder so that you can actually test the changes in the game. This last step can be automated by having some variant of

xcopy "$(TargetPath)" "[...]\Farthest Frontier\Farthest Frontier (Mono)\Mods\" /y

in Project Properties → Build Events → Post-build event command line.

Private fields

Because both MelonLoader and HarmonyPatch work from the “outside” of a class, it’s a very common occurrence in modding that you end up wanting to change a private / protected field, which the compiler forbids. For example, in the previous post we were lucky that ForagerShack.foragingRadius had a public setter. If it hadn’t we would have had to set the private field _foragingRadius manually. In c# it’s possible to use reflection to get around these restrictions. This is normally something that you should not do when codding, but in this case it’s inevitable.

The following are some util functions that automate this reflection, I have them in their own file that I share across all my mods because they are so useful so often:

    public static object GetPrivateField<T>(string fieldName, T instance)
    {
        FieldInfo field = typeof(T).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
        return field.GetValue(instance);
    }

    public static void SetPrivateField<T1, T2>(string fieldName, T1 instance, T2 value)
    {
        FieldInfo field = typeof(T1).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(instance, value);
    }

    public static object CallPrivateMethod<T>(string methodName, T instance, object[] args = null)
    {
        MethodInfo method = typeof(T).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
        return method.Invoke(instance, args);
    }
2 Likes