Difference between revisions of "Modding Tutorials/DefModExtension"

From RimWorld Wiki
Jump to navigation Jump to search
(first iteration - more code to follow)
Line 1: Line 1:
{{stub}}
 
 
{{BackToTutorials}}
 
{{BackToTutorials}}
  
A DefModExtension is a way to add fields to Defs. DefModExtensions are a simple way to extend functionality to a Def.
+
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''':<br/>
 
'''Benefits''':<br/>
Line 17: Line 16:
 
<nowiki>-</nowiki> Can't save data.<br/>
 
<nowiki>-</nowiki> Can't save data.<br/>
 
<nowiki>-</nowiki> Only works on Defs.<br/>
 
<nowiki>-</nowiki> Only works on Defs.<br/>
 +
<nowiki>-</nowiki> 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#)<br/>
  
 
=Requirements=
 
=Requirements=
Line 28: Line 28:
 
<source lang="csharp">
 
<source lang="csharp">
 
using Verse;
 
using Verse;
using RimWorld;
 
  
 
namespace ExampleNameSpace
 
namespace ExampleNameSpace
 
{
 
{
     public class RimQuest_ModExtension : DefModExtension
+
     public class ExampleModExtension : DefModExtension
 
     {
 
     {
 
         public bool canBeARimQuest = true;
 
         public bool canBeARimQuest = true;
Line 39: Line 38:
 
</source>
 
</source>
 
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.
 
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==
 +
<source lang=csharp>
 +
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;
 +
        }
 +
    }
 +
}
 +
</source>
 +
Or if you like your code shorter, you can use null conditionals and null coalescing:
 +
<source lang = "csharp">
 +
            return incidentDef.targetTags.Contains(IncidentTargetTagDefOf.World)
 +
                && incidentDef.letterDef != LetterDefOf.NegativeEvent
 +
                && (incidentDef.GetModExtension<ExampleModExtension>()?.canBeARimQuest ?? true);
 +
                //mod extension value if not null, otherwise assumed true.
 +
</source>
 +
 +
==Adding it to your def==
 +
<source lang = "xml">
 +
<Defs>
 +
    <ExampleDef>
 +
        <defName>Example</defName>
 +
        <modExtensions>
 +
            <li Class="ExampleNameSpace.ExampleModExtension">
 +
                <canBeARimQuest>false</canBeARimQuest>
 +
            </li>
 +
        </modExtensions>
 +
    </ExampleDef>
 +
</Defs>
 +
</source>
 +
 +
===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:
 +
<source lang = "xml">
 +
<Patch>
 +
    <Operation Class="PatchOperationAddModExtension">
 +
        <xpath>Defs/ExampleDef[defName="Example"]</xpath>
 +
        <value>
 +
            <li Class="ExampleNameSpace.ExampleModExtension">
 +
                <canBeARimQuest>false</canBeARimQuest>
 +
            </li>
 +
        </value>
 +
    </Operation>
 +
</Patch>
 +
</source>
  
 
=See also=
 
=See also=
 
[https://ludeon.com/forums/index.php?topic=46351.0 Ludeon thread on Mod Extensions]<br>
 
[https://ludeon.com/forums/index.php?topic=46351.0 Ludeon thread on Mod Extensions]<br>
[https://github.com/jecrell/RimQuest/commit/54413b821233316c4057db87031aa6e528ab2db6 The pull request the code this article is based on]
+
[https://github.com/jecrell/RimQuest/commit/54413b821233316c4057db87031aa6e528ab2db6 The pull request on which the code in this article is based on]
  
 
[[Category: Modding]]
 
[[Category: Modding]]
 
[[Category: Modding tutorials]]
 
[[Category: Modding tutorials]]

Revision as of 11:12, 31 January 2019

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