Difference between revisions of "Modding Tutorials/Modifying defs"

From RimWorld Wiki
Jump to navigation Jump to search
(→‎Custom def class: Fixed namespaces)
(Stripped part of it to be in the Introduction to Def Classes)
Line 1: Line 1:
 
{{BackToTutorials}}
 
{{BackToTutorials}}
  
This tutorial shows you several ways to modify defs' (ThingDef, PawnKindDef) C# classes and alternatives to changing the XML format for them.<br/><br/>
+
This tutorial shows you several ways to modify defs' (ThingDef, PawnKindDef) C# classes and alternatives to changing the XML format for them.<br/>
 +
 
 +
There's several ways to achieve this goal. Three are listed here.<br/><br/>
  
 
=Requirements=
 
=Requirements=
  
 +
* [[Modding Tutorials/Decompiling source code|Decompiling source code]] is required.
 
* This tutorial requires you to know [[Modding Tutorials/Writing custom code|how to write custom code]].
 
* This tutorial requires you to know [[Modding Tutorials/Writing custom code|how to write custom code]].
* [[Modding Tutorials/Decompiling source code|Decompiling source code]] is required.<br/><br/>
+
* [[Modding Tutorials/Def classes|Introduction to Def Classes]] is highly recommended.<br/><br/>
 
 
=Modifying defs=
 
 
 
There's a few ways to modify existing def formats. We'll go over writing a ''custom defClass'', ''custom comp'' and other ways of integrating XML tags into C# for example through the use of ''weaponTags'' or ''apparelTags''.<br/><br/>
 
 
 
==Vanilla defClasses==
 
 
 
This chapter helps you develop a broad understanding of the def classes. First we take a look at tags inside <thingDef>, then we take a look at tags inside those tags and finally we take a look at &lt;li&gt; items.<br/><br/>
 
 
 
===Tags===
 
 
 
The structure of a base game defClass might look like this:<br/>
 
 
 
<source lang="csharp">using RimWorld;
 
using System;
 
using UnityEngine;
 
 
 
namespace SomeNameSpace
 
{
 
public class SomeDef : Def
 
{
 
public SomeType someTagName;
 
public SomeOtherType someOtherTagName;
 
 
 
public SomeType someGetClass
 
{
 
get
 
{
 
return (SomeType)this.someOtherTagName;
 
}
 
}
 
}
 
}</source><br/>
 
 
 
A few notes could be made about this code.<br/>
 
* Without the ''using'' part, you'd have to call for '''''Namespace'''.Class.Method()'' instead of ''Class.Method()'' or ''((Class)partOfClass).Method()'',<source lang="csharp">using ...; /* this tells the compiler which namespaces to look for when searching for class and method calls. */</source><br/>
 
* The ''':''' is inheritance and means you can access all methods in the parent and you can call back to for example the ''Tick()'' method using '''''base'''.Tick()'',<source lang="csharp">public class SomeDef : Def /* inherits "everything" from Def, !! except for privates !! */</source><br/>
 
* Everything of the following format '''is an XML tag''':<source lang="csharp">public SomeType someTagName /* shows up as <ThingDef><someTagName>SomeType value</someTagName></ThingDef> */</source><br/>
 
* Besides these tags there's also script-only methods in the code:<source lang="csharp">public SomeType someGetClass /* this is only used in C#. XML doesn't change anything about this */</source><br/>
 
* Specifically ''get'' methods are only there for easily accessing or calculating certain values:<source lang="csharp">return ...; /* example: "public bool IsApparel()" returns "this.apparel != null". This could be checked easily but Thing.IsApparel() is more readable sometimes */</source><br/>
 
 
 
In practice one could find the following code:<br/>
 
 
 
<source lang="csharp">using RimWorld;
 
using System;
 
using System.Collections.Generic;
 
using System.Diagnostics;
 
using System.Linq;
 
using UnityEngine;
 
 
 
namespace Verse
 
{
 
public class ThingDef : BuildableDef /* BuildableDef inherits from Def */
 
{
 
public bool alwaysHaulable;
 
 
 
public bool designateHaulable;
 
 
 
public StuffProperties stuffProps; /* the StuffProperties class defines what this might look like in XML */
 
 
 
public bool IsStuff
 
{
 
get
 
{
 
return this.stuffProps != null; /* with some types you can check whether they're defined by checking if they're not equal to null */
 
}
 
}
 
 
 
public bool EverHaulable
 
{
 
get
 
{
 
return this.alwaysHaulable || this.designateHaulable; /* "return A || B" will return true if either A or B is true, and false if both are false. */
 
}
 
}
 
}
 
}</source><br/>
 
 
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<ThingDefs>
 
<thingDef>
 
<alwaysHaulable>true</alwaysHaulable>
 
<stuffProps>
 
<!-- whatever you'd find in StuffProperties -->
 
</stuffProps>
 
</thingDef>
 
</ThingDefs></source><br/>
 
*Note: these are snippets of Verse.ThingDef. To find the full source please [[Modding Tutorials/Decompiling source code|decompile the source code]] yourself.<br/><br/>
 
 
 
===Subtags===
 
 
 
To explain how subtags work we'll take a look at (parts of) StuffProperties. The contents of the StuffProperties class, formally called Verse.StuffProperties, are very similar to the ThingDef class. The structure is mostly the same.<br/>
 
In case you're wondering why we didn't have to add "using Verse;" to access Verse.StuffProperties, "namespace Verse {}" basically includes a ''using'' statement.<br/>
 
 
 
<source lang="csharp">using RimWorld;
 
using System;
 
using System.Collections.Generic;
 
using UnityEngine;
 
 
 
namespace Verse
 
{
 
public class StuffProperties
 
{
 
public bool smeltable;
 
 
 
public StuffAppearance appearance;
 
 
 
public SoundDef soundImpactStuff;
 
 
 
public List<StatModifier> statOffsets;
 
 
 
public List<StuffCategoryDef> categories = new List<StuffCategoryDef>();
 
}
 
}</source><br/>
 
*Note: These are snippets of Verse.StuffProperties. To find the full source please [[Modding Tutorials/Decompiling source code|decompile the source code]] yourself.<br/><br/>
 
 
 
Because SomeDef defines which subtags it has you have to modify Verse.SomeDef to alter which subtags it has (this includes modified versions of existing subtags, you have to modify SomeDef at some point).<br/>
 
This is one of the reasons you might be better off [[Modding Tutorials/Modifying defs#Custom comp|writing a custom comp]]. These can be added to an XML overwrite or [[Modding Tutorials/Injection|injected through C#]].<br/><br/>
 
 
 
===Lists===
 
 
 
Some tags include a list (it contains &lt;li&gt; subtags). If you look at the subtags of StuffProperties you can see two List<SomeType> elements. If you look at the XML you can see a clear distinction between the two:<br/>
 
 
 
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
 
<ThingDefs>
 
<thingDef>
 
<stuffProps>
 
<categories>
 
<li>Metallic</li> /* Metallic is a defName from a StuffCategoryDef */
 
</categories>
 
<statOffsets>
 
<Beauty>6</Beauty> /* Beauty is a defName from a StatDef, but the list's type is StatModifier */
 
</statOffsets>
 
</stuffProps>
 
</thingDef>
 
</ThingDefs></source><br/>
 
 
 
A clear difference between the two is that one of them defines a list and the other one doesn't. If you look at RimWorld.StatModifier you can find another cause:<br/>
 
 
 
<source lang="csharp">using System;
 
using System.Xml;
 
using Verse;
 
 
 
namespace RimWorld
 
{
 
public class StatModifier
 
{
 
public StatDef stat;
 
 
 
public float value;
 
 
 
public void LoadDataFromXmlCustom(XmlNode xmlRoot)
 
{
 
CrossRefLoader.RegisterObjectWantsCrossRef(this, "stat", xmlRoot.Name);
 
this.value = (float)ParseHelper.FromString(xmlRoot.FirstChild.Value, typeof(float));
 
}
 
}
 
}</source><br/>
 
*Note: These are snippets of RimWorld.StatModifier. To find the full source please [[Modding Tutorials/Decompiling source code|decompile the source code]] yourself.<br/><br/>
 
 
 
Because of this code the two act completely differently. One is a list with integer keys and StuffCategoryDef values, the other is a list with StatDef keys and float values.<br/><br/>
 
<!--
 
Would be nice to expand on this.
 
-->
 
  
 
==Custom def class==
 
==Custom def class==

Revision as of 08:06, 2 October 2015

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

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.

Pros and cons

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.

Example

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:

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

	<thingDef Name="BaseHumanGun" ParentName="BaseGun" Class="myNamespace.ThingDef_CustomTag">
	</thingDef>

	<thingDef ParentName="BaseHumanGun" Class="myNamespace.ThingDef_CustomTag">
		<myNewFloat>1.1</myNewFloat>
		<myNewBool>true</myNewBool>
		<myNewThingDefList>
			<li>Silver</li>
			<li>Pistol</li>
		</myNewThingDefList>
	</thingDef>
</ThingDefs>


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 one C# class, namely myNamespace.ThingDef_CustomTag:

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


namespace myNameSpace
{
	public class ThingDef_CustomTag : ThingDef
	{
		public float myNewFloat;

		public bool myNewBool;

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


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

	ThingDef_CustomTag customThingDef = someThingDef as ThingDef_CustomTag;
	if (customThingDef != null)
	{
		if (2 * Rand.Value() > customThingDef.myNewFloat)
		{
			ThingDef_CustomTag customThingDef2 = customThingDef.myNewThingDefList.RandomElement() as ThingDef_Custom;
			if (customThingDef2 != null)
			{
				return customThingDef2 .myNewBool;
			}
			return false;
		}
		return customThingDef.myNewBool;
	}


Please note that any of the code might not work in a newer alpha. It's for demonstration purposes only.

Counterexample

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:

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

	<thingDef Name="BaseAnimalRace" ParentName="BaseRace" Class="myNamespace.ThingDefWithCustomRaceProps">
	</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>


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.
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.

For this reason custom defs are advised against.

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