Skip to content

Commit fa68f4c

Browse files
committed
Added a new page: Merging Module XML Files with Native
1 parent 6bb0ac4 commit fa68f4c

3 files changed

Lines changed: 804 additions & 0 deletions

File tree

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
+++
2+
title = "Merging Module XML Files with Native"
3+
4+
[menu.main]
5+
identifier = "merging_module_xml_files_with_native"
6+
parent = "bestpractices"
7+
+++
8+
9+
With Bannerlord v1.3, we have implemented an XML merging algorithm that assists in merging XML files defined in other modules' module data without using XSLT. We can add new elements to the XMLs or change some attributes during merging, using their unique attributes and other information gathered from XSDs. Follow these steps to enable the merging algorithm:
10+
11+
1. Create an XML file that needs to be merged with the base game
12+
2. Add new elements with the content of the mod; they will be added to the appropriate places in the final merged XML file that is going to be used by the game
13+
3. Change elements that are already present in the base game
14+
15+
In the engine, merging of native and other modules' data is done in two ways.
16+
17+
## Merging for Native
18+
19+
These are the XML files that start with "soln". In ModuleData, they are defined in the "project.mbproj" file.
20+
21+
In the following code, we can see that this module registers:
22+
23+
* action_sets
24+
* action_types
25+
* movement_sets
26+
* full_movement_sets
27+
* combat_system
28+
* skins
29+
30+
```xml
31+
<?xml version="1.0" encoding="utf-8"?>
32+
<base xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="solution">
33+
<outputDirectory>../MBModule/MBModule/</outputDirectory>
34+
<XMLDirectory>../WOTS/Modules/Naval DLC/</XMLDirectory>
35+
<ModuleAssemblyDirectory>../WOTS/bin/</ModuleAssemblyDirectory>
36+
<file id="soln_action_sets" name="ModuleData/action_sets.xml" type="action_set" />
37+
<file id="soln_action_types" name="ModuleData/action_types.xml" type="action_type" />
38+
<file id="soln_movement_sets" name="ModuleData/movement_sets.xml" type="movement_set" />
39+
<file id="soln_full_movement_sets" name="ModuleData/full_movement_sets.xml" type="full_movement_set" />
40+
<file id="soln_combat_system" name="ModuleData/native_parameters.xml" type="native_parameters" />
41+
<file id="soln_skins" name="ModuleData/skins.xml" type="skin" />
42+
</base>
43+
```
44+
45+
These files are automatically found by the system and merged with native.
46+
47+
## Managed XML Files
48+
49+
The other kind of merging is done with managed XML files such as items.xml, heroes.xml, and settlements.xml. They are defined in the SubModule.xml file located at the module's root. Here's an example of a SubModule file:
50+
51+
```xml
52+
<?xml version="1.0" encoding="utf-8"?>
53+
<Module>
54+
<Name value="NavalDLC" />
55+
<Id value="NavalDLC" />
56+
<Version value="v1.3.0" />
57+
<DefaultModule value="false" />
58+
<ModuleCategory value="Singleplayer" />
59+
<ModuleType value="OfficialOptional" />
60+
<DependedModules>
61+
...
62+
</DependedModules>
63+
<SubModules>
64+
...
65+
</SubModules>
66+
<Xmls>
67+
<XmlNode>
68+
<XmlName id="Items" path="items" />
69+
<IncludedGameTypes>
70+
<GameType value="Campaign" />
71+
<GameType value="CampaignStoryMode" />
72+
</IncludedGameTypes>
73+
</XmlNode>
74+
</Xmls>
75+
</Module>
76+
```
77+
78+
In the above example, we have registered an XMLNode to the game with the id="Items". So when we need merging, items.xml from the base game will merge with the items.xml from the mod. The only thing that needs to be done is to implement the items.xml file as usual, and they will be merged.
79+
80+
Let's have a look at the Torch item located in the native "items.xml":
81+
82+
```xml
83+
<Item
84+
id="torch"
85+
name="{=ErOLa263}Torch"
86+
is_merchandise="false"
87+
body_name="bo_mace_a"
88+
recalculate_body="true"
89+
mesh="torch_g"
90+
prefab="torch_a_wm_only_flame"
91+
culture="Culture.empire"
92+
value="4"
93+
weight="0.2"
94+
Type="OneHandedWeapon"
95+
item_holsters="abdomen_left">
96+
<ItemComponent>
97+
<Weapon
98+
weapon_class="OneHandedAxe"
99+
weapon_balance="100"
100+
thrust_speed="78"
101+
speed_rating="97"
102+
missile_speed="0"
103+
weapon_length="65"
104+
swing_damage="6"
105+
swing_damage_type="Blunt"
106+
item_usage="banner"
107+
physics_material="metal_weapon">
108+
<WeaponFlags MeleeWeapon="true" />
109+
</Weapon>
110+
</ItemComponent>
111+
<Flags
112+
DropOnWeaponChange="true"
113+
ForceAttachOffHandPrimaryItemBone="true"
114+
HeldInOffHand="true"
115+
HasToBeHeldUp="true" />
116+
</Item>
117+
```
118+
119+
We can change some properties of this item by rewriting it in our own module. When the merge algorithm runs, it searches through the XML file and looks at the "unique" attributes of that element (which is "id" in this case). If a unique attribute is found, that element is changed, and if it is not found, the new item is added to the XML file. Let's say we wanted to change the above Torch item, which has the following unique attribute id: "torch". We want to make it available in shops, change its swing_damage to 9, and set the DropOnWeaponChange flag to false. We would insert the following code into our module's items.xml:
120+
121+
```xml
122+
<Item
123+
id="torch"
124+
is_merchandise="true"
125+
body_name="bo_mace_a">
126+
<ItemComponent>
127+
<Weapon swing_damage="9">
128+
</Weapon>
129+
</ItemComponent>
130+
<Flags DropOnWeaponChange="false" />
131+
</Item>
132+
```
133+
134+
The algorithm then searches through the items and finds an item with a unique attribute that is already present in native files. It then starts the merging process. This is the resulting item:
135+
136+
```xml
137+
<Item
138+
id="torch"
139+
name="{=ErOLa263}Torch"
140+
is_merchandise="true"
141+
body_name="bo_mace_a"
142+
recalculate_body="true"
143+
mesh="torch_g"
144+
prefab="torch_a_wm_only_flame"
145+
culture="Culture.empire"
146+
value="4"
147+
weight="0.2"
148+
Type="OneHandedWeapon"
149+
item_holsters="abdomen_left">
150+
<ItemComponent>
151+
<Weapon
152+
weapon_class="OneHandedAxe"
153+
weapon_balance="100"
154+
thrust_speed="78"
155+
speed_rating="97"
156+
missile_speed="0"
157+
weapon_length="65"
158+
swing_damage="9"
159+
swing_damage_type="Blunt"
160+
item_usage="banner"
161+
physics_material="metal_weapon">
162+
<WeaponFlags MeleeWeapon="true" />
163+
</Weapon>
164+
</ItemComponent>
165+
<Flags
166+
DropOnWeaponChange="false"
167+
ForceAttachOffHandPrimaryItemBone="true"
168+
HeldInOffHand="true"
169+
HasToBeHeldUp="true" />
170+
</Item>
171+
```
172+
173+
For each XML file, there is an XSD schema file that gives information about the structure of the XML file. The XSD files are located in the XmlSchemas folder. These files also tell us which attributes of an element are unique attributes. And there are some elements where the algorithm always prefers merging. In the XML files above, if we look at the corresponding XSD schemas, we will see that for the element "Item", the unique attribute is "id", for the element "ItemComponent", we should always prefer merging because it is restricted that there is only one "ItemComponent" element for each "Item". All these information is read from the xsd files by the algorithm. For example, let's look at the xsd schema of "ShipHulls":
174+
175+
```xml
176+
<?xml version='1.0' encoding='UTF-8'?>
177+
<xs:schema xmlns=""
178+
xmlns:xs="http://www.w3.org/2001/XMLSchema"
179+
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="ShipHulls">
180+
<xs:simpleType name="positiveIntType">
181+
<xs:restriction base="xs:int">
182+
<xs:minExclusive value="0"/>
183+
</xs:restriction>
184+
</xs:simpleType>
185+
<xs:element name="ShipHulls" msdata:IsDataSet="true">
186+
<xs:complexType>
187+
<xs:sequence>
188+
<xs:element name="ShipHull" maxOccurs="unbounded" minOccurs="0">
189+
<xs:complexType>
190+
<xs:sequence>
191+
<xs:element name="AvailableSlots" maxOccurs="1">
192+
<xs:annotation>
193+
<xs:appinfo>
194+
<appSpecificNote>AlwaysPreferMerge</appSpecificNote>
195+
</xs:appinfo>
196+
</xs:annotation>
197+
<xs:complexType>
198+
<xs:sequence>
199+
<xs:element name="ShipSlot" maxOccurs="unbounded" minOccurs="0">
200+
<xs:complexType>
201+
<xs:attribute type="xs:string" name="id" use="required"/>
202+
<xs:attribute type="xs:string" name="tag_id" use="required"/>
203+
</xs:complexType>
204+
</xs:element>
205+
</xs:sequence>
206+
</xs:complexType>
207+
<!-- id attribute is unique for ShipSlot -->
208+
<xs:unique name="ShipSlot_unique_attribute">
209+
<xs:selector xpath="ShipSlot"/>
210+
<xs:field xpath="@tag_id"/>
211+
</xs:unique>
212+
</xs:element>
213+
</xs:sequence>
214+
<xs:attribute type="xs:string" name="id" use="required"/>
215+
<xs:attribute type="xs:string" name="mission_ship" use="required"/>
216+
<xs:attribute type="xs:string" name="name" use="required"/>
217+
<xs:attribute type="xs:string" name="ship_type" use="required"/>
218+
<xs:attribute type="xs:float" name="base_speed" use="required"/>
219+
<xs:attribute type="xs:int" name="value" use="required"/>
220+
<xs:attribute type="xs:string" name="default_group" use="required"/>
221+
<xs:attribute type="xs:boolean" name="can_navigate_shallow_water" use="required"/>
222+
<xs:attribute type="xs:float" name="production_build_weight" use="required"/>
223+
<xs:attribute type="xs:int" name="sea_worthiness" use="required"/>
224+
<xs:attribute type="xs:int" name="inventory_capacity" use="required"/>
225+
<xs:attribute type="xs:float" name="map_visual_scale" use="required"/>
226+
<!-- Ship's maximum hitpoints. -->
227+
<xs:attribute name="max_hitpoints" type="positiveIntType" use="required"/>
228+
<!-- Ship's total crew capacity. Including both the crew on ship's main deck and also the reserves within ship's lower decks -->
229+
<xs:attribute name="total_crew_capacity" type="positiveIntType" use="required"/>
230+
<!-- Ship's main deck crew capacity. Must match the count of crew spawn entities in ship's prefab -->
231+
<xs:attribute name="main_deck_crew_capacity" type="positiveIntType" use="required"/>
232+
<!-- Skeletal crew capacity. Minimum crew count to operate the ship -->
233+
<xs:attribute name="skeletal_crew_capacity" type="positiveIntType" use="required"/>
234+
</xs:complexType>
235+
</xs:element>
236+
</xs:sequence>
237+
</xs:complexType>
238+
<!-- id attribute is unique for ShipHull -->
239+
<xs:unique name="ShipHull_unique_attribute">
240+
<xs:selector xpath="ShipHull"/>
241+
<xs:field xpath="@id"/>
242+
</xs:unique>
243+
</xs:element>
244+
</xs:schema>
245+
```
246+
247+
In the schema above, we can see that "xs:unique" elements tells us which attributes of the element specified with "xpath" are unique. For example lets look at:
248+
249+
```xml
250+
<!-- id attribute is unique for ShipHull -->
251+
<xs:unique name="ShipHull_unique_attribute">
252+
<xs:selector xpath="ShipHull"/>
253+
<xs:field xpath="@id"/>
254+
</xs:unique>
255+
```
256+
257+
This tells that, "id" attribute of an element "ShipHull" is unique. So, the merging algorithm checks this "id" field while merging. There are also some annotations that can be seen in the xsd files such as:
258+
259+
```xml
260+
<xs:element name="AvailableSlots" maxOccurs="1">
261+
<xs:annotation>
262+
<xs:appinfo>
263+
<appSpecificNote>AlwaysPreferMerge</appSpecificNote>
264+
</xs:appinfo>
265+
</xs:annotation>
266+
```
267+
268+
This tells us that an "AvailableSlots" element occurs only once in its parent and when the algorithm encounters an "AvailableSlots" item while modifying the parent item with merging, it knows that the "AvailableSlots" present in the other element that we are trying to merge directly merges with the existing "AvailableSlots" without resulting in two "AvailableSlots" elements. Elements that are annotated like this do not have unique id's as well because we don't need one.

0 commit comments

Comments
 (0)