[Tutorial] How to Make a RimWorld Mod, Step by Step

Started by jecrell, June 03, 2017, 05:00:43 AM

Previous topic - Next topic

Toby Larone

Great guide, found it super helpful thank you, managed to get it working after a long while. In the example "RangedWeapon_PlagueGun.xml" for download:

<AddHediffChance>0.05<AddHediffChance>

missing the / at the end if anyone has a problem with the example check that you've added that.

P.S. is it normal to get a message in the top left corner of the game telling you whether the shot infected the target or not? I am getting them even with dev mode turned off.

Ghasty

Quote from: Toby Larone on June 06, 2017, 02:57:23 PM
P.S. is it normal to get a message in the top left corner of the game telling you whether the shot infected the target or not? I am getting them even with dev mode turned off.

Yeah I got the same message pop-up. I think it was to show off the translation feature.

arenoobies

#32
Thank you for this! I've been looking for a step by step in modding and I've been rummaging through the defs in RimWorld folder trying to learn how to mod or atleast modified something on my own, with this guide it helps me even better! Please do continue and perhaps in the future add a bit more complicated one like one of your mods, factions! Really appreciated! <3

Edit: I can't find the gun in dev mode spawn weapon, it says these:

QuoteRimWorld 0.17.1557 rev1153
Verse.Log:Message(String)
RimWorld.VersionControl:LogVersionNumber()
Verse.Root:CheckGlobalInit()
Verse.Root:Start()
Verse.Root_Entry:Start()

Exception reading RangedWeapon_PlagueGun.xml as XML: System.Xml.XmlException: 'AddHediffChance' is expected  Line 21, position 6.
  at Mono.Xml2.XmlTextReader.Expect (System.String expected) [0x00000] in <filename unknown>:0
  at Mono.Xml2.XmlTextReader.ReadEndTag () [0x00000] in <filename unknown>:0
  at Mono.Xml2.XmlTextReader.ReadContent () [0x00000] in <filename unknown>:0
  at Mono.Xml2.XmlTextReader.ReadContent () [0x00000] in <filename unknown>:0
  at Mono.Xml2.XmlTextReader.Read () [0x00000] in <filename unknown>:0
  at System.Xml.XmlTextReader.Read () [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNodeCore (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNodeCore (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNodeCore (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNodeCore (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNodeCore (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.ReadNode (System.Xml.XmlReader reader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.Load (System.Xml.XmlReader xmlReader) [0x00000] in <filename unknown>:0
  at System.Xml.XmlDocument.LoadXml (System.String xml) [0x00000] in <filename unknown>:0
  at Verse.LoadableXmlAsset..ctor (System.String name, System.String fullFolderPath, System.String contents) [0x00000] in <filename unknown>:0
Verse.Log:Warning(String)
Verse.LoadableXmlAsset:.ctor(String, String, String)
Verse.<XmlAssetsInModFolder>c__Iterator224:MoveNext()
System.Collections.Generic.List`1:AddEnumerable(IEnumerable`1)
System.Collections.Generic.List`1:.ctor(IEnumerable`1)
System.Linq.Enumerable:ToList(IEnumerable`1)
Verse.ModContentPack:LoadDefs(IEnumerable`1)
Verse.LoadedModManager:LoadAllActiveMods()
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.Root:<Start>m__84E()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__84C()

Loaded file (Scenario) is from version 0.17.1546 rev887, we are running version 0.17.1557 rev1153.
Verse.Log:Warning(String)
Verse.ScribeMetaHeaderUtility:LoadGameDataHeader(ScribeHeaderMode, Boolean)
Verse.GameDataSaveLoader:TryLoadScenario(String, ScenarioCategory, Scenario&)
RimWorld.ScenarioFiles:RecacheData()
RimWorld.ScenarioLister:RecacheData()
RimWorld.ScenarioLister:RecacheIfDirty()
RimWorld.<ScenariosInCategory>c__Iterator128:MoveNext()
System.Linq.Enumerable:FirstOrDefault(IEnumerable`1)
RimWorld.Page_SelectScenario:EnsureValidSelection()
RimWorld.Page_SelectScenario:PreOpen()
Verse.WindowStack:Add(Window)
RimWorld.MainMenuDrawer:<DoMainMenuControls>m__5FF()
Verse.ListableOption:DrawOption(Vector2, Single)
Verse.OptionListingUtility:DrawOptionListing(Rect, List`1)
RimWorld.MainMenuDrawer:DoMainMenuControls(Rect, Boolean)
RimWorld.MainMenuDrawer:MainMenuOnGUI()
Verse.UIRoot_Entry:DoMainMenu()
Verse.UIRoot_Entry:UIRootOnGUI()
Verse.Root:OnGUI()

Initializing new game with mods Core and PlagueGun
Verse.Log:Message(String)
Verse.Game:InitNewGame()
Verse.Root_Play:<Start>m__850()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__84C()

Kappten

When I use ILSpy to decompile Assembly-CSharp I only get:
// C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin_Data\Managed\Assembly-CSharp.dll
// Assembly-CSharp, Version=0.17.6362.34601, Culture=neutral, PublicKeyToken=null

// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: .NET 2.0

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyVersion("0.17.6362.34601")]
[assembly: AssemblyCompany("Ludeon Studios")]
[assembly: AssemblyCopyright("Copyright Ludeon Studios.")]
[assembly: AssemblyTrademark("RimWorld is a registered trademark of Ludeon Studios.")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]


Even when I press CTRL + F and search for "Projectile_" I get no results. I used the Link in the Post to download ILSpy. What am I doing wrong?

mrofa

Quote from: arenoobies on June 09, 2017, 12:34:47 AM
Thank you for this! I've been looking for a step by step in modding and I've been rummaging through the defs in RimWorld folder trying to learn how to mod or atleast modified something on my own, with this guide it helps me even better! Please do continue and perhaps in the future add a bit more complicated one like one of your mods, factions! Really appreciated! <3

Edit: I can't find the gun in dev mode spawn weapon, it says these:


This error regards XML, something is wrong in your xml file, prapobly missing "[/" somewhere

Quote from: Kappten on June 14, 2017, 12:13:53 PM
When I use ILSpy to decompile Assembly-CSharp I only get:
// C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin_Data\Managed\Assembly-CSharp.dll
// Assembly-CSharp, Version=0.17.6362.34601, Culture=neutral, PublicKeyToken=null

// Global type: <Module>
// Architecture: AnyCPU (64-bit preferred)
// Runtime: .NET 2.0

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyVersion("0.17.6362.34601")]
[assembly: AssemblyCompany("Ludeon Studios")]
[assembly: AssemblyCopyright("Copyright Ludeon Studios.")]
[assembly: AssemblyTrademark("RimWorld is a registered trademark of Ludeon Studios.")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]


Even when I press CTRL + F and search for "Projectile_" I get no results. I used the Link in the Post to download ILSpy. What am I doing wrong?

Try this




All i do is clutter all around.


Kappten

When I start the game no errors occur and when I go into "Spawn Weapon..." I can't find PlagueGun when I then go to "Spawn item collection..." and Spawn Weapons I get at some point an error:

Exception drawing PG_Gun_Plague60985: System.NullReferenceException: Object reference not set to an instance of an object
  at Verse.Projectile.get_StartingTicksToImpact () [0x00000] in <filename unknown>:0
  at Verse.Projectile.get_ExactPosition () [0x00000] in <filename unknown>:0
  at Verse.Projectile.get_DrawPos () [0x00000] in <filename unknown>:0
  at Verse.Projectile.Draw () [0x00000] in <filename unknown>:0
  at Verse.DynamicDrawManager.DrawDynamicThings (Verse.DrawTargetDef drawTarget) [0x00000] in <filename unknown>:0
Verse.Log:Error(String)
Verse.DynamicDrawManager:DrawDynamicThings(DrawTargetDef)
Verse.Map:MapUpdate()
Verse.Game:UpdatePlay()
Verse.Root_Play:Update()


Here is my Projectile_PlagueBullet.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using Verse;

namespace Plague
{
    class Projectile_PlagueBullet : Bullet
    {
        #region Properties
        //
        public ThingDef_PlagueBullet Def
        {
            get
            {
                return this.def as ThingDef_PlagueBullet;

            }

        }


        #endregion Properties

        #region Overrides
        protected override void Impact(Thing hitThing)
        {
            base.Impact(hitThing);

            /*
             * Null checking is very important in RimWorld.
             * 99% of errors reported are from NullReferenceExceptions (NREs).
             * Make sure your code checks if things actually exist, before they
             * try to use the code that belongs to said things.
             */
            if (Def != null && hitThing != null && hitThing is Pawn hitPawn) //Fancy way to declare a variable inside an if statement. - Thanks Erdelf.
            {
                var rand = Rand.Value; // This is a random percentage between 0% and 100%
                if (rand <= Def.AddHediffChance) // If the percentage falls under the chance, success!
                {
                    /*
                     * Messages.Message flashes a message on the top of the screen.
                     * You may be familiar with this one when a colonist dies, because
                     * it makes a negative sound and mentioneds "So and so has died of _____".
                     *
                     * Here, we're using the "Translate" function. More on that later in
                     * the localization section.
                     */
                    Messages.Message("PG_PlagueBullet_SuccessMessage".Translate(new object[] {
                        this.launcher.Label, hitPawn.Label
                    }), MessageSound.Standard);

                    //This checks to see if the character has a heal differential, or hediff on them already.
                    var plagueOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);
                    var randomSeverity = Rand.Range(0.15f, 0.30f);
                    if (plagueOnPawn != null)
                    {
                        //If they already have plague, add a random range to its severity.
                        //If severity reaches 1.0f, or 100%, plague kills the target.
                        plagueOnPawn.Severity += randomSeverity;
                    }
                    else
                    {
                        //These three lines create a new health differential or Hediff,
                        //put them on the character, and increase its severity by a random amount.
                        Hediff hediff = HediffMaker.MakeHediff(Def.HediffToAdd, hitPawn, null);
                        hediff.Severity = randomSeverity;
                        hitPawn.health.AddHediff(hediff, null, null);
                    }
                }
                else //failure!
                {
                    /*
                     * Motes handle all the smaller visual effects in RimWorld.
                     * Dust plumes, symbol bubbles, and text messages floating next to characters.
                     * This mote makes a small text message next to the character.
                     */
                    MoteMaker.ThrowText(hitThing.PositionHeld.ToVector3(), hitThing.MapHeld, "PG_PlagueBullet_FailureMote".Translate(Def.AddHediffChance), 12f);
                }
            }
        }
        #endregion Overrides

    }
}

What have I done wrong?

Kilroy232

Just wanna say that this is a very good tutorial but I am having one problem. I am sure I just have done something silly but some help would be great.
As of right now I have just copy and pasted the example code to see if I did something wrong but I am still getting the error.


Severity Code Description Project File Line Suppression State
Error CS0103 The name 'hitPawn' does not exist in the current context PlagueGun C:\Program Files (x86)\Steam\steamapps\common\RimWorld\Mods\PlagueGun\PlagueGun\Projectile_PlagueBullet.cs 34 Active



mrofa

Kilroy232 this means your missing variable hitPawn which is created in wierd way in
if (Def != null && hitThing != null && hitThing is Pawn hitPawn) //Fancy way to declare a variable inside an if statement. - Thanks Erdelf.
Not sure if that statment need some special reference to work..
but to fix it you can try it like this

if (Def != null && hitThing != null ) //Fancy way to declare a variable inside an if statement. - Thanks Erdelf.
            {
                 Pawn hitPawn = hitThing as Pawn
               if(hitPawn!=null)
                {
                var rand = Rand.Value; // This is a random percentage between 0% and 100%
                if (rand <= Def.AddHediffChance) // If the percentage falls under the chance, success!
                {
                    /*
                     * Messages.Message flashes a message on the top of the screen.
                     * You may be familiar with this one when a colonist dies, because
                     * it makes a negative sound and mentioneds "So and so has died of _____".
                     *
                     * Here, we're using the "Translate" function. More on that later in
                     * the localization section.
                     */
                    Messages.Message("PG_PlagueBullet_SuccessMessage".Translate(new object[] {
                        this.launcher.Label, hitPawn.Label
                    }), MessageSound.Standard);

                    //This checks to see if the character has a heal differential, or hediff on them already.
                    var plagueOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);
                    var randomSeverity = Rand.Range(0.15f, 0.30f);
                    if (plagueOnPawn != null)
                    {
                        //If they already have plague, add a random range to its severity.
                        //If severity reaches 1.0f, or 100%, plague kills the target.
                        plagueOnPawn.Severity += randomSeverity;
                    }
                    else
                    {
                        //These three lines create a new health differential or Hediff,
                        //put them on the character, and increase its severity by a random amount.
                        Hediff hediff = HediffMaker.MakeHediff(Def.HediffToAdd, hitPawn, null);
                        hediff.Severity = randomSeverity;
                        hitPawn.health.AddHediff(hediff, null, null);
                    }
                }
                else //failure!
                {
                    /*
                     * Motes handle all the smaller visual effects in RimWorld.
                     * Dust plumes, symbol bubbles, and text messages floating next to characters.
                     * This mote makes a small text message next to the character.
                     */
                    MoteMaker.ThrowText(hitThing.PositionHeld.ToVector3(), hitThing.MapHeld, "PG_PlagueBullet_FailureMote".Translate(Def.AddHediffChance), 12f);
                }
              }
            }


It will take out the hitPawn creation from "if" statment and create it after that if check, then check if its not null(if thing is actually a pawn).
All i do is clutter all around.

Kilroy232

#39
There was a semicolon missing after the variable declaration but otherwise this fixed the problem I was having, thank you VERY much.


if (Def != null && hitThing != null ) //Fancy way to declare a variable inside an if statement. - Thanks Erdelf.
            {
                 Pawn hitPawn = hitThing as Pawn
               if(hitPawn!=null)


Also a big thank you to jecrell for this very well written tutorial!

jamaicancastle

Thanks for a remarkably in-depth tutorial! I have a question that might make for a good addition and/or follow-on tutorial. I'm working on a mod for new ship parts, and as part of it, I'd like to add a check to the launch report that the ship has enough thrust for its weight, etc. I know to do that I need to modify the existing starship code, and I know I'm supposed to use the Harmony library to do it, but I have no idea how to do either of these things. :-[ If anyone has a tutorial along those lines that would be spectacular.

Quote from: steeveebr on June 04, 2017, 12:58:03 PM
Out of curiosity:  Step 11 states to copy all of the ThingDef Parents and Bases to your XML...
And then your code snippet also includes BaseHumanGun.  Is that inclusion necessary?  I don't see it as a parent in either the Pistol Bullet or the Pistol Gun.

You don't need to include core defs at all. Parenting them will work just fine using the core files (as long as you have the parent's name down correctly).

Cryusaki

Probably the best modding tutorial for Rimworld on the internet but it is still missing so much detail that it's hard to follow

jecrell

Quote from: Cryusaki on July 03, 2017, 04:23:21 PM
Probably the best modding tutorial for Rimworld on the internet but it is still missing so much detail that it's hard to follow
Where? I'd like to improve the tutorial. So what's confusing?
...Psst. Still there? If you'd like to support
me and my works, do check out my Patreon.
Someday, I could work for RimWorld full time!

https://www.patreon.com/jecrell

Spdskatr

Quote from: jecrell on June 03, 2017, 05:01:31 AM
Required Items







Notepad++ or
Atom or
Sublimetext
|Use any text editor that allows you to edit XML files and use "Find in Files" for referencing.
Visual Studio Community|Use this or any other C# compiler to turn scripts into .dll files that RimWorld can use.
Zentar's ILSpy|This is for referencing the game's decompiled C# scripts. **NOTE** Regular ILSpy will not give you the cleanest code. For best results, please use Zhentar's ILSpy.
Just a quick pointer (0x154ca6e7 hahahah) You may want to specify the Visual Studio Community version (2017). I see so many people asking questions because their VS is an earlier version that cannot read C# 7...

Cheers
My mods

If 666 is evil, does that make 25.8069758011 the root of all evil?

RemingtonRyder

Hey Jecrell,

Something that's sort of missing is a newbies guide to what to do after your mod is on Workshop.

For example, I have this problem where some of my mods are more well-known than others.

Now, I don't know if this is the best solution, but I figured it couldn't hurt to try. I added the following to the end of the description on my most popular mods on Workshop:

If you like this mod, why not consider looking at some of my other mods?

[b]Harsher Tundra[/b]
No trees in the tundra biome, fewer grazing animals. Ice patches.

[b]Really Toxic Fallout[/b]
Even brief exposure to toxic fallout can now cause life-threatening complications. Stay indoors!

[b]Less Annoying UI Sounds[/b]
You no longer need go berserk because of an annoying ding or boo-doop.

[b]No Doomed Friendlies[/b]
Friendlies will not show up if you haven't opened the ancient danger room.

[b]Solar Apocalypse & Rogue Planet[/b]
These planet-threatening game conditions will have you researching ship-building like a boss.

[b]Peaceful Fixes[/b]
Peaceful difficulty is new, and there are a few events which show up despite it supposedly being free of threats.

[b]Difficulty Expansion[/b]
Is Extreme not extreme enough for you? Are the easier difficultites not easy enough?

[b]Combat Readiness Check[/b]
Raids now scale mostly based on your colonists, how healthy they are, and what weapons or armour you can equip them with.

[b]No Doomed Newbies[/b]
Wanderers who join, and chased refugees who refuge, and even escape pod survivors will be more relevant to your colony.


Looks good, right? :)