Mod Support


Lancer Tactics now has official mod support & a source-available modkit! For players, mods can now be downloaded through the Omninet browser alongside maps and modules, and then managed in a new Omninet tab:

There’s a lot to talk about here so it seems like a natural focus of this month’s update.

Background — how do Godot mods work?

Godot projects have an internal filesystem much like the one on your computer; a series of folders and files. The root is referred to as “res://”

However, to run the game Godot needs assets (images, music, etc) to be in a different format than you’d find with them sitting in your filesystem. It compiles these files into a big pile of files that look like this, all jumbled together in one folder:

To keep knowing where things are, it maintains a skeleton of the original filesystem with all the orignal files swapped out with “.remap” placeholders that just point to where in that big pile the asset they represent went. Furthermore, whenever the game attempts to load a specific asset at a location (e.g. “res://assets/icons/mech.svg”), it caches whatever it loaded into memory. Whenever we ask it to load that asset again, it doesn’t have to go ask the .remap file where the original one went and load it up — it just returns whatever it ended up loading from there last time. This cache is like a ghost sitting on top of a skeleton.

For modding, this gives us an opportunity. Instead of needing to actually copy in modded files and override the originals, we can manipulate this ghost-filesystem-cache by lying about what files are at what locations. Here’s what a mod looks like for Lancer Tactics (using a modified system from Godot Mod Loader):

We have a special folder where we put the actual mod files: “unpacked”, then another folder with the specific mod’s identifier “Wickworks-Sawhorse”. Within that, we have a “res” folder, and that’s where the lying starts. When loading up the mod, we can grab everything inside of the mod’s “res” and tell the ghost-cache that the mod’s files are the ones that are actually at that location. In this example, by the end of it when we ask Godot to load “res://assets/frames/horus/mf_sawhorse.png”, it will return the file that’s actually over in “res://unpacked/Wickworks-Sawhorse/res/assets/frames/horus/mf_sawhorse.png”.

Now that we have the power to insert files as we’d like, Godot Mod Loader additionally has some functions that through dark magic allows multiple mods to all alter the same script instead of overriding it. I’m a little fuzzy on these details, but the shape of it is that while loading each successive script that extends the same file actually extends the previous one that also extended that script so all the changes end up getting applied.

(though there is a bug in the Godot engine that’s currently preventing doing this dark magic to any file that has a class_name; a pretty friggin major restriction!! If a Godot engine dev is reading this, a fix would be much appreciated thank you!)

In order to change what mods are active, the out-of-the-box Godot Mod Loader addon expects you to restart the game so it can start work with a clean slate each time. For Lancer Tactics, I modified this so it tracks all files inserted so we can go and undo all of our lies and reset the ghost-cache to files’ original locations. This was important because I’m expecting people to want to be able to make maps and modules with bundled-in mods that substantially alter the content of the game, and requiring a restart whenever you load one up would be a terrible experience.

Speaking of bundling mods with other content…

Security Concerns

Mods are code that some rando wrote that the game downloads and then runs on your machine. Godot does not have built-in sandboxing for gdscript, so this code has just as much access as the game does, ie, can read or write any file on your computer and talk to the internet. Uh oh!

To be fair, this is a issue with most mods for most games. It’s a very trust-based at-your-own-risk sort of country. This is fine if mods aren’t tightly integrated, but as soon as you as a developer start removing barriers and letting people accidentally stumble into downloading harmful code by just pressing buttons in-game, you start taking on the responsibility in the case of something bad happening.

Slay The Spire 2, another game made in Godot, handles this by putting a big warning screen that it forces you to click through before you can start installing mods. We’ve done something similar:

Additionally, LT’s system distinguishes between three different types of mods:

  • Asset Pack: There’s no security concern if there’s no scripts. Mods that consist of only inert files (images, sound, json) will be allowed to be bundled with maps and modules and loaded without requiring a confirmation from the user. Assets are placed entirely implicitly by their location in the mod’s “res” subfolder. Good for things like additional portrait options or alternate mech tokens.
  • Content Pack: These can have scripts, and will likely only swap in files implicitly like asset packs, but do have the power to control where their files go if they have special needs, but are responsible for unspooling any nonstandard changes they make on being deactivated.
  • Substantial Mod: as above, but the changes are not cleanly reversible so require a game restart to turn off or on. Mods are in charge of telling LT if they’re a content pack or substantial; the security concern is equivalent.

Given an extra four months, I could probably add the option to script game content in a nice sandboxed environment by setting up an internal .lua API for new abilities to use. This would allow new mechs and whatnot to count as asset packs, but the time-benefit tradeoff isn’t favorable given our time and labor constraints. I have it filed away as possible avenue to explore after we make a million dollars on Steam and the security concerns become less theoretical.

Mod Tooling

So! That’s a lot of preamble. How does one actually make a mod?

First, you need to get your hands on the source of the game. It’s possible to decompile any game made in Godot which is what bootleg (affectionate) modders have been doing up til now, but we’re making the source available through official channels for convenience. Lancer’s TTRPG creator ecosystem is based on a belief that it should be hackable & extensible, and we want to carry that culture forward.

We’re talking to a lawyer to make sure we have all our hatches buttoned down legally to make sure we maintain ownership of the game code itself (and maybe like watermark some assets?), but once we get that squared away we’ll add the modkit alongside the rest of the downloads on itch. Until then, you can request early access to it on LT’s Discord server.

Once you get the modkit, there’s a modding_guide.txt that should give you the information you need to start making stuff:

And make the mods in the godot editor itself; the same view I have when making the game:

I’d love to establish way to start accumulating community knowledge about modding the game that’s not controlled by google/discord. Getting a wiki spun up seems like the obvious choice — it’s on my to-do list.

I’m a bit nervous opening the floodgates for modding because it’s a significant increase in “surface area” that I’ll have to maintain. Mods can cause crashes I don’t have control over, changes I make will break mods that modders don’t have control over, the tooling is another editor to make sure correctly syncs with data, and runtime juggling of all these files is delicate and impossible to set up automated testing for.

But we gotta pull the trigger sometime, so might as well be now to give us a long runway before Steam release to work out the kinks with the whole thing.

The rest of the changelog

To reduce copy+paste work that I have to do, I’m leaving the nitty-gritty of the changelog over on itch.

Points still worth keeping here:

  • I’ve bumped up the version number of the game from 0.7.x to 1.0.x. This doesn’t mean the game is out of early access or anything; popping this cherry is purely a matter of not breaking semantic compatibility with mods in the future. Read this for more about semantic versioning, but the short of it is that the first number bumping up indicates a major breaking change that requires mods to be updated. Up til now I’ve been definitely operating on pride versioning instead lol
  • Fixed downloading combats with empty names from mod.io; a few versions back there was a bug that meant all uploaded maps were named “.json” with no filename. This build should now be able to handle it when it downloads a combat with no name like that.
  • Marko noted that we’ve hit the 2000th bug card card on Trello! That so many 🪲😵‍💫🪲
  • I think I’ve finally fixed the issue where the game crashes on exit. I’m pretty sure the culprit was FMOD, gasp.
  • I set up a better export-build pipeline so I only have to run a script on the command line to get PC/Mac/Linux + modkit builds that takes like four minutes. It doesn’t sound like a lot but will save me a half hour of manually running around switching computers and deleting folders every time we make an update.
  • We have a website! Aidan Grealish designed the new ✨ lancertactics.com ✨ and it looks so good. (that’s this site! that you’re reading this version of this post on! nice!)

We also showcased at PIGCON, the first games convention the Portland Indie Game squad has done! I went to some of the first meetups the organizers put on back in 2012 & have been in-and-out since then, but it was so cool to see what a force of indie game community it’s become.

I always come away with pages of playtest notes & it was very cool to see some of my indie dev inspirations speak in-person. 🙂

— Olive