User Tools

Site Tools


guide_firstsrmod

This is an old revision of the document!


My First Slime Rancher Mod (Modding Guide)

This guide will help you create a simple Slime Rancher Mod for UMF that lets you modify your max health.
It will guide you through all the different methods you could use to modify the players max health, along with the method best suited for this particular task.
It is up to you to determine which method is better suited for anything else you might attempt to mod.

Preliminary Setup

  1. Start by creating the project files as seen in Mod Creation.
    • Use the parameters as seen in the image below.
  2. Open the newly generated solution in Visual Studio.
  3. Open and browse the game code as seen in Mod Creation: Decompiling Game Code.
  4. Use the search bar in dnSpy to search for maxHealth.
    • Here we can see that the max health is retrieved by the PlayerState through the GetMaxHealth function for reading purposes.
    • Double clicking GetMaxHealth will also reveal that it retrieves the real maxHealth value from the PlayerModel, just as seen in the second search result.
  5. Double click the maxHealth search result pointing to the PlayerModel class.
    • Here we can see that maxHealth is stored as a 32bit int.
  6. Right click maxHealth and select Analyze.
    • Here we see that maxHealth is assigned by PlayerModel.ApplyUpgrade and PlayerModel.Reset.
    • Further analyzing of ApplyUpgrade and Reset will teach you that ApplyUpgrade is used by the in-game upgrade console to apply upgrades the player has purchased, including max health upgrades. The Reset function is used when a save is loaded or when a new game is started and also resets the max health.
    • If we look at the Reset function we see that the default health without any upgrades is 100.
    • If we look at the ApplyUpgrade function we see that maxHealth is 150, 200, 250, and 350 depending on the health upgrade the player has.
    • We have now learned that maxHealth is of the int type and that ApplyUpgrade and Reset will modify the maxHealth.
  7. Create a MaxHealth config setting in MyFirstSRModConfig.cs with the following code.
    • Add the following line below
      //Add your config vars here.

      public static int MaxHealth;

    • Add the following line below
      //Add your settings here

      MaxHealth = cfg.Read("MaxHealth", new UMFConfigInt(999, 1, 9999), "This is the player's max health.");

    • Your config class should now look something like this:
      using System;
      using UModFramework.API;
      
      namespace MyFirstSRMod
      {
          public class MyFirstSRModConfig
          {
              private static readonly string configVersion = "1.0";
      
              //Add your config vars here.
              public static int MaxHealth;
      
              internal static void Load()
              {
                  MyFirstSRMod.Log("Loading settings.");
                  try
                  {
                      using (UMFConfig cfg = new UMFConfig())
                      {
                          string cfgVer = cfg.Read("ConfigVersion", new UMFConfigString());
                          if (cfgVer != string.Empty && cfgVer != configVersion)
                          {
                              cfg.DeleteConfig(false);
                              MyFirstSRMod.Log("The config file was outdated and has been deleted. A new config will be generated.");
                          }
      
                          //cfg.Write("SupportsHotLoading", new UMFConfigBool(false)); //Uncomment if your mod can't be loaded once the game has started.
                          cfg.Read("LoadPriority", new UMFConfigString("Normal"));
                          cfg.Write("MinVersion", new UMFConfigString("0.52.1"));
                          //cfg.Write("MaxVersion", new UMFConfigString("0.54.99999.99999")); //Uncomment if you think your mod may break with the next major UMF release.
                          cfg.Write("UpdateURL", new UMFConfigString(""));
                          cfg.Write("ConfigVersion", new UMFConfigString(configVersion));
      
                          MyFirstSRMod.Log("Finished UMF Settings.");
      
                          //Add your settings here
                          MaxHealth = cfg.Read("MaxHealth", new UMFConfigInt(999, 1, 9999), "This is the player's max health.");
      
                          MyFirstSRMod.Log("Finished loading settings.");
                      }
                  }
                  catch (Exception e)
                  {
                      MyFirstSRMod.Log("Error loading mod settings: " + e.Message + "(" + e.InnerException?.Message + ")");
                  }
              }
          }
      }
    • You have now added a config setting that can be adjusted from the UMF Menu in-game.
  8. Proceed by testing out all the different methods you can mod the max health below.




Method 1 (Unity Scripting)

This method uses only Unity Scripting and the game's own code to modify the max health value.
You can find this method by looking through the game code which will take some experience and analyzing.
For this particular scenario this method is not the best to use.

In Slime Rancher the PlayerModel class instance can be accessed through the GameModel instance class which in turn can be retrieved from the SceneContext using the SRSingleton class.

We can set this value directly using the following line:

SRSingleton<SceneContext>.Instance.GameModel.GetPlayerModel().maxHealth = MyFirstSRModConfig.MaxHealth;
This would set maxHealth to whatever the config value is set to, in the default case 999.
However we have to set this in a way that it is done after ApplyUpgrade and Reset which is why the other methods are normally better for this case. So to ensure the maxHealth is always set after those we can apply it using Unity Scripting's Update function, which require some extra checks to ensure we are in the game.

  1. In your MyFirstSRMod.cs add the following at the top:
    using MonomiPark.SlimeRancher.DataModel;
  2. Comment out the [UMFHarmony(1)] line.
    //[UMFHarmony(1)]
  3. Add the following line to the class:
    private static PlayerModel playerModel;
  4. Add the following to the bottom of the Awake method:
    MyFirstSRModConfig.Load();
  5. Add the following code to the Update function.
    if (!Levels.isSpecial() && SRSingleton<SceneContext>.Instance?.GameModel != null) //Makes sure we are in game and that the GameModel exists.
    {
        playerModel = SRSingleton<SceneContext>.Instance.GameModel.GetPlayerModel();
    }
    if (playerModel != null) //Make sure that the PlayerModel has been retrieved.
    {
        playerModel.maxHealth = MyFirstSRModConfig.MaxHealth; //Set the max health to our config value.
    }
    
  6. Your MyFirstSRMod.cs should now look something like this:
    using UnityEngine;
    using UModFramework.API;
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using MonomiPark.SlimeRancher.DataModel;
    
    namespace MyFirstSRMod
    {
        //[UMFHarmony(1)] //Set this to the number of harmony patches in your mod.
        [UMFScript]
        class MyFirstSRMod : MonoBehaviour
        {
            private static PlayerModel playerModel;
    
            internal static void Log(string text, bool clean = false)
            {
                using (UMFLog log = new UMFLog()) log.Log(text, clean);
            }
    
            [UMFConfig]
            public static void LoadConfig()
            {
                MyFirstSRModConfig.Load();
            }
    
    		void Awake()
    		{
    			Log("MyFirstSRMod v" + UMFMod.GetModVersion().ToString(), true);
                UMFGUI.RegisterPauseHandler(Pause);
                MyFirstSRModConfig.Load();
            }
    
            public static void Pause(bool pause)
            {
                TimeDirector timeDirector = null;
                try
                {
                    timeDirector = SRSingleton<SceneContext>.Instance.TimeDirector;
                }
                catch { }
                if (!timeDirector) return;
                if (pause)
                {
                    if (!timeDirector.HasPauser()) timeDirector.Pause();
                }
                else timeDirector.Unpause();
            }
    
            void Update()
            {
                if (!Levels.isSpecial() && SRSingleton<SceneContext>.Instance?.GameModel != null) //Makes sure we are in game and that the GameModel exists.
                {
                    playerModel = SRSingleton<SceneContext>.Instance.GameModel.GetPlayerModel();
                }
                if (playerModel != null) //Make sure that the PlayerModel has been retrieved.
                {
                    playerModel.maxHealth = MyFirstSRModConfig.MaxHealth; //Set the max health to our config value.
                }
            }
    	}
    }
    • This will make sure that your max health is always 999 every single frame regardless of which health upgrade the player has. This is obviously not normally recommended.
    • This code also ensures the config value takes effect immediately upon applying it in the UMF Menu.
  7. See Building for the next steps.
    • WARNING: If this is the first method you try, you will also need to comment out Patch_PURPOSEOFPATCH.cs before building.




Method 2 (Harmony Postfix)

This method is the easiest way to do it, but it's still not entirely optimal.

  1. If you did Method 1 before this method then perform the following steps:
    1. Uncomment the UMFHarmony attribute in MyFirstSRMod.cs.
      [UMFHarmony(1)]
    2. Comment out the extra config load you added to the awake function.
      //MyFirstSRModConfig.Load();
      • This is because the UMFHarmony will automatically trigger the UMFConfig attribute before applying the patches in the mod.
    3. Uncomment everything in Patch_PURPOSEOFPATCH.cs.
    4. Comment out any code you added with other methods so they do not conflict with what you are currently doing.
    5. Your MyFirstSRMod.cs should now look something like this:
      using UnityEngine;
      using UModFramework.API;
      using System;
      using System.Linq;
      using System.Collections.Generic;
      using MonomiPark.SlimeRancher.DataModel;
      
      namespace MyFirstSRMod
      {
          [UMFHarmony(1)] //Set this to the number of harmony patches in your mod.
          [UMFScript]
          class MyFirstSRMod : MonoBehaviour
          {
              //private static PlayerModel playerModel;
      
              internal static void Log(string text, bool clean = false)
              {
                  using (UMFLog log = new UMFLog()) log.Log(text, clean);
              }
      
              [UMFConfig]
              public static void LoadConfig()
              {
                  MyFirstSRModConfig.Load();
              }
      
      		void Awake()
      		{
      			Log("MyFirstSRMod v" + UMFMod.GetModVersion().ToString(), true);
                  UMFGUI.RegisterPauseHandler(Pause);
                  //MyFirstSRModConfig.Load();
              }
      
              public static void Pause(bool pause)
              {
                  TimeDirector timeDirector = null;
                  try
                  {
                      timeDirector = SRSingleton<SceneContext>.Instance.TimeDirector;
                  }
                  catch { }
                  if (!timeDirector) return;
                  if (pause)
                  {
                      if (!timeDirector.HasPauser()) timeDirector.Pause();
                  }
                  else timeDirector.Unpause();
              }
      
              /*void Update()
              {
                  if (!Levels.isSpecial() && SRSingleton<SceneContext>.Instance?.GameModel != null) //Makes sure we are in game and that the GameModel exists.
                  {
                      playerModel = SRSingleton<SceneContext>.Instance.GameModel.GetPlayerModel();
                  }
                  if (playerModel != null) //Make sure that the PlayerModel has been retrieved.
                  {
                      playerModel.maxHealth = MyFirstSRModConfig.MaxHealth; //Set the max health to our config value.
                  }
              }*/
      	}
      }
  2. Rename Patch_PURPOSEOFPATCH.cs to Patch_MaxHealth.cs along with the class name for it.
  3. Add the following to the top of Patch_MaxHealth.cs:
    using MonomiPark.SlimeRancher.DataModel;
  4. Set typeof in the first HarmonyPatch attribute to use the PlayerModel class we discovered with dnSpy.
    [HarmonyPatch(typeof(PlayerModel))]
  5. Set the second HarmonyPatch attribute to the ApplyUpgrade function we discovered with dnSpy.
    [HarmonyPatch("ApplyUpgrade")]
  6. Inside the patch class create the following postfix function:
            public static void Postfix(PlayerModel __instance)
            {
            }
    
  7. Add the following line to our new function:
    __instance.maxHealth = MyFirstSRModConfig.MaxHealth;
    • At this point the max health will be set regardless of which health upgrades the player has due to this function being run everytime a save is loaded.
    • Since this function is only run on a save load or when the player buys a new upgrade, changes to the config will not take effect immediately like this.
  8. Your Patch_MaxHealth.cs should now look something like this:
    using UnityEngine;
    using HarmonyLib;
    using MonomiPark.SlimeRancher.DataModel;
    
    namespace MyFirstSRMod.Patches
    {
        [HarmonyPatch(typeof(PlayerModel))]
        [HarmonyPatch("ApplyUpgrade")]
        static class Patch_MaxHealth
        {
            public static void Postfix(PlayerModel __instance)
            {
                __instance.maxHealth = MyFirstSRModConfig.MaxHealth;
            }
        }
    }
    • A Harmony Postfix patch will make the code in it execute at the end of the function when all other code in the original function has executed.
    • You could change it to a Prefix by simply renaming the function to Prefix. This would cause the code to be run before the other code in the original function. However that would not work for this scenario since the other code then overwrites our max health again.
  9. See Building for the next steps.




Method 3 (Harmony Transpiler)

This method will show you how you can use a Transpiler to overwrite code in memory rather than inject new code into an existing function.
This method is really solid for when you really need to modify something that can't be otherwise modified with Method 1 or 2.



Method 4

Coming tomorrow



Method 5

Coming tomorrow



Building

  1. In Visual Studio in the top bar menu click Build > Rebuild solution.
  2. Start the game and test if your mod works.
  3. Proceed to test out the other methods or see Finalizing.
    • Don't forget to comment out the code from each method so it doesn't affect your test results.




Finalizing

Since this is just an example mod you should not publicize it anywhere since anyone can easily do this.
However for the purpose of completion these steps should be taken before releasing any mod.

  • Edit ModInfo.txt to whatever you want to show users who install or update the mod.
  • Edit Properties\AssemblyInfo.cs and fill in all the details of your mod.
  • Edit configVersion in MyFirstSRModConfig.cs to match the version in AssemblyInfo.cs.
  • Clean and remove or comment out any unused code.



This topic does not exist yet

You've followed a link to a topic that doesn't exist yet. If permissions allow, you may create it by clicking on “Create this page”.

guide_firstsrmod.1562222225.txt.gz · Last modified: 2019/07/04 06:37 by umfdev