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)
 
(4 intermediate revisions by 3 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/>
+
This tutorial shows you several ways to modify existing ''Defs'' and alternatives to changing the XML format for them.<br/>
  
There's several ways to achieve this goal. Three are listed here.<br/><br/>
+
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/>
 +
 
 +
{| class="wikitable"
 +
|-
 +
! 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=
 
=Requirements=
  
* [[Modding Tutorials/Decompiling source code|Decompiling source code]] is required.
+
* [[Modding Tutorials/XML Defs|Understanding Defs]] helps.
* This tutorial requires you to know [[Modding Tutorials/Writing custom code|how to write custom code]].
+
* For the C# portions:
* [[Modding Tutorials/Def classes|Introduction to Def Classes]] is highly recommended.<br/><br/>
+
** [[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/>
  
==Custom def class==
+
=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.
  
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/>
+
==Cons==
 +
No compatibility.
  
===Pros and cons===
+
==When to use==
 +
Not.
  
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/>
+
=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="xml"><?xml version="1.0" encoding="utf-8"?>
+
==Pros==
<ThingDefs>
+
Highly flexible, highly compatible.
<thingDef Name="BaseThing" Class="myNamespace.myCustomClass">
 
</thingDef>
 
  
<thingDef Name="BaseSpecificThing" ParentName="BaseThing" Class="myNamespace.myCustomClass">
+
==Cons==
</thingDef>
+
Limited to Defs defined in XML (no meat, corpses or other generated Defs). More complex operations require more fiddly syntax.
  
<thingDef ParentName="BaseSpecificThing" Class="myNamespace.myCustomClass">
+
==When to use==
</thingDef>
+
All the time.
</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/>
+
=Adding a (self-made) Comp=
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/>
+
{{Main|Modding Tutorials/ThingComp}}
 +
ThingComps are like little modules you can add to any ThingWithComp to give them added functionality.
  
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/>
+
===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.
  
===Example===
+
===Cons===
 +
Not suited for every type of functionality.
  
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/>
+
===When to use===
 +
When you want to add functionality, non-static data or behaviour.
  
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
+
=DefModExtensions=
<ThingDefs>
+
{{Main|Modding Tutorials/DefModExtension}}
<thingDef Name="BaseGun" Class="myNamespace.ThingDef_CustomTag">
+
DefModExtensions can be seen as a way to add fields to Defs.
</thingDef>
 
  
<thingDef Name="BaseHumanGun" ParentName="BaseGun" Class="myNamespace.ThingDef_CustomTag">
+
===Pros===
</thingDef>
+
Very simple, very lightweight, highly compatible.
  
<thingDef ParentName="BaseHumanGun" Class="myNamespace.ThingDef_CustomTag">
+
===Cons===
<myNewFloat>1.1</myNewFloat>
+
Static data only.
<myNewBool>true</myNewBool>
 
<myNewThingDefList>
 
<li>Silver</li>
 
<li>Pistol</li>
 
</myNewThingDefList>
 
</thingDef>
 
</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/>
+
===When to use===
Now for the code we will have to create one C# class, namely myNamespace.ThingDef_CustomTag:</br>
+
When you want to add (static) fields/data to Defs.
  
<source lang="csharp">using RimWorld;
+
=Subclassing=
using System;
+
{{Main|Modding_Tutorials/Def_classes}}
using System.Collections.Generic;
+
Inherit from a Def and explicitly tell RimWorld to use that specific Type.  
using System.Diagnostics;
 
using System.Linq;
 
using UnityEngine;
 
  
 +
==Pros==
 +
They're you're own Type, so you can extend their functionality until your heart is content.
  
namespace myNameSpace
+
==Cons==
{
+
* Still bound to the base Def without access to its private methods
public class ThingDef_CustomTag : ThingDef
+
* Using them requires a lot of casting
{
+
* Only one custom class per Def
public float myNewFloat;
+
* 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]
  
public bool myNewBool;
+
==When to use==
 +
When neither a Comp nor a DefModExtension works.
  
public List<ThingDef> myNewThingDefList = new List<ThingDef>();
+
=Custom Def=
}
+
{{Main|Modding Tutorials/Def_classes}}
}</source><br/>
+
(Optionally) inherit from a Def and tell RimWorld to use your custom Def.
  
And now that that exists you can call on it with any ThingDef as input like so:<br/>
+
==Pros==
 +
Fully your own Type, complete control. No incompatibility issues, because they're all yours.
  
<source lang="csharp"> ThingDef_CustomTag customThingDef = someThingDef as ThingDef_CustomTag;
+
==Cons==
if (customThingDef != null)
+
Implementation from scratch.
{
 
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;
 
}</source><br/>
 
  
Please note that any of the code might not work in a newer alpha. It's for demonstration purposes only.<br/><br/>
+
==When to use==
 +
If your Def is really unique to your mod or specific goal.
  
===Counterexample===
+
=Other ways=
''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/>
+
Sometimes creative, sometimes hacky:
 +
==Checking for tags==
  
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/>
+
Instead of using custom defClasses and comps you could also use tags. This is especially useful for lightweight features and simple compatibility.<br/><br/>
  
<source lang="xml"><?xml version="1.0" encoding="utf-8"?>
+
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/>
<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></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/>
 
Now for the code we will have to create two C# classes, namely myNamespace.ThingDefWithCustomRaceProps and myNamespace.RacePropertiesCustom:</br>
 
 
 
<source lang="csharp">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 */
 
}
 
}</source><br/>
 
 
 
And for the RacePropertiesCustom we make the following script:<br/>
 
 
 
<source lang="csharp">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>();
 
}
 
}</source><br/>
 
 
 
One might think that's all required to make it work. However.<br/>
 
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/>
 
 
 
For this reason custom defs are advised against.<br/><br/>
 
 
 
==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)<br/><br/>
 
 
 
===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<sup>[?]</sup> in case a mod isn't loaded, but it's not possible to guarantee that the comp is actually added.<br/><br/>
 
  
===Method===
+
===Pros===
 +
Lightweight, easy.
  
==Checking for tags==
+
===Cons===
 +
Risk of potential side-effects. Kinda hacky.
  
Instead of using custom defClasses and comps you could also use tags. This is especially useful for lightweight features and simple compatibility.<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.
  
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/>
+
===Cons===
 +
Subverts any Harmony patches on the original Class, so potential issues with compatibility.
  
===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]