☰

将模块XML文件与原生数据合并

在Bannerlord v1.3中,我们实现了一种XML合并算法,可以在不使用XSLT的情况下,帮助将其他模块的模块数据中定义的XML文件进行合并。我们可以在合并过程中添加新元素或更改某些属性,这些操作会利用这些元素的唯一属性以及从XSD中收集到的其他信息。请按照以下步骤启用合并算法:

  1. 创建一个需要与基础游戏合并的XML文件
  2. 添加包含模组内容的新元素;它们将被添加到最终合并后的XML文件中的相应位置,该文件将被游戏使用
  3. 更改基础游戏中已经存在的元素

在引擎中,原生数据与其他模块数据的合并有两种方式。

与原生数据的合并

这些是以”soln”开头的XML文件。在ModuleData中,它们在”project.mbproj”文件中定义。

在下面的代码中,我们可以看到该模块注册了:

<?xml version="1.0" encoding="utf-8"?>
<base xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="solution">
  <outputDirectory>../MBModule/MBModule/</outputDirectory>
  <XMLDirectory>../WOTS/Modules/Naval DLC/</XMLDirectory>
  <ModuleAssemblyDirectory>../WOTS/bin/</ModuleAssemblyDirectory>
  <file id="soln_action_sets" name="ModuleData/action_sets.xml" type="action_set" />
  <file id="soln_action_types" name="ModuleData/action_types.xml" type="action_type" />
  <file id="soln_movement_sets" name="ModuleData/movement_sets.xml" type="movement_set" />
  <file id="soln_full_movement_sets" name="ModuleData/full_movement_sets.xml" type="full_movement_set" />
  <file id="soln_combat_system" name="ModuleData/native_parameters.xml" type="native_parameters" />
  <file id="soln_skins" name="ModuleData/skins.xml" type="skin" />
</base>

系统会自动找到这些文件并将其与原生数据合并。

托管XML文件

另一种合并方式是针对托管XML文件进行的,例如items.xml、heroes.xml和settlements.xml。它们在位于模块根目录的SubModule.xml文件中定义。以下是一个SubModule文件的示例:

<?xml version="1.0" encoding="utf-8"?>
<Module>
  <Name value="NavalDLC" />
  <Id value="NavalDLC" />
  <Version value="v1.3.0" />
  <DefaultModule value="false" />
  <ModuleCategory value="Singleplayer" />
  <ModuleType value="OfficialOptional" />
  <DependedModules>
    ...
  </DependedModules>
  <SubModules>
    ...
  </SubModules>
  <Xmls>
    <XmlNode>
      <XmlName id="Items" path="items" />
      <IncludedGameTypes>
        <GameType value="Campaign" />
        <GameType value="CampaignStoryMode" />
      </IncludedGameTypes>
    </XmlNode>
  </Xmls>
</Module>

在上面的示例中,我们已经向游戏注册了一个id=“Items”的XMLNode。因此,当需要进行合并时,基础游戏中的items.xml将与模组中的items.xml合并。唯一需要做的就是像往常一样实现items.xml文件,它们就会被自动合并。

让我们看看位于原生”items.xml”中的火把(Torch)物品:

<Item
    id="torch"
    name="{=ErOLa263}Torch"
    is_merchandise="false"
    body_name="bo_mace_a"
    recalculate_body="true"
    mesh="torch_g"
    prefab="torch_a_wm_only_flame"
    culture="Culture.empire"
    value="4"
    weight="0.2"
    Type="OneHandedWeapon"
    item_holsters="abdomen_left">
    <ItemComponent>
        <Weapon
            weapon_class="OneHandedAxe"
            weapon_balance="100"
            thrust_speed="78"
            speed_rating="97"
            missile_speed="0"
            weapon_length="65"
            swing_damage="6"
            swing_damage_type="Blunt"
            item_usage="banner"
            physics_material="metal_weapon">
            <WeaponFlags MeleeWeapon="true" />
        </Weapon>
    </ItemComponent>
    <Flags
        DropOnWeaponChange="true"
        ForceAttachOffHandPrimaryItemBone="true"
        HeldInOffHand="true"
        HasToBeHeldUp="true" />
</Item>

我们可以通过在自己的模块中重写该物品来更改它的某些属性。当合并算法运行时,它会遍历XML文件并查看该元素的”唯一”属性(在本例中为”id”)。如果找到唯一属性,则会更改该元素;如果没有找到,则会将新物品添加到XML文件中。假设我们想更改上面的Torch物品,它的唯一属性id为”torch”。我们希望让它在商店中可用,将其swing_damage更改为9,并将DropOnWeaponChange标志设置为false。我们需要在模块的items.xml中插入以下代码:

<Item
    id="torch"
    is_merchandise="true"
    body_name="bo_mace_a">
    <ItemComponent>
        <Weapon swing_damage="9">
        </Weapon>
    </ItemComponent>
    <Flags DropOnWeaponChange="false" />
</Item>

然后,算法会遍历所有物品,找到一个在原生文件中已经存在相同唯一属性的物品。接着,它开始进行合并处理。这是合并后的结果物品:

<Item
    id="torch"
    name="{=ErOLa263}Torch"
    is_merchandise="true"
    body_name="bo_mace_a"
    recalculate_body="true"
    mesh="torch_g"
    prefab="torch_a_wm_only_flame"
    culture="Culture.empire"
    value="4"
    weight="0.2"
    Type="OneHandedWeapon"
    item_holsters="abdomen_left">
    <ItemComponent>
        <Weapon
            weapon_class="OneHandedAxe"
            weapon_balance="100"
            thrust_speed="78"
            speed_rating="97"
            missile_speed="0"
            weapon_length="65"
            swing_damage="9"
            swing_damage_type="Blunt"
            item_usage="banner"
            physics_material="metal_weapon">
            <WeaponFlags MeleeWeapon="true" />
        </Weapon>
    </ItemComponent>
    <Flags
        DropOnWeaponChange="false"
        ForceAttachOffHandPrimaryItemBone="true"
        HeldInOffHand="true"
        HasToBeHeldUp="true" />
</Item>

对于每个XML文件,都有一个XSD模式文件,提供关于该XML文件结构的信息。XSD文件位于XmlSchemas文件夹中。这些文件还会告诉我们元素的哪些属性是唯一属性。而且有一些元素,算法总是更倾向于进行合并。在上面的XML文件中,如果我们查看相应的XSD模式,会发现对于元素”Item”,唯一属性是”id”;对于元素”ItemComponent”,我们应该总是优先进行合并,因为规定每个”Item”只能有一个”ItemComponent”元素。所有这些信息都是算法从xsd文件中读取的。例如,让我们看一下”ShipHulls”的xsd模式:

<?xml version='1.0' encoding='UTF-8'?>
<xs:schema xmlns=""
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="ShipHulls">
  <xs:simpleType name="positiveIntType">
    <xs:restriction base="xs:int">
      <xs:minExclusive value="0"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="ShipHulls" msdata:IsDataSet="true">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ShipHull" maxOccurs="unbounded" minOccurs="0">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="AvailableSlots" maxOccurs="1">
                <xs:annotation>
                  <xs:appinfo>
                    <appSpecificNote>AlwaysPreferMerge</appSpecificNote>
                  </xs:appinfo>
                </xs:annotation>
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="ShipSlot" maxOccurs="unbounded" minOccurs="0">
                      <xs:complexType>
                        <xs:attribute type="xs:string" name="id" use="required"/>
                        <xs:attribute type="xs:string" name="tag_id" use="required"/>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                </xs:complexType>
                <!-- id attribute is unique for ShipSlot -->
                <xs:unique name="ShipSlot_unique_attribute">
                  <xs:selector xpath="ShipSlot"/>
                  <xs:field xpath="@tag_id"/>
                </xs:unique>
              </xs:element>
            </xs:sequence>
            <xs:attribute type="xs:string" name="id" use="required"/>
            <xs:attribute type="xs:string" name="mission_ship" use="required"/>
            <xs:attribute type="xs:string" name="name" use="required"/>
            <xs:attribute type="xs:string" name="ship_type" use="required"/>
            <xs:attribute type="xs:float" name="base_speed" use="required"/>
            <xs:attribute type="xs:int" name="value" use="required"/>
            <xs:attribute type="xs:string" name="default_group" use="required"/>
            <xs:attribute type="xs:boolean" name="can_navigate_shallow_water" use="required"/>
            <xs:attribute type="xs:float" name="production_build_weight" use="required"/>
            <xs:attribute type="xs:int" name="sea_worthiness" use="required"/>
            <xs:attribute type="xs:int" name="inventory_capacity" use="required"/>
            <xs:attribute type="xs:float" name="map_visual_scale" use="required"/>
            <!-- Ship's maximum hitpoints. -->
            <xs:attribute name="max_hitpoints" type="positiveIntType" use="required"/>
            <!-- Ship's total crew capacity. Including both the crew on ship's main deck and also the reserves within ship's lower decks -->
            <xs:attribute name="total_crew_capacity" type="positiveIntType" use="required"/>
            <!-- Ship's main deck crew capacity. Must match the count of crew spawn entities in ship's prefab -->
            <xs:attribute name="main_deck_crew_capacity" type="positiveIntType" use="required"/>
            <!-- Skeletal crew capacity. Minimum crew count to operate the ship -->
            <xs:attribute name="skeletal_crew_capacity" type="positiveIntType" use="required"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
    <!-- id attribute is unique for ShipHull -->
    <xs:unique name="ShipHull_unique_attribute">
      <xs:selector xpath="ShipHull"/>
      <xs:field xpath="@id"/>
    </xs:unique>
  </xs:element>
</xs:schema>

在上面的模式中,我们可以看到”xs:unique”元素告诉我们由”xpath”指定的元素的哪些属性是唯一的。例如,让我们看看:

<!-- id attribute is unique for ShipHull -->
<xs:unique name="ShipHull_unique_attribute">
  <xs:selector xpath="ShipHull"/>
  <xs:field xpath="@id"/>
</xs:unique>

这表明元素”ShipHull”的”id”属性是唯一的。因此,合并算法在合并时会检查这个”id”字段。在xsd文件中还可以看到一些注解,例如:

<xs:element name="AvailableSlots" maxOccurs="1">
  <xs:annotation>
    <xs:appinfo>
      <appSpecificNote>AlwaysPreferMerge</appSpecificNote>
    </xs:appinfo>
  </xs:annotation>

这告诉我们”AvailableSlots”元素在其父元素中只出现一次,当算法在对父项进行合并修改时遇到”AvailableSlots”项时,它知道我们试图合并的另一个元素中存在的”AvailableSlots”会直接与现有的”AvailableSlots”合并,而不会产生两个”AvailableSlots”元素。以这种方式注解的元素也不需要唯一id,因为我们不需要它。

本页面由 AI 翻译,可能存在不准确之处。请以英文原版为准。