User Tools

Site Tools


guide_firstsrmod

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
guide_firstsrmod [2019/07/04 06:37]
umfdev [Method 2 (Basic Harmony)]
guide_firstsrmod [2019/07/04 11:27] (current)
umfdev
Line 1: Line 1:
 ~~NOTOC~~ ~~NOTOC~~
 ~~Title: My First Slime Rancher Mod (Modding Guide) ~~ ~~Title: My First Slime Rancher Mod (Modding Guide) ~~
-{{tag>​Modding}}+{{tag>​Modding ​Guide dnSpy Harmony UnityScripting}}
 ===== My First Slime Rancher Mod (Modding Guide) ===== ===== My First Slime Rancher Mod (Modding Guide) =====
 <WRAP center round info 100%> <WRAP center round info 100%>
Line 181: Line 181:
     * 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 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.     * This code also ensures the config value takes effect immediately upon applying it in the UMF Menu.
 +    * For more information on the **Awake** function, see [[https://​docs.unity3d.com/​ScriptReference/​MonoBehaviour.Awake.html|Unity Script Reference: Awake]].
 +    * For more information on the **Update** function, see [[https://​docs.unity3d.com/​ScriptReference/​MonoBehaviour.Update.html|Unity Script Reference: Update]].
   - See [[#​Building]] for the next steps.   - 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.     * WARNING: If this is the first method you try, you will also need to comment out ''​Patch_PURPOSEOFPATCH.cs''​ before building.
Line 209: Line 211:
     class MyFirstSRMod : MonoBehaviour     class MyFirstSRMod : MonoBehaviour
     {     {
-        //private static PlayerModel playerModel;​ 
- 
         internal static void Log(string text, bool clean = false)         internal static void Log(string text, bool clean = false)
         {         {
Line 222: Line 222:
         }         }
  
- void Awake() +        ​void Awake() 
- +        
- Log("​MyFirstSRMod v" + UMFMod.GetModVersion().ToString(),​ true);+            Log("​MyFirstSRMod v" + UMFMod.GetModVersion().ToString(),​ true);
             UMFGUI.RegisterPauseHandler(Pause);​             UMFGUI.RegisterPauseHandler(Pause);​
-            //​MyFirstSRModConfig.Load();​ 
         }         }
  
Line 244: Line 243:
             else timeDirector.Unpause();​             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. +
-            } +
-        }*/ +
- }+
 }</​sxh>​ }</​sxh>​
   - Rename ''​Patch_PURPOSEOFPATCH.cs''​ to ''​Patch_MaxHealth.cs''​ along with the class name for it.   - Rename ''​Patch_PURPOSEOFPATCH.cs''​ to ''​Patch_MaxHealth.cs''​ along with the class name for it.
   - Add the following to the top of ''​Patch_MaxHealth.cs'':​ <sxh csharp>​using MonomiPark.SlimeRancher.DataModel;</​sxh>​   - Add the following to the top of ''​Patch_MaxHealth.cs'':​ <sxh csharp>​using MonomiPark.SlimeRancher.DataModel;</​sxh>​
 +    * This lets us target the **PlayerModel** class which is inside the **MonomiPark.SlimeRancher.DataModel** namespace without having to type the full namespace everytime.
   - Set **typeof** in the first HarmonyPatch attribute to use the **PlayerModel** class we discovered with dnSpy. <sxh csharp>​[HarmonyPatch(typeof(PlayerModel))]</​sxh>​   - Set **typeof** in the first HarmonyPatch attribute to use the **PlayerModel** class we discovered with dnSpy. <sxh csharp>​[HarmonyPatch(typeof(PlayerModel))]</​sxh>​
 +    * You could also use **MonomiPark.SlimeRancher.DataModel.PlayerModel** instead for the type.
   - Set the second **HarmonyPatch** attribute to the **ApplyUpgrade** function we discovered with dnSpy. <sxh csharp>​[HarmonyPatch("​ApplyUpgrade"​)]</​sxh>​   - Set the second **HarmonyPatch** attribute to the **ApplyUpgrade** function we discovered with dnSpy. <sxh csharp>​[HarmonyPatch("​ApplyUpgrade"​)]</​sxh>​
   - Inside the patch class create the following postfix function: <sxh csharp> ​       public static void Postfix(PlayerModel __instance)   - Inside the patch class create the following postfix function: <sxh csharp> ​       public static void Postfix(PlayerModel __instance)
Line 287: Line 276:
     * 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.     * 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.     * 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.
 +    * It would also be a good idea to set the current health to be equal to your max health here, however we show you this in Method 6.
 +    * You can also alternatively apply this patch to the **Reset** function instead if you do not have any upgrades at all.
   - See [[#​Building]] for the next steps.   - See [[#​Building]] for the next steps.
 \\ \\
Line 293: Line 284:
 \\ \\
  
-====== Method 3 (Harmony Transpiler) ======+====== Method 3 (Harmony Traverse) ====== 
 +Sometimes a field/​variable can be private or readonly and you may not be able to directly modify it through the game code or a harmony postfix/​prefix patch.\\ 
 +In these cases Harmony'​s Traverse comes in handy.\\ 
 +This Method assumes you have successfully completed Method 2 first. 
 +  - Comment out the only line in the **Postfix** function of ''​Patch_MaxHealth.cs''​. <sxh csharp>//​__instance.maxHealth = MyFirstSRModConfig.MaxHealth;</​sxh>​ 
 +  - Add the following line to the **Postfix** function: <sxh csharp>​Traverse.Create(__instance).Field<​int>​("​maxHealth"​).Value = MyFirstSRModConfig.MaxHealth;</​sxh>​ 
 +  - Your ''​Patch_MaxHealth.cs''​ should now look something like this: <sxh csharp>​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;​ 
 +            Traverse.Create(__instance).Field<​int>​("​maxHealth"​).Value = MyFirstSRModConfig.MaxHealth;​ 
 +        } 
 +    } 
 +}</​sxh>​ 
 +    * The Traverse will bypass any private or readonly variable and let you read/write to it. 
 +    * Traverse does not need to be used inside a Harmony patch, it can be used anywhere anytime in any code, however you have to make sure it doesn'​t try to access code that has not yet been loaded into the game. 
 +    * This will function exactly like Method 2, and is wholly unnecessary since **maxHealth** is public and not readonly. 
 +  - See [[#​Building]] for the next steps. 
 +\\ 
 +---- 
 + 
 +\\ 
 + 
 +====== Method 4 (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 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. This method is really solid for when you really need to modify something that can't be otherwise modified with Method 1 or 2.
 +
 +  - If you have followed the methods in order like you should, then perform the following steps:
 +    - Comment out the **Postfix** function in ''​Patch_MaxHealth.cs''​.
 +  - Add the following variable to ''​MyFirstSRModConfig.cs'':​ <sxh csharp>​public static float MaxHealthFloat;</​sxh>​
 +  - Below the **MaxHealth** config being loaded add the following line: <sxh csharp>​MaxHealthFloat = MaxHealth;</​sxh>​
 +  - Your ''​MyFirstSRModConfig.cs''​ should now look something like this: <sxh csharp>​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;
 +
 +        public static float MaxHealthFloat;​
 +
 +        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."​);​
 +                    MaxHealthFloat = MaxHealth;
 +
 +                    MyFirstSRMod.Log("​Finished loading settings."​);​
 +                }
 +            }
 +            catch (Exception e)
 +            {
 +                MyFirstSRMod.Log("​Error loading mod settings: " + e.Message + "​("​ + e.InnerException?​.Message + "​)"​);​
 +            }
 +        }
 +    }
 +}</​sxh>​
 +    * We need this extra float because we are patching a float in memory now, rather than modifying the maxHealth int variable.
 +    * You could also change the **MaxHealth** from **int** to **float** instead if the int was no longer needed.
 +  - Add the following lines to the top of ''​Patch_MaxHealth.cs'':​ <sxh csharp>​using System.Reflection;​
 +using System.Reflection.Emit;​
 +using System.Collections.Generic;</​sxh>​
 +  - Add the following function to the **Patch_MaxHealth** class: <sxh csharp> ​       public static IEnumerable<​CodeInstruction>​ Transpiler(IEnumerable<​CodeInstruction>​ instructions)
 +        {
 +            foreach (var instruction in instructions)
 +            {
 +                if (instruction.opcode.Equals(OpCodes.Ldc_R4) && instruction.operand.Equals(350f))
 +                {
 +                    //yield return new CodeInstruction(OpCodes.Ldc_R4,​ MyFirstSRModConfig.MaxHealthFloat);​
 +                    yield return new CodeInstruction(OpCodes.Ldsfld,​ typeof(MyFirstSRModConfig).GetField(nameof(MyFirstSRModConfig.MaxHealthFloat),​ BindingFlags.Public | BindingFlags.Static));​
 +                    continue;
 +                }
 +                yield return instruction;​
 +            }
 +        }
 +</​sxh>​
 +  - Your ''​Patch_MaxHealth.cs''​ should now look something like this: <sxh csharp>​using UnityEngine;​
 +using HarmonyLib;
 +using MonomiPark.SlimeRancher.DataModel;​
 +using System.Reflection;​
 +using System.Reflection.Emit;​
 +using System.Collections.Generic;​
 +
 +namespace MyFirstSRMod.Patches
 +{
 +    [HarmonyPatch(typeof(PlayerModel))]
 +    [HarmonyPatch("​ApplyUpgrade"​)]
 +    static class Patch_MaxHealth
 +    {
 +        public static IEnumerable<​CodeInstruction>​ Transpiler(IEnumerable<​CodeInstruction>​ instructions)
 +        {
 +            foreach (var instruction in instructions)
 +            {
 +                if (instruction.opcode.Equals(OpCodes.Ldc_R4) && instruction.operand.Equals(350f))
 +                {
 +                    //yield return new CodeInstruction(OpCodes.Ldc_R4,​ MyFirstSRModConfig.MaxHealthFloat);​
 +                    yield return new CodeInstruction(OpCodes.Ldsfld,​ typeof(MyFirstSRModConfig).GetField(nameof(MyFirstSRModConfig.MaxHealthFloat),​ BindingFlags.Public | BindingFlags.Static));​
 +                    continue;
 +                }
 +                yield return instruction;​
 +            }
 +        }
 +    }
 +}
 +</​sxh>​
 +    * This example assumes you have all health upgrades and only affects the final health upgrade of 350 max health.
 +    * If you wish to test this with the other health upgrades simply change 350 to the max health upgrade you are on.
 +    * In this event we are using an if condition to check for the existence of 350 which in this scenario only happens once in the **ApplyUpgrade** function. If it happened more than once it would be prudent to use different conditions or even replace the instruction directly in it's expected position.
 +    * You could also add additional conditions for each health upgrade along with a separate config setting for each if you wanted.
 +    * Making Transpilers require you to be well acquainted with IL code. You can view the IL code of C# code by right clicking something within a method of dnSpy.
 +    * Transpilers have their specific use scenarios and are sometimes required in order to mod something, but should be avoided by inexperienced modders as they can just as easily break or mess up more code than intended if you are not careful.
 +    * Commented out but included is an alternative **CodeInstruction** way to patch in the config number or any number just at game start, however this method would require restarting the game each time the config changes.
 +  - See [[#​Building]] for the next steps.
 \\ \\
 ---- ----
Line 301: Line 436:
 \\ \\
  
-====== Method ​====== +====== Method ​5 (UMF Patch) ​====== 
-Coming tomorrow+This method overwrites code in in the dll file on disk and is not reversible.\\ 
 +There are some cases where code may be inlined or simply inaccessible without overwriting something in the dll file.\\ 
 +This method should only ever be used for these rare cases, and is certainly the worst one you could use for this max health scenario. 
 +<WRAP center round important 100%> 
 +WARNING: This Method is currently still in development and still missing a lot of functionality,​ as well as has several bugs. 
 +</​WRAP>​ 
 +  - If you completed steps from any other method do the following steps: 
 +    - Comment out the UMFHarmony attribute in ''​MyFirstSRMod.cs''​. <sxh csharp>//​[UMFHarmony(1)]</​sxh>​ 
 +    - Comment out everything in ''​Patch_MaxHealth.cs''​ or ''​Patch_PURPOSEOFPATCH.cs''​. 
 +  - Make a backup of Assembly-CSharp.dll. 
 +  - Add the following variable to ''​MyFirstSRMod.cs'':​ <sxh csharp> ​       private const string UMFPatchMaxHealth = 
 +            "FILE: Assembly-CSharp.dll\n"​ + 
 +            "TYPE: MonomiPark.SlimeRancher.DataModel.PlayerModel\n"​ + 
 +            "​METHOD:​ ApplyUpgrade\n"​ + 
 +            "FIND: ldc.r4 350\n" + 
 +            "​REPLACE:​ ldc.r4 350\n" + 
 +            "WITH: ldc.r4 999" 
 +        ; 
 +</​sxh>​ 
 +  - Add the following lines to the bottom of the **Awake** function: <sxh csharp> ​           MyFirstSRModConfig.Load();​ 
 +            UMFPatch.ApplyPatch("​MaxHealthPatch",​ UMFPatchMaxHealth);​ 
 +</​sxh>​ 
 +  - Your ''​MyFirstSRMod.cs''​ should now look something like this: <sxh csharp>​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 const string UMFPatchMaxHealth = 
 +            "FILE: Assembly-CSharp.dll\n"​ + 
 +            "TYPE: MonomiPark.SlimeRancher.DataModel.PlayerModel\n"​ + 
 +            "​METHOD:​ ApplyUpgrade\n"​ + 
 +            "FIND: ldc.r4 350\n" + 
 +            "​REPLACE:​ ldc.r4 350\n" + 
 +            "WITH: ldc.r4 999" 
 +        ; 
 + 
 +        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();​ 
 +            UMFPatch.ApplyPatch("​MaxHealthPatch",​ UMFPatchMaxHealth);​ 
 +        } 
 + 
 +        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();​ 
 +        } 
 +    } 
 +}</​sxh>​ 
 +    * This same umf patch could be made by creating a file called MaxHealth.umfpatch and putting the contents of the **UMFPatchMaxHealth** variable in there instead. This patch would need to always be included/​copied to the Release folder. 
 +    * See [[api:​umfpatch:​start|UMF Patch API]] to learn more about this system. 
 +  - See [[#​Building]] for the next steps. 
 +    * Don't forget to restore your Assembly-CSharp.dll backup after you are done with this test.
 \\ \\
 ---- ----
Line 308: Line 526:
 \\ \\
  
-====== Method ​====== +====== Method ​6 (Optimal) ​====== 
-Coming tomorrow+This shows you the most optimal way to mod Max Health in Slime Rancher.\\ 
 +While this is the best way to do it for this scenario, it is not necessarily that for modding other things.\\ 
 +You will have to determine your self which method is better for modding something.\\ 
 +Often times you will have to use more than one either in combination to patch one thing, or for several different things in a mod. 
 + 
 +  - Comment out the **MaxHealth** variable in ''​MyFirstSRModConfig.cs''​. <sxh csharp>//​public static int MaxHealth;</​sxh>​ 
 +  - Comment out the loading of the **MaxHealth** variable as well. <sxh csharp>//​MaxHealth = cfg.Read("​MaxHealth",​ new UMFConfigInt(999,​ 1, 9999), "This is the player'​s max health."​);</​sxh>​ 
 +  - Add the following variables to ''​MyFirstSRModConfig.cs'':​ <sxh csharp> ​       public static int MaxHealth0;​ 
 +        public static int MaxHealth1;​ 
 +        public static int MaxHealth2;​ 
 +        public static int MaxHealth3;​ 
 +        public static int MaxHealth4;​ 
 +</​sxh>​ 
 +  - Add the follow lines below the **Add your settings here** line: <sxh csharp> ​                   MaxHealth0 = cfg.Read("​MaxHealth0",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health without any health upgrades."​);​ 
 +                    MaxHealth1 = cfg.Read("​MaxHealth1",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the first health upgrade"​);​ 
 +                    MaxHealth2 = cfg.Read("​MaxHealth2",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the second health upgrade"​);​ 
 +                    MaxHealth3 = cfg.Read("​MaxHealth3",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the third health upgrade"​);​ 
 +                    MaxHealth4 = cfg.Read("​MaxHealth4",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the fourth health upgrade"​);​ 
 +</​sxh>​ 
 +  - Change the **configVersion** variable to "​1.1"​. 
 +    * This is done so that the old MaxHealth config variable which is no longer used is also removed from existing older config files. 
 +  - Your ''​MyFirstSRModConfig.cs''​ should now look something like this: <sxh csharp>​using System; 
 +using UModFramework.API;​ 
 + 
 +namespace MyFirstSRMod 
 +
 +    public class MyFirstSRModConfig 
 +    { 
 +        private static readonly string configVersion = "​1.1";​ 
 + 
 +        //Add your config vars here. 
 +        //public static int MaxHealth;​ 
 +        //public static float MaxHealthFloat;​ 
 +        public static int MaxHealth0;​ 
 +        public static int MaxHealth1;​ 
 +        public static int MaxHealth2;​ 
 +        public static int MaxHealth3;​ 
 +        public static int MaxHealth4;​ 
 + 
 +        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."​);​ 
 +                    //​MaxHealthFloat = MaxHealth;​ 
 +                    MaxHealth0 = cfg.Read("​MaxHealth0",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health without any health upgrades."​);​ 
 +                    MaxHealth1 = cfg.Read("​MaxHealth1",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the first health upgrade"​);​ 
 +                    MaxHealth2 = cfg.Read("​MaxHealth2",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the second health upgrade"​);​ 
 +                    MaxHealth3 = cfg.Read("​MaxHealth3",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the third health upgrade"​);​ 
 +                    MaxHealth4 = cfg.Read("​MaxHealth4",​ new UMFConfigInt(999,​ 1, 9999), "The player'​s max health with the fourth health upgrade"​);​ 
 + 
 +                    MyFirstSRMod.Log("​Finished loading settings."​);​ 
 +                } 
 +            } 
 +            catch (Exception e) 
 +            { 
 +                MyFirstSRMod.Log("​Error loading mod settings: " + e.Message + "​("​ + e.InnerException?​.Message + "​)"​);​ 
 +            } 
 +        } 
 +    } 
 +}</​sxh>​ 
 +    * We have now created a setting for each health upgrade. 
 +  - Add the following function to the **Patch_MaxHealth** class: <sxh csharp> ​       public static void Postfix(PlayerModel __instance, ref PlayerState.Upgrade upgrade) 
 +        { 
 +            switch (upgrade) 
 +            { 
 +                case PlayerState.Upgrade.HEALTH_1:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth1;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_2:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth2;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_3:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth3;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_4:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth4;​ 
 +                    break; 
 +            } 
 +            __instance.currHealth = __instance.maxHealth;​ 
 +        } 
 +</​sxh>​ 
 +    * This ensures we are patching all of the different health upgrades only when they are applied. 
 +    * We are still missing the health you have without any health upgrades though. 
 +  - Add the following code to ''​Patch_MaxHealth.cs'':​ <sxh csharp> ​   [HarmonyPatch(typeof(PlayerModel))] 
 +    [HarmonyPatch("​Reset"​)] 
 +    static class Patch_MaxHealth2 
 +    { 
 +        public static void Postfix(PlayerModel __instance) 
 +        { 
 +            __instance.maxHealth = MyFirstSRModConfig.MaxHealth0;​ 
 +            __instance.currHealth = __instance.maxHealth;​ 
 +        } 
 +    } 
 +</​sxh>​ 
 +    * Here we also patch in the health you have with no health upgrades as seen in the **Reset** function with dnSpy. 
 +  - Your ''​Patch_MaxHealth.cs''​ should now look something like this: <sxh csharp>​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, ref PlayerState.Upgrade upgrade) 
 +        { 
 +            switch (upgrade) 
 +            { 
 +                case PlayerState.Upgrade.HEALTH_1:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth1;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_2:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth2;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_3:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth3;​ 
 +                    break; 
 +                case PlayerState.Upgrade.HEALTH_4:​ 
 +                    __instance.maxHealth = MyFirstSRModConfig.MaxHealth4;​ 
 +                    break; 
 +            } 
 +            __instance.currHealth = __instance.maxHealth;​ 
 +        } 
 +    } 
 + 
 +    [HarmonyPatch(typeof(PlayerModel))] 
 +    [HarmonyPatch("​Reset"​)] 
 +    static class Patch_MaxHealth2 
 +    { 
 +        public static void Postfix(PlayerModel __instance) 
 +        { 
 +            __instance.maxHealth = MyFirstSRModConfig.MaxHealth0;​ 
 +            __instance.currHealth = __instance.maxHealth;​ 
 +        } 
 +    } 
 +}</​sxh>​ 
 +    * We have also made sure that the current player health is set to the new max health when applied, just like the normal game code does. 
 +  - Change the **UMFHarmony** attribute to 2 in ''​MyFirstSRMod.cs''​. <sxh csharp>​[UMFHarmony(2)]</​sxh>​ 
 +    * This tells UMF the number of patches that is expected to be successfully applied, and will show a warning to the user should any of them fail. 
 +    * Since you now have two patches, one targeting the **ApplyUpgrade** function, and one targeting the **Reset** function, this number should be 2. 
 +  - Your ''​MyFirstSRMod.cs''​ should now look something like this: <sxh csharp>​using UnityEngine;​ 
 +using UModFramework.API;​ 
 + 
 +namespace MyFirstSRMod 
 +
 +    [UMFHarmony(2)] //Set this to the number of harmony patches in your mod. 
 +    [UMFScript] 
 +    class MyFirstSRMod : MonoBehaviour 
 +    { 
 +        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);​ 
 +        } 
 + 
 +        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();​ 
 +        } 
 +    } 
 +}</​sxh>​ 
 +  - See [[#​Building]] for the next steps. 
 +    * You should now have a fully working Max Health Mod.
 \\ \\
 ---- ----
Line 317: Line 741:
 ====== Building ====== ====== Building ======
   - In Visual Studio in the top bar menu click **Build > Rebuild solution**.   - In Visual Studio in the top bar menu click **Build > Rebuild solution**.
 +    * Your mod is automatically packed, placed into the game's ''​Mods''​ folder, and ready to play.
   - Start the game and test if your mod works.   - Start the game and test if your mod works.
 +    * You can verify that the mod works by seeing if your Max Health bar is higher than your current health.
   - Proceed to test out the other methods or see [[#​finalizing|Finalizing]].   - Proceed to test out the other methods or see [[#​finalizing|Finalizing]].
     * Don't forget to comment out the code from each method so it doesn'​t affect your test results.     * Don't forget to comment out the code from each method so it doesn'​t affect your test results.
Line 328: Line 754:
 Since this is just an example mod you should not publicize it anywhere since anyone can easily do this.\\ 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. 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 ''​Properties\AssemblyInfo.cs''​ and fill in all the details of your mod. +  - Edit ''​ModInfo.txt''​ to whatever you want to show users who install or update the mod. 
-  * Edit ''​configVersion''​ in ''​MyFirstSRModConfig.cs''​ to match the version in ''​AssemblyInfo.cs''​. +  ​Edit ''​configVersion''​ in ''​MyFirstSRModConfig.cs''​ to match the version in ''​AssemblyInfo.cs''​. 
-  * Clean and remove or comment out any unused code.+    After your first release you should only need to edit this variable when you remove or rename a config option. 
 +  - 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