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

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

Previous topic - Next topic

Bendigeidfran

Phenomenal, thank you. There were a couple places that could use a bit of clarity and the MessageSound doesn't exist, I had to change it to something else, but overall thank you very much.

tudy

Hi,

I basically signed up to the forums to tell you what a good job you did on the tutorial. I followed the steps and so many things are much clearer now. I encountered a few little problems on the way that others already mentioned. To be precise:

This

if (Def != null && hitThing != null)
            {
            Pawn hitPawn = hitThing as Pawn;
            if(hitPawn != null)

helps, if you get an error in the if-clause, when initiating "hitPawn" inside of it.

This

Messages.Message("TST_PlagueBullet_SuccessMessage", new MessageTypeDef());

helped me with the error in the Message-function. I guess the game got an update, which is not taken care of yet, in the tutorial?

Then this line prevented me from building:

var plagueOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);


I cancelled out the question-marks, resulting in the buildable version:

var plagueOnPawn = hitPawn.health.hediffSet.GetFirstHediffOfDef(Def.HediffToAdd);


Not 100% sure what these "?" are actually doing, but the result works as intended, so, if anyone else got that problem, this might be your solution as well.

I do have a few questions, however, that maybe some helpful person can answer:
1) What is the first .cs file good for (Steps 31 - 35)? I can't wrap my head around, what it actually does. Isn't all of that done already in the XML-file? If it is just about making a blueprint, couldn't we put all of what's happening there just into the second .cs file created?
2) To test out the mod I wanted to put the chance to apply the Plague to 100%. So, I went into the first .cs file (the one I wonder what it actually is doing) and set the value to "1.0f". But it had no effect whatsoever. Only after I changed it in the XML to "1.0" it applied the Plague to each shot. Why is that? What's the difference between the value set in the .cs-file and the xml-file? Where should I actually change such values in good practice?

Thanks again for this great tutorial. Maybe the answers of the above could be implemented into it, to give more clarity to what's actually happening "behind the curtains".

jamaicancastle

Quote from: tudy on December 28, 2017, 05:02:41 PM
This

if (Def != null && hitThing != null)
            {
            Pawn hitPawn = hitThing as Pawn;
            if(hitPawn != null)

helps, if you get an error in the if-clause, when initiating "hitPawn" inside of it.
If I'm understanding correctly, the error-checking you're thinking of is encapsulated in the is operator already. It will return false if hitThing can't be coerced into being a Pawn, which will prevent the entire block from happening at all. (If you have as for is in the if statement, then you're correct, it is possible to get a null result stored in hitPawn if given a non-pawn object.)

Quote
Then this line prevented me from building:

var plagueOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);


I cancelled out the question-marks, resulting in the buildable version:

var plagueOnPawn = hitPawn.health.hediffSet.GetFirstHediffOfDef(Def.HediffToAdd);


Not 100% sure what these "?" are actually doing, but the result works as intended, so, if anyone else got that problem, this might be your solution as well.
I had no problems building with the ?s using the suggested IDE. It's possible it's a compatibility

As for what they do: "." means "from the object specified to the left, find the attribute specified to the right". "?." means "from the object specified to the left, if it isn't null, find the attribute specified to the right". It's a compact way of avoiding errors if for whatever bizarre reason the pawn doesn't have a hediff set or whatever.

Quote
1) What is the first .cs file good for (Steps 31 - 35)? I can't wrap my head around, what it actually does. Isn't all of that done already in the XML-file? If it is just about making a blueprint, couldn't we put all of what's happening there just into the second .cs file created?
Basically the class tells Rimworld that a def category exists. It's a bridge between the engine and the XML files; without that, it doesn't know how to parse the XML. It also allows other code to refer to a def class; that's not important for this specific class, but it's critical for many classes.

As for putting them in the same file: I think you could, if you wanted to, but having each class in its own file is considered better practice.

Quote
2) To test out the mod I wanted to put the chance to apply the Plague to 100%. So, I went into the first .cs file (the one I wonder what it actually is doing) and set the value to "1.0f". But it had no effect whatsoever. Only after I changed it in the XML to "1.0" it applied the Plague to each shot. Why is that? What's the difference between the value set in the .cs-file and the xml-file? Where should I actually change such values in good practice?
The values in the .cs file are basically placeholders or defaults. You can think of it like having a default parameter for a function. For example, if a default AddHediffChance exists in the code definition for ThingDef_PlagueBullet, then if your XML happens to have a ThingDef_PlagueBullet that lacks an AddHediffChance, it will use the default. If there's no default and nothing in the XML it will give an error when loaded.

Sandy

Quote from: tudy on December 28, 2017, 05:02:41 PM
Then this line prevented me from building:

var plagueOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);


I cancelled out the question-marks, resulting in the buildable version:

var plagueOnPawn = hitPawn.health.hediffSet.GetFirstHediffOfDef(Def.HediffToAdd);


Not 100% sure what these "?" are actually doing, but the result works as intended, so, if anyone else got that problem, this might be your solution as well.

thanks man, i had this error too and u helped clear it..  :)

and Thanks VERY MUCH Jecrell, for this wonderful tutorial.. using this, i managed to make the plague gun.. i had some trouble, bcoz i was using SharpDevelop.. but the steps in the modding tutorial in the wiki made it easier.. :)
i decided to try my hand at this and made a small mod by copying the core weapons xml file and changing most of the weapons Bullet damage to Flame damage.. it worked nicely too.. raiders were burning and panicking and fleeing..  ;D ;D ;D
Thanks to everyone that provided tips and fixes..  :) :) :)

fyarn

Hey jecrell, great guide! Could I suggest you add the Rimworld cookiecutter template?
https://ludeon.com/forums/index.php?topic=39038

It has a command-line tool for setup without using VisualStudio, as well as a VisualStudio integration to create a mod without even leaving VS to make folders.

ttamttam

Thank you for demystifying mods and c#. I've always wanted to get into more serious coding but setting up environments always scared me.

Khaligufzel

#81
Hey, new moders!
Don't try to find 'Bullet_Gun' in your files!

Since v 0.18, you should search for Bullet_Revolver or Bullet_Autopistol. There is no more just 'Pistol' in RimWorld :)

@jecrell
Thanks for this tutorial mate!


edit:
Also <defaultProjectile> not <projectileDef>

Jernfalk

The tutorial is fantastic. As a first-timer, there were a lot of things that I had no idea that existed.
However, to do different objects is still a struggle. Is there a more generic tutorial? One with only the absolute necessary amount of .xml and C#?

Negomir99

#83
This tutorial is great, I managed to get the gun into the game, even tweak its accuracy and other stats, but every time the bullet hits anything I get this error:
QuoteException ticking NEGO_Bullet_PlagueGun110669: System.StackOverflowException: The requested operation caused a stack overflow.
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
.
. have to shorten it because it's too long for the post...
.
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
Verse.Log:Error(String)
Verse.TickList:Tick()
Verse.TickManager:DoSingleTick()
Verse.TickManager:TickManagerUpdate()
Verse.Game:UpdatePlay()
Verse.Root_Play:Update()

I know this tutorial isn't really recent, but if anyone can help it would be greatly appreciated!!

here is the XML file:
Quote<?xml version="1.0" encoding="utf-8"?>

<ThingDefs>

   <ThingDef Class="Plague.ThingDef_PlagueBullet" ParentName="BaseBullet">
    <defName>NEGO_Bullet_PlagueGun</defName>
    <label>pistol bullet</label>
    <graphicData>
      <texPath>Things/Projectile/Bullet_Small</texPath>
      <graphicClass>Graphic_Single</graphicClass>
    </graphicData>
    <projectile>
      <flyOverhead>false</flyOverhead>
      <damageDef>Bullet</damageDef>
      <DamageAmountBase>1</DamageAmountBase>
      <Speed>55</Speed>
   </projectile>
   <AddHediffChance>1.0</AddHediffChance>
   <HediffToAdd>Plague</HediffToAdd>
   <thingClass>Plague.Projectile_PlagueBullet</thingClass>
  </ThingDef>
 
  <ThingDef ParentName="BaseHumanMakeableGun">
    <defName>NEGO_Gun_PlagueGun</defName>
    <label>plague gun</label>
    <description>A curious weapon notable for its horrible health effects.</description>
    <graphicData>
      <texPath>Things/Item/Equipment/WeaponRanged/Pistol</texPath>
      <graphicClass>Graphic_Single</graphicClass>
    </graphicData>
    <soundInteract>InteractPistol</soundInteract>
    <statBases>
      <WorkToMake>15000</WorkToMake>
      <Mass>1.2</Mass>
      <AccuracyTouch>1.0</AccuracyTouch>
      <AccuracyShort>1.0</AccuracyShort>
      <AccuracyMedium>1.0</AccuracyMedium>
      <AccuracyLong>1.0</AccuracyLong>
      <RangedWeapon_Cooldown>1.26</RangedWeapon_Cooldown>
    </statBases>
    <costList>
      <Steel>30</Steel>
      <Component>2</Component>
    </costList>
    <verbs>
      <li>
        <verbClass>Verb_Shoot</verbClass>
        <hasStandardCommand>true</hasStandardCommand>
        <projectileDef>NEGO_Bullet_PlagueGun</projectileDef>
        <warmupTime>0.3</warmupTime>
        <range>24</range>
        <soundCast>ShotPistol</soundCast>
        <soundCastTail>GunTail_Light</soundCastTail>
        <muzzleFlashScale>9</muzzleFlashScale>
      </li>
    </verbs>
  </ThingDef>
 
</ThingDefs>

and the CS file
Quoteusing 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

        #region Overrides

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

            Pawn hitPawn = hitThing as Pawn;
            float randomSeverity = Rand.Value;

            if (Def != null && hitThing != null && hitPawn != null)
            {
                var rand = Rand.Value;
                if(rand <= Def.AddHediffChance)
                {
                    Messages.Message("NEGO_PlagueBullet_SuccessMessage".Translate(new object[]
                    {
                        this.launcher.Label, hitPawn.Label
                    }), MessageSound.Standard);
                }

                var plagueOrPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(Def.HediffToAdd);
                if (plagueOrPawn != null)
                {
                    plagueOrPawn.Severity += randomSeverity;
                }
                else
                {
                    Hediff hediff = HediffMaker.MakeHediff(Def.HediffToAdd, hitPawn, null);
                    hediff.Severity = randomSeverity;
                    hitPawn.health.AddHediff(hediff, null, null);
                }
            }
            else
            {
                MoteMaker.ThrowText(hitThing.PositionHeld.ToVector3(), hitThing.MapHeld, "NEGO_PlagueBullet_FailureMote".Translate(Def.AddHediffChance), 12f);
            }
        }

        #endregion
    }
}

and on top of that error, no plague is ever added.

jamaicancastle

Quote from: Negomir99 on April 11, 2018, 11:43:25 AM
This tutorial is great, I managed to get the gun into the game, even tweak its accuracy and other stats, but every time the bullet hits anything I get this error:
QuoteException ticking NEGO_Bullet_PlagueGun110669: System.StackOverflowException: The requested operation caused a stack overflow.
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
As a general rule, a stack overflow happens when you cause RW to try to do something recursively and end up in an infinite loop. In this case, you can see that it's in the Def getter, here:

        public ThingDef_PlagueBullet Def
        {
            get
            {
                return this.Def as ThingDef_PlagueBullet;
            }
        }

In this case it's calling this.Def - which is to say, itself, hence the infinite loop. It should be calling this.def (note the capitalization), which is a property of the base object.

The code goes along fine until it actually tries to get Def, which it does in Impact, specifically in this line:
            if (Def != null && hitThing != null && hitPawn != null)
Because it runs into the error, the rest of the Impact method doesn't get run, hence why it doesn't apply its impact effects.

Negomir99

Quote from: jamaicancastle on April 11, 2018, 01:01:34 PM
Quote from: Negomir99 on April 11, 2018, 11:43:25 AM
This tutorial is great, I managed to get the gun into the game, even tweak its accuracy and other stats, but every time the bullet hits anything I get this error:
QuoteException ticking NEGO_Bullet_PlagueGun110669: System.StackOverflowException: The requested operation caused a stack overflow.
  at Plague.Projectile_PlagueBullet.get_Def () [0x00000] in <filename unknown>:0
As a general rule, a stack overflow happens when you cause RW to try to do something recursively and end up in an infinite loop. In this case, you can see that it's in the Def getter, here:

        public ThingDef_PlagueBullet Def
        {
            get
            {
                return this.Def as ThingDef_PlagueBullet;
            }
        }

In this case it's calling this.Def - which is to say, itself, hence the infinite loop. It should be calling this.def (note the capitalization), which is a property of the base object.

The code goes along fine until it actually tries to get Def, which it does in Impact, specifically in this line:
            if (Def != null && hitThing != null && hitPawn != null)
Because it runs into the error, the rest of the Impact method doesn't get run, hence why it doesn't apply its impact effects.

You are completely right, and I'm completely stupid.. Thank you very much!!

Negomir99

ok another thing... my bullets keep giving the target severity 1 or 100% and killing them instantly.

here is the bullet in xml:
Quote<ThingDef Class = "PlagueGun.ThingDef_PlagueBullet" ParentName="BaseBullet">
      <defName>Bullet_PlagueGun</defName>
      <label>PlagueGun bullet</label>
      <graphicData>
        <texPath>Things/Projectile/Bullet_Small</texPath>
        <graphicClass>Graphic_Single</graphicClass>
      </graphicData>
      <projectile>
        <flyOverhead>false</flyOverhead>
        <damageDef>Bullet</damageDef>
        <damageAmountBase>11</damageAmountBase>
        <speed>55</speed>
      </projectile>
      <AddHediffChance>0.5</AddHediffChance>
      <HediffToAdd>Plague</HediffToAdd>
      <thingClass>PlagueGun.Projectile_PlagueGun</thingClass>
  </ThingDef>

and here is the 2 classes:
Quotenamespace PlagueGun
{
    public class ThingDef_PlagueBullet : ThingDef
    {
        public float AddHediffChance = .5f;
        public HediffDef HediffToAdd = HediffDefOf.Plague;
    }
}
and
Quotenamespace PlagueGun
{
    class Projectile_PlagueGun : Bullet
    {
        public ThingDef_PlagueBullet Def
        {
            get
            {
                return this.def as ThingDef_PlagueBullet;
            }
        }

        #region Overrides
        protected override void Impact(Thing hitThing)
        {
            Pawn hitPawn = hitThing as Pawn;

            if (Def != null && hitThing != null && hitPawn != null)
            {
                var rand = Rand.Value;
                if (rand <= Def.AddHediffChance)
                {
                    var plagueOnPawn = hitPawn.health.hediffSet.GetFirstHediffOfDef(Def.HediffToAdd);
                    var randomSeverity = .3f;
                    if (plagueOnPawn != null)
                    {
                        plagueOnPawn.Severity += randomSeverity;
                    }
                    else
                    {
                        Hediff hediff = HediffMaker.MakeHediff(Def.HediffToAdd, hitPawn, null);
                        hediff.Severity = randomSeverity ;
                        hitPawn.health.AddHediff(hediff, null, null);
                    }
                }
            }
        }
        #endregion Overrides
    }
}

dninemfive

#87
How do you get the .NET Framework 3.5 in a C# Library project? I have it loaded and can access it in projects of other types, but for that one in particular I can only access versions 1.0 through 2.0. I'm using Visual Studio 2017 Community.

edit: I figured it out. For anyone else with this problem, make sure you're creating a "Windows Classic Desktop" project.

Sokestu


zivshek

Quote from: Eck on August 06, 2017, 03:06:36 PM
Awesome tutorial, but I think there are some unnecessary steps. You don't need to (and I don't think you should) include all the parent base items in your custom RangedWeapon_PlagueGun.xml file. Those entries will already included from the Core game files so the copied entries aren't necessary (and might cause conflicts). Basically skip step 11, and remove every ThingDef in the RangedWeapon_PlagueGun.xml file after the BASES comment.

I followed the tutorial through and got everything working even though I raised my eyebrow at the copy/pasted base classes. After that, I deleted all the base def entries, changed some other things like fire rate and range, and everything still worked.

@Tynan - Could you double check my post and make sure I'm giving good advice. I only just started messing with modding Rim World and even though I tested what I said, I wouldn't mind a sanity check. :)

- Eck

Hey Eck,

You are totally right, shouldn't have messed with the base classes. He changed the names of all the base classes, some of which don't even exist, such as "TST_BaseWeapon", he didn't create this one. It's not about confusion, it's wrong...