Jump to content

Help! Making a loot mod.


Ensrick

Recommended Posts

I'm familiar with tweaking the contents of XML files from various games, however, I want to take my changes and package them as a mod. To my understanding, this requires some "set xpath="..." method or I have to remove the nodes I want to modify and use a node like "<insertAfter/> with all of my modified changes.

 

So far, neither has worked without complications; and I don't like to remove existing nodes because there could compatibility problems with mods. The syntax for the XPath method evades me and with so many tiny changes it is extremely tedious and inefficient.

 

Here's what I'm doing to the loot.xml:

 

1. Adding a chance for items to have certain mods.

2. Adding chances for containers to contain higher amounts of loot.

3. Adding chances for containers to have more loot in their inventory slots than they might otherwise

 

For example:

1. Making the Beretta have a 15% chance to have a mod. (Also, this is just an example, but I want to do this for every item that can have mods)

<item name="gunHandgunT1Pistol" mods="barrelAttachments,sideAttachments,smallTopAttachments,scope,trigger" mod_chance=".15"/>

2.  In this example, just making the book piles that normally have 1-5 paper instead have 1 to 50, but at a reduced chance for the higher amounts.

<lootgroup name="groupBookPile01">
    <item name="resourcePaper" count="1,5" loot_prob_template="high"/>
    <item name="resourcePaper" count="5,15" loot_prob_template="medHigh"/>
    <item name="resourcePaper" count="15,25" loot_prob_template="med"/>
    <item name="resourcePaper" count="25,50" loot_prob_template="medLow"/>
</lootgroup>

3. In this example, I just want more books/schematics of various types, but at a lower probability for each additional book. The thing is, I want to modify almost every loot group to allow containers to have more loot in each slot, including more varied loot at a reduced chance for each additional slot having loot.

<lootgroup name="groupBookPile02">
    <item group="booksAllScaled"/>
</lootgroup>

<lootgroup name="groupBookPile" count="all">
    <item group="groupBookPile01" count="1"/>
    <item group="groupBookPile02" loot_prob_template="high" force_prob="true"/>
    <item group="groupBookPile02" loot_prob_template="medHigh" force_prob="true"/>
    <item group="groupBookPile02" loot_prob_template="med" force_prob="true"/>
    <item group="groupBookPile02" loot_prob_template="medLow" force_prob="true"/>
    <item group="groupBookPile02" loot_prob_template="low" force_prob="true"/>
    <item group="groupBookPile02" loot_prob_template="veryLow" force_prob="true"/>
</lootgroup>
Edited by Ensrick (see edit history)
Link to comment
Share on other sites

2 hours ago, Ensrick said:

I'm familiar with tweaking the contents of XML files from various games, however, I want to take my changes and package them as a mod. To my understanding, this requires some "set xpath="..." method or I have to remove the nodes I want to modify and use a node like "<insertAfter/> with all of my modified changes.

 

So far, neither has worked without complications; and I don't like to remove existing nodes because there could compatibility problems with mods. The syntax for the XPath method evades me and with so many tiny changes it is extremely tedious and inefficient.

 

 

Why not post what you have tried and we can examine it and explain why it is not working for you?

Link to comment
Share on other sites

The following are two examples that I've tried.

 

The first one does seem to work, but not without issues at times. I want to break down the loot mod I'm working on into modlets so that users can choose which ones they want. Removing things seems to cause conflicts with mods that use nodes. It could be I'm doing it wrong though.

 

I can use insertAfter successfully when not trying to replace existing nodes by removing them and that seems fine and doesn't seem to cause conflicts.

 

	<remove xpath="/lootcontainers/lootgroup[@name='groupScrapCommon']"/>
	
	<insertAfter xpath="/lootcontainers/lootqualitytemplates">
		<!-- Resources_Scrap -->
		<lootgroup name="groupScrapCommon" count="1">
			<item name="resourceFishingWeight" count ="1,9"/>
			<item name="resourceScrapLead" count ="1,40"/>
			<item name="drinkCanEmpty" count ="1,6"/>
			<item name="resourceScrapIron" count ="1,40"/>
		</lootgroup>
 	</insertAfter>

The second method here is the xpath override; slow and tedious for large changes, but I've had it work when I use it to tweak item properties. What I've tried here doesn't even work, and the error in the log indicates the way I've pathed it or the syntax is not accurate, but I'm not sure how to do it correctly.

 

Here's an example of how I'd use it to increase the ceiling on the number of fishing weights in a junk pile.

<set xpath="/lootcontainers/lootgroup[@name='resourceScrapCommon')]/item[contains(@name, 'resourceFishingWeight')]/@count">1,5</set>

 

 

Also, couple of other questions. There are many items for which I want to simply make a small change like 1.5 more maximum loot count for specific items without changing the minimum amount. Surely there must be a way to make sweeping changes like that.

Link to comment
Share on other sites

5 minutes ago, Ensrick said:

The following are two examples that I've tried.

 

The first one does seem to work, but not without issues at times. I want to break down the loot mod I'm working on into modlets so that users can choose which ones they want. Removing things seems to cause conflicts with mods that use nodes. It could be I'm doing it wrong though.

 

	<remove xpath="/lootcontainers/lootgroup[@name='groupScrapCommon']"/>
	
	<insertAfter xpath="/lootcontainers/lootqualitytemplates">
		<!-- Resources_Scrap -->
		<lootgroup name="groupScrapCommon" count="1">
			<item name="resourceFishingWeight" count ="1,9"/>
			<item name="resourceScrapLead" count ="1,40"/>
			<item name="drinkCanEmpty" count ="1,6"/>
			<item name="resourceScrapIron" count ="1,40"/>
		</lootgroup>
 	</insertAfter>

 

Change the insertAfter line to this

 

<insertAfter xpath="//lootgroup[@name='empty']">

 

That will properly put it inside the loot table.  What I expect you are seeing is the new group is being placed behind the first instance of lootqualitytemplates in the file (note the code will run top to bottom so the first condition it finds that meets it, it will add your code).

 

8 minutes ago, Ensrick said:

The second method here is the xpath override; slow and tedious for large changes, but I've had it work when I use it to tweak item properties. What I've tried here doesn't even work, and the error in the log indicates the way I've pathed it or the syntax is not accurate, but I'm not sure how to do it correctly.

 

Here's an example of how I'd use it to increase the ceiling on the number of fishing weights in a junk pile.

<set xpath="/lootcontainers/lootgroup[@name='resourceScrapCommon')]/item[contains(@name, 'resourceFishingWeight')]/@count">1,5</set>

 

 

Look at the lootgroup, you have a ) in there which is causing the error

 

12 minutes ago, Ensrick said:

Also, couple of other questions. There are many items for which I want to simply make a small change like 1.5 more maximum loot count for specific items without changing the minimum amount. Surely there must be a way to make sweeping changes like that.

 

Someone smarter than I might know of how to do this, probably setup a script to run,

Link to comment
Share on other sites

Unity uses C#, but I have more experience doing simple things in Java. I could make a method to do this:

using System.Xml;

// Load the loot.xml file
XmlDocument doc = new XmlDocument();
doc.Load("loot.xml");

// Find the item you want to modify (in this example,it is an item with a name attribute of "resourcePaper")
XmlNode itemNode = doc.SelectSingleNode("//item[@name='resourcePaper']");

if (itemNode != null)
{
    // Get the current count range
    string countRange = itemNode.Attributes["count"].Value;

    // Split the count range into min and max values
    string[] countValues = countRange.Split(',');
    int currentMinCount = int.Parse(countValues[0]);
    int currentMaxCount = int.Parse(countValues[1]);

    // Update the max count to be 3x its current amount, with a minimum value of 10
    int newMaxCount = UpdateCount(currentMaxCount, 10);

    // Update the count range attribute to the new values
    itemNode.Attributes["count"].Value = currentMinCount.ToString() + "," + newMaxCount.ToString();

    // Save the modified loot.xml file
    doc.Save("loot.xml");
}

 

But I have no idea how to make use of this within a script.

 

I'll try fixing my issue with the ')' you pointed out. That's silly of me from copy/pasting. I just don't prefer that method for large edits to the loot file. I think with Unity there is a way I could just name it loot_override.xml and copy/paste the loot.xml stuff and make my edits that way.

 

EDIT: the fixes you recommended worked. I was thinking, I might be able to create a little Java program or something to make sweeping changes to the XML files because it looks like it'll take me forever either way.

Edited by Ensrick (see edit history)
Link to comment
Share on other sites

55 minutes ago, Ensrick said:

Unity uses C#, but I have more experience doing simple things in Java. I could make a method to do this:

using System.Xml;

// Load the loot.xml file
XmlDocument doc = new XmlDocument();
doc.Load("loot.xml");

// Find the item you want to modify (in this example,it is an item with a name attribute of "resourcePaper")
XmlNode itemNode = doc.SelectSingleNode("//item[@name='resourcePaper']");

if (itemNode != null)
{
    // Get the current count range
    string countRange = itemNode.Attributes["count"].Value;

    // Split the count range into min and max values
    string[] countValues = countRange.Split(',');
    int currentMinCount = int.Parse(countValues[0]);
    int currentMaxCount = int.Parse(countValues[1]);

    // Update the max count to be 3x its current amount, with a minimum value of 10
    int newMaxCount = UpdateCount(currentMaxCount, 10);

    // Update the count range attribute to the new values
    itemNode.Attributes["count"].Value = currentMinCount.ToString() + "," + newMaxCount.ToString();

    // Save the modified loot.xml file
    doc.Save("loot.xml");
}

 

But I have no idea how to make use of this within a script.

 

I'll try fixing my issue with the ')' you pointed out. That's silly of me from copy/pasting. I just don't prefer that method for large edits to the loot file. I think with Unity there is a way I could just name it loot_override.xml and copy/paste the loot.xml stuff and make my edits that way.

 

You can't do what you're doing with that code.

 

Unity uses C# but that's not the issue. The issue is that 7D2D itself uses its own implementation of XPath, which uses XML as an XPath DSL. (If you're familiar with XSLT, it's similar to that.)

 

The basic format of an XPath command in 7D2D is this:

 

<command xpath="xpath expression">...data...</command>

 

...where "command" is an XPath command: "append", "insertAfter", "remove", etc. There is also no "SelectSingleNode" feature - the XPath expression always returns multiple nodes, so it's equivalent to calling "selectNodes" in dom4j.

 

There are no variables, and data is always a raw unparsed string - you could us an XML string when you should be using an attribute value, and the XPath engine wouldn't know nor care.

 

So there is no way to, say, "take the value of attribute X, parse it as a number, and add 1." There's no way to "take a value" (no variables); there's no way to "parse" (no data types except string); and there's no way to "add" (no numeric operations).

Edited by khzmusik (see edit history)
Link to comment
Share on other sites

Thanks, I've been trying to familiarize myself with XPath now that I know it is a query language. Though this is all new to me, I think there might still be a way I can create an expression that utilizes XPath hooks to accomplish most of what I'm trying to do without going line by line and making using an XPath command per each change I want to make.

 

I just tried this:

 

<modify xpath="/hook[@file='loot.xml']/lootgroup[@name='groupScrapCommon' or @name='groupScrapUncommon' or @name='groupScrapRare']//item[@count]" attribute="count" replace="substring-before(@count, ',') + ',' + concat(floor(substring-after(@count, ',') * 2 div 5) * 5, '')"/>

 

There were no errors, but I'd need to test more to see if it had the intended effect of multiplying the maximum loot count by 2 and rounding it down to the nearest 5. This is specifically meant to modify the lootgroup stacks for scrap. Is there a better way to test this, or do I just have to playtest by throwing massive numbers in there?

Edited by Ensrick (see edit history)
Link to comment
Share on other sites

I've found an XPath tester online to see if the path is correct, so I tried the following path:

 

//lootgroup[@name='groupScrapCommon' or @name='groupScrapUncommon' or @name='groupScrapRare']//item[@count]

 

and it works, but when I try to change an attribute using an xpath operation like so:

 

<modify xpath="//lootgroup//item[@count]" attribute="count" replace="format-number(number(@count) * 100, '###,###,###')"/>

 

it doesn't actually change the count, though it produces no error.

 

Is there a way I could find out what commands and operations are supported by 7D2D's XPath implementation?

Edited by Ensrick (see edit history)
Link to comment
Share on other sites

6 hours ago, Ensrick said:

Is there a way I could find out what commands and operations are supported by 7D2D's XPath implementation?

One i have used: yeah its old, so ignore anything not xpath related.


heres another with videos. Not explicitly xpath but some people like watching a video vs reading:

 

Edited by doughphunghus (see edit history)
Link to comment
Share on other sites

That's it?

  • append
  • set
  • replace
  • insertAfter
  • insertBefore
  • remove
  • removeAll
  • addIfMissing
  • setIfMissing
  • addIfNotPresent
  • removeIfPresent
  • removeIfAny
  • selectSingleNode
  • selectNodes

Well that's a bit more limiting than the functionality XPath would otherwise allow. I'll try making a Java program for parsing the game's XML files and outputting an XML for modding. For now, I'll just keep it simple by just having it modify the "count" attribute, but it'll be something I can build on.

Link to comment
Share on other sites

5 hours ago, Ensrick said:

That's it?

  • append
  • set
  • replace
  • insertAfter
  • insertBefore
  • remove
  • removeAll
  • addIfMissing
  • setIfMissing
  • addIfNotPresent
  • removeIfPresent
  • removeIfAny
  • selectSingleNode
  • selectNodes

 

Not even that. In your list, all the commands after "remove" are not available. This is the actual list:

  • set
  • setattribute
  • append
  • prepend
  • insertBefore
  • insertAfter
  • remove
  • removeattribute

(If you decompile the game's assembly-csharp.dll file, it's in the XmlPatcher class.)

 

But this is plenty. For example, here's how you could select any gun in the game, and if there is a chance for it to spawn with a mod, you set that chance to 0.5 (50%):

 

<set xpath="//item[starts-with(@name, 'gun')]/@mod_chance">.50</set>

 

For the paper, you could create a new group that chooses between the existing paper groups:

 

<insertAfter xpath="//lootgroup[@name='groupResourcePaperLarge']">
    <lootgroup name="groupResourcePaperAll">
        <item group="groupResourcePaperSmall" loot_prob_template="high" />
        <item group="groupResourcePaperMedium" loot_prob_template="med" />
        <item group="groupResourcePaperLarge" loot_prob_template="low" />
    </lootgroup>
</insertAfter>

 

Then, use that lootgroup instead of the "resourcePaper" item, by adding and removing the appropriate attributes:

 

<setattribute xpath="//lootgroup[not(contains(@name,'groupResourcePaper'))]/item[@name='resourcePaper']" name="group">groupResourcePaperLarge</setattribute>
<removeattribute xpath="//lootgroup[not(contains(@name,'groupResourcePaper'))]/item[@name='resourcePaper']/@count" />
<removeattribute xpath="//lootgroup[not(contains(@name,'groupResourcePaper'))]/item[@name='resourcePaper']/@name" />

 

...And so on.

 

EDIT: Also, if you want to try out your XPath expression on the game's XML files, then I recommend BaseX:
https://basex.org/

 

It's free and open source.

Edited by khzmusik (see edit history)
Link to comment
Share on other sites

That's still quite a bit of tedious work when I want to modify the entire loot system in several modlets among other modlets I want to make.

 

Even if it takes me a lot longer to make a program, I think I'd prefer the added flexibility it gives me and the value it might have to other users. I making a program that parses the XML, identifies the elements to modify, then outputs a new xml with the desired modifications. It's pretty crude so far but I managed to get a working output file that contains hundreds of lines like this one:

<set xpath="//item[@name='resourceFishingWeight']" attribute="count" value="format-number(number(@count) * 100, '###,###,###')"/>

For every item that has a count. It's not at all efficient yet; but I it wasn't to difficult to make a program that made a node full of these shows promise. I just need to refine it and add more functionality.

 

Of course, that line doesn't even work, but I just need to modify the StringBuilder strings.

Edited by Ensrick (see edit history)
Link to comment
Share on other sites

1 hour ago, Ensrick said:

That's still quite a bit of tedious work when I want to modify the entire loot system in several modlets among other modlets I want to make.

 

Even if it takes me a lot longer to make a program, I think I'd prefer the added flexibility it gives me and the value it might have to other users. I making a program that parses the XML, identifies the elements to modify, then outputs a new xml with the desired modifications. It's pretty crude so far but I managed to get a working output file that contains hundreds of lines like this one:

<set xpath="//item[@name='resourceFishingWeight']" attribute="count" value="format-number(number(@count) * 100, '###,###,###')"/>

For every item that has a count. It's not at all efficient yet; but I it wasn't to difficult to make a program that made a node full of these shows promise. I just need to refine it and add more functionality.

 

Of course, that line doesn't even work, but I just need to modify the StringBuilder strings.

 

So your program's output is supposed to be the XPath XML that you put into a modlet? That's an interesting way to do it.

 

FWIW, the reason that XML doesn't work is that the <set> tag only takes the "xpath" attribute. The value is the text/XML children of the <set> tag (and, hopefully obviously, it's not parsed for methods, which only work in XPath predicates). Also, you target attributes by putting them in the XPath expression; they're prepended by the "@" symbol.

 

Hopefully you didn't need me to tell you that, and that's just "placeholder" text for what your program is going to be doing itself.

 

EDIT: I'm also curious how it's going to deal with other modlets. Is it going to first apply all those modlets' XPath patches on the vanilla game XML config, then examine the result to see what needs to be modified?

Edited by khzmusik (see edit history)
Link to comment
Share on other sites

It's just placeholder text, the only thing that changed from line to line was which item in the @name='' by parsing the input XML and generating lines for every item with count attribute.

 

As of right now the idea for the program is to input an XML by choosing it from the file system and then it will change something simple to start like the count attribute, and output a bunch of XPath expressions to the mod XML. Eventually, when I get a UI up, the idea is to have user options as to what the user wants to change.

 

I'll figure what to do about other modlets once I have something better.

Link to comment
Share on other sites

	public static void updateLootXml(Document doc) {
	    XPathFactory xPathfactory = XPathFactory.newInstance();
	    XPath xpath = xPathfactory.newXPath();

	    try {
	        NodeList lootgroups = (NodeList) xpath.evaluate("//lootgroup[starts-with(@name, 'groupScrap')]", doc, XPathConstants.NODESET);
	        for (int i = 0; i < lootgroups.getLength(); i++) {
	            Node lootgroup = lootgroups.item(i);
	            NodeList items = (NodeList) xpath.evaluate("item[starts-with(@name, 'resource')]", lootgroup, XPathConstants.NODESET);
	            for (int j = 0; j < items.getLength(); j++) {
	                Node item = items.item(j);
	                String countStr = item.getAttributes().getNamedItem("count").getNodeValue().trim();
	                int minCount, maxCount;
	                if (countStr.contains(",")) {
	                    String[] counts = countStr.split(",");
	                    minCount = Integer.parseInt(counts[0].trim());
	                    maxCount = Integer.parseInt(counts[1].trim()) * 3;
	                } else {
	                    minCount = Integer.parseInt(countStr);
	                    maxCount = minCount * 3;
	                }
	                String xpathStr = String.format("//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='%s']/@count", item.getAttributes().getNamedItem("name").getNodeValue());
	                String cmdStr = String.format("<set xpath=\"%s\">%d, %d</set>", xpathStr, minCount, maxCount);
	                System.out.println(cmdStr);
	            }
	        }
	    } catch (XPathExpressionException e) {
	        e.printStackTrace();
	    }
	}

 

I made this little method which takes the XML assigned to a Document type variable and prints out XPath commands that take any loot item which starts with the the name "groupScrap" and changes the max count of all items within it max count to x3; and if it's count is not separated by a comma, as in it has a count of 2, then it makes the max count 3x that.

 

I should be able to build off of this method to make a program that outputs XPath commands that do all sorts of things for easily manipulating large amounts of existing items.

 

Here is the output:

<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceFishingWeight']/@count">1, 9</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceScrapLead']/@count">1, 60</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceScrapIron']/@count">1, 60</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceTrophy1']/@count">1, 15</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceTrophy2']/@count">1, 15</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceScrapBrass']/@count">40, 750</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceDoorKnob']/@count">1, 15</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceCandleStick']/@count">1, 6</set>
<set xpath="//lootgroup[starts-with(@name, 'groupScrap')]/item[@name='resourceRadiator']/@count">1, 6</set>

 

I got a simple UI to start.

 

Screenshot2023-04-07155944.thumb.jpg.26d18a130cf4f1ffa8440f91015b0bf8.jpg

Edited by Ensrick (see edit history)
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...