Difference between revisions of "Modding Tutorials/DefModExtension"
(first iteration - more code to follow) |
|||
Line 1: | Line 1: | ||
− | |||
{{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; | ||
− | |||
namespace ExampleNameSpace | namespace ExampleNameSpace | ||
{ | { | ||
− | public class | + | 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
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