RimWorld:补丁操作(PatchOperations)

前言

相关内容来自RimWorld Wiki ,本文仅做翻译和有限的补充,除非另有说明,所属内容均在 CC BY-SA 3.0 下提供。

RimWorld Wiki:https://www.rimworldwiki.com/wiki/Main_PageRimWorld

RimWorld Wiki - PatchOperations:https://www.rimworldwiki.com/wiki/Modding_Tutorials/PatchOperations

水平有限,如有错漏,敬请见谅

如果您喜欢这篇文章,麻烦大家帮我点个赞,谢谢大家的支持!

基础

补丁操作(PatchOperations,以下称为Patch操作)是一个允许你修改XML Def内容的功能。在RimWorld alpha17版本之前,模组只能通过覆盖原来的Defs来修改Defs。这样做通常会导致兼容问题,如果有多个模组试图覆盖相同的Def,只有最后一个加载的模组会成功,因为前面的模组都将被覆盖。

补丁操作是以XML节点的形式编写的,位于模组根文件夹的Patches文件夹中。

MyModFolder
  ├ About
  ├ Defs
  └ Patches
  	  └ MyPatchFile.xml

XML Defs一样,Patches文件夹中的子文件夹名称和文件名并不重要,可以按照个人习惯进行命名。每个单个patches文件是一个以<Patch>为根标签的标准XML文件:

<?xml version="1.0" encoding="utf-8"?>
<Patch>

  <!-- PatchOperations -->

</Patch>

XPath

大多数Patch操作必须针对主XML文档中的一个或多个XML节点。通过XML路径语言(xpath)实现Path操作与XML节点的对应。

注意,xpath的目标是经过RimWorld解析器解析的XML文档的结构,因此它与实际的文件或文件夹路径无关。例如,如果想给原版的墙添加一个统计值,可以使用这样的xpath:

`Defs/ThingDef[defName="Wall"]/statBases`
  • 任何针对XML Def的xpath的开始部分都是Defs/,因为所有XML Def都使用<Defs>作为根标签;

  • 方括号表示谓词匹配。在上例中,我们正在寻找具有子标签<defName><defName>值等于“Wall”的<thingdef>

以属性为目标

对于没有defNameDef(如abstract bases),可以它们的标识属性来进行定位。例如,如果想为所有Stuff的abstract bases Def添加另一个Stuff类别,可以使用下面的xpath:

`Defs/ThingDef[@Name="ShelfBase"]/stuffCategories`

可以使用相同的技巧来定位所有继承了某个common base的Defs。例如,你可以使用下面的xpath来定位所有继承了ApparelBase<Thingdef>:

`Defs/ThingDef[@ParentName="ApparelBase"]`

PatchOperation类型

PatchOperation类型总览

基本的XML节点操作XML属性操作
PatchOperationAdd:将提供的节点作为子节点添加到所选节点中;
PatchOperationInsert:将提供的节点作为同级节点插入在所选节点之上;
PatchOperationRemove:删除选中的节点;
PatchOperationReplace:将选中的节点替换为提供的节点。
PatchOperationAttributeAdd:当且仅当所提供的属性不存在时,将提供的属性添加到所选节点;
PatchOperationAttributeSet:为所选节点设置属性,如果属性已经存在,则覆盖属性值;
PatchOperationAttributeRemove:删除所选节点的属性。
特殊操作条件操作
PatchOperationSequence:包含一组其他的Path操作,并且在任何操作失败时终止;
PatchOperationAddModExtension:添加一个<ModExtension>
PatchOperationSetName:用于修改节点名称。
PatchOperationFindMod:测试是否存在另一个模组,并且可以根据结果执行不同的操作;
PatchOperationConditional:测试节点,并且可以根据结果执行不同的操作;
PatchOperationTest:测试节点,在PatchOperationSequence中很有用。

基本的XML节点操作

PatchOperationAdd

将指定的值作为操作的xpath所针对的XML节点的子节点插入。默认情况下,新节点将插入到任何现有子节点之后(Append)。可以在PatchOperationAdd中使用<order>Prepend</order>中将它们插入到现有子节点之前。

注意:PatchOperationAdd不会覆盖任何现有的标签。如果插入某个值与现有节点的值重叠,并且目标不是列表节点,那么将导致游戏加载错误。
在这里插入图片描述

PatchOperationInsert

将指定的值作为同级节点插入选定节点上方。可以使用<order>Append</order>将其插入到目标节点之后(默认为Prepend)。
在这里插入图片描述

PatchOperationRemove

删除目标节点。
在这里插入图片描述

PatchOperationReplace

用提供的值替换所选节点的值。
在这里插入图片描述

XML属性操作

PatchOperationAttributeAdd

当且仅当目标节点不存在所提供的属性时,将提供的属性添加到目标节点。
在这里插入图片描述

PatchOperationAttributeSet

为所选节点设置属性,如果属性已经存在,则覆盖属性值。
在这里插入图片描述

PatchOperationAttributeRemove

删除所选节点的属性。
在这里插入图片描述

特殊操作

PatchOperationSequence

包含一个或多个按顺序执行的子Patch操作。如果其中任何一个失败,则Sequence停止并且不会继续执行后续的子Patch操作。

警告

  • 即使不使用PatchOperationSequence, XML文件中的Patch操作也会按顺序运行。
  • 使用PatchOperationSequence会导致错误混淆或隐藏,如果PatchOperationSequence中的子Path操作有错误,将会难以调试。
  • 除非需要用单个PatchOperationConditionalPatchOperationFindMod对多个Patch操作进行排序,或者需要在子Patch操作上使用MayRequire,否则不要使用PatchOperationSequence
  • 即使这样,也强烈建议将Patch操作编写为独立执行的,以确保它们按预期工作。
<Operation Class="PatchOperationSequence">
  <operations>
    <li Class="PatchOperationAdd">
      <xpath>Defs/ExampleDef[defName="Sample"]/statBases</xpath>
      <value>
        <Mass>10</Mass>
      </value>
    </li>
    <li Class="PatchOperationSetName">
      <xpath>Defs/ExampleDef[defName="Sample"]/statBases/Flammability</xpath>
      <name>ToxicEnvironmentResistance</name>
    </li>
    <!-- etc -->
  </operations>
</Operation>

如前文所述,你可以在PatchOperationSequence的子Patch操作上使用MayRequire属性,但仍应该在将它们添加到序列之前进行单独测试:

<Operation Class="PatchOperationSequence">
  <operations>
    <li Class="PatchOperationAdd" MayRequire="Ludeon.Rimworld.Biotech"> <!-- Only runs if Biotech is active -->
      <xpath>Defs/ThingDef[defName="MechGestator"]/recipes<xpath>
      <value>
        <li>MyCustomMech</li>
      </value>
    </li>
    <li Class="PatchOperationAdd" MayRequire="MyProject.OtherModPackageId"><!-- Only runs if the specific mod is active -->
      <xpath>Defs/ThingDef[defName="OtherModWorkbench"]/recipes</xpath>
      <value>
        <li>MyCustomResource</li>
      </value>
    </li>
  </operations>
</Operation>
PatchOperationAddModExtension

将指定的DefModExtension添加到目标Def。如果目标Def不存在<ModExtensions>节点,将会自动创建。
在这里插入图片描述

PatchOperationSetName

修改节点名称。最适用于更改字典节点中的键节点名称而不更改其内容,例如stat和配方。
在这里插入图片描述

条件操作

PatchOperationFindMod

检查指定的模组或DLC是否被加载,并允许使用子Patch操作来处理匹配和不匹配结果。

警告:与RimWorld中的所有其他模组兼容性功能不同,PatchOperationFindMod使用模组name而不是其packageId

注意

  • PatchOperationFindMod只应该在为目标模组实现可选兼容性时使用,即本模组可以使用,也可以不使用相关的目标模组。
  • 如果本模组是解决目标模组兼容问题的模组,没有目标模组的话本模组就没有意义,那么最好在About.xml中简单地指定目标模组作为依赖模组,并放弃使用PatchOperationFindMod,避免引入的潜在问题。

示例1:若RimQuest模组被加载则添加一个<ModExtension>到目标Defs原文连接)。

<Operation Class="PatchOperationFindMod">
  <mods>
    <li>RimQuest</li>
  </mods>
  <match Class="PatchOperationAddModExtension">
    <xpath>Defs/IncidentDef[defName="MFI_DiplomaticMarriage" or defName="MFI_HuntersLodge" or defName="MFI_Quest_PeaceTalks"]</xpath>
    <value>
      <li Class = "RimQuest.RimQuest_ModExtension">
        <canBeARimQuest>false</canBeARimQuest>
      </li>
    </value>
  </match>
</Operation>

示例2:若未加载Relations Tab模组,则替换派系按钮的tabWindowClass。

<Operation Class="PatchOperationFindMod">
  <mods>
    <li>Relations Tab</li>
  </mods>
  <nomatch Class="PatchOperationReplace">
    <xpath>/Defs/MainButtonDef[defName="Factions"]/tabWindowClass</xpath>
    <value>
      <tabWindowClass>MyNameSpace.MyTabWindowClass</tabWindowClass>
    </value>
  </nomatch>
</Operation>
PatchOperationConditional

测试目标节点的存在性/有效性,并允许根据结果运行match或nomatch的Patch操作。

示例: 如果Caravan Def中不存在<comps>节点,将<comps>节点添加到Caravan Def中,然后将自定义的comp项添加到<comps>中:

<?xml version="1.0" encoding="utf-8" ?>
<Patch>

  <!-- add comps field to Caravan WorldObjectDef if it doesn't exist -->
  <Operation Class="PatchOperationConditional">
    <xpath>Defs/WorldObjectDef[defName="Caravan"]/comps</xpath>
    <nomatch Class="PatchOperationAdd">
      <xpath>Defs/WorldObjectDef[defName="Caravan"]</xpath>
      <value>
        <comps />
      </value>
    </nomatch>
  </Operation>

  <!-- add pyromaniac caravan handler comp to Caravan WorldObjectDef -->
  <Operation Class="PatchOperationAdd">
    <xpath>Defs/WorldObjectDef[defName="Caravan"]/comps</xpath>
    <value>
      <li Class="BetterPyromania.WorldObjectCompProperties_Pyromania">
        <fuelCount>20</fuelCount>
        <cooldown>30000</cooldown>
        <needThreshold>0.5</needThreshold>
      </li>
    </value>
  </Operation>

</Patch>
PatchOperationTest(可能过时)

测试xpath的存在性/有效性。作为主动停止PatchOperationSequence的一种方法很有用。

注意: 以这种方式条件性地应用patches被认为是过时的,因为这里使用<success>Always</success>也会抑制合法错误,使序列难以调试。使用PatchOperationConditional是更好的方法,

示例: 来自Shinzy的Apparello。

<Operation Class="PatchOperationSequence">
  <!-- 因为PatchOperationSequence,必须使用 <success>Always</success>  -->
  <success>Always</success>
  <!-- 检查wornGraphicPath,如果没有发现,添加一个 -->
  <operations>
    <li Class="PatchOperationTest">
      <xpath>Defs/ThingDef[defName = "Apparel_Pants"]/apparel/wornGraphicPath</xpath>
      <success>Invert</success>
    </li>
    <li Class="PatchOperationAdd">
      <xpath>Defs/ThingDef[defName = "Apparel_Pants"]/apparel</xpath>
      <value>
        <wornGraphicPath>Accessorello/Pants/Pants</wornGraphicPath>
      </value>
    </li>
  </operations>
</Operation>

杂项

自定义Path操作

自定义Patch操作可以通过在C#子类化Verse. PatchOperation来创建。这对于基于ModSettings值或其他自定义行为执行Patch操作非常有用。
Wiki上目前还没有此类教程,但你可以查看一下文章作为自定义Patch操作的参考:

Success选项(可能过时)

  • <success>...</success>节点决定如何处理错误,通常在PatchOperationSequence中使用。
  • 请注意,此标签的使用应被视为过时。在PatchOperationConditional引入之前,该标签被经常使用,但如今停止继续使用该标签。<success>标签可能会引起混乱,因为它会抑制合法错误的出现。

可用Success选项包括:

  • Always:此Patch操作始终被视为成功,它会抑制所有可能发生的错误。曾经与PatchOperationTest一起用于PatchOperationSequence,以便条件性地运行Patch操作,但现在已过时;
  • Normal:正常错误处理;
  • Invert:反转处理,错误被视为成功,成功被视为失败。这曾在PatchOperationSequences中用于测试PatchOperationTest的否定式。如今可以在PatchOperationConditional上使用<nomatch>作为替代;
  • Never:此Patch操作始终被视为错误。通常只用于测试Sequence是否正常工作,不应该用于已发布的模组。

提示和技巧

  • Path在将所有XML Def加载到内存中后开始按模组列表顺序运行。如果遇到与另一个模组的Patch操作的兼容性问题,可以在About.xml中使用<loadBefore><loadAfter>来帮助玩家解决;
  • Path发生在Def继承之前,这意味着不能以一个从父类标签继承的标签作为目标;不过如果直接更改父类标签,其所有子类标签都将继承Patch操作的值;
  • 可以使用Inherit=“False”属性覆盖从父类Def继承的值,示例如下:
<Operation Class="PatchOperationAdd">
  <xpath>/Defs/ThingDef[defName = "Apparel_KidPants"]</xpath>
  <value>
    <thingCategories Inherit="False">
      <li>NewValue</li>
    </thingCategories>
  </value>
</Operation>
  • 可以在谓词中使用or来同时定位多个节点,如:Defs/ThingDef[dfName=“Cassowary” or defName=“Emu” or defName=“Ostrich” or defName:“Turkey”]。但需要保证对每个目标进行相同的更改,这通常比使用多个Patch操作要好;
  • 对于PatchOperationReplace等操作,可以使用/text()来定位标签的文本内容,而不是整个标签。如果不想意外删除整个属性,这一点尤其有用。

常见问题及处理

  • xpath和XML节点通常区分大小写,并且必须正确拼写。强烈建议通过复制取值,如defName字段,以避免出现错误;
  • 格式不正确的XML(如未封闭或不匹配的标签,不完整的Def)可能会导致XML解析器完全崩溃,这可能导致启动时出现空白屏幕或“从致命错误中恢复”屏幕,并清除所有启用的模组。如果发生这种情况,请通过XML验证器(xml validator)运行所有XML,以确保其结构正确。检查Player.log文件也有助于诊断确切的原因;
  • Insert和Add和操作很容易混淆;Insert将把值“添加”为目标节点的同级节点,而Add将把值作为目标节点的子节点“插入”;
  • 请记住,xpath的目标是XML数据结构,而不是文件路径;
  • <li>列表项之外的XML中的节点在每个级别上都必须是唯一的。如果添加或插入重复的节点,将在加载时生成红色错误,并导致Def无法加载;
  • 如果仍然感到困惑,随时可以加入RimWorld Discord服务器,并在#mod-development频道中提问。

参考资料及链接(可能过时)

Zhentar创建的PatchOperations原始教程:Introduction to PatchOperation

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值