Ensrick Posted April 5, 2023 Share Posted April 5, 2023 (edited) 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 April 5, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
meganoth Posted April 5, 2023 Share Posted April 5, 2023 (edited) Please use the section "Discussion and Request" for modding questions. "Mods" is for announcing mods. Moved. Edited April 5, 2023 by meganoth (see edit history) Link to comment Share on other sites More sharing options...
BFT2020 Posted April 5, 2023 Share Posted April 5, 2023 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 More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 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 More sharing options...
BFT2020 Posted April 6, 2023 Share Posted April 6, 2023 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 More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 (edited) 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 April 6, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
khzmusik Posted April 6, 2023 Share Posted April 6, 2023 (edited) 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 April 6, 2023 by khzmusik (see edit history) Link to comment Share on other sites More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 (edited) 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 April 6, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 (edited) 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 April 6, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
doughphunghus Posted April 6, 2023 Share Posted April 6, 2023 (edited) 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 April 6, 2023 by doughphunghus (see edit history) Link to comment Share on other sites More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 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 More sharing options...
khzmusik Posted April 6, 2023 Share Posted April 6, 2023 (edited) 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 April 7, 2023 by khzmusik (see edit history) Link to comment Share on other sites More sharing options...
Ensrick Posted April 6, 2023 Author Share Posted April 6, 2023 (edited) 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 April 6, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
khzmusik Posted April 7, 2023 Share Posted April 7, 2023 (edited) 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 April 7, 2023 by khzmusik (see edit history) Link to comment Share on other sites More sharing options...
Ensrick Posted April 7, 2023 Author Share Posted April 7, 2023 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 More sharing options...
Ensrick Posted April 7, 2023 Author Share Posted April 7, 2023 (edited) 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. Edited April 7, 2023 by Ensrick (see edit history) Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now