|
| 1 | +--- |
| 2 | +title: "Part 2: Modding Basics" |
| 3 | +description: A comprehensive guide for making mods for Timberborn |
| 4 | +date: 2025-03-13 |
| 5 | +tags: mods timberborn guide modding |
| 6 | +--- |
| 7 | + |
| 8 | +[Home](../) / [Modding Guide](./) / Getting Started |
| 9 | + |
| 10 | +In this part, we will make a copy of [Faster Underwater Movement](https://steamcommunity.com/sharedfiles/filedetails/?id=3421130054) mod. The source code at the end of this and the next part is [here](https://github.com/datvm/TimberbornMods/tree/master/FasterUnderwaterMovement). In this part, we do not use Mod Settings yet. |
| 11 | + |
| 12 | +## Tools |
| 13 | + |
| 14 | +Beside the assets from [previous part](./getting-started.MD#assetripper), you will need some more tools for coding mods. |
| 15 | + |
| 16 | +### .NET |
| 17 | + |
| 18 | +_Personal choice: .NET 9 or latest_ |
| 19 | + |
| 20 | +The game uses .NET so you need to have the .NET SDK installed on your computer. The game uses `netstandard2.1` but you can install the latest version of the .NET SDK to use the latest language features as long as they are syntactical sugar and not new features. |
| 21 | + |
| 22 | +### IDE |
| 23 | + |
| 24 | +_Personal choice: Visual Studio_ |
| 25 | + |
| 26 | +I personally use Visual Studio for modding Timberborn but you can also use Visual Studio Code or any other IDE you like. |
| 27 | + |
| 28 | +- Download: https://visualstudio.microsoft.com/downloads/ (the Community version is free) |
| 29 | + |
| 30 | +### Decompiler |
| 31 | + |
| 32 | +_Personal choice: ILSpy_ |
| 33 | + |
| 34 | +I use ILSpy to decompile the game's assemblies. It is useful for understanding how the game works and finding the classes and methods you need to modify. You can also use DnSpy or any other decompiler you like. |
| 35 | + |
| 36 | +- Download: https://github.com/icsharpcode/ILSpy or https://github.com/dnSpyEx/dnSpy |
| 37 | + |
| 38 | +Once you have the app, go to your game's folder `steamapps\common\Timberborn\Timberborn_Data\Managed` and drag all `Timberborn.*.dll` files into the app. |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +The two features I use the most are Search and Analyze to find out where a method is called and what it does. |
| 43 | + |
| 44 | +## Create a .NET project |
| 45 | + |
| 46 | +_Reference: [Common.target](https://github.com/datvm/TimberbornMods/blob/master/Common.target)_ |
| 47 | + |
| 48 | +Create a .NET Class Library project. These are the popular items I use in my project's `.csproj` file: |
| 49 | + |
| 50 | +```xml |
| 51 | + <PropertyGroup> |
| 52 | + <TargetFramework>netstandard2.1</TargetFramework> |
| 53 | + <LangVersion>preview</LangVersion> |
| 54 | + <ImplicitUsings>enable</ImplicitUsings> |
| 55 | + <Nullable>enable</Nullable> |
| 56 | + |
| 57 | + <DebugSymbols>true</DebugSymbols> |
| 58 | + <DebugType>embedded</DebugType> |
| 59 | + |
| 60 | + <AssemblyPath>E:\SteamLibrary\steamapps\common\Timberborn\Timberborn_Data\Managed</AssemblyPath> |
| 61 | + </PropertyGroup> |
| 62 | + |
| 63 | + <ItemGroup> |
| 64 | + <!-- Publicizer --> |
| 65 | + <PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.3"> |
| 66 | + <PrivateAssets>all</PrivateAssets> |
| 67 | + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| 68 | + </PackageReference> |
| 69 | + |
| 70 | + <!-- Game Assemblies --> |
| 71 | + <Reference Include="$(AssemblyPath)\Unity*.dll" Publicize="true"> |
| 72 | + <DestinationSubDirectory>libs/</DestinationSubDirectory> |
| 73 | + <Private>False</Private> |
| 74 | + </Reference> |
| 75 | + <Reference Include="$(AssemblyPath)\Timberborn.*.dll" Publicize="true"> |
| 76 | + <DestinationSubDirectory>libs/</DestinationSubDirectory> |
| 77 | + <Private>False</Private> |
| 78 | + </Reference> |
| 79 | + <Reference Include="$(AssemblyPath)\Bindito.*.dll" Publicize="true"> |
| 80 | + <DestinationSubDirectory>libs/</DestinationSubDirectory> |
| 81 | + <Private>False</Private> |
| 82 | + </Reference> |
| 83 | + <Reference Include="$(AssemblyPath)\System.Collections.Immutable.dll"> |
| 84 | + <Private>False</Private> |
| 85 | + </Reference> |
| 86 | + <Reference Include="$(AssemblyPath)\Newtonsoft.Json.dll"> |
| 87 | + <Private>False</Private> |
| 88 | + </Reference> |
| 89 | + </ItemGroup> |
| 90 | + |
| 91 | + <ItemGroup> |
| 92 | + <None Update="manifest.json"> |
| 93 | + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
| 94 | + </None> |
| 95 | + <None Update="Localizations\**"> |
| 96 | + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
| 97 | + </None> |
| 98 | + </ItemGroup> |
| 99 | +``` |
| 100 | + |
| 101 | +- We compile the project with `netstandard2.1` and use the latest language features. |
| 102 | +- We also enable nullable reference types and implicit usings to make the code cleaner. These are personal choices. |
| 103 | +- `DebugSymbols` and `DebugType` are set to `true` and `embedded` respectively to embed the debug symbols into the assembly. This helps that when your code crashes the game, we can see the line number in the stack trace. |
| 104 | + |
| 105 | +> **Tip:** |
| 106 | +> Later in [Advanced modding](./advanced-modding), I will show you how to make these into a shared file so you can reuse them in all your projects like what I did in my mods. |
| 107 | +
|
| 108 | +### The Publicizer |
| 109 | + |
| 110 | +The `BepInEx.AssemblyPublicizer.MSBuild` package is used to publicize the game's assemblies. This is very helpful to expose all the classes and methods in the game's assemblies so you can use them in your code. |
| 111 | + |
| 112 | +Put your game path into the `AssemblyPath` property. This is the path to the `Managed` folder in your game's directory. |
| 113 | + |
| 114 | +`<Private>false</Private>` is used to tell the compiler to not embed the game's assemblies into your mod's assembly. |
| 115 | + |
| 116 | +### Manifest and Localizations files |
| 117 | + |
| 118 | +Like [previous part](./getting-started.MD#step-1-create-manifestjson-file), your mod needs a `manifest.json` file. We use the `CopyToOutputDirectory` property to copy it to the output directory. |
| 119 | + |
| 120 | +You can also add a `Localizations` folder to keep all your texts there and other modders can easily translate your mod as well. If you do, create `enUS.csv` file in there with content like this: |
| 121 | + |
| 122 | +```csv |
| 123 | +ID,Text,Comment |
| 124 | +LV.FUM.SwimmingBonus,"Swimming speed bonus", |
| 125 | +LV.FUM.SwimmingBonusDesc,"The bonus speed for beavers moving in water. 0.5 means 50% faster than walking on land.", |
| 126 | +``` |
| 127 | + |
| 128 | +## Faster Underwater Movement |
| 129 | + |
| 130 | +Create a class called `MSettings`. We do not use ModSettings for now but we will make a simple **singleton** class for now. |
| 131 | + |
| 132 | +> **Note:** |
| 133 | +> In my older code, I call them `ModSettings` but it causes name collision so IntelliSense won't work well. I use `MSettings` now. |
| 134 | +
|
| 135 | +```csharp |
| 136 | +public class ModSettings : ILoadableSingleton |
| 137 | +{ |
| 138 | + static readonly FieldInfo SwimmingPenalty = typeof(WalkerSpeedManager).GetField("SwimmingPenalty", BindingFlags.NonPublic | BindingFlags.Static); |
| 139 | + |
| 140 | + public void Load() |
| 141 | + { |
| 142 | + Debug.Log("Previous swimming penalty: " + SwimmingPenalty.GetValue(null)); |
| 143 | + SetValue(0.5f); |
| 144 | + } |
| 145 | + |
| 146 | + void SetValue(float bonus) |
| 147 | + { |
| 148 | + SwimmingPenalty.SetValue(null, -bonus); |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +Using ILSpy or DnSpy, we can find the `SwimmingPenalty` field in `WalkerSpeedManager` class. We use reflection to set the value of this field to `-0.5f` to make beavers swim 50% faster. |
| 154 | + |
| 155 | +> **Warning:** |
| 156 | +> I use Reflection here because the variable is a `readonly` one. However recently I discovered it's a bad idea to set a `readonly` field using reflection because of some optimization. Luckily for this mod it's only set once before the value is grabbed so it still work. |
| 157 | +
|
| 158 | +### Register the singleton |
| 159 | + |
| 160 | +The above code is not run by anything yet. We need to register it with the game's DI system. See [Timberborn Architecture](./timberborn-architecture) for more information. |
| 161 | + |
| 162 | +Create a `ModConfigs.cs` file: |
| 163 | + |
| 164 | +_Reference: [ModConfigurator.cs](https://github.com/datvm/TimberbornMods/blob/master/FasterUnderwaterMovement/ModConfigurator.cs)_ |
| 165 | + |
| 166 | +```csharp |
| 167 | +[Context("MainMenu")] |
| 168 | +[Context("Game")] |
| 169 | +public class ModConfigurator : IConfigurator |
| 170 | +{ |
| 171 | + public void Configure(IContainerDefinition containerDefinition) |
| 172 | + { |
| 173 | + containerDefinition.Bind<ModSettings>().AsSingleton(); |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +When the game reaches the `MainMenu` or `Game` context, it will create an instance of `ModSettings` and call its `Load` method. |
| 179 | + |
| 180 | +## Testing the mod |
| 181 | + |
| 182 | +Build the project and copy the output files (you will have a `manifest.json` file and a `<project name>.dll` file at least) to your `Documents\Timberborn\Mods\<AnyModName>` folder. |
| 183 | + |
| 184 | +> **Tip:** |
| 185 | +> In [Advanced modding](./advanced-modding), I will show you how to automate this process so you can just press a button to build and copy the files to the game's folder. |
| 186 | +
|
| 187 | +Run the game and you should see that beavers swim faster than before. You can also open the `Player.log` file (or the in-game console with <kbd>Alt + `</kbd>) to see the log message printed. |
| 188 | + |
| 189 | +Personally, I find keeping VS Code open at the `Assets` folder, plus opening the `Player.log` file in that same workspace is very helpful. |
| 190 | + |
| 191 | +## Conclusion |
| 192 | + |
| 193 | +In this part, we have made a simple mod that makes beavers swim faster. We have also learned how to use reflection to modify the game's code. In the next part, we will learn how to use Harmony to patch the game's code and use Mod Settings to let players configure our mods. |
0 commit comments