Lots of work this week, all of course towards the level generator. To preface the need for this work: the problem with making up tasks yourself and working by yourself is that there's nobody to reign you in. So, when I get obsessed with adding support for something, well, that's it.

This time, my new goal/desire for the level generator was: I want to be able to generate rooms within a zone that they have a bit more special configuration/behaviour in terms of content. For example: a haunted crypt in a cemetery. Problem? Rooms inherit configuration/behaviour and don't have their own. And that can/will not change for Reasons. So, how to work around that? On the quest for a solution I solved a number of other not-as-pressing issues, so here we go.

Tag granularity

That was the main nemesis. I want an abandoned lodge in the woods. If the lodge is a room in a lodge area, then there was a problem: the "abandoned" tag was applied to the area and I got cobwebs in the forest and outside. That's not right, I just want stuff inside. Same with carnage (imagine a murder scene in a lodge). I might not want blood spatters outside. So what to do? One approach is to create special placement rules for e.g. "SpiderwebIndoors" or "GoreIndoors". This could be a fix, but I'm afraid it won't scale, so it's off the menu.

The approach I took: instead of using a room for the lodge, make it a zone! So the zone configuration specifies another zone, that is really just the same room, expressed differently. Perks of using a zone vs room: the zone can have its own tags and settings, so problem solved!

Sparse feature granularity

Ok great, so I now have the lodge as a single zone, comprised of about 10 tiles. But now I realise that my sparse feature generator, for things like blood spatters, cobwebs etc, does not always place something in the room, or does not place enough. The reason is that the sparse feature generator was designed for larger maps, and works with tile clusters, with a minimum size of 10. So you can't have e.g. two traps spawned next to each other. So, I could never have a room with spiderwebs in all its 4 corners, because the entire zone/room is a single cluster. But I like the idea AND I WANT TO HAVE IT!! (yes, the gamedev tantrums). So, I had the grand idea to allow multiple instances of a sparse feature per cluster, and while it's 1 instance for most things, for spiderwebs I can make it four. By adjusting existing other quantities like the percentage of clusters that it can appear and the minimum appearances (1), I can now have my spiderweb-infested rooms at last!

Abandoned and bloody lodge in the forest #1
Abandoned and bloody lodge in the forest #1
Abandoned and bloody lodge in the forest #2
Abandoned and bloody lodge in the forest #2

Cemetery: rooms-by-corridor layout updates

Alright, this is another element from my list of locations to be able to generate. I had a cemetery before, but it was completely static. Now I wanted a procgen cemetery with support for crypts and so on. How to do it? The most compatible existing generator is the rooms-by-corridor (I know, horrible name) one that I have been using for prisons. The catch: the prison generator was limited to have rectangular rooms that share walls. So part one of the modification was to add support for different sized rooms, and very importantly, "open" rooms (e.g. imagine a square grass patch with gravestones). So now the algorithm supports that, but still uses a same-sized room size, and that's the maximum room size from all candidate rooms for this area. If a room is smaller than its alloted area, it's placed anywhere within that area, randomly.

Ok, so now we can have tombstone-grass-patch "rooms" and crypt rooms, yay! But we're not done. What if I want to turn a crypt into a zone, so that it can have its own tags and other special elements? That's a bummer because the child zone placement algorithm really just picks random points on the map. But we don't want that at the cemetery, as we have clear allocated "blocks" for areas. So, I converted the placement algorithm to overridable behaviour, and now the generator calls a function to obtain a random point on which to attempt to spawn a zone, and the rooms-by-corridor algorithm overrides that and selects one of the fixed starting points/slots in its grid.

Final touch: if we do generate a zone, we don't want a room spawned next to or on top of that zone. So, if a slot is taken by an area, we don't generate a room there. Done!

Cemetery with 2 crypts #1, layout: 2x3 blocks, each 1x3. Each crypt has a coffin and spiderwebs. The cemetery is surrounded by a metal fence.
Cemetery with 2 crypts #1, layout: 2x3 blocks, each 1x3. Each crypt has a coffin and spiderwebs. The cemetery is surrounded by a metal fence.
Cemetery with 2 crypts #2.
Cemetery with 2 crypts #2.

Themes

Theme-specific graphics were originally handled in C#. I eventually realised that the C++ generator can benefit from knowing about themes too, and the more work is done in C++, the less work will be done in C#. So now I've added themes and overrides support embedded in the generation script. What does this mean exactly? We have a snow map, and we want to spawn a building outside. The building specification is predefined in the database, but the material/appearance is a bit more dynamic: since we have a snow map, we should use something compatible with 1) snow maps 2) being a building. I've made a bunch of presets for all combinations of biomes and zone architecture (outdoors/cavern/dungeon), but now they are a bit more granular in terms of overriding. E.g.:

  • Stable: wood/biome-specific dungeon walls, biome floor
  • Village storage, biome or other material floor
  • Outdoors lean-to: wood/concrete for walls, biome floor
  • Cemetery: fence for wall, biome floor
  • Crypt: biome-specific dungeon walls, biome-specific dungeon/cavern floor

All this could just be '#' and '.' in ASCII, oh well.

Other changes

  • JSON configuration database. During this work, I've found myself adding increasing support of JSON flyweight configurations in C++ as well, while previously limited to room prefabs and zone configuration, now also for things like biomes, floor/wall/liquid information (useful for themes for example)
  • More tags. E.g. supporting creature-free areas, haunted areas and so on
  • Support temporary rooms/zones. This is useful for e.g. some quest areas/rooms, so we add them to the database just before generation, then generate and refer to the database elements by name, then remove them again at the end
  • Prefab zones from rooms. So far I had prefab zones where I could specify tiles explicitly, or prefab rooms where I can do the same, with a few more limitations. Now I can specify a prefab zone by referencing a prefab room. Why is that useful? Cemetery and crypt: we can have generic crypts that contain encounters/treasures, but we can also have a special crypt, with special stuff going on, and maybe even a staircase down...
  • Ruined rooms bugfixes. If a corner tile is getting erased in a ruined room, then I erase an adjacent one as well, to make sure I don't end up with a checkerboard pattern, and to satisfy some other code that assumes that if we have any tiles removed, we don't need to add doors.

Oof that was a lot.