Difference between revisions of "Modding Tutorials/Modifying defs"

From RimWorld Wiki
Jump to navigation Jump to search
(Stripped part of it to be in the Introduction to Def Classes)
(→‎Custom def class: Rewrote large parts, changed example some, took out no-longer relevant warnings)
Line 13: Line 13:
 
==Custom def class==
 
==Custom def class==
  
The use of def classes is very popular in small mods. As long as the defs in the mod aren't overwriting core defs, it's very compatible.<br/><br/>
+
This is where you create custom defs for your mod and C# code to go along with them.
  
===Pros and cons===
+
The use of def classes is very popular in small mods: as long as the defs in the mod aren't overwriting core defs, it's very compatible.
  
Def classes require a pretty hefty overhaul of XML code to work. First of all you will have to change anything in the inheritance to refer to the same class:<br/>
+
So if you have your own objects that you've created, and have C# code to go with them, it's fairly straightforward to add a custom def. On the other hand, if you want to add a custom def to a core def that gets used everywhere in the code, it will be extremely difficult and you might be better off with a different approach.
 
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<ThingDefs>
 
<thingDef Name="BaseThing" Class="myNamespace.myCustomClass">
 
</thingDef>
 
 
 
<thingDef Name="BaseSpecificThing" ParentName="BaseThing" Class="myNamespace.myCustomClass">
 
</thingDef>
 
 
 
<thingDef ParentName="BaseSpecificThing" Class="myNamespace.myCustomClass">
 
</thingDef>
 
</ThingDefs></source><br/>
 
 
 
And in case you have more thingDefs which require the changes that come with myNamespace.myCustomClass you'll have to set all of their classes for it to work.<br/>
 
Applied to core defs this way of doing things introduces incompatibilities with other mods that modify the same def. Creating compatibility patches is inevitable.<br/><br/>
 
  
On the plus side you make it possible to change the root thingDef tags. In most cases you don't need this but certain things such as Pawn code might have no alternatives.<br/><br/>
+
===Simple Example===
 +
Suppose you add a new gun to RimWorld (assuming you're violent), that has a special feature.
  
===Example===
+
Your XML might look something like this:
 
 
This example will simply add a new tag to the root thingDef. This is the best or even only way to implement new tags through custom def classes:<br/>
 
  
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<ThingDefs>
 
<ThingDefs>
<thingDef Name="BaseGun" Class="myNamespace.ThingDef_CustomTag">
+
<thingDef ParentName="BaseHumanGun" Class="myNamespace.MyCustomDef_ThingDef">
</thingDef>
+
<defName>MyCoolNewGun</defName>
 
+
<thingClass>myNamespace.MyCoolNewGun</thingClass>
<thingDef Name="BaseHumanGun" ParentName="BaseGun" Class="myNamespace.ThingDef_CustomTag">
+
        <etc/>
</thingDef>
 
 
 
<thingDef ParentName="BaseHumanGun" Class="myNamespace.ThingDef_CustomTag">
 
 
<myNewFloat>1.1</myNewFloat>
 
<myNewFloat>1.1</myNewFloat>
 
<myNewBool>true</myNewBool>
 
<myNewBool>true</myNewBool>
Line 56: Line 37:
 
</myNewThingDefList>
 
</myNewThingDefList>
 
</thingDef>
 
</thingDef>
</ThingDefs></source><br/>
+
</ThingDefs></source>
 +
 
 +
Two things were added to the XML for the custom defs: the Class="myNamespace.MyCustomDef_ThingDef" and the new definitions.
  
Without assigning the Class to each thingDef inheriting from and being inherited by a certain thingDef with a certain Class, '''the mod will cause errors'''.<br/>
+
Corresponding C# code might look like this:
Now for the code we will have to create one C# class, namely myNamespace.ThingDef_CustomTag:</br>
 
  
 
<source lang="csharp">using RimWorld;
 
<source lang="csharp">using RimWorld;
Line 69: Line 51:
  
  
namespace myNameSpace
+
namespace myNamespace
 
{
 
{
public class ThingDef_CustomTag : ThingDef
+
public class MyCustomDef_ThingDef : ThingDef // Here we are adding our custom defs
 
{
 
{
 
public float myNewFloat;
 
public float myNewFloat;
Line 79: Line 61:
 
public List<ThingDef> myNewThingDefList = new List<ThingDef>();
 
public List<ThingDef> myNewThingDefList = new List<ThingDef>();
 
}
 
}
}</source><br/>
+
public class MyCoolNewGun : etc //etc etc
 +
{
 +
  //etc
 +
}
 +
}</source>
  
 
And now that that exists you can call on it with any ThingDef as input like so:<br/>
 
And now that that exists you can call on it with any ThingDef as input like so:<br/>
  
<source lang="csharp"> ThingDef_CustomTag customThingDef = someThingDef as ThingDef_CustomTag;
+
<source lang="csharp">
if (customThingDef != null)
+
  // maybe var weapon=new myNamespace.MyCoolNewGun();
{
+
  // or maybe a function was called with a parameter "weapon"
if (2 * Rand.Value() > customThingDef.myNewFloat)
+
 
{
+
  if (weapon as myNamespace.MyCoolNewGun != null) // make sure it actually IS a MyCoolNewGun, HAS the code, etc:
ThingDef_CustomTag customThingDef2 = customThingDef.myNewThingDefList.RandomElement() as ThingDef_Custom;
+
  {
if (customThingDef2 != null)
+
      // use the thingdef:
{
+
      var def = weapon.def as myNamespace.MyCustomDef_ThingDef;
return customThingDef2 .myNewBool;
+
      var theFloat = def.myNewFloat;
}
+
 
return false;
+
      // or access directly:
}
+
      var myBool = ((myNamespace.MyCustomDef_ThingDef)weapon.def).myNewBool;
return customThingDef.myNewBool;
+
      //    this will throw an exception if somehow the weapon isn't a MyCoolNewGun!
}</source><br/>
+
 
 +
      // use the defs, etc
 +
      // this works in beta 0.18.1722
 +
  }
 +
</source>
 +
 
 +
 
 +
===Further Example===
 +
You might want to add a whole class of guns with this new feature:
 +
 
 +
Your XML might look something like this:
 +
 
 +
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 +
<ThingDefs>
 +
<thingDef Name="MyCoolNewGun" ParentName="BaseHumanGun" Class="myNamespace.MyCustomDef_ThingDef" Abstract="true">
 +
<thingClass>myNamespace.MyCoolNewGun</thingClass>
 +
        <etc/>
 +
<myNewFloat>1.1</myNewFloat>
 +
<myNewBool>true</myNewBool>
 +
<myNewThingDefList>
 +
<li>Silver</li>
 +
<li>Pistol</li>
 +
</myNewThingDefList>
 +
</thingDef>
 +
 
 +
<thingDef ParentName="MyCoolNewGun">
 +
<defName>MyCoolNewPistol</defName>
 +
<myNewFloat>2.2</myNewFloat>
 +
</thingDef>
 +
 
 +
<thingDef ParentName="MyCoolNewGun">
 +
<defName>MyCoolWaterPistol</defName>
 +
<myNewBool>false</myNewBool>
 +
<etc />
 +
</thingDef>
 +
</ThingDefs></source>
 +
 
 +
Each will inherit the parent's def class and link in with your code, and you can have a whole class of things using your cool new weapon mod.
 +
 
 +
===When Not To Use It===
 +
 
 +
There are two big catches:
  
Please note that any of the code might not work in a newer alpha. It's for demonstration purposes only.<br/><br/>
+
#  If you modify an existing definition to use your class, and someone else does too, there might be mod conflicts.  Oops.
 +
#  Your defs only work for your own code. An example:
  
===Counterexample===
+
====Problem Example====
''The following method shows you '''why not to use''' custom defs. In some cases you could attempt to use this method but it's generally considered the least favourable way to deal with things.''<br/><br/>
+
''The following method shows you '''why not to use''' custom defs. In some cases you could attempt to use this method but it's generally considered the least favourable way to deal with things.''
  
For this example we're gonna '''try to''' change the <race> tag to contain more tags. The value of these tags isn't exactly important, as is explained in the first few sections:<br/>
+
For this example we're gonna '''try to''' change the <race> tag to contain more tags. The value of these tags isn't exactly important to this example.
  
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<ThingDefs>
 
<ThingDefs>
<thingDef Name="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps">
+
<thingDef Name="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps"><!-- May not be needed in recent versions-->
 
</thingDef>
 
</thingDef>
  
<thingDef Name="BaseAnimalRace" ParentName="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps">
+
<thingDef Name="BaseAnimalRace" ParentName="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps"><!-- may not be needed -->
 
</thingDef>
 
</thingDef>
  
Line 125: Line 153:
 
</ThingDefs></source><br/>
 
</ThingDefs></source><br/>
  
Without assigning the Class to each thingDef inheriting from and being inherited by a certain thingDef with a certain Class, '''the mod will cause errors'''.<br/>
+
(earlier versions) Without assigning the Class to each thingDef inheriting from and being inherited by a certain thingDef with a certain Class, '''the mod will cause errors'''.
Now for the code we will have to create two C# classes, namely myNamespace.ThingDefWithCustomRaceProps and myNamespace.RacePropertiesCustom:</br>
+
 
 +
Now for the code we will have to create two C# classes, namely myNamespace.ThingDefWithCustomRaceProps and myNamespace.RacePropertiesCustom:
  
 
<source lang="csharp">using RimWorld;
 
<source lang="csharp">using RimWorld;
Line 164: Line 193:
 
}</source><br/>
 
}</source><br/>
  
One might think that's all required to make it work. However.<br/>
+
One might think that's all required to make it work. However!
Since the race variable tag is called by several methods all around the game's script, and it's called like ((ThingDef)someVariable).race.someMethod(), this means it won't use YOUR ThingDefWithCustomRaceProps and to fix this the only thing you can do is modify '''all''' these classes to cast to ThingDefWithCustomRaceProps like (ThingDefWithCustomRaceProps)((ThingDef)someVariable).race.someMethod(), which is less than ideal in every single case. It means copying and rewriting large portions of code, finding out they're called by certain other methods that also require the same treatment to call the new custom method to fix the custom thingDef class you made.<br/><br/>
+
 
 +
Wherever the race variable tag is called in the game's code, it's called like <code>((ThingDef)someVariable).race.someMethod()</code>.  It's NOT calling your code!  It's calling the base game's code.
 +
 
 +
Everywhere you need someMethod() to use your code, you need to cast the race:  ThingDefWithCustomRaceProps like <code>(ThingDefWithCustomRaceProps)((ThingDef)someVariable).race.someMethod()</code>.  '''EVERY''' single time. It means copying and rewriting large portions of code, finding out they're called by certain other methods that also require the same treatment to call the new custom method to fix the custom thingDef class you made.
 +
 
 +
If your mod is restricted to something very small in scope, this might be okay.  But if it affects a large part of the game...you will have to do a lot of work to make it fit, and you might want to go another route.
 +
 
 +
 
 +
 
 +
===Older Versions===
 +
***NOTE: This seems to work fine in 0.18.1722, so you can ignore this section***
 +
 
 +
Def classes require a pretty hefty overhaul of XML code to work. First of all you will have to change anything in the inheritance to refer to the same class:<br/>
 +
 
 +
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 +
<ThingDefs>
 +
<thingDef Name="BaseThing" Class="myNamespace.myCustomClass">
 +
</thingDef>
  
For this reason custom defs are advised against.<br/><br/>
+
<thingDef Name="BaseSpecificThing" ParentName="BaseThing" Class="myNamespace.myCustomClass">
 +
</thingDef>
 +
 
 +
<thingDef ParentName="BaseSpecificThing" Class="myNamespace.myCustomClass">
 +
</thingDef>
 +
</ThingDefs></source><br/>
 +
 
 +
And in case you have more thingDefs which require the changes that come with myNamespace.myCustomClass you'll have to set all of their classes for it to work.<br/>
 +
Applied to core defs this way of doing things introduces incompatibilities with other mods that modify the same def. Creating compatibility patches is inevitable.<br/><br/>
 +
 
 +
On the plus side you make it possible to change the root thingDef tags. In most cases you don't need this but certain things such as Pawn code might have no alternatives.<br/><br/>
  
 
==Custom comp class==
 
==Custom comp class==

Revision as of 20:09, 15 August 2018

Modding Tutorials

This tutorial shows you several ways to modify defs' (ThingDef, PawnKindDef) C# classes and alternatives to changing the XML format for them.

There's several ways to achieve this goal. Three are listed here.

Requirements

Custom def class

This is where you create custom defs for your mod and C# code to go along with them.

The use of def classes is very popular in small mods: as long as the defs in the mod aren't overwriting core defs, it's very compatible.

So if you have your own objects that you've created, and have C# code to go with them, it's fairly straightforward to add a custom def. On the other hand, if you want to add a custom def to a core def that gets used everywhere in the code, it will be extremely difficult and you might be better off with a different approach.

Simple Example

Suppose you add a new gun to RimWorld (assuming you're violent), that has a special feature.

Your XML might look something like this:

<?xml version="1.0" encoding="utf-8"?>
<ThingDefs>
	<thingDef ParentName="BaseHumanGun" Class="myNamespace.MyCustomDef_ThingDef">
		<defName>MyCoolNewGun</defName>
		<thingClass>myNamespace.MyCoolNewGun</thingClass>
	        <etc/>
		<myNewFloat>1.1</myNewFloat>
		<myNewBool>true</myNewBool>
		<myNewThingDefList>
			<li>Silver</li>
			<li>Pistol</li>
		</myNewThingDefList>
	</thingDef>
</ThingDefs>

Two things were added to the XML for the custom defs: the Class="myNamespace.MyCustomDef_ThingDef" and the new definitions.

Corresponding C# code might look like this:

using RimWorld;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;


namespace myNamespace
{
	public class MyCustomDef_ThingDef : ThingDef // Here we are adding our custom defs
	{
		public float myNewFloat;

		public bool myNewBool;

		public List<ThingDef> myNewThingDefList = new List<ThingDef>();
	}
	public class MyCoolNewGun : etc //etc etc
	{
	  //etc
	}
}

And now that that exists you can call on it with any ThingDef as input like so:

  // maybe var weapon=new myNamespace.MyCoolNewGun();
  // or maybe a function was called with a parameter "weapon"

  if (weapon as myNamespace.MyCoolNewGun != null) // make sure it actually IS a MyCoolNewGun, HAS the code, etc:
  {
      // use the thingdef:
      var def = weapon.def as myNamespace.MyCustomDef_ThingDef;
      var theFloat = def.myNewFloat;

      // or access directly:
      var myBool = ((myNamespace.MyCustomDef_ThingDef)weapon.def).myNewBool;
      //     this will throw an exception if somehow the weapon isn't a MyCoolNewGun!

      // use the defs, etc
      // this works in beta 0.18.1722
  }


Further Example

You might want to add a whole class of guns with this new feature:

Your XML might look something like this:

<?xml version="1.0" encoding="utf-8"?>
<ThingDefs>
	<thingDef Name="MyCoolNewGun" ParentName="BaseHumanGun" Class="myNamespace.MyCustomDef_ThingDef" Abstract="true">
		<thingClass>myNamespace.MyCoolNewGun</thingClass>
	        <etc/>
		<myNewFloat>1.1</myNewFloat>
		<myNewBool>true</myNewBool>
		<myNewThingDefList>
			<li>Silver</li>
			<li>Pistol</li>
		</myNewThingDefList>
	</thingDef>

	<thingDef ParentName="MyCoolNewGun">
	 	<defName>MyCoolNewPistol</defName>
		<myNewFloat>2.2</myNewFloat>
	</thingDef>

	<thingDef ParentName="MyCoolNewGun">
		 <defName>MyCoolWaterPistol</defName>
		 <myNewBool>false</myNewBool>
		 <etc />
	</thingDef>
</ThingDefs>

Each will inherit the parent's def class and link in with your code, and you can have a whole class of things using your cool new weapon mod.

When Not To Use It

There are two big catches:

  1. If you modify an existing definition to use your class, and someone else does too, there might be mod conflicts. Oops.
  2. Your defs only work for your own code. An example:

Problem Example

The following method shows you why not to use custom defs. In some cases you could attempt to use this method but it's generally considered the least favourable way to deal with things.

For this example we're gonna try to change the <race> tag to contain more tags. The value of these tags isn't exactly important to this example.

<?xml version="1.0" encoding="utf-8"?>
<ThingDefs>
	<thingDef Name="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps"><!-- May not be needed in recent versions-->
	</thingDef>

	<thingDef Name="BaseAnimalRace" ParentName="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps"><!-- may not be needed -->
	</thingDef>

	<thingDef ParentName="BaseAnimalRace" Class="myNamespace.ThingDefWithCustomRaceProps">
		<race Class="myNamespace.RacePropertiesCustom">
			<myNewFloat>1.1</myNewFloat>
			<myNewBool>true</myNewBool>
			<myNewThingDefList>
				<li>Silver</li>
				<li>Pistol</li>
			</myNewThingDefList>
		</race>
	</thingDef>
</ThingDefs>


(earlier versions) Without assigning the Class to each thingDef inheriting from and being inherited by a certain thingDef with a certain Class, the mod will cause errors.

Now for the code we will have to create two C# classes, namely myNamespace.ThingDefWithCustomRaceProps and myNamespace.RacePropertiesCustom:

using RimWorld;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;


namespace myNameSpace
{
	public class ThingDefWithCustomRaceProps : ThingDef
	{
		new public RacePropertiesCustom race;	/* requires the new keyword to overwrite the existing race */
	}
}


And for the RacePropertiesCustom we make the following script:

using RimWorld;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;

namespace myNameSpace
{
	public class RacePropertiesCustom : RaceProperties
	{
		public float myNewFloat;

		public bool myNewBool;

		public List<ThingDef> myNewThingDefList = new List<ThingDef>();
	}
}


One might think that's all required to make it work. However!

Wherever the race variable tag is called in the game's code, it's called like ((ThingDef)someVariable).race.someMethod(). It's NOT calling your code! It's calling the base game's code.

Everywhere you need someMethod() to use your code, you need to cast the race: ThingDefWithCustomRaceProps like (ThingDefWithCustomRaceProps)((ThingDef)someVariable).race.someMethod(). EVERY single time. It means copying and rewriting large portions of code, finding out they're called by certain other methods that also require the same treatment to call the new custom method to fix the custom thingDef class you made.

If your mod is restricted to something very small in scope, this might be okay. But if it affects a large part of the game...you will have to do a lot of work to make it fit, and you might want to go another route.


Older Versions

      • NOTE: This seems to work fine in 0.18.1722, so you can ignore this section***

Def classes require a pretty hefty overhaul of XML code to work. First of all you will have to change anything in the inheritance to refer to the same class:

<?xml version="1.0" encoding="utf-8"?>
<ThingDefs>
	<thingDef Name="BaseThing" Class="myNamespace.myCustomClass">
	</thingDef>

	<thingDef Name="BaseSpecificThing" ParentName="BaseThing" Class="myNamespace.myCustomClass">
	</thingDef>

	<thingDef ParentName="BaseSpecificThing" Class="myNamespace.myCustomClass">
	</thingDef>
</ThingDefs>


And in case you have more thingDefs which require the changes that come with myNamespace.myCustomClass you'll have to set all of their classes for it to work.
Applied to core defs this way of doing things introduces incompatibilities with other mods that modify the same def. Creating compatibility patches is inevitable.

On the plus side you make it possible to change the root thingDef tags. In most cases you don't need this but certain things such as Pawn code might have no alternatives.

Custom comp class

Comp classes are a more advanced method of introducing compatibility to mods. Large mods might prefer comps to defs because it requires less XML editing in most cases. Along with that it's possible to inject comps into almost everything (because almost everything inherits from ThingWithComps) without having to change the comp while defs require a new class for each SomeDef they overwrite (E.g PawnKindDef and ThingDef require different classes)

Pros and cons

Comps are easier to add than defClasses and can be injected by C# code. It's lighter on the XML code (doesn't require inheritance to be consistent) and C# code in most cases because it's more of a contained system. There's many hooks implemented which call for C# comp classes, such as CompTick() and PostDestroy(). Just like defs they can introduce incompatibilities. Adding another mod's comps requires the game to have both mods loaded, which can be a problem. Implementing smarter injection might fix this[?] in case a mod isn't loaded, but it's not possible to guarantee that the comp is actually added.

Method

Checking for tags

Instead of using custom defClasses and comps you could also use tags. This is especially useful for lightweight features and simple compatibility.

Pros and cons

Some tags are never used by a certain Thing such as ApparelTag on a Building. If a tag is never used it doesn't throw an error and therefore you could introduce as many useless tags as you want to a mod without the game complaining. When other mods check for these tags they can also do it without a problem. This way you could add tags of whatever name you want and let others check for this tag for compatibility.

Method

See also