
# Cult of the Lamb Modding Guide
So, you wanna make a mod for Cult of the Lamb! Here are some helpful tips on how to get started.
>[!Tip] Modding Discord
>Join the [Modding Discord](https://discord.gg/MUjww9ndx2) for discussions about modding the game.
## Good to Know
If you have some knowledge of these things, it will make modding much easier. But, if you do not, no worries! There will be links provided for you to read on your own time.
Here are some essentials that you may need in order to create a mod:
- [C# Programming](https://www.programiz.com/csharp-programming)
- Unity Development
- [Bepinex & HarmonyX Plugin Development](https://harmony.pardeike.net/articles/patching.html)
## Information on COTL
Cult of the lamb uses these tools for gameplay. You may read up on these if you are going to mod a specific part of the game. It is not necessary to use the same tools as the game, but it would be easier to mod using existing tools within the game.
> This is not an exhaustive list of tools that the game uses. There may be more.
- Unity 2019 (Game Engine)
- Spine 3.8.99 (Animated Sprites)
- FMod (Audio)
- Rewired (Input)
- Basic JSON + Encryption (Save data)
You'll also need your own set of tools to start modding the game. These are not necessary, but helpful for building your mods.
- An IDE, such as Visual Studio or Rider with compile tools
- DLL readers, such as dnSpy or dotPeek
- [Unity Explorer](https://github.com/sinai-dev/UnityExplorer), an in-game unity inspector
- [Mod Template](https://github.com/InfernoDragon0/COTL_ForgottenSins) (it is not really a template, but it is a blank plugin ready to be used in game)
## Bepinex Setup
First, we will need to setup our game to run mods. There are two main ways to setup your game to run mods.
### Using R2 Mod Manager
If you are using the Thunderstore's mod manager, you can simply install Bepinex pack for COTL and it should setup the folders in the correct place.
### Manual Installation
Alternatively, the manual method is to download the Bepinex pack from thunderstore.
{%preview https://thunderstore.io/c/cult-of-the-lamb/p/BepInEx/BepInExPack_CultOfTheLamb/ %}
Unzip and place the Bepinex Pack in the COTL Game folder.
>[!Note] Folder Location
>The folder for Steam users will be similar to "C:\Steam\steamapps\common\Cult of the Lamb"
It should look something like this, once you are done unzipping the folders in.

Run the game to test if you have set it up correctly. It should run the game along with a Bepinex Console that you can use later on to debug your mods.
With that out of the way, you should now be able to begin modding by using the mod template!
## IDE Setup & Compile for Testing
Assuming you have downloaded or cloned the mod template using the link provided above, you can now open the ```.sln``` file from the mod template with your preferred IDE.
The solution will open up and you should be able to compile the mod without any errors. Try building the project. Once successful, you will get a .dll file with the template contents.

> Project Building via Visual Studio as an example
>[!Warning] Build Unsuccessful?
>If you have errors related to compiling the project for the first time, try running `dotnet restore` and build it again.
The `.dll` file that is built will be in the `bin/Debug/` folder

You can then use this to test if the mod template was successfully built. Copy this file and place it into the `Bepinex/plugins` folder.
> [!Tip] Be Organized
> You can create a folder inside the plugins folder, such as `Bepinex/plugins/myFirstMod/`, and add your .dll file into it to keep it organized.
## COTL API
The Mod template also comes with COTL API pre-installed. This means that you get to use the convenience features of the COTL API library to build your mods. You can learn more about its features in the [documentation](https://cotl-api.vercel.app/)
## HarmonyX Patches
Now that you have setup your game for modding, and have tested that the initial compile works, here are some essential information about HarmonyX Patches. It will not be the full documentation of HarmonyX, but a brief description of how we will use patches as our main method of adding our modded code into the game.
### What is a HarmonyX Patch?
A HarmonyX Patch is an injection of code into an original method/function that is available within the game's code. You choose the time and place for your part of the code to run, during the execution of the original method.
It is important that your code runs at the correct TIME and PLACE in order for your intended features to run correctly. There are a few types of patches, you can learn more about them in the harmony wiki. We will only be focusing on Prefix and Postfix patches, as these are the ones that will be regularly used.
You might want to read up on [Unity's Monobehavior lifecycle](https://docs.unity3d.com/6000.2/Documentation/Manual/execution-order.html) if you have not, as it will help you with knowing when it is the best time to inject your code.
### What are Prefix and Postfix patches?
Here is an example of a prefix and postfix patch. Note that you can have multiple patches inside a single class file, and the class file should have the [HarmonyPatch] annotation. Each of your methods that are patches should also have the [HarmonyPatch] annotation. For now, you can ignore the contents of the patch, and focus on the annotation and method signatures. We will discuss more about how to find and structure your methods later down below.
All patches must have the [HarmonyPatch] annotation, a patch type [HarmonyPrefix] or [HarmonyPostfix], and must be a static method.
>[!Note] Original Method and Patch Method
> We will call the game code's method "Original Method", and our patches as "Patch Method"
```csharp
namespace COTL_API.CustomSkins;
[HarmonyPatch]
public partial class CustomSkinManager
{
internal static readonly Dictionary<string, Texture2D> CachedTextures = [];
[HarmonyPatch(typeof(SkeletonData), nameof(SkeletonData.FindSkin), typeof(string))]
[HarmonyPostfix]
private static void SkeletonData_FindSkin(ref Skin? __result, SkeletonData __instance, string skinName)
{
if (__result != null) return;
if (skinName.StartsWith("CustomTarotSkin/"))
{
__result = CreateOrGetTarotSkinFromTemplate(__instance, skinName);
return;
}
if (CustomFollowerSkins.TryGetValue(skinName, out var skin)) __result = skin;
if (AlwaysUnlockedSkins.TryGetValue(skinName, out var alwaysUnlocked) && alwaysUnlocked)
DataManager.SetFollowerSkinUnlocked(skinName);
}
[HarmonyPatch(typeof(CharacterSkinAlerts), nameof(CharacterSkinAlerts.OnSkinUnlocked), typeof(string))]
[HarmonyPrefix]
private static bool CharacterSkinAlerts_OnSkinUnlocked(string skinName)
{
return false;
}
```
In this example, SkeletonData_FindSkin uses a **Postfix patch**. A Postfix patch always runs AFTER the original method has completed, regardless of its return value (except when it throws an error). It is highly recommended to use postfixes as it makes your mod more compatible with other mods that rely on patching the same methods.
CharacterSkinAlerts_OnSkinUnlocked uses a **Prefix patch**. A prefix patch will run BEFORE the original method, when it is called. if the patch method will return a bool value, a value of TRUE will tell the original method to continue as per usual, after this prefix is done. A value of FALSE will tell the original method to not run at all, and only run this prefix patch method.
If we want to access "this", we will need to add a parameter with the className and` __instance`, such as `SkeletonData __instance` will give us the instance of the class SkeletonData that called the original method.
The flow of the patches are as follows:
- Prefix patch methods
- Original method
- Postfix patch methods
### What is a Transpiler Patch?
What if you needed to patch something in the middle of the original method? Then you will need a Transpiler patch. In this documentation, we will be focusing more on Prefix and Postfix patches, so this will be a brief description of Transpiler Patches, and you can find out more about this in the harmony wiki.
Here is an example of a transpiler patch method. It will not make sense until you look at the original method as it does not contain any code, but instructions for the IL intepreter.
```c#
[HarmonyPatch]
internal class CrisisAvertedPatch
{
[HarmonyPatch(typeof(RitualRessurect), nameof(RitualRessurect.DoRessurectRoutine), MethodType.Enumerator)]
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> RitualRessurect_DoRessurectRoutine(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions)
.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_S),
new CodeMatch(OpCodes.Ldc_R4),
new CodeMatch(OpCodes.Bge_Un)
)
.Advance(1)
.SetOperandAndAdvance(Plugin.cursedChance.Value)
.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_S),
new CodeMatch(OpCodes.Ldc_R4),
new CodeMatch(OpCodes.Bge_Un)
)
.Advance(1)
.SetOperandAndAdvance(Plugin.crisisChance.Value)
.InstructionEnumeration();
}
}
```
Transpiler patches allows you to change the code of any original method to your liking.
### How does the patch know which method to inject code into?
When annotating with [HarmonyPatch], it allows extra parameters to specify which method to patch.
An example of a full annotation could be
```csharp
[HarmonyPatch(typeof(CharacterSkinAlerts), nameof(CharacterSkinAlerts.OnSkinUnlocked), typeof(string))]
```
The first parameter, `typeof(Class)` will select the Class to patch.
The second parameter, `nameof(Class.methodname)` will select the method from the class to patch.
There are a few overloads for this annotation, but for this case, the third parameter, `typeof(type)` specifies the parameters of the original method in the event that there method overloads.
The example will patch an original method of `CharacterSkinAlerts.OnSkinUnlocked(string)`
## How do I find what to patch?
You will need to have a general idea of which feature you want to patch in game. There are a few ways of identifying what class and method to patch.
### Via Decompiled Source Code
The easiest method is to guess by the name of the object. For example, if you want to patch a Ritual, then you should look for the Ritual by searching its name in dnspy or dotpeek. You will need to load `Assembly-CSharp.dll` from the `Cult of the Lamb_Data/Managed` folder to view the decompiled source code.
### Via Unity Explorer Inspector
Another method is entering the game, perform or navigate to the action, and inspect the GameObjects via Unity Explorer. The GameObjects will usually have scripts with exact names of the classes you can look up in dnspy or dotpeek.
Sometimes, other mod developers may have also modded similar objects to what you want to mod, and you may find out from them via the modding discord.
## I found what I want to patch, how do I do it?
Once you have found the method that you want to patch, simply add a patch method into any of your [HarmonyPatch] annotated class files.
### Example 1: Splash Screen Prefix Patch
Here is a full walkthrough on an example patch, where we will skip the splash screen that runs everytime we open the game.
First, we will find out more about the method that we want to patch, via dotPeek. Search for "LoadMainMenu" in dotPeek.

Open it up and you will see the source code of `LoadMainMenu` class. Scroll to the `RunSplashScreens()` method and you will be able to see the decompiled source code of the original method.
> Note that Decompiled Source Code does not 100% match the actual source code of the developers, so there may be some artifacts in the decompiled source code

Here is the decompiled source code in text format for better viewing:
```csharp
private IEnumerator RunSplashScreens()
{
LoadMainMenu loadMainMenu = this;
yield return (object) new WaitForEndOfFrame();
yield return (object) new WaitForSeconds(2f);
loadMainMenu.spine.gameObject.SetActive(true);
AudioManager.Instance.PlayOneShot("event:/music/mm_pre_roll/mm_pre_roll");
yield return (object) loadMainMenu.spine.YieldForAnimation("animation");
DeviceLightingManager.StopVideo();
DeviceLightingManager.PlayVideo();
loadMainMenu.spine.AnimationState.Event -= new Spine.AnimationState.TrackEntryEventDelegate(loadMainMenu.HandleEvent);
bool PlayingDevolverSplash = true;
MMVideoPlayer.Play("Devolver_Animated_Logo_v001_4k_Audio-_HQ_HBR_MP4", (System.Action) (() => PlayingDevolverSplash = false), MMVideoPlayer.Options.DISABLE, MMVideoPlayer.Options.DISABLE);
AudioManager.Instance.PlayOneShot("event:/music/devolver_pre_roll/DevolverSplash");
while (PlayingDevolverSplash)
yield return (object) null;
yield return (object) new WaitForEndOfFrame();
DeviceLightingManager.StopVideo();
if ((UnityEngine.Object) loadMainMenu.ControllerRecommended != (UnityEngine.Object) null)
loadMainMenu.ControllerRecommended.SetActive(true);
yield return (object) new WaitForSeconds(3f);
yield return (object) new WaitForEndOfFrame();
MMTransition.Play(MMTransition.TransitionType.ChangeSceneAutoResume, MMTransition.Effect.BlackFade, "Main Menu", 1f, "", (System.Action) null);
}
```
We now know some information about the method that we want to patch. Which is:
- This method is an IEnumerator (in unity, these are usually used as coroutines)
- This methods class and name is `LoadMainMenu.RunSplashScreens()`
- We can guess that the last line is what finishes the splash screen and moves to the main menu.
- This means that we would only run the last method and skip the whole original method. We can use a prefix patch for this.
This is the patch that we write to skip the splash screen.
```csharp
[HarmonyPatch]
internal class SplashPatch
{
// This patch skips splash
[HarmonyPatch(typeof(LoadMainMenu), nameof(LoadMainMenu.RunSplashScreens), MethodType.Enumerator)]
[HarmonyPrefix]
public static void LoadMainMenu_RemoveSplash()
{
if (!Plugin.skipSplash.Value) return true;
MMTransition.Play(MMTransition.TransitionType.ChangeSceneAutoResume, MMTransition.Effect.BlackFade, "Main Menu", 1f, "", (System.Action)null);
return false;
}
}
```
A few things are happening here:
- The first thing is that we are annotating the `[HarmonyPatch]` with the information about the original method to patch.
- The patch specifies `typeof(LoadMainMenu)` for the Class to patch.
- The patch specifies `nameof(LoadMainMenu.RunSplashScreens)` for the original method to patch
- The patch additionally specifies that this MethodType is an `Enumerator`. This is required specifically for `Enumerator` methods since they act differently from normal methods.
- We also annotate it as a Prefix patch by adding `[HarmonyPrefix]`
- The name of the patch can be anything, you can follow a naming convention of class_methodname if it works for you.
- In this example, there is a config value to check if the player would like to skip the splash screen. If the player does not want to skip the splash screen, we `return true`, telling the original method to continue running.
- We run the last line of the method, and `return false` so that it skips the original method.
### Example 2: Shrine Souls Postfix Patch with return value modding
In this example, we will also learn how to modify the return value of an original method, so that it returns our modded value instead of the original value.
The Original method looks like this:
```csharp
public override int SoulMax
{
get
{
switch (this.Data.Type)
{
case StructureBrain.TYPES.SHRINE:
return 50;
case StructureBrain.TYPES.SHRINE_II:
return 70;
case StructureBrain.TYPES.SHRINE_III:
return 90;
case StructureBrain.TYPES.SHRINE_IV:
return 175;
default:
return 0;
}
}
}
```
Similar to the first example, we can derive some information from the original method:
- This method is a Getter method (in C#, get/set methods can be specified)
- This methods class and name is `Stuctures_Shrine.SoulMax()`
- This method returns an int value
- We want to modify the return value to make the shrine larger.
- We can use a postfix patch to intercept the return value, and return our own value.
This is the patch that we write to modify the return value.
```csharp
[HarmonyPatch(typeof(Structures_Shrine), nameof(Structures_Shrine.SoulMax)", MethodType.Getter)]
[HarmonyPostfix]
public static void Structures_Shrine_SoulMax(ref int __result)
{
if (!Plugin.biggerShrine.Value) return;
__result = 2000;
}
```
Similar patch structure to example 1:
- Annotate the `[HarmonyPatch]` with the information about the original method to patch.
- `typeof(Structures_Shrine)` for the Class to patch.
- `nameof(Structures_Shrine.SoulMax)` for the original method to patch
- The patch additionally specifies that this MethodType is an `Getter`. This is required specifically for `Getter` methods as there may be both Get and Set methods in the same original variable.
- Annotate it as a Postfix patch by adding `[HarmonyPostfix]`
- In order to intercept the return value, we have a reference variable `ref int __result`. it must be named that way for harmony purposes.
- In this example, there is a config value to check if the player would like to increase the shrine size. If the player does not, we `return` to skip this postfix.
- We modify the return value by assigning a value to `__result`
The method will then return the modified value whenever it is called.
## I want to add new things, how do I do it?
If you want to add new things, such as a whole new ritual, item, or command, there are convenience features for you to do so with COTL API. We will have an example about a simple new Command for the followers.
>[!Tip] Remember that this is a unity mod!
> You can use most of the tools available in Unity for your mods as well, such as MonoBehaviors.
Here, we are building a command to flip a coin using COTL API's CustomFollowerCommand class. We extend that and override the methods for the follower command to do what we need it to do.
You may ignore the contents of the Execute method, as that is up to you to decide what code is written for the command to do.
```csharp
internal class FlipCoinCommand : CustomFollowerCommand
{
public override string InternalName => "Flip_Coin_Command";
public override Sprite CommandIcon => TextureHelper.CreateSpriteFromPath(Path.Combine(Plugin.PluginPath, "Assets/flipcoin.png"));
public override List<FollowerCommandCategory> Categories { get; } = new List<FollowerCommandCategory> { FollowerCommandCategory.DEFAULT_COMMAND };
public override string GetTitle(Follower follower)
{
return "Flip Coin";
}
public override string GetDescription(Follower follower)
{
return "Flip a coin and win gold!";
}
public override void Execute(interaction_FollowerInteraction interaction,
FollowerCommands finalCommand)
{
interaction.StartCoroutine(interaction.FrameDelayCallback(delegate
{
GameManager.GetInstance().OnConversationNew();
new WaitForSeconds(0.5f);
if (Random.Range(1, 100) < 50)
{
InventoryItem.Spawn(InventoryItem.ITEM_TYPE.BLACK_GOLD, 5, interaction.follower.transform.position);
interaction.follower.Brain.HardSwapToTask((FollowerTask)new FollowerTask_ManualControl());
PlayerFarming.Instance.state.CURRENT_STATE = StateMachine.State.CustomAnimation;
PlayerFarming.Instance.Spine.UseDeltaTime = false;
PlayerFarming.Instance.simpleSpineAnimator.Animate("build", 0, true);
PlayerFarming.Instance.Spine.UseDeltaTime = false;
PlayerFarming.Instance.Spine.skeleton.Update(Time.deltaTime);
PlayerFarming.Instance.simpleSpineAnimator.Animate("reactions/react-happy", 0, false);
interaction.follower.TimedAnimation("Reactions/react-sad", 1.3f, () =>
{
interaction.follower.Brain.CompleteCurrentTask();
GameManager.GetInstance().OnConversationEnd();
PlayerFarming.Instance.Spine.UseDeltaTime = true;
});
}
else
{
Inventory.ChangeItemQuantity(InventoryItem.ITEM_TYPE.BLACK_GOLD, -5);
interaction.follower.Brain.HardSwapToTask((FollowerTask)new FollowerTask_ManualControl());
PlayerFarming.Instance.state.CURRENT_STATE = StateMachine.State.CustomAnimation;
PlayerFarming.Instance.Spine.UseDeltaTime = false;
PlayerFarming.Instance.simpleSpineAnimator.Animate("build", 0, true);
PlayerFarming.Instance.Spine.UseDeltaTime = false;
PlayerFarming.Instance.Spine.skeleton.Update(Time.deltaTime);
PlayerFarming.Instance.simpleSpineAnimator.Animate("knucklebones/lose-dice", 0, false);
interaction.follower.TimedAnimation("Reactions/react-laugh", 2.4f, () =>
{
interaction.follower.Brain.CompleteCurrentTask();
GameManager.GetInstance().OnConversationEnd();
PlayerFarming.Instance.Spine.UseDeltaTime = true;
});
}
}));
interaction.Close(true, reshowMenu: false);
}
}
```
Once you are done building the command, call `CustomFollowerCommandManager.Add(FlipCoinCommand);` to add it into game and the API will do the rest.
## How can i see IL Instructions in dotPeek?
Go to a method, right click and select IL Code to view the method's IL instructions.

On the right, the IL Instructions will appear.

>[!Tip] Highlighting
>If you highlight code while having the IL viewer open, the corresponding IL instructions will also be highlighted.
## Publishing a COTL Mod
Once you have compiled and tested your mod, you can share it with other players that might be interested in your mod! The COTL modding community uses two main mod distribution sites, NexusMods and Thunderstore. It is up to you to decide where to upload it, but the instructions here are for NexusMods and Thunderstore.
### Publishing to Thunderstore
{%preview https://thunderstore.io/c/cult-of-the-lamb/ %}
You can follow the instructions [here](https://thunderstore.io/c/cult-of-the-lamb/create/docs/).
Typically, your zip file should have a similar structure to this so that it can be easily installed by the R2 Mod Manager.

### Publishing to NexusMods
{%preview https://www.nexusmods.com/games/cultofthelamb %}
For NexusMods, there is no fixed structure to follow, so you will just need to upload your plugin folder as a zip file, and provide some images and descriptions for the mod. You can upload them [here](https://www.nexusmods.com/games/cultofthelamb)