• If you have a mod, tool or prefab, please use the Resources section. Click Mods at the top of the forums.

XPath Modding Explanation Thread

This is a large amount of information in these posts. I'll be working on a full, properly formed tutorial as we get access to A17 and we can really make the xpath system shine. I've listed some examples here, and we'll be posting a ton more xpath samples over the coming weeks, as we port the SDX modlets over to use the native hooks.

By all means, begin posting your comments, questions, or requests for clarity.

Since we now know that xpath support will be built-into the vanilla base game, and discussions earlier were getting a bit confusing, I've decided to make a new thread to try to demystify xpath and what it'll mean for mods and modders going forward in A17. The information in this thread has been pieced together from forum and discord discussions. They may not be 100% accurate, but I will update this thread when we know the full implementation.

XPath is a way of adding, changing, or removing XML lines and nodes, without directly editing the XML files. It allows you to apply patches to the XML files without manually editing the vanilla XML files.

SDX has had partial support for this since its initial release, and a lot of the SDX Modlets are already written this way. We will be using some of the SDX's xpath as examples in this thread, but SDX is not required to do this in A17 and onwards.

I believe in using examples to explain concept, so in the next few posts, you'll see a mixture of explanation, along with sample xpath code.

The following Mod structure is expected for A17 and beyond:

Code:
Mods/
   <ModName>/
       Config/
       UIAtlases/ItemIconAtlas/
       ModInfo.xml

   <ModName2>/
       Config/
       ModInfo.xml
You will be able to have multiple mods loaded, and loading will occur as long as the ModInfo.xml exists. This is unchanged from what we've been doing doing with other alphas, with the exception of the Config folder.

This Config folder will support loading XML files, written using xpath, in the following folder structure:

Code:
Config/entityclasses.xml
Config/gamestages.xml
Config/items.xml
The files in the Config folder will not be a full copy of the vanilla file with your changes. Rather, it'll contain the changes you want done to the vanilla files. The files under the Config must match the vanilla file name. You cannot create an entityclasses2.xml file, and expect it to work. Any changes in the entityclasses.xml will have to be done in the same file. However, each mod can have its own entityclasses.xml.

During the game's initialization, it will perform the xpath merge in-memory only; no files will be actually be modified. This would allow us to remove mods we no longer want, without re-validating against steam, or previous copies of the xml. That's a big one. No more half merging of a mod, and not having it work, then trying to pull it back out.

What this means for us is that we'll be able to make a variety of smaller mods, which I've been calling modlets, which can add, remove and change smaller pieces of the game. They can be used together, or they could be added to an overhaul mod, in order to style your game play easier.

These modlets would also exists outside of the Data/Config folder, so if you have made direct XML changes in your Alpha 17.1 Data/Config files, and Steam updated the game to 17.2, you would have lost your changes, or would have to re-merge them in. We've all been there before. But if they existed as modlets, under the Mods folder, they would be safe. And as long as your xpath is still valid to the new XML, it should load up with no additional work on your part.

If we could use xyth's JunkItems modlet, which adds random, scrappable junk items to loot containers, and add them to Valmod Overhaul. Likewise, if Valmod Overhaul did not have the No Ammo modlet (which gives you the ability to unload a gun and get its bullets back without disassembling it), you could drop the NoAmmo modlet into your Mods folder. Headshots only? Want to increase stack sizes? Same deal.

With a properly constructed modlet, we'll be able to piece together new play styles for people to enjoy and share. A modder working on a large overhaul won't have to duplicate work. If they wanted to include the No Ammo mod, they wouldn't have to code it themselves, letting them focus on the bits that make their mod really unique.

Let's get started on your journey...

 
Last edited by a moderator:
What's wrong with this? The game tells me something starts with an illegal character.

<7DTDmod>

<set xpath="/recipes/recipe[@name=foodMeatStew]/ingredient[@name=foodRawMeat]/@count">5</set>

</7DTDmod>
I don't know if the game engine is using a standard XML document model, but normally element names can't start with a number. The have to start with a letter or an underscore. So, "7DTDmod" isn't a legal element name.

 
I don't know if the game engine is using a standard XML document model, but normally element names can't start with a number. The have to start with a letter or an underscore. So, "7DTDmod" isn't a legal element name.
That would explain why when I pasted the <set path...> line into my existing modded files which have a <Better...> element name it worked.

 
I don't know if the game engine is using a standard XML document model, but normally element names can't start with a number. The have to start with a letter or an underscore. So, "7DTDmod" isn't a legal element name.
Good eye. I think that's the issue here.

The root node can really nearly anything, other than numbers.

 
Looking to change up the progression of the game, basically what I want to do is change when certain levels of perks become available.

Example from:

Code:
Perception
1 level requirement - 1
2 level requirement - 1
3 level requirement - 1
4 level requirement - 20
5 level requirement - 30
6 level requirement - 40
7 level requirement - 50
etc.
Example to:

Code:
Perception
1 level requirement - 1
2 level requirement - 5
3 level requirement - 10
4 level requirement - 15
5 level requirement - 20
etc.
I did similar things to this with a16 but since a17 has moved to xpath I don't really know how to do a whole lot.

 
Can anyone help, i'm trying to reduce the number of opened lootable items but it is not liking the ends-with?
Thanks

2018-11-27T21:21:34 12.869 ERR XML loader: Patching 'blockplaceholders.xml' from mod 'Better Balance Mod' failed

2018-11-27T21:21:34 12.869 EXC function ends-with not found

Code:
<!-- Reduce 'opened' lootable items -->
<set xpath="/blockplaceholders/*/block[ends-with(@name, 'Open')]/@prob">.8</set>
<set xpath="/blockplaceholders/*/block[ends-with(@name, 'Closed')]/@prob">.2</set>
This sounds like a bug in the implementation.

I'll report it.

Until then, would contains() give you enough uniqueness to make your changes?

- - - Updated - - -

Looking to change up the progression of the game, basically what I want to do is change when certain levels of perks become available.
Example from:

Code:
Perception
1 level requirement - 1
2 level requirement - 1
3 level requirement - 1
4 level requirement - 20
5 level requirement - 30
6 level requirement - 40
7 level requirement - 50
etc.
Example to:

Code:
Perception
1 level requirement - 1
2 level requirement - 5
3 level requirement - 10
4 level requirement - 15
5 level requirement - 20
etc.
I did similar things to this with a16 but since a17 has moved to xpath I don't really know how to do a whole lot.
If you show us the XML you want to change, then we can show you how to reference it.

 
Sorry, didn't think the actual xml was required. I want to change this:

Code:
<attribute name="attPerception" name_key="attPerceptionName" desc_key="attPerceptionDesc" icon="ui_game_symbol_stealth">
<level_requirements level="1"><requirement name="PlayerLevel" target="self" operation="GTE" value="1" desc_key="reqGenericLevel01"/></level_requirements>
<level_requirements level="2"><requirement name="PlayerLevel" target="self" operation="GTE" value="1" desc_key="reqGenericLevel02"/></level_requirements>
<level_requirements level="3"><requirement name="PlayerLevel" target="self" operation="GTE" value="1" desc_key="reqGenericLevel03"/></level_requirements>
<level_requirements level="4"><requirement name="PlayerLevel" target="self" operation="GTE" value="20" desc_key="reqGenericLevel04"/></level_requirements>
<level_requirements level="5"><requirement name="PlayerLevel" target="self" operation="GTE" value="30" desc_key="reqGenericLevel05"/></level_requirements>
<level_requirements level="6"><requirement name="PlayerLevel" target="self" operation="GTE" value="40" desc_key="reqGenericLevel06"/></level_requirements>
<level_requirements level="7"><requirement name="PlayerLevel" target="self" operation="GTE" value="50" desc_key="reqGenericLevel07"/></level_requirements>
<level_requirements level="8"><requirement name="PlayerLevel" target="self" operation="GTE" value="60" desc_key="reqGenericLevel08"/></level_requirements>
<level_requirements level="9"><requirement name="PlayerLevel" target="self" operation="GTE" value="80" desc_key="reqGenericLevel09"/></level_requirements>
<level_requirements level="10"><requirement name="PlayerLevel" target="self" operation="GTE" value="100" desc_key="reqGenericLevel10"/></level_requirements>
To this:

Code:
<attribute name="attPerception" name_key="attPerceptionName" desc_key="attPerceptionDesc" icon="ui_game_symbol_stealth">
<level_requirements level="1"><requirement name="PlayerLevel" target="self" operation="GTE" value="1" desc_key="reqGenericLevel01"/></level_requirements>
<level_requirements level="2"><requirement name="PlayerLevel" target="self" operation="GTE" value="5" desc_key="reqGenericLevel02"/></level_requirements>
<level_requirements level="3"><requirement name="PlayerLevel" target="self" operation="GTE" value="10" desc_key="reqGenericLevel03"/></level_requirements>
<level_requirements level="4"><requirement name="PlayerLevel" target="self" operation="GTE" value="15" desc_key="reqGenericLevel04"/></level_requirements>
<level_requirements level="5"><requirement name="PlayerLevel" target="self" operation="GTE" value="20" desc_key="reqGenericLevel05"/></level_requirements>
<level_requirements level="6"><requirement name="PlayerLevel" target="self" operation="GTE" value="25" desc_key="reqGenericLevel06"/></level_requirements>
<level_requirements level="7"><requirement name="PlayerLevel" target="self" operation="GTE" value="30" desc_key="reqGenericLevel07"/></level_requirements>
<level_requirements level="8"><requirement name="PlayerLevel" target="self" operation="GTE" value="35" desc_key="reqGenericLevel08"/></level_requirements>
<level_requirements level="9"><requirement name="PlayerLevel" target="self" operation="GTE" value="40" desc_key="reqGenericLevel09"/></level_requirements>
<level_requirements level="10"><requirement name="PlayerLevel" target="self" operation="GTE" value="45" desc_key="reqGenericLevel10"/></level_requirements>
I suppose while I'm on this I take it adding additional levels to it would be easy as well? So what I mean is adding this to the end of the code:

Code:
<level_requirements level="11"><requirement name="PlayerLevel" target="self" operation="GTE" value="50" desc_key="reqGenericLevel10"/></level_requirements>
 
Sorry, didn't think the actual xml was required. I want to change this:
<snip>
Here's the code from my level changes you could tweak;

Code:
	<!-- lower level requirements on progression -->
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='4']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">8</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='5']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">16</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='6']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">24</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='7']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">32</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='8']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">40</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='9']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">50</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='10']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">60</set>
 
Last edited by a moderator:
This sounds like a bug in the implementation.
I'll report it.

Until then, would contains() give you enough uniqueness to make your changes?
Thanks sphereii, in the end i extracted the names i wanted and added them line by line. I'll revisit if it turns out the end-with is bugged once fixed.

 
Here's the code from my level changes you could tweak;

Code:
	<!-- lower level requirements on progression -->
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='4']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">8</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='5']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">16</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='6']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">24</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='7']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">32</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='8']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">40</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='9']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">50</set>
	<set xpath="/progression/attributes/attribute[starts-with(@name, 'att')]/level_requirements[@level='10']/requirement[@name='PlayerLevel' and @operation='GTE']/@value">60</set>
That sets me in the right direction, thanks

 
So I keep getting warnings and apply not set, could someone tell me what is wrong with this code?

Code:
<configs>
<set xpath="/entityclasses/entity_class[@name='DroppedLootContainer']/property[@name='TimeStayAfterDeath']/@value">1800</set>
</configs>
Trying to change from

Code:
<entity_class name="DroppedLootContainer"> <!-- used after a container block is destroyed -->
<property name="Mesh" value="LootContainers/backpack_droppedPrefab"/>
<property name="ModelType" value="Custom"/>
<property name="Prefab" value="Backpack"/>
<property name="Class" value="EntityLootContainer"/>
<property name="Parent" value="Backpack"/>
<property name="TimeStayAfterDeath" value="60"/>
<property name="IsEnemyEntity" value="false"/>
<property name="LootListOnDeath" value="4"/> <!-- used to determine container X/Y size -->
<property name="Faction" value="none"/>
</entity_class>
To this.

Code:
<entity_class name="DroppedLootContainer"> <!-- used after a container block is destroyed -->
<property name="Mesh" value="LootContainers/backpack_droppedPrefab"/>
<property name="ModelType" value="Custom"/>
<property name="Prefab" value="Backpack"/>
<property name="Class" value="EntityLootContainer"/>
<property name="Parent" value="Backpack"/>
<property name="TimeStayAfterDeath" value="1800"/>
<property name="IsEnemyEntity" value="false"/>
<property name="LootListOnDeath" value="4"/> <!-- used to determine container X/Y size -->
<property name="Faction" value="none"/>
</entity_class>
 
So I keep getting warnings and apply not set, could someone tell me what is wrong with this code?

Code:
<configs>
<set xpath="/entityclasses/entity_class[@name='DroppedLootContainer']/property[@name='TimeStayAfterDeath']/@value">1800</set>
</configs>
You missed the underscore from the entity_classes.

From my mod;

Code:
<!-- Loot and player bag remain for longer -->
	<set xpath="/entity_classes/entity_class[@name='Backpack']/property[@name='TimeStayAfterDeath']/@value">3600</set>
	<set xpath="/entity_classes/entity_class[@name='DroppedLootContainer']/property[@name='TimeStayAfterDeath']/@value">240</set>
	<set xpath="/entity_classes/entity_class[@name='EntityLootContainerRegular']/property[@name='TimeStayAfterDeath']/@value">1200</set>
	<set xpath="/entity_classes/entity_class[@name='EntityLootContainerStrong']/property[@name='TimeStayAfterDeath']/@value">1200</set>
	<set xpath="/entity_classes/entity_class[@name='EntityLootContainerBoss']/property[@name='TimeStayAfterDeath']/@value">1200</set>
	<set xpath="/entity_classes/entity_class[@name='EntityLootContainerBandit']/property[@name='TimeStayAfterDeath']/@value">1200</set>
 
I don't know if the game engine is using a standard XML document model, but normally element names can't start with a number. The have to start with a letter or an underscore. So, "7DTDmod" isn't a legal element name.
Ha ha! How typical of me. Thank you for that.

 
I have done a lot of xml modding in the past, but I am new to this xpath method. I'm making a mod that speeds up character progression a little bit. Can someone tell me if I'm doing this right?

I wanted to change the XP curve from 1.0149 to 1.01, so I think this is what I want to do for progression.xml:

Code:
<configs>
<set xpath="/progression[@level]/@experience_multiplier">1.01</set>
</configs>
And I wanted to add a skill point reward for finding the trader, so I think this is what I want to do for quests.xml:

Code:
<configs>
<append xpath="/quests/quest[@id='quest_whiteRiverCitizen1']">
	<reward type="SkillPoints" value="1" />
</append>
</configs>
Edit: I tested these changes, and the extra point for finding the trader is definitely working. I can't tell about the change in the XP curve. My math isn't coming out right for the amount needed to go from lvl 2 to lvl 3.

 
Last edited by a moderator:
I'm trying to get rid of all gravel... I hate gravel... Will the following work?

Code:
<setattribute xpath="/worldgeneration/biomes/biome/subbiome/layers/layer/resource[@blockname='terrOreGravelPlusIron']" name="blockname">terrOreIron</set>
Basically a block to replace any "gravelpluswhatever' with orewhatever. Above is the iron example, I'll have more lines for the other resources, but the main issue is not knowing if I can change the attribute that I then want to override.

Edit: Okay I tested it and the above worked great.

 
Last edited by a moderator:
Do you need to do something special before you try to utilize something that you append with xpath?

I'm adding a new entity template here, and then trying to utilize it, but I get the following error. This is for my Friendly Animal mod.

Code:
2018-11-28T22:39:27 12.539 ERR XML loader: Loading and parsing 'entityclasses.xml' failed
2018-11-28T22:39:27 12.539 EXC Did not find 'extends' entity 'animalTemplateFriendly'
Exception: Did not find 'extends' entity 'animalTemplateFriendly'
 at EntityClassesFromXml.LoadEntityClasses (.XmlFile _xmlFile) [0x00000] in <filename unknown>:0 
 at WorldStaticData+<loadSingleXml>c__Iterator1.MoveNext () [0x00000] in <filename unknown>:0
That error happens when I do:

Code:
<configs>
<append xpath="/entity_classes">
       <entity_class name="animalTemplateFriendly">
       	<property name="Tags" value="animal"/>
               <!-- snip -->
       	<property name="MapIcon" value="ui_game_symbol_bullet_point"/>
       </entity_class>
</append>

<!-- animalBear -->
   <set xpath="/entity_classes/entity_class[@name='animalBear']/@extends">animalTemplateFriendly</set>
</configs>
The moment I try to call the entity_class animalTemplateFriendly that I just added like there with animalBear, it bombs out with the above error.

Anyone have any idea why?

Thanks!

 
Do you need to do something special before you try to utilize something that you append with xpath?
I'm adding a new entity template here, and then trying to utilize it, but I get the following error. This is for my Friendly Animal mod.

Code:
2018-11-28T22:39:27 12.539 ERR XML loader: Loading and parsing 'entityclasses.xml' failed
2018-11-28T22:39:27 12.539 EXC Did not find 'extends' entity 'animalTemplateFriendly'
Exception: Did not find 'extends' entity 'animalTemplateFriendly'
 at EntityClassesFromXml.LoadEntityClasses (.XmlFile _xmlFile) [0x00000] in <filename unknown>:0 
 at WorldStaticData+<loadSingleXml>c__Iterator1.MoveNext () [0x00000] in <filename unknown>:0
That error happens when I do:

Code:
<configs>
<append xpath="/entity_classes">
       <entity_class name="animalTemplateFriendly">
       	<property name="Tags" value="animal"/>
               <!-- snip -->
       	<property name="MapIcon" value="ui_game_symbol_bullet_point"/>
       </entity_class>
</append>

<!-- animalBear -->
   <set xpath="/entity_classes/entity_class[@name='animalBear']/@extends">animalTemplateFriendly</set>
</configs>
The moment I try to call the entity_class animalTemplateFriendly that I just added like there with animalBear, it bombs out with the above error.

Anyone have any idea why?

Thanks!
That happens because the animalBear probably appears before the animalTemplateFriendly.

Instead of an append, which puts your animalTemplateFriendly at the bottom of the entityclasses.xml, try an insertBefore

Code:
<insertBefore xpath="/entity_classes/entity_class[@name='animalBear']" >
    <entity_class name="animalTemplateFriendly" >
               <!-- snip -->
   </entity_class>
</insertBefore>
- - - Updated - - -

I'm trying to get rid of all gravel... I hate gravel... Will the following work?
Code:
<setattribute xpath="/worldgeneration/biomes/biome/subbiome/layers/layer/resource[@blockname='terrOreGravelPlusIron']" name="blockname">terrOreIron</set>
Basically a block to replace any "gravelpluswhatever' with orewhatever. Above is the iron example, I'll have more lines for the other resources, but the main issue is not knowing if I can change the attribute that I then want to override.

Edit: Okay I tested it and the above worked great.
Interesting. Glad it worked. I don't think what you pasted would have been your final line though. It's probably something like:

Code:
<set xpath="/worldgeneration/biomes/biome/subbiome/layers/layer/resource[@blockname='terrOreGravelPlusIron']/@blockname">terrOreIron</set>
<Setattribute> can add a new attribute that does not exist. Maybe it updates the value if its found to exist. But your starting tag of <setattribute> wasn't matching your ending tag of </set>

 
@sphereii

Bro this is driving me crazy lol I am seeing in the posts some using <config> and others using <configs> which should we be using lol sorry want to make sure it's all in line.

Also I am not sure how to handle this and hoping you can tell me the best way to do this. I am adding about 95 new entities and and not really sure with the entitiesgroup.xml how should I take care of this? Here is what I mean, adding the new entities to each group easy.

The part I am not sure how to deal with this is alllllll the Sleeper generated and Horde generated codes at the bottom that HUGE LONG list. I mean I know I have to do it if I want the new entities in different game stages to appear properly. Would we really have to remove each and every single one of those or is there a faster way for that?

 
Back
Top