Jump to content

I've been putting together a guide for using the modular mod system


Max_Caine

Recommended Posts

Ever since Chris asked if someone could write a guide to the modular mod system, I've been working away on such a guide. I've gotten to the point where I can start showing drafts of the more well-written bits, and I'd like to ask the modders on the forum if you'd be willing to look over what has been written so far and critique it for both writing style and technical accuracy. Please note this is not in it's final form - there's a lot more to write, especially about the more advanceed tools you can use and I'm shaky on a fair bit of the technical areas, so if you'd be willing to take a look and give some advice, I'd very much appreciate it!

EDIT: Credit to kabill, both for his advice and his examples, which I have shamelessly abused.

EDIT2: Credit to llunak, for his examples which I have also shamelessly abused.

---------------------------------------------------------------------------------------------------------

Official manual

All information in this guide is based upon the offical documentation for the Modular Mod System, which I encourage you to read. You can find it here, as well as tutotials with live examples of how various things work.

Introduction.

The most important - and the most difficult - coding concept to learn when modding Xenonauts is the modular mod system (MMS). The main game has numerous few files which are very likely to be changed each time a mod is created. Without a way to take modifications and add them in a structured way each mod would overwrite the work of all the other mods you want to add, so you wouldn't be able to run different mods together without a lot of careful preparation work. The MMS (created by llunak) provides this structure so the player can run as many mods as he likes!

So how does it work? Imagine the MMS as an automatic text editor. It takes the changes you've make and either adds, deletes or edits the appropriate files with those changes. You never have to alter the original files - the MMS does that for you. This leads to an important rule when coding with with the MMS.

When making a mod, only put the changes and modifications you want to see into the modded files as well as any header/footer information that is required.

Let's look at why this is so important. The strings.xml file almost always changes whenever a mod is made. This is because strings.xml has 90% of all the text in the game. Because it has 90% of the text it is a very, very long file. If you wanted to change a pop-up message,you would have to scroll through the whole file for the message to make the change. Every time Xenonauts is updated you would then have to scroll through the strings.xml file, check for any changes to the file and make do an update based on the new string.xml file. With the MMS, your changes are independant of the main files, so you don't have to do half as much work. The MMS will then take that changed message and automatically edit the appropriate bit of the original file to put the changed message in.

File Types and the Modular Mod System

There are four kinds of file the the MMS recognises.

Binaries. These include things artwork, spritesheets and executables. The MMS cannot alter these, but will use altered binaries in the place of the standard binaries..

Lua scripts. Xenonauts has quite a few scripts written in .lua to handle things like popups and the UI. The MMS cannot alter these, but will replace standard scripts with modified scripts.

XML Spreadsheets. Some of the files that Xenonauts uses (and these are usually the most popular files to mod!) are Excel Spreadhseets that are saved in an XML format which Xenonauts can understand. The MMS handles these in a very specific way, which I will go into later.

XML. Other files (which are also as popular, especially the weapons files) use proper XML markup. The MMS handles these in a different way to XML Spreadsheets, and these are the first we'll look at.

But first... error checking

The modular mod system outputs a log file you can check to see if you have any problems with mods. To activate the log file, run the xenonauts.exe file with a -modinfo switch. The log file is written to the /Goldhawk Interactive/Xenonauts/ folder inside your local application data folder.

XML

Most lines of code that you change will need to be marked using one of a family of special attributes which tells the MMS what is changed, and how to change it. These attributes are called MODMERGE attributes.

Parents, children, attributes and elements

I'm going to be using the phrase "parent", "child", "atttribute" and "element" a lot in this text, so it behoves me to explain what I mean. An element refers to a tag. <Armour> is an opening element. </Armour> is the closing element. An attribute is a value within that tag. Attributes consist of two parts, a name and a value. So in <Resistance kinetic="0"> , "Resistance" is the element, "kinetic" is an attribute of "Resistance" and "0" is the value of the attrbute.

A child is an element which is nested inside another element. So

<Armour><Resistance kinetic="0" energy="0" chemical="0" incendiary="0" /></Armour> 

As "Resistance" is nested inside "Armour", "Resistance" automatically becomes the child of "Armour". Children can have children of their own. So when I refer to a "child element", I mean the tag or tags which are nested inside each other.

The first tag is, ironically enough, MODMERGE. This instructs the MMS how it should insert your changes - whether it should add a new line, delete a line, edit some of the existing entry... it's the basic instruction to the MMS on how to proceed with your changes. The way MODMERGE works is by comparing the element you want to mod with the element in the game files, then it decides how to proceed. There are a variety of values you can give MODMERGE. These are:

"insert". This tells the MMS that the specific element and children should be inserted into the file. It's like adding a new line of code.

"replace". This tells the MMS the specific entry should replace the existing element and all children. Note that replace means that the elements involved are completely replaced with whatever you have written in. This means if you have incorrectly written the line of code, or missed something out, or if the line of code changes then your mod will break.

"replaceifexists". This tells the MMS to only replace the specific element and childrenif it already exists.

"delete". This tells the MMS to delete the specific element (this will also delete children)

"deleteall". This is a more wide-ranging delete that uses wider parameters (which we will get into when we discuss MODMERGEATTRIBUTE)

"update". This is similar to replace, but instead of deleting the element and replacing it with what you have written, the MMS scans the values of every attribute in the element and replaces those values with whatever you have written in the modified entry. This means that you don't need to write complete entries - you only need to write in the attributes which you want to change. Update will also do this for all children.

"updateifexists". This is like "replaceifexists".

"updateall". Like deleteall, this is a more wide-ranging update which uses wider parameters.

Now we know what MODMERGE does, let's take a look at some examples (as provided by kabill)

Let's say I wanted to change the armour level on basic armour. We're using a special armourcloth which can reflect bullets. If I wanted to use the replace function, I would have to write the entry like this:

<Armour MODMERGE="replace" name="armour.basic"<Resistance kinetic="20" energy="0" chemical="0" incendiary="0" /><VisualParams range="18" coneAngle="90" nightRangeBonus="0" /><PsionicDefence defence="0" points="0" degradation="0" /><Props moveSpeed="300" stairsMoveSpeed="180" vaultMoveSpeed="24" /></Armour>

Note that while I only want to change one value (kinetic) I have to write the whole entry. Because it replaces all of the entry, if I leave anything out that will no longer exist when the MMS does the replacement. In comparison, if I used update, the code would look something like this:

<Armour MODMERGE="update" name="armour.basic"><Resistance kinetic="20" energy="0" chemical="0" incendiary="0" /></Armour>

As you can see, I've only had to include what I need to change, and it doesn't potentially harm the existing file.

MODMERGE is useless by itself. The MMS does not automatically know which line of code you want to alter, delete or insert. Without any guidance from you it tries to find the appropriate line by looking at the element and matching it with an exact match. However, if there are multiple elements of the same type in the same file, it gets confused and as almost all moddable files have multiple elements of the same type it will get confused. Let me demonstrate. Our previous example modded basic armour. But in the armour file you also have jackal, wolf, buzzard , predator and sentinel armour. All the armour type have exactly the same element - <Armour></Armour>, so if I want to edit basic armour I have to tell the MMS what element I want to alter. You do this with MODMERGEATTRIBUTE.

MODMERGEATTRIBUTE tells the MMS which particular attribute of which particular element you want to use to match the line of code you've altered/written so it can be executed by MODMERGE. That's quite a mouthful, so let's go back to our previous example to show you how it works.

<Armour MODMERGE="update" name="armour.basic"><Resistance kinetic="20" energy="0" chemical="0" incendiary="0" /></Armour>

With this example, the MMS will try to match the code you've written by the element name, i.e. <Armour>, which is no good. So we have to specify a particular attribute within <Armour> to modify only basic armour. Like this:

<Armour MODMERGEATTRIBUTE="name" MODMERGE="update" name="armour.basic"><Resistance kinetic="20" energy="0" chemical="0" incendiary="0" /></Armour>

MODMERGEATTRIBUTE sets the matching attribute to "name". We then define "name" to be "armour.basic". The MMS will look for <Armour> tags which have the attribute name="armour.basic". As there is only one "armour.basic" in all the <Armour> tags, the MMS will execute MODMERGE's "update" on one <Armour> tag and all the children of that specific <Armour> tag.

MODMERGEATTRIBUTE is useful when looking for a specific entry, and you can use it when making wider-ranging changes. Let's say you wanted to increase the range of all non-special weapons. You could do this:

<Weapon MODMERGEATTRIBUTE="bulletType" MODMERGE="updateall" bulletType="normal" ><props range="30></Weapon>

The MMS will now look for all elements of <Weapon> with the defining attribute bulletType="normal" then update the range attribute of the child element <props> to 30. So all weapons now have a range of 30. If I had typed "update" rather than "updateall", it would only have updated the first element which matched bulletType in the list.

However, if you want the MMS to look for a specific value then alter that value, you have to use a different set tag, as any value with MODMERGEATTRIBUTE cannot be altered. MODMERGEATTRIBUTESET will alter the value of the MODMERGEATTRIBUTE. So if you wanted to alter the visual parameter for all armour types so you could have a longer-ranged battle, you would have to do the following:

<Armour MODMERGE="updateall">	<VisualParams MODMERGEATTRIBUTE="range" MODMERGE="updateall" MODMERGEATTRIBUTESET="25" range="18" /></Armour>

The MMS will now look for all <VisualParams> elements with the attribute range=18, then update it with the set attribute of 25.

Advanced techniques

MODMERGE and MODMERGEATTRIBUTE cover roughly 90% of modding with pure XML files. There are certain XML files which need a different approach, and these files have their own attributes.

MODMERGECHILD and MODMERGECHILDCONTENT

There are certain XML files which feature parents that have no attributes or content and have children which have no attributes and only content. A good example one llunak uses is cities.xml.

  <City>   <Name>London</Name>   <Country>United Kingdom</Country>   <Population>3271110</Population>   <Location>4104,0875</Location>   <PopupDistance>0.4</PopupDistance>   <DetailDistance>0.7</DetailDistance> </City>

There are no attributes to match MODMERGEATTRIBUTE against, and as every <city> element is like this if you want to mod a specific entry you need a specialised tool for matching specific element which would be MODMERGECHILD/MODMERGECHILDCONTENT. These two attributes work in tandem. MODMERGECHILD sets a specific child element, MODMERGECHILDCONTENT then specifies which content in the child element to look for. To make this more clear if we go back to our example:

  <City MODMERGECHILD="Name" MODMERGECHILDCONTENT="London">   <Name>London</Name>   <Country>United Kingdom</Country>   <Population>3271110</Population>   <Location>4104,0875</Location>   <PopupDistance>0.4</PopupDistance>   <DetailDistance>0.7</DetailDistance> </City>

MODMERGECHILD sets the child element to look in, which is <Name>. MODMERGECHILDCONTENT then sets the content to look for, which is "London". If <Name> had attributes, this would be the same as

<City> <Name MODMERGEATTRIBUTE="name" name="London"/></City>

If you wanted to change some details of London, you would do it like this:

<City MODMERGECHILD="Name" MODMERGECHILDCONTENT="London">   <Name>Londinium</Name>   <Country>Britannica</Country>   <Population>10000</Population> </City>

There you go, London did the time warp. Note there is no need to use MODMERGE="update" . It will in fact confuse the MMS if you do use update.

MODMERGEINDEX

Another sub-type of file, similar to the one listed is a sub-type where you have a list ofelements presented in a specific order where certain elements are copies of each other. You can see this in submap design. Here's a sample from the artic submaps, small_lab_3.xml

   <level>       <row>           <cell />           <cell />           <cell />           <cell />           <cell />           <cell />           <cell />           <cell />           <cell />       </row>       <row>           <cell />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-07" layer1="arctic/modularbuilding/red_scheme/wall_nw" layer1_1="arctic/modularbuilding/red_scheme/wall_sw" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-08" layer1="arctic/modularbuilding/red_scheme/wall_nw" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-09" layer1="arctic/modularbuilding/red_scheme/door_nw" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-10" layer1="arctic/modularbuilding/red_scheme/wall_nw" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-11" layer1="arctic/modularbuilding/red_scheme/window_nw" layer1_1="arctic/copies/base/laboratory/lab_table_c_1" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-12" layer1="arctic/modularbuilding/red_scheme/window_nw" />           <cell layer0="arctic/copies/industrial/ground/warehouse15/Warehouse-01-13" layer1="arctic/modularbuilding/red_scheme/wall_ne" layer1_1="arctic/modularbuilding/red_scheme/wall_nw" layer1_2="arctic/copies/base/storeroom/shelves_1x1_4" />           <cell />       </row>

Let's say you want to edit a specific line from that. Thankfully, each ground tile (layer 0) has a unique identifier so it's possible to use MODMERGEATTRIBUTE. But supposing it didn't? Suppose the submap was only concerned with, say, fences. From the same artic mapset.

<tilemap name="fence_B5_1" width="5" height="1" version="1">   <level>       <row>           <cell layer1="arctic/fence/FenceSE1" />           <cell layer1="arctic/fence/FenceSE2" />           <cell layer1="arctic/fence/FenceSE1" />           <cell layer1="arctic/fence/FenceSE3" />           <cell layer1="arctic/fence/FenceSE2" />       </row>   </level></tilemap>

How would the MMS know which one to select? It's clear that each line represents a certain position on the submap, but the MMS doesn't know that. All it can see is the same attribute value used twice. MODMERGEATTRIBUTE won't help here, nor will setting the child content. It would be useful to be able to select a specific line from the list of elements, and you can do this with MODMERGEINDEX.

MODMERGEINDEX tells the MMS to count through all of a particular <element> (starting from the start of the file) and select the X-th element as a matching element. The element can be either a child or a parent - it doesn't matter. Once you set MODMERGEINDEX to a particular element, it will count those elements only. To show you what I mean, take the above example. If I wanted to change the fifth <cell> element so it's attribute layer1 was equal to arctic/fence/FenceSE1 instead of arctic/fence/FenceSE2, I would do the following:

<tilemap name="fence_B5_1" width="5" height="1" version="1">   <level>       <row>           <cell MODMERGEINDEX="5" MODMERGE="update" layer1="arctic/fence/FenceSE1" />       </row>   </level></tilemap>

MODMERGEINDEX will count through all <cell> elements until it reaches the 5th <cell> element. It will then perform an update on the layer1 attribute. Here's another example:

<Armour MODMERGEINDEX="2" MODMERGE="update" ><Resistance kinetic="20" energy="0" chemical="0" incendiary="0" /></Armour>

In this example, MODMERGEINDEX counts through to the second <Armour> element (armour.jackal) and adjusts all the resistance properties. However this is strongly discouraged. MODMERGEINDEX is only really useful when you have values which are set in a fixed structure, as in a structure which you are unlikely to add, subtract or move elements around. A good example of a fixed structure would be the <tilemap> example or a prop spectre where all the elements are in a neat order which will not change. Bad examples are files like weapons_gc or armour_gc which are very likely to have things added, removed and moved around, especially if a player is running several mods at once.

Edited by Max_Caine
Link to comment
Share on other sites

That's pretty usefull, but i have a question. I used modmerge as described but it dosen't seems to work. I tried different combinaition of options and still no effects in game. Here's the current code :

<?xml version="1.0" ?><Config><LOS MODMERGE="update">	<LOSAngle>180</LOSAngle>	<DefaultViewRange>50</DefaultViewRange></LOS></Config>

The tutorial tells you how to update elements with attributes and names but these don't have any. How should it be handled?

Thanks

Link to comment
Share on other sites

I'm not going to sticky this until I have included everything and everything works as advertised. llunak has put in some powerful tools, but most mods don't take advantage of them so I'm having to experiment with them to make sure that what I say works, works!

PTC. I believe you're going to have to use a subset of commands that I haven't covered yet, which, and I warn you I only have little experience of using. You'll need to use MODMERGECHILD and MODMERGECHILDCONTENT. MODMERGECHILD sets a specific child element to be altered, MODMERGECHILDCONTENT specifies the specific content of the element so the MMS can match the element to be altered. As you're an experienced C/C++ developer, you might make more use of llunak's documentation as it's more geared towards developers. llunak's documentation

EDIT: Just remembered - the way you've written it there wouldn't work anyway. "update" only works on attributes and direct content, not the direct content of child elements. One of the many things I will have to alter before the document can be considered to be final.

EDIT 2: kabill has helpfully pointed out in another thread that what you're trying to do won't work anyway - vision is not set in config.

Edited by Max_Caine
Link to comment
Share on other sites

I don't understand. The armour.basic example is word-for-word the example kabill gave me. I will work it out. But I have spent 12 joyless, exhausting hours at work. I've just woken up from collapsing in bed, and I have to go back to bed to get enough sleep to get back to work tomorrow. Thankfully this text is a work in progress, so things like this are good for showing that the examples and the guide need more work. At the moment the only thing I can think to suggest to force it to work is to introduce another MODMERGEATTRIBUTE/MODMERGE line in <VisualParams>, so it would be somehting like:

<Armours>

<Armour MODMERGEATTRIBUTE="name" MODMERGE="update" name="armour.jackal">

<VisualParams MODMERGEATTRIBUTE="range" MODMERGE="update" MODMERGEATTRIBUTESET="30" range="18" coneAngle="90" nightRangeBonus="0" />

</Armour>

</Armours>

But you shouldn't have to do that - it should do that automatically.

Incidently, my visualparams example doesn't quite work. I figured that one out yesterday. I have since updated the guide. I forgot that <Armour> as the parent also needs to be set if I'm not referencing it directly.

Link to comment
Share on other sites

Edit: Fixed, see at the bottom

I tried the -modinfo to look at the compilation log and everything went fine :

Wed Nov 19 16:46:29 2014: MOD INFO: Replacing XML for armours_gc.xml from mod mods/xce/ .Wed Nov 19 16:46:29 2014: MOD INFO: Replacing ok.Wed Nov 19 16:46:29 2014: MOD INFO: Merging XML for armours_gc.xml from mod mods/dynamicfirefight/ .Wed Nov 19 16:46:29 2014: MOD INFO: Merging element Armours .Wed Nov 19 16:46:29 2014: MOD INFO: Merging element Armour .Wed Nov 19 16:46:29 2014: MOD INFO: Updating element VisualParams .Wed Nov 19 16:46:29 2014: MOD INFO: Merge ok.

So it's kind of wierd that i still can't see the effect.

Off topic:

You should use the code beacons when writting code in a post, it makes it way easier to read. It's the # button on the edit panel.

I left a message in your drone strike topic about a smal bug i found, might wanna look at it.

Update:

I added the basic armor to the list and started a new game. The view range update worked, so i don't know if it's because they are in basic armor or because i started a new game.

Update2:

It really was because i was already in a crash site. Leaving and taking another one fixed the range. Here's my current code.

<?xml version="1.0" ?> <Armours> <Armour MODMERGEATTRIBUTE="name" MODMERGE="update" name="armour.basic">   <VisualParams range="50" coneAngle="90" nightRangeBonus="0" /> </Armour> <Armour MODMERGEATTRIBUTE="name" MODMERGE="update" name="armour.jackal">   <VisualParams range="50" coneAngle="90" nightRangeBonus="0" /> </Armour> <VehicleArmour MODMERGEATTRIBUTE="name" MODMERGE="update" name="HUNTER_ARMOUR">   <VisualParams range="50" coneAngle="90" nightRangeBonus="0" /> </VehicleArmour></Armours>
Edited by Part time commy
Link to comment
Share on other sites

Update: Now added MODMERGECHILD and MODMERGECHILDCONTENT. @PTC, I did check it out, I will do a fix, but not at the moment.

EDIT: Guys, I really would appreciate any kind of criticism, beyond "good" or "bad". I know this guide is taking ages to come out, but I'm doing this in the spare time I have which has not been frequent.

Edited by Max_Caine
Link to comment
Share on other sites

  • I think you should link to my documentation. It's the official documentation for this after all, even if this intro may be easier for people new to this, they may sooner or later need to know some exact detail. Also, the documentation points to a tutorial composed of example mods, which should be useful as yet another source of information.
  • I do not see mention of -modinfo anywhere, which should be probably the most important tool when creating something using the modular mod system.
  • Using armours_gc.xml for examples might have been a problematic choice, because if somebody actually decides to try out those, some of them will not work because of the lack of MODMERGENAME, IMO that should be more stressed in the text.
  • Otherwise looks good to me, from just reading.

(I also think these things are a much better fit for a wiki than a forum ...)

Link to comment
Share on other sites

  • 3 months later...

Hey,

I'm new to this stuff, and the guide clarified a few concepts. I had been through the doc already, but it was kind of dense and I needed a softer approach, which your guide provides.

This is really helpful, and should most definitely be a sticky. IMO

Thx for your hard work.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...