Modding Tutorials/DefModExtension

From RimWorld Wiki
Revision as of 11:12, 31 January 2019 by Mehni (talk | contribs)
Jump to navigation Jump to search

Modding Tutorials

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