Difference between revisions of "Modding Tutorials/Modifying defs"

From RimWorld Wiki
Jump to navigation Jump to search
(Created, WIP)
 
 
(7 intermediate revisions by 4 users not shown)
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 existing ''Defs'' and alternatives to changing the XML format for them.<br/>
  
=Requirements=
+
There are several ways to achieve this goal. The table below is the [https://en.wikipedia.org/wiki/TL;DR TL;DR] version of this article. C# related operations are marked '''bold'''.<br/><br/>
  
* This tutorial requires you to know [[Modding Tutorials/Writing custom code|how to write custom code]].
+
{| class="wikitable"
* [[Modding Tutorials/Decompiling source code|Decompiling source code]] is required.<br/><br/>
+
|-
 +
! Method !! Pros !! Cons !! When to use
 +
|-
 +
| Overwrite the Def || Braindead easy || So incompatible you gotta be braindead || When you don't care about the original, or anyone else
 +
|-
 +
| XPath change || Highly specific, highly compatible || Limited to XML-defined Defs || When you need to change a few XML values
 +
|-
 +
| Adding a '''(self-made)''' Comp || Very flexible, well-supported, highly-compatible || Does not work on every Def || When you want to add functionality
 +
|-
 +
| '''DefModExtension''' || Very simple, well-supported, highly-compatible || Static data || When you want to add fields/data
 +
|-
 +
| '''SubClassing''' || Fairly powerful, half the work is already done || Compatibility issues, not very flexible || When neither a Comp nor a DefModExtension works
 +
|-
 +
| '''Custom Def''' || Full control || Very specific to your mod || When the given Defs don't suffice for you
 +
|}
  
=Modifying defs=
+
=Requirements=
  
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/>
+
* [[Modding Tutorials/XML Defs|Understanding Defs]] helps.
 +
* For the C# portions:
 +
** [[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]].
 +
** [[Modding Tutorials/Def classes|Introduction to Def Classes]] is highly recommended.<br/><br/>
  
==Vanilla defClasses==
+
=Overwriting Defs=
 +
{{Main|Modding_Tutorials/Compatibility_with_defs}}
 +
If two mods share the same defName for a single type of ''Def'', the last mod wins. If mod A adds a ResearchDef with defName Pemmican and mod B also adds a ResearchDef with defName Pemmican, the game will use mod B's Pemmican.
 +
==Pros==
 +
Braindead easy.
  
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/>
+
==Cons==
 +
No compatibility.
  
===Tags===
+
==When to use==
 +
Not.
  
The structure of a base game defClass might look like this:<br/>
+
=XPath=
 +
{{Main|Modding Tutorials/PatchOperations}}
 +
XPath allows you to change specific values of Defs (multiple in a single operation, if you want) across mods with surgical precision.
  
<source lang="csharp">using RimWorld;
+
==Pros==
using System;
+
Highly flexible, highly compatible.
using UnityEngine;
 
  
namespace SomeNameSpace
+
==Cons==
{
+
Limited to Defs defined in XML (no meat, corpses or other generated Defs). More complex operations require more fiddly syntax.
public class SomeDef : Def
 
{
 
public SomeType someTagName;
 
public SomeOtherType someOtherTagName;
 
  
public SomeType someGetClass
+
==When to use==
{
+
All the time.
get
 
{
 
return (SomeType)this.someOtherTagName;
 
}
 
}
 
}
 
}</source><br/>
 
  
A few notes could be made about this code.<br/>
+
=Adding a (self-made) Comp=
* 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/>
+
{{Main|Modding Tutorials/ThingComp}}
* 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/>
+
ThingComps are like little modules you can add to any ThingWithComp to give them added functionality.
* 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/>
+
===Pros===
 +
Very flexible, well-supported and highly-compatible. There are many ready-made (example) Comps available that can be employed to do a wide variety of things.
  
<source lang="csharp">using RimWorld;
+
===Cons===
using System;
+
Not suited for every type of functionality.
using System.Collections.Generic;
 
using System.Diagnostics;
 
using System.Linq;
 
using UnityEngine;
 
  
namespace Verse
+
===When to use===
{
+
When you want to add functionality, non-static data or behaviour.
public class ThingDef : BuildableDef /* BuildableDef inherits from Def */
 
{
 
public bool alwaysHaulable;
 
  
public bool designateHaulable;
+
=DefModExtensions=
 +
{{Main|Modding Tutorials/DefModExtension}}
 +
DefModExtensions can be seen as a way to add fields to Defs.
  
public StuffProperties stuffProps; /* the StuffProperties class defines what this might look like in XML */
+
===Pros===
 +
Very simple, very lightweight, highly compatible.
  
public bool IsStuff
+
===Cons===
{
+
Static data only.
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
+
===When to use===
{
+
When you want to add (static) fields/data to Defs.
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"?>
+
=Subclassing=
<ThingDefs>
+
{{Main|Modding_Tutorials/Def_classes}}
<thingDef>
+
Inherit from a Def and explicitly tell RimWorld to use that specific Type.  
<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===
+
==Pros==
 +
They're you're own Type, so you can extend their functionality until your heart is content.
  
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/>
+
==Cons==
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/>
+
* Still bound to the base Def without access to its private methods
 +
* Using them requires a lot of casting
 +
* Only one custom class per Def
 +
* They don't offer a lot of extra functionality over DefModExtensions or [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods C# Extension methods]
  
<source lang="csharp">using RimWorld;
+
==When to use==
using System;
+
When neither a Comp nor a DefModExtension works.
using System.Collections.Generic;
 
using UnityEngine;
 
  
namespace Verse
+
=Custom Def=
{
+
{{Main|Modding Tutorials/Def_classes}}
public class StuffProperties
+
(Optionally) inherit from a Def and tell RimWorld to use your custom Def.
{
 
public bool smeltable;
 
  
public StuffAppearance appearance;
+
==Pros==
 +
Fully your own Type, complete control. No incompatibility issues, because they're all yours.
  
public SoundDef soundImpactStuff;
+
==Cons==
 +
Implementation from scratch.
  
public List<StatModifier> statOffsets;
+
==When to use==
 +
If your Def is really unique to your mod or specific goal.
  
public List<StuffCategoryDef> categories = new List<StuffCategoryDef>();
+
=Other ways=
}
+
Sometimes creative, sometimes hacky:
}</source><br/>
+
==Checking for tags==
*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/>
+
Instead of using custom defClasses and comps you could also use tags. This is especially useful for lightweight features and simple compatibility.<br/><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 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.<br/><br/>
 
 
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==
 
 
 
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/>
 
 
 
===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:<br/>
 
 
 
<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/>
 
  
===Method===
+
===Pros===
 +
Lightweight, easy.
  
==Custom comp class==
+
===Cons===
 +
Risk of potential side-effects. Kinda hacky.
  
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)<br/><br/>
+
==Changing the class used by the Def==
 +
A lot of ''Defs'' have a field entry that specifies the C# class they are tied to. For instance, the ''GameConditionDef'' for the Eclipse has a ''conditionClass'' of ''GameCondition_Eclipse''. Modders who wish to add some sparkles to the Eclipse could create a new GameCondition class called ''GameCondition_EclipseWithSparkles'' and use XPath to change the conditionClass to MyNameSpace.GameCondition_EclipseWithSparkles.
  
===Pros and cons===
+
===Pros===
 +
Pretty easy. Keeps most values of the original Def intact as well.
  
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<sup>[?]</sup> in case a mod isn't loaded, but it's not possible to guarantee that the comp is actually added.<br/><br/>
+
===Cons===
 
+
Subverts any Harmony patches on the original Class, so potential issues with compatibility.
===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.<br/><br/>
 
 
 
===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.<br/><br/>
 
  
===Method===
+
==Harmony patching==
 +
{{Main|Modding Tutorials/Harmony}}
 +
If you still want to use Harmony in the face of all these alternatives, you might have fallen for the [https://exceptionnotfound.net/the-golden-hammer-anti-pattern-primers/ Golden Hammer Design Pattern]. But sure, go ahead.
  
 
=See also=
 
=See also=

Latest revision as of 13:16, 5 February 2019

Modding Tutorials

This tutorial shows you several ways to modify existing Defs and alternatives to changing the XML format for them.

There are several ways to achieve this goal. The table below is the TL;DR version of this article. C# related operations are marked bold.

Method Pros Cons When to use
Overwrite the Def Braindead easy So incompatible you gotta be braindead When you don't care about the original, or anyone else
XPath change Highly specific, highly compatible Limited to XML-defined Defs When you need to change a few XML values
Adding a (self-made) Comp Very flexible, well-supported, highly-compatible Does not work on every Def When you want to add functionality
DefModExtension Very simple, well-supported, highly-compatible Static data When you want to add fields/data
SubClassing Fairly powerful, half the work is already done Compatibility issues, not very flexible When neither a Comp nor a DefModExtension works
Custom Def Full control Very specific to your mod When the given Defs don't suffice for you

Requirements[edit]

Overwriting Defs[edit]

If two mods share the same defName for a single type of Def, the last mod wins. If mod A adds a ResearchDef with defName Pemmican and mod B also adds a ResearchDef with defName Pemmican, the game will use mod B's Pemmican.

Pros[edit]

Braindead easy.

Cons[edit]

No compatibility.

When to use[edit]

Not.

XPath[edit]

XPath allows you to change specific values of Defs (multiple in a single operation, if you want) across mods with surgical precision.

Pros[edit]

Highly flexible, highly compatible.

Cons[edit]

Limited to Defs defined in XML (no meat, corpses or other generated Defs). More complex operations require more fiddly syntax.

When to use[edit]

All the time.

Adding a (self-made) Comp[edit]

ThingComps are like little modules you can add to any ThingWithComp to give them added functionality.

Pros[edit]

Very flexible, well-supported and highly-compatible. There are many ready-made (example) Comps available that can be employed to do a wide variety of things.

Cons[edit]

Not suited for every type of functionality.

When to use[edit]

When you want to add functionality, non-static data or behaviour.

DefModExtensions[edit]

DefModExtensions can be seen as a way to add fields to Defs.

Pros[edit]

Very simple, very lightweight, highly compatible.

Cons[edit]

Static data only.

When to use[edit]

When you want to add (static) fields/data to Defs.

Subclassing[edit]

Inherit from a Def and explicitly tell RimWorld to use that specific Type.

Pros[edit]

They're you're own Type, so you can extend their functionality until your heart is content.

Cons[edit]

  • Still bound to the base Def without access to its private methods
  • Using them requires a lot of casting
  • Only one custom class per Def
  • They don't offer a lot of extra functionality over DefModExtensions or C# Extension methods

When to use[edit]

When neither a Comp nor a DefModExtension works.

Custom Def[edit]

(Optionally) inherit from a Def and tell RimWorld to use your custom Def.

Pros[edit]

Fully your own Type, complete control. No incompatibility issues, because they're all yours.

Cons[edit]

Implementation from scratch.

When to use[edit]

If your Def is really unique to your mod or specific goal.

Other ways[edit]

Sometimes creative, sometimes hacky:

Checking for tags[edit]

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

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.

Pros[edit]

Lightweight, easy.

Cons[edit]

Risk of potential side-effects. Kinda hacky.

Changing the class used by the Def[edit]

A lot of Defs have a field entry that specifies the C# class they are tied to. For instance, the GameConditionDef for the Eclipse has a conditionClass of GameCondition_Eclipse. Modders who wish to add some sparkles to the Eclipse could create a new GameCondition class called GameCondition_EclipseWithSparkles and use XPath to change the conditionClass to MyNameSpace.GameCondition_EclipseWithSparkles.

Pros[edit]

Pretty easy. Keeps most values of the original Def intact as well.

Cons[edit]

Subverts any Harmony patches on the original Class, so potential issues with compatibility.

Harmony patching[edit]

If you still want to use Harmony in the face of all these alternatives, you might have fallen for the Golden Hammer Design Pattern. But sure, go ahead.

See also[edit]