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 09:09]
umfdev [Method 5 (UMF Patch)]
guide_firstsrmod [2019/07/04 12: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 188: Line 190:
 \\ \\
  
-====== Method 2 (Harmony Traverse) ====== +====== Method 2 (Harmony Postfix) ======
-\\ +
----- +
- +
-\\ +
- +
-====== Method 3 (Harmony Postfix) ======+
 This method is the easiest way to do it, but it's still not entirely optimal. This method is the easiest way to do it, but it's still not entirely optimal.
-  - If you did Method 1 or 2 before this method then perform the following steps:+  - If you did Method 1 before this method then perform the following steps:
     - Uncomment the UMFHarmony attribute in ''​MyFirstSRMod.cs''​. <sxh chsarp>​[UMFHarmony(1)]</​sxh>​     - Uncomment the UMFHarmony attribute in ''​MyFirstSRMod.cs''​. <sxh chsarp>​[UMFHarmony(1)]</​sxh>​
     - Comment out the extra config load you added to the awake function. <sxh csharp>//​MyFirstSRModConfig.Load();</​sxh>​     - Comment out the extra config load you added to the awake function. <sxh csharp>//​MyFirstSRModConfig.Load();</​sxh>​
Line 215: 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 228: 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 250: 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 293: 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.     * 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.
 +\\
 +----
 +
 +\\
 +
 +====== 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.   - See [[#​Building]] for the next steps.
 \\ \\
Line 377: Line 393:
             }             }
         }         }
 +</​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>​ </​sxh>​
     * This example assumes you have all health upgrades and only affects the final health upgrade of 350 max health.     * This example assumes you have all health upgrades and only affects the final health upgrade of 350 max health.
Line 396: Line 441:
 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. 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%> <WRAP center round important 100%>
-WARNING: This Method is still in development and missing a lot of functionality.+WARNING: This Method is currently ​still in development and still missing a lot of functionality, as well as has several bugs.
 </​WRAP>​ </​WRAP>​
   - If you completed steps from any other method do the following steps:   - If you completed steps from any other method do the following steps:
Line 472: Line 517:
     }     }
 }</​sxh>​ }</​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 482: Line 531:
 You will have to determine your self which method is better for modding something.\\ 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. 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 489: 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 500: 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.1562227791.txt.gz · Last modified: 2019/07/04 09:09 by umfdev