Hello everyone! Long time no see.

Last time I was here I posted a demo of my long-term project. Having worked on this intensively for two years straight, I figured out I’d take a small break for a month or two. That was a year and a half ago…

What happened was that my all-time favourite VR game Pistol Whip (a rhythm shooter) got an official map editor and I got hooked immediately, bordering on obsession. I’ve released 28 maps since then, a pretty big chunk of the 123 maps currently tagged as “completed”. However, making maps for this game eventually lead me back to Defold.

The map file is a zip containing several files in the json format* (as well as a poster file and music). Being human-readable, it didn’t take the community long to figure out ways to modify these files in a text editor to gain access to a lot of features not available from the official editor thanks to data-mining, some educated guesses as well as direct help from the developers. These range from very simple changes to change the enemy sets or obstacle graphics in the level, but some of them require complex edits to the file which holds all the custom model data inported into the map, which can be hundreds of megabytes in size, making manual edits very combersome. Enter Geomancer.

The tool started as a way to generate “volumes”, auxiliary shapes used by the editor to generate basic level geometry (or geo, hence the name) out of png pictures in a specific format. This is a neat feature which I haven’t really used at all since implementing it however.

The scope of the program started to grow uncontrollably, however, and today it can do almost every trick we’re aware of - changing materials attached to models, enabling animations for built-in props as well as simple tweens for custom ones, triggering events in the level, changing particular enemies to non-standard types and more. Just writing out a guide after a recent release took me a better part of a day.

While the project started as a means to save myself some time when making certain edits, there is now a handful of mappers who use it as well and I’ve already implemented several QoL changes based on their input. This does put a bit more pressure on me to make the UX somewhat decent - the tool started very bare-bones, since I didn’t expect anyone but myself to use it. But it’s nice to work on something that is already useful - and it makes these advanced mapping techniques available for anyone, not just the few people who were previously willing to make these error-prone edits.

Most of the tool consists of piles of guis. I started off using Gooey, but ended up writing my own implementation of UI elements - mainly buttons, text fields and scrolling lists allowing a lot of customisation. As for writing the modified data, well, there’s a reason why I put an asterisk at the word “json format” earlier. The official editor (and the game) use a custom-written parser which has some pecularities and doesn’t quite follow the spec. The official editor tends to generate superflous commas at times which make json.decode fail, though that was an easy fix using a simple sanitiser function.

Much worse is that some of the unordered lists in the data files do actually require specific order. At first I loaded the json as a lua table, made the modifications I needed and esported it back to json. This made the maps fail to open in the game, despite being semantically identical to the unmodified files. The way I saw it I had three options - I could figure out which items needed to be in particular order and sort them after encoding the json. This sounded daunting, since it would be easy to miss something leading to corrupted maps. Second, probably best option would be to write my own json parser to preserve the ordering. So of course I picked the third way and instead of working with tables, I’m modifying the base string directly, with a ton of string.find’s and string.sub’s. It’s messy, easy to mess up and extremely inefficient, but my favourite thing about this project is that performance is not a consideration at all.

The only thing that happens on update is a bit code handling scollbars on lists. Most of the string manipulations happen either when loading a map or exporting it, and given the massive time save using the tool is compared to manual edits, yeah - the user can wait. Most of maps are exported practically instantly in any case, even with all the inefficiecies. Extremely large ones can take several seconds, but that’s still a non-issue. I do plan to reimplement this to use a custom parser at some point - certain advanced edits which are looming on the horizon would be way too painful to implement by just string manipulation, but for now I’m happy.

That’s about it. I’m not nearly done with Geomancer - there are still new discoveries being made that I will want to support in the future. I have some features planned, like a way to generate level geometry based on heightmaps, and there’s always UX improvements that can be added. Just this weekend I started implementing a model previewer - it took some doing to feed the vertex data to the mesh object, since it’s stored in a unity-friendly way using vertex indexing… or whatever it’s called. But I got it working. Now it just needs a whole buch of UI to be actually useful, the first step is done.

The tool is open-sourced on github in case you want to have a peek (you don’t).

And here’s one of the Pistol Whip maps which wouldn’t be possible without the kind of edits Geomancer does - my recreation of Doom’s E1M1, published on the game’s 30th anniversary. Normally the game only limits you to three colours at any given point, and one of these is just a hue-inverted version of another one. Changing the materials of models representing the various sprites (and some rudimentary wall textures) let me use a lot more than just these three colours. The animated doors as well as the fireworks at the end are also enabled by these edits. Lastly, the full-length of the map is designated as a “no-beat section”, effectively removing the “rhythm” part of the “rhythm shooter” gameplay. The map actually predates Geomancer, but is a nice showcase of some of the features and making it would have been so much easier if I had access to this tool back then.