The Beginnings of Mod Support (Part 1 - Data)


NOTE: this post gets technical so if that kind of stuff bores you, or if you're scared of jargon, beware!

Sometimes, usually in moments of intense frustration, the question pops into my head:

What were you thinking, Noah?

Just now, I had that thought. What was I thinking when I decided that the prototype of Turk's Crawling Dungeon needed mod support? Surely this would be a Herculean task spattered with my blood and tears, spanning thousands of lines of tedious code. And for what benefit? On the surface of it, there was no benefit to myself, nor to the development of the game. Only the vaguely pleasing thought that someday, in the distant future, someone might deign to pry open my silly little dungeon crawler and hack together some custom content. Maybe add a weapon here, a monster there. In practical terms, however, I struggled to envision a use for adding mod support.

Still, I was getting stuck on the particularly frustrating job of refactoring, well, the majority of my code (curse my past self for being a mediocre programmer), and I needed something fresh to sink my teeth into. It couldn't hurt to tinker around a bit, see what ideas I could come up with.

Lots of Data

There's a lot of data getting passed around in Turk's Crawling Dungeon. Attack types, damage types, status effects, instance prefabs, part configurations, crafting properties, and on the list goes. Until the start of my little mod support adventure, all of these bits of data were hardcoded, embedded in the source code and irretrievable to any tinkerers (save the brave few that decompile the main DLL). The result was obnoxious: static instances of a type would be declared for use as "templates", cluttering up the parent class with dozens of definitions. Messy to look at, messier to extend.


Pictured above: An example of the clutter that would sit at the top of a class file.

Looking to other games for inspiration, I settled on the obvious solution: softcoding the data; extracting the template definitions into external, human-readable files that are loaded when the game starts up. By softcoding all my definitions, I've also made them easily accessible to modders. Fully editable, extensible, mix-and-matchable fun is now just a mouse click away!

Extracting the Data

Then came task of taking all my hardcoded definitions and kicking them to the curb - or more accurately, into their own .XML files.


Pictured above: an example of a data entry for the "Body Plan" type.

The result was pleasing to the eye and the soul - at least, my eye and my soul. Certainly it was a weight off my mind knowing that I wouldn't have to stuff my classes full of static definitions. I could also communicate a lot about the data I was representing through the structure of the file. Take the above example of the body plan definition "BaseHumanoid". Now, all quibbling aside about the word "Humanoid" being used to describe the body of an anthropomorphic blue cat, you have to admit it gets its point across. It's clearly a series of nested body parts; presumably, each "BodyPart" node is parented to the "BodyPart" node above it, with the torso being the natural origin point from which the body is built outwards. The plan has one name through which it can be accessed by the deserializer as well as by other definitions. Its attached body parts have two names, the first corresponding to the body part definition it's loading and the second being, well, the name that's displayed in-game for that particular body part. Very human-readable, indeed!

However, I rapidly encountered a dilemma: each of the extracted data types had their own unique structures; unique needs, wants, hopes, and desires. Deserializing them, or in other words, loading them at runtime, requires careful attention to the idiosyncrasies of each data type. As far as I know, there isn't a one-stop-shop for deserializing multiple types from a bunch of uniquely-formatted XML documents. I had to come up with my own solution.

Deserializing At Runtime

I needed a class that could deserialize all this XML data into its appropriate runtime type. Moreover, I needed the choice as to how these different data types were transcribed. As mentioned previously, different files needed to be read in their own unique way.


Pictured above: an attack template definition.

Compare the above attack definition to the previous body plan. Looks different, right? Attacks have, comparatively, more variables to define. I chose to represent these variables as child nodes rather than tack on a hundred attributes to the object node. The result is a representation of data that is (if slightly) dissimilar from the other.

To handle the task of loading the XML documents, I created the class DefinitionDeserializer. DefinitionDeserializer is an abstract class, meaning that it is meant to be derived from and share its functionality with said derivatives. First, the DefinitionDeserializer will get all the XML documents located within a given directory. From there, I passed the bulk of the deserializing to its derivative classes.

The BodyPlanDeserializer class is responsible for reading body plan definitions, and it derives from DefinitionDeserializer. It transcribes the body plan into an intermediate BodyPlanDefinition type; deriving from the base class Definition, BodyPlanDefinition is hereafter responsible for transcribing itself to the final type, the BodyPlanTemplate.

The purpose of the intermediate step - deserializing to a Definition - is to avoid a bit of confusion. Since body plans rely on body parts, the intermediate Definition is able to keep track of the body part relations/names until said body parts have been deserialized. If we were to deserialize directly into the BodyPlanTemplate, its required parts might not exist yet, resulting in an error when the game searches for an asset that hasn't been loaded.

An alternative method would be to establish a specific order in which data types are deserialized, ensuring that body parts come before their dependent body plans, but there is a danger. Dependencies, should they become circular (i.e. body parts rely on attacks which rely on effects which rely on body plans which rely on body parts) would break the sequence of deserialization. 

So, at the cost of a little more time and a little more effort, I settled for the extra step.

try-catch-FINALLY

Softcoding data has laid the groundwork for mod support in Turk's Crawling Dungeon. Now, to edit the game, one need only navigate to the Definitions folder and get hacking. Additionally, you can make a mod folder and have the game load definitions from there! I plan to externalize quite a few data types in the future, allowing for modders to influence the game in myriad ways.

At the onset of this journey, my thoughts on mod support were hesitant at best. As I raked through the classes I meant to serialize, I soured on the idea that mod support was a good idea at all for this project. What were you thinking, Noah? A project like this? Mods? Turns out I was thinking ahead! Now that the bulk of the work deserializing XML is over and done with, data is a lot nicer to add, edit, and extend. This process has given the data a proper home. Long gone are the days of staticky hardcoded clutter! This process has also given the data a purpose: to be extended and transmuted. To afford players the means with which to sculpt this game.

The work I've put into mod support thus far has been tremendously helpful in my ongoing quest to master game design. It's taught me a lot, and instilled in me just a teeny bit of confidence in my ability to solve (mildly, by professional standards) complex conundrums. As evidenced by my overlong explanation of how DefinitionDeserializer works, I'm a teeny bit proud of how far this project has come.

Thank you, from the bottom of my heart, everyone who's downloaded the game and kept up with development. It's still a small-scale, rinky-dink roguelike. It's buggy and the code is shot full of holes. It's untested and it's niche. But it's my baby, so I'll blog about it if I wanna!

The next modding devlog will be about the in-development Chamber Editor tool, which will allow modders to influence dungeon generation. Until then, thanks for reading, and have a good one!

Files

Prototype Build 0.0.2 (Windows) 27 MB
Mar 04, 2021

Get Turk's Crawling Dungeon

Leave a comment

Log in with itch.io to leave a comment.