Modding Tutorials/DefModExtension
A DefModExtension is a way to add fields to Defs. DefModExtensions are a simple way to extend functionality to a Def. A DefModExtension doesn't directly add fields to classes as that's technically impossible to do at runtime. The implementation is that the Def class has a public List<DefModExtension> which can easily be extended.
Benefits:
+ Is really simple and light-weight to use.
+ Works on any Def.
+ Exposes its functionality to XML.
+ Compatible with savefiles, other mods, you name it.
+ Does not come with the compatibility issues and pitfalls of creating a custom Def class.
+ Does not have the overhead of a ThingComp.
+ More broadly applicable than a ThingComp (not every Def is a ThingWithComps!).
Downsides:
- Static and global data.
- Can't save data.
- Only works on Defs.
- A DefModExtension does not know what Def it belongs to. (There are ways around this, like setting a Def field in your DefModExtension and populating it in XML/C#)
Requirements
You know the deal by now.
- Solution
- Custom code
- C#
- XML
The Code
using Verse; namespace ExampleNameSpace { public class ExampleModExtension : DefModExtension { public bool canBeARimQuest = true; } }
That's it. DefModExtensions can of course contain any field you wish: a C# Type, a Thingfilter, a list -- but for the sake of a simple example, we'll use a bool.
Using the DefModExtension
using Verse; using RimWorld; namespace ExampleNameSpace { public class OurExampleClass { private static bool IsAcceptableQuest(IncidentDef incidentDef) { if (!incidentDef.targetTags.Contains(IncidentTargetTagDefOf.World)) return false; if (incidentDef.letterDef == LetterDefOf.NegativeEvent) return false; if (incidentDef.HasModExtension<ExampleModExtension>()) return incidentDef.GetModExtension<ExampleModExtension>().canBeARimQuest; return true; } } }
Or if you like your code shorter, you can use null conditionals and null coalescing:
return incidentDef.targetTags.Contains(IncidentTargetTagDefOf.World) && incidentDef.letterDef != LetterDefOf.NegativeEvent && (incidentDef.GetModExtension<ExampleModExtension>()?.canBeARimQuest ?? true); //mod extension value if not null, otherwise assumed true.
Adding it to your def
<Defs> <ExampleDef> <defName>Example</defName> <modExtensions> <li Class="ExampleNameSpace.ExampleModExtension"> <canBeARimQuest>false</canBeARimQuest> </li> </modExtensions> </ExampleDef> </Defs>
Using xpath to add it to a def
If you wanted to xpath patch a mod extension onto an existing def, you'd use PatchOperationAddModExtension, which is like PatchOperationAdd but saves having to check if modExtensions has already been added/having to include it in your value field:
<Patch> <Operation Class="PatchOperationAddModExtension"> <xpath>Defs/ExampleDef[defName="Example"]</xpath> <value> <li Class="ExampleNameSpace.ExampleModExtension"> <canBeARimQuest>false</canBeARimQuest> </li> </value> </Operation> </Patch>
See also
Ludeon thread on Mod Extensions
The pull request on which the code in this article is based on