Skip to content

Instantly share code, notes, and snippets.

@Zhentar
Last active November 12, 2023 23:06
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Zhentar/4a1b71cea45b9337f70b30a21d868782 to your computer and use it in GitHub Desktop.
Save Zhentar/4a1b71cea45b9337f70b30a21d868782 to your computer and use it in GitHub Desktop.
Introduction to PatchOperation

There are 11 new "PatchOperation" actions we can perform to modify Defs without copying the whole thing. This in turn improves mod compatability dramatically - it allows two mods to modify different parts of the same Def without conflict. This guide only describes the basics of each operation available to us.

Patches go in xml files in a subfolder named "Patches" in your mod folder (So "MyMod\Patches", just like you might have "MyMod\Defs" or "MyMod\Textures"). Example.

Each PatchOperation has an "xpath" node, which should contain an xpath selector for the xml node(s) that the operation should affect. This is not an xpath tutorial. If you don't know xpath, not my problem. But minimurgle has you covered!

To illustrate the operations, I'll use this simple example def:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
    <specialDesignatorClasses>
      <li>Designator_Cancel</li>
    </specialDesignatorClasses>
  </DesignationCategoryDef>

And my xpath selector in each example will select the specialDesignatorClasses node.

Operations

Basic Operations

There are four basic operations: Add, Insert, Remove, Replace.

PatchOperationAdd adds a child node to the selected node.

	<Operation Class="PatchOperationAdd">
	  <xpath>*/DesignationCategoryDef[defName = "ExampleCategory"]/specialDesignatorClasses</xpath>
	  <value>
		<li>ZhentarExample.ExampleDesignator</li>
	  </value>
	</Operation> 

transforms the def into this:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
    <specialDesignatorClasses>
      <li>Designator_Cancel</li>
      <li>ZhentarExample.Example Designator</li>
    </specialDesignatorClasses>
  </DesignationCategoryDef>

PatchOperationInsert adds a sibling to the selected node.

	<Operation Class="PatchOperationInsert">
	  <xpath>*/DesignationCategoryDef[defName = "ExampleCategory"]/specialDesignatorClasses</xpath>
	  <value>
        <description>Pointless example designators.</description>
	  </value>
	</Operation> 

transforms the def into this:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
    <specialDesignatorClasses>
      <li>Designator_Cancel</li>
    </specialDesignatorClasses>
    <description>Pointless example designators.</description>
  </DesignationCategoryDef>

PatchOperationRemove removes the selected node.

	<Operation Class="PatchOperationRemove">
	  <xpath>*/DesignationCategoryDef[defName = "ExampleCategory"]/specialDesignatorClasses</xpath>
	</Operation> 

transforms the def into this:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
  </DesignationCategoryDef>

PatchOperationReplace replaces the selected node.

	<Operation Class="PatchOperationReplace">
	  <xpath>*/DesignationCategoryDef[defName = "ExampleCategory"]/specialDesignatorClasses</xpath>
	  <value>
        <specialDesignatorClasses/>
	  </value>
	</Operation> 

transforms the def into this:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
    <specialDesignatorClasses/>
  </DesignationCategoryDef>

Attribute Operations

There are three operations for attributes: Add, Set, and remove.

PatchOperationAttributeAdd and PatchOperationAttributeSet will both add an attribute and set it to the provided value. The difference is that Add will set the value if and only if the attribute is not already present, while Set will overwrite an existing value.

	<Operation Class="PatchOperationAttributeSet">
	  <xpath>*/DesignationCategoryDef[defName = "ExampleCategory"]/specialDesignatorClasses</xpath>
	  <attribute>ExampleAttribute</attribute>
	  <value>ExampleValue</value>
	</Operation> 

transforms the def into this:

  <DesignationCategoryDef>
    <defName>ExampleCategory</defName>
    <label>Example</label>
    <specialDesignatorClasses ExampleAttribute="ExampleValue">
      <li>Designator_Cancel</li>
    </specialDesignatorClasses>
  </DesignationCategoryDef>

PatchOperationAttributeRemove removes an attribute. Our example def doesn't have any attributes to remove, so you'll just have to use your imagination ;-)

Advanced Operations

Finally, there are four more advanced operations: PatchOperationAddModExtension, PatchOperationSetName, PatchOperationSequence, and PatchOperationTest.

PatchOperationAddModExtension is fairly straightforward... it adds a ModExtension. If you want to know what a ModExtension is... you'll need a different guide.

PatchOperationSetName changes the name of a node to a different name. This operation was only included for completeness; there is no known use case for it.

PatchOperationSequence and PatchOperationTest can be used to conditionally perform patches. This is useful for mod compatibility, because it can be used to prevent adding the same element twice. PatchOperationSequence will perform each operation in its list in sequence, aborting if an operation fails. PatchOperationTest simply determines if the xpath selector matches any elements.

This example adds the costList element if it is not already present:

<Operation Class="PatchOperationSequence">
  <success>Always</success>
  <operations>
    <li Class="PatchOperationTest">
      <xpath>*/ThingDef[defName = "DiningChair"]/costList</xpath>
      <success>Invert</success>
    </li>
    <li Class="PatchOperationAdd">
      <xpath>*/ThingDef[defName = "DiningChair"]</xpath>
      <value>
        <costList />
      </value>
    </li>
  </operations>
</Operation>

Patching Techniques

This section will explain some situations where we can apply some more advanced techniques.

This will be our reference.

<Defs>
  <BlahDef Name="BlahBase" Abstract="True">
    <label>Blah</label>
  </BlahDef>
  <BlahDef>
    <defName>Zip<defName>
    <bleh>50<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zop<defName>
    <bleh>50<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zap<defName>
    <bleh>50<bleh>
  </BlahDef>
</Defs>

Patching Abstracts

What if you wanted to patch an abstract like LocalInjuryBase or BuildingBase? Obviously the xpath would need a few modifications. Let's try adding <category>Item</category> to the above BlahBase.

  <Operation Class="PatchOperationAdd">
    <xpath>*/BlahDef[@Name="BlahBase"]</xpath>
    <value><category>Item</category></value>
  </Operation>

Result:

<Defs>
  <BlahDef Name="BlahBase" Abstract="True">
    <label>Blah</label>
    <category>Item</category>
  </BlahDef>
  <BlahDef>
    <defName>Zip<defName>
    <bleh>50<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zop<defName>
    <bleh>50<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zap<defName>
    <bleh>50<bleh>
  </BlahDef>
</Defs>

The @Name="BlahBase" selector tests a BlahDef to see if it has an attribute called Name, and if so, if the value of that attribute is BlahBase.

Multi-select

If you're going to do the same thing to multiple defs, did you know that you can condense your xpath into 1 patch?

This example below sets the bleh value on all 3 BlahDefs to 20.

  <Operation Class="PatchOperationReplace">
    <xpath>*/BlahDef[defName="Zip" or defName="Zop" or defName="Zap"]/bleh</xpath>
    <value><bleh>20</bleh></value>
  </Operation>

Result:

<Defs>
  <BlahDef Name="BlahBase" Abstract="True">
    <label>Blah</label>
  </BlahDef>
  <BlahDef>
    <defName>Zip<defName>
    <bleh>20<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zop<defName>
    <bleh>20<bleh>
  </BlahDef>
  <BlahDef>
    <defName>Zap<defName>
    <bleh>20<bleh>
  </BlahDef>
</Defs>

Notice how the or operator is used here.

Condensing 3 PatchOperations into 1 can have a dramatic impact on your mod's load time, as the game only has to pass through the xml database once.

@roxxploxx
Copy link

Literally threw me how easy this is to use. I was trying to convert def tags into the actual RimWorld type... but this is just straight up text. Thanks for the guide and thanks to Tynan and Co for this gift to modders!

@cuproPanda
Copy link

cuproPanda commented May 22, 2017

This has been a big help! I got the PatchOperationAdd down, but immediately ran into an issue when trying to check for duplicate entries among my mods. This guide helped clear it right up 👍

@NoImageAvailable
Copy link

Regarding PatchOperationSetName, I could think of a number of uses. For example if your mod is adding wood planks back into the game and you want to replace all recipe costLists using WoodLog with WoodPlank but keep the count the same. SetName allows you to do it all in one operation across multiple mods where otherwise you'd need a Replace operation for every single occurrence.

@Mehni
Copy link

Mehni commented May 25, 2017

For anyone else that was confused by xpath:

This thread links to this tutorial.

@kaptain-kavern
Copy link

@VaniatD
Copy link

VaniatD commented Nov 19, 2017

How to use PatchOperationAddModExtension?

@NinjaSiren
Copy link

Hello there, can PatchOperationFindMod find mods automatically with or without the said mod installed and not give any errors?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment