Overview
An in-depth technical explanation of how Defender’s Quest DX’s atomic “multi-mod” system works.
Introduction
An “atomic” mod system is one that allows each mod to be treated as an individual “atom” that can be combined together according to certain rules. You might have noticed that the kind of content the editor is designed for is also the kind of content that easily mixes together.
In the original game, you could only have one mod active at a time. If you wanted to mix mods together, you’d have to compose them manually — a tedious, and error-prone process.
Now, you can mix several mods together.
From the in-game mod browser, one can select individual mods from the left, then arrange the order in which they load on the right. Pressing “play” restarts the game with all of the selected mods applied.
Let’s talk a bit about how it works.
First, let’s open the mod directory for “legendsofawesome”, which is the same mod described in the Level Editor tutorial.
“legendsofawesome” does exactly two things:
- Adds one new bonus battle
- Adds one new special item
But as we can see from the screenshot, there’s a lot more going on. The settings.xml file contains the game’s basic metadata, such as the title and description that show up on steam, whether the mod is publicly visible, etc. The icon.png file is what will be used as the mod’s preview image both in the in-game mod browser and on Steam. The rest are data files to be used in the game.
There are three special directories, /_append, /_merge, and the root directory (everything that’s not in /_append or /_merge, and isn’t icon.png, or _settings.xml).
root directory
This one’s the simplest. Anything that’s in the root directory will be treated as a *replacement* for the file of the same name in the base game. If multiple mods are loaded in succession, this behavior cascades.
Example:
Say have a file called foo.txt, which in the base game contains the text “Hello, World!”. We create a mod, “A” which provides it’s own foo.txt in the root directory, but with the content “Hi, World!”, and a mod, “B” which does the same except here it’s “Aloha, World!”.
The result of loading the two mods in the following configurations is:
- A foo.txt = “Hi, World!”
- B foo.txt = “Aloha, World!”
- A+B foo.txt = “Aloha, World!”
- B+A foo.txt = “Hi, World!”
So as you can see, load order matters, and the Defender’s Quest mod loader lets you control this.
It’s very easy to just open a base file, make some changes, and save a copy of that file to a mod folder. The only downside to this method is that copying an entire file to make one change can be overkill, as it requires frequent updates if the game later releases an update that changes other parts of that file.
The “legendsofawesome” mod uses the root folder to add some new files to the game, in this case, a new bonus battle:
The map looks like this (increased in scale 6x):
And the XML has this content:
This provides all the data necessary for a battle, but does nothing to actually register it anywhere the player can access it.
append directory
Files in the /_append folder are *partial* changes that are simply added to the *end* of the base file with the same name. Similarly to files in the root folder, this behavior cascades with multiple mods (and order likewise matters).
Currently, the /_append folder will only recognize text files, and has special rules for handling .tsv files (tables, mostly used for localization and human-readable text) and .xml files (generic game data).
The “legendsofawesome” mod uses this method to add references to the aforementioned bonus battle, so that it actually shows up in game. This requires modifying the bonus battle registry and the localization tables.
Example 1 (TSV):
The mod includes localization text so that the game can display the title and description of the new bonus battle. The American English (“en-US”) data is stored in `/_append/locales/en-US/maps.tsv`, for instance, and looks like this:
These two lines will now simply be added to the end of each of the base game’s maps.tsv files on a per-locale basis. If multiple mods are loaded together, each of which adds new TSV lines to the same file, the load order shouldn’t affect the game’s behavior. The exception is if several mods have lines with the exact same entry flag names (ie, “$LEGENDSOFAWESOME~AWESOMEINTRO_TITLE”). In this case, I believe the last entry will overwrite the previous one when it gets loaded in game.
Example 2 (XML):
Displaying the correct text is nice, but the new battle needs to actually be accessible from somewhere. The bonus registry file controls this, which the mod edits with the /_append/xml/bonus.xml file:
When XML data like this is appended, the game strips off the `<?xml>` header at the top as well as the `<data>` envelope tag that every Defender’s Quest XML file uses, and then inserts the rest of the contents at the very bottom of the base file with the same filename, right before the closing `</data>` tag.
This is an easy way to add new entries to simply-structured data files, such as the bonus battle registry.
Not all modifications are so easily expressed, however, which is where the _merge folder comes in handy.
merge directory
The /_merge folder is mainly meant for .xml files, and as the name implies, is means for merging new data into the middle of existing base files.
In this case, what the mod is doing is disabling the game’s “original graphics” mode. (Defender’s Quest DX includes new HD graphics as well as the original version’s old sprite art, but for the sake of reducing complexity, we wanted to disable this secondary data set by default for mods created with the official game editor. Advanced modders can of course use whatever settings they want).
To do this we need to change an existing tag in the game’s graphics.xml file, which controls various graphical settings. Here’s a snippet of the base file:
We want to change this tag:
To this:
It’d be easy enough just to copy the file to the root directory, make that change, and call it a day, but it’d be nice to just do the one change directly.
Here’s how it’s done:
This file contains both data *and* merge instructions. The <merge> child tag tells the mod loader what to do, and will not be included in the final data. The actual payload is just this:
The <merge> tag instructs the mod loader thus:
- Look for any tags with the same name as my parent (in this case, <mode>)
- Look within said tags for a “key” attribute (in this case, one named id)
- Check if the key’s value matches what I’m looking for (in this case, “sprites”)
As soon as it finds the first match, it stops and merges the payload with the specified tag. Any attributes will be added to the base tag (overwriting any existing attributes with the same name, which in this case changes values from “original,hd” to just “hd”, which is what we want). Furthermore, if the payload has child nodes, all of its children will be merged with the target tag as well.
.tsv files can be merged as well, but no logic needs to be supplied. In this case, the mod loader will look for any lines in the base file with the same flag names as those in the merge file, and replace them with the lines from the merge file.
collisions
Another important part in an atomic mod system is avoiding collisions — ideally we want everybody’s mods to play nicely together. Nothing stops modders from creating incompatible mods in theory, especially if they mess with the data directly, but we can put some “guard rails” on the editor to keep this from happening trivially.
The easiest way to do this is to guard the internal string id references with prefixes. For instance, if the player has a mod named “myproject” and creates a battle with the id “mylevel”, the actual reference id for the battle used internally is “myproject~mylevel”. If the player changes the battle’s id, or the project’s id, all of these references are likewise updated and cleaned up properly. When creating a mod, the level editor will check Steam for registered mod IDs and warn you if you try to use one that’s already taken. Little safeguards like this go a long way to preventing thorny issues before they happen and make sure that mods have a good chance of playing nicely together.