The Design of Headquarters (Doom SnapMap)
By: ChockrickBear | Jul. 6, 2018 | Views: 153 | Keywords: technical guide
A documentation of my methods in creating my map.
There were a few things about Doom that I felt were held back. More precisely, movement speed felt a bit sluggish, the effective range of the shotguns were lacking, and there was no support for endless fighting. So, I wanted to create a level where I rectified those issues. I enjoyed the gameplay concept of Doom, so Headquarters is my own refinement of the base formula rather than an attempt at creating something completely out of this world. It is also my first attempt at level design. If you want to play, use this map ID: P83XPQD2. You can also search for the keyword, "office", and Headquarters will be on the list of maps. You can also check out my gameplay video of it.
SnapMap isn't a full-blown software development kit and it has its own map construction and logic system that's designed more for intuitiveness than flexibility, although you can still do quite a bit with it as long as you have the logical imagination for it. SnapMap provides a controlled environment for level design that doesn't bomb you with buttons and options of obscure purpose and doesn't require knowledge of a programming language. That said, you can easily create your own mess of logic if you don't have a methodical way of doing things. Modularizing logic into intuitive components is important to avoid confusing yourself. You don't want to turn your work into a maintenance nightmare, which ultimately limits the size and scope of what you can accomplish. This article describes some of my methods in designing my level and some of the intricacies of SnapMap.
Deus Ex: Office War
In case you don't know anything about SnapMap or just don't care about level design, here's a bit of personal history that probably had some influence on this map. I happen to have a thing for fancy offices, back when I played Deus Ex as a kid. UNATCO Headquarters (*wink**wink*) was a fun place to explore. I liked the NPCs' reactions when I threw stuff at them or interacted with stuff in their line of sight. I also used cheat codes to spawn furniture to spruce up my office and start massive fights. What I would do is go to the conference room, spawn a bunch of Paul Dentons, spawn Bob Page, kill Bob Page, which triggers the UNATCO trooper to attack me, which triggers the Paul Dentons to attack the UNATCO trooper, and the noise attracts everyone from around the office to join in the battle. Since everyone was plot essential, they were immortal and the fight just went on and on, even spilling out into the corridor. I would also spawn a bunch of mortal NPCs to join in and then watch them get utterly slaughtered, resulting in bodies littering the place.
The Paul Dentons had plasma rifles, Gunther Hermann had his flamethrower, while everyone else had assault rifles, and it was an impressive floor show of everyone strafing around shooting bullets, plasma, and fire everywhere. Normally, the AI forgives transgressions if you don't keep attacking them, but since everyone was attacking each other, violence begets more violence, and they would never stop. On rare occasions, Anna Navarre would briefly turn idle and being near her would trigger the conversation sequence with her while the battle raged on in the background. Gunther would then briefly pause from the carnage to speak his lines and then carry on flaming. Eventually, everyone ran out of ammo and just started hacking at each other with their knives and swords.
Desire for endless battle
One problem I have with a lot of games, even with the Doom campaign, is that they don't give me the means to fight as much as I want. I go into an encounter, kill stuff, then it just ends and I have to move on. You give me the ability to strangle enemies to death and you don't let me do it as much as I want? I played Unreal Tournament 2004 a lot, especially capture the flag in BridgeOfFate because of the ability to repeatedly kill opponents at a variety of engagement ranges and having easy access to health, shields, and ammo to sustain it as long as I wanted. Endless, sustainable battle kept me engaged with the game mechanics and it is not something often seen in games outside of multiplayer. It was never about the challenge or reaching an end state, but about the simple enjoyment of carnage and skillful play.
I prefer more open-ended environments over linear ones. An open-ended level makes navigating around the map feel mindful, like I am making my own choices of where to go and how to get there. While Doom provides plenty of open arenas for contained battles, I wanted to create a meta-arena that consists of most of the map rather than have a linearly connected series of modules. I wanted a map where you could run around the whole level in an endless battle, but I also wanted to create a proper level, which means locked doors and gated progression. Unfortunately, white collar modules don't have a lot of options for interconnections because a lot of the arenas are just straight-through connecting. Add that to resource usage limits and it is difficult to create a big, complex level that makes sense.
So instead of making a grid of rooms, the level is laid out as a giant loop, which is the simplest form of an open layout. Which way you go around the loop changes what modules you pass through to get to your destination. Following the loop makes you inevitably go through the entire map, resulting in minimal waste. The level should require you to perform objectives around the map to make effective use of the layout. However, a map that's too open-ended trivializes navigation and makes the level feel short. To pad out the level, two of the needed power cores are in adjacent rooms, but taking one of the cores will cut off the direct route to the other core, requiring you to go the long way around. You have choice for which core you want to pursue first, but choosing one will complicate getting the other.
Environments with purpose
Every part of the level should have a real-life purpose to make it look like a place where people actually work in. Research modules should be within easy reach of the executive module so executives can observe the scientists' work, the cargo bay should be near the armoury for easy restocking and inventory management, and different parts of the facility are keycarded to prevent unauthorized access. A high-class office environment is about taking care of employees' needs so they can be subjected to long, oppressive hours. Therefore, a cafeteria, a break area, and washrooms are important. Corridors are about efficiently taking you to places you need to go and should be short and simple.
Symmetry, fitting things into the environment like a glove, putting stuff in empty spaces, and appropriately sizing custom geometry are important to making environments look nice while leaving out extraneous detail saves time and resources for developing more important things. Using a possessed soldier as a reference point is a good way to decide the height of custom counters and railings, but I also set the box dimensions as multiples of four, which is the grid size I use for most object placements, which allows me to easily make pieces flush against each other and place props flush on top. I also enforce symmetrical placement of objects by using blocking boxes as guides, since I can resize them while keeping them in one place.
Items placed in the map should have some semblance of logic to it. Who leaves ammunition, armour shards, and health kits randomly scattered on the floor? However, it's annoying to have no ammo in arenas of infinitely spawning enemies, so I place pickups on various surfaces so they are more blended into the environment rather than just sitting on the ground in the open.
There's not much of a story. Object placement, boss names, and computer text were made up for the sake of fitting in to the office theme of the level. It's all just an excuse to fight lots of enemies and any story you interpret just sort of emerges as a result of placed objects not being completely random and out of place.
Many of the arenas have natural cover, but some of the more empty arenas, like the cargo bay and the archive hall, need added cover like boxes and pillars. Fighting enemies in a flat, empty room is boring because there's no sense of uncertainty of where enemies are and there are few positioning decisions to be made to help you survive and dominate. Adding obstacles improve the viability of fighting from certain positions and help make arenas more dynamic because it creates mini-locations with their own tactical considerations. However, the AI have limits to how well they can navigate custom obstacles and they will not go the long way around if you block off the shortest path, resulting in them crowding in front of obstacles. Therefore, it is best not to alter an arena's layout.
The AI also have built-in jumping and climbing interactions in each arena that override custom obstacles, so they will pass through those obstacles and even get stuck in them. When placing boxes, I had to position them away from ledges and other jump points to prevent enemies from jumping into the boxes and being stuck in them, but larger enemies, like the mancubus, take up so much space that it's not practical to allocate space for them, so there will be some clip-through. As long as the enemies aren't unkillable by being fully inside a box, it should be fine.
Crates are not climbable by default, so they are enclosed by invisible blocking boxes. It takes some trial and error to make boxes climbable because the blocking box needs to be slightly bigger than the crate it encloses to work. Making stuff climbable is important to give you options for vertical navigation and escape.
The beginning is all about warm-up. You start off with fists and there are four zombies for you to beat up to practice weaving in and out to avoid damage. Because of the twelve-enemy limit, the boss is not spawned until after you pick up the pistol, which means you should have killed the zombies to free up space. The goal of this arena is to fight your up to the command post. I put the defenders to combat points to make them stay put and I use possessed security to make you use grenades. However, combat points are not 100% reliable and there's not much that can be done about it. It seems that the AI has hidden, arena-specific pathing and triggers that don't fully co-operate with combat points, so sometimes they leave the radius and even run off to a separate area. Increasing the radius seems to help, but it's not perfect.
Unlike AI path points, there is no way to break out of a combat point once assigned. Using a path point to move them out will only result in them running back to the combat point. However, enemies can move from one combat point to another. When you walk into the command post, I use an AI iterator to order all remaining survivors to follow the combat point the leader was following in order to swarm you because there's no point staying on the defensive. However, they will still be stuck to the combat point when it is better to just set them loose. But since we're just talking about survivors, they should be quick to clean up, so it's not too big of a deal.
The straightaways and flanks are ideal for pinkies and soldiers, but also explosive and hazard barrels. If you time it right, you can use the stun mod on the plasma rifle to lock an enemy in a puddle of goo. I put only a few pinkies, but I don't want the place to be swarming with soldiers, so I put in a few possessed engineers, which act as randomly placed explosive barrels.
You have to manually disable the lockdown, but unlike the previous encounter, nothing else happens when you do. Since the office arena set a precedent that lockdowns have to be manually disabled, this fact is used to obscure the secret. However, manual disabling gets annoying, so later encounters automatically do it.
After you beat The President, you come here to use the BFG on the reactor. It's simple to use shootable triggers for this. However, you cannot set what weapon can be used to trigger the trigger. Weapon filters only check whether the player has that weapon in their inventory, not whether that weapon is used on the trigger. As long as you have the BFG, you can shoot the reactor with any other weapon and it will still work. To mitigate this, I set the damage threshold to be high enough so only the BFG is practical for triggering it.
The power stations start empty, but that means I can't reset them if you die in the final survival sequence because there is no option to remove a power core. Instead, I allow you to retain progress if you have plugged in a core, but any cores in your inventory will be removed and respawned.
A secret should affect gameplay progression; otherwise, it is not rewarding. In fact, it's almost necessary to find the secret or else the rest of the level gets hard without the more powerful weapons and jump boots. However, the armoury is not a place to visit once and move on, it's a reliable source of ammo and armour for the final sequence. Everything respawns, so you are able to switch out weapon mods and grenades you don't want and the munitions box resets itself to let you quickly replenish your armour rather than wait for it to slowly regenerate.
I want to include all the weapons for player preference, armoury aesthetics, and the ability to complete SnapMap challenges. Because the multiplayer weapons and the chainsaw occupy the same weapon slots as the campaign weapons, it's slower to select them because you have to press the hotkey twice and put up with the weapon switch delay. There is also an issue with the chainsaw. Not only is it hard-coded to the '1' key, picking up the chainsaw before picking up the weapon bound to the '1' key will result in the chainsaw always being selected first. Because I bind the '1' key to the heavy assault rifle, it will also select the Hellshot, and you can't switch between three weapons on the same key. Therefore, I added the option to drop your weapon to make it easier to select weapons you actually want to use. To avoid accidentally dropping weapons, I make it double-tap. Double-tap works by setting a boolean to be true, then pressing the key again will trigger the drop. The boolean sets itself back to false after the double-tap delay.
Having a tight knit of lockers, crates, and other furniture makes the environment feel cozy. I also use cone lighting to highlight the weapons to make them look fancy. An armoury should have lots of ammo to go with lots of guns, so it is necessary to have big crates of ammo. The placement of the ammo and armour crates is deliberate so you can smoothly run around the room and load up. I place one ammo pickup on top of each crate to identify the ammo in it. To allow you to stock up with peace of mind during the final sequence, I provide a button you can press to activate a laser trap.
An armoury should have lots of guns. Only the middle gun in each locker is real, the rest are props because you can't put pickups too close to each other.
If you found the reactor room secret, you will be able to get to the cargo bay. This encounter is technically optional because you can just avoid the cargo bay entirely, but it is the only way to acquire the super shotgun, which is very helpful against the enemies you face, and it's hard to miss. You can even do this after you get the Argent upgrade, but you can't do it if you have started the final sequence. The revenant boss here is set to a combat point on the highest arch, so he will continuously launch micro-missiles at you from afar. However, like all combat points, it does not guarantee he will stay.
I set the soldiers to spawn in close proximity to the boss. This will pretty much guarantee you will have to deal with enemies on high ground and it also makes it harder to isolate the boss. Because the central area is so exposed, I placed teleporters that let's you go up or down the side areas safely. I put lots of boxes to make the side areas safe to retreat to and fight from, but the Hell knight and pinky will try to flush you out while the cacodemon tries to snipe you. Ammo is also scattered, so you can't stay in one area for long, and having to go from place to place helps make combat more dynamic.
Normally, this arena uses a floor grating in the middle of the room. However, I found that it causes grenades to fall through and be ineffective. So, I put a solid floor over it. I also put jump pads at the front and back of the arena for easy vertical escape because there are no escape options at those locations if you get swarmed. While it is possible to ledge grab the lower arch near the entrance, I found that it is awkward because of the odd shape of the platform and the lack of space between it and the wall for a running start, making it easy to miss and bump into the pillar.
The boss spawns on the top arch and rains missiles from above. Cover needs to be placed to divide enemies while crates need to be enclosed by blocking boxes to make them climbable.
This area is not used for an encounter. Instead, it gives you a health kit and some things to see. It's also a good opportunity to tell you that there is a secret in the reactor room in case you missed it. SnapMap does not provide blocking cylinders, but does provide cylinder triggers that can be textured. Since you can walk through triggers, you can't use cylinders as part of regular geometry, but they can be used for showcasing stuff behind glass, which is what I do for this room.
Initially, I had no idea what this room was for because it looked so terribly inefficient to be a room actual people would use. I got the idea to place the round computer in the corner from Third Rail, which used the same module. However, unlike in Third Rail, I turned the corner into a contained corner office space where multiple people work. Then I realized this room would make a good research lab where scientists could study things inside the big windowed chambers. Given the size and connection points of all the modules, connecting this research lab to the rest of the facility required some extra corridor. Having empty corridors is unnatural, so I made a break room on one side and a decontamination area in the other.
I didn't know what to do with stairs leading to a dead-end. Playing someone else's map gave me the inspiration to turn it into an office space.
Cylinders are simple, yet iconic. Layering a see-through cylinder over a solid cylinder makes it glow.
The scientists need a nice lounge to distract them from the unethical nature of their research. Unfortunately, there are no round, solid surfaces, so the couch has to be boxy.
This encounter is designed for the berserk power-up, as in, it makes it less powerful by throwing lots of trash enemies at you to get in your way and waste the berserk. Initially, I tried doing a find-the-cacodemon encounter, since their ability to fly makes berserk difficult to use, but it made the encounter either too easy or too hard because dealing with so many cacodemons when berserk runs out is a problem while having fewer of them made it easy to brute-force search for the boss and resolve the encounter quickly. Horde imps are much better, and rather than have one of the bigger demons be the boss, I break the convention and make the boss an imp hiding among all the other imps. I make the boss a sentinel imp so he stays away, which makes him harder to find. Imps have very low base health and you can only increase the health of an enemy by up to 1000%, which is still pretty squishy. So, I gave him very high regeneration. Of course, you can finish him very quickly with berserk and he dies quickly to powerful, single-shot weapons like the super shotgun.
One thing to note about this module is that applying the built-in decal layer is very resource intensive, causing the memory meter to spike up 14.21%. I use it because the bloody environment is very appropriate for berserk and that I had just enough resources to spare.
SnapMap doesn't provide nice-looking tile floors, so I had to create my own tiles using decals to draw the lines. Creating even-sized tiles is a matter making a decal stripe as wide as the tiles, duplicating it, lining them up side-by-side using the grid snap, and then shrinking the stripes into lines. Do the same for the other axis and you get a nice grid. Snapping with decals is inconsistent if you snap from different camera perspectives, but as long as you keep looking in the same direction with each snap, it should line up neatly. To centre the grid, I start applying the decals from the middle. There are no shiny textures for the floor, but a shiny decal can be applied and stretched over the plain, textured floor. Setting the decal's normal map opacity to zero and overriding the floor's normal map makes it look smooth. However, this makes the decal look like a flat texture. To add a bit of texture to the floor, I reduce the decal's opacity a bit so you can see a bit of the floor texture underneath.
The ceiling is done by duplicating the floor with the decals by just double-clicking on the floor, which selects everything. However, moving decals as a group causes them to be absolutely positioned in the map and not stick to the surface it's on when you move the surface by itself. To rectify this, I just select and put back each decal without moving. Decals do not like to be placed on the underside of blocking boxes, so in order to make the decal show on the ceiling, I had to flip the ceiling over so the "top" is facing down. It is important to rebind the decals to the surface because trying to rotate the ceiling and decals as a group will prevent you from using the more precise and easy-to-use rotation system. Group rotate is so janky and unresponsive that it's not worth skipping the rebinding step. The grid room makes it easy to see if the ceiling is level after rotating it by putting the ceiling at a corner and lining it up with the room lines.
The president's office is inspired by the Illusive Man's office from Mass Effect. SnapMap doesn't provide curved surfaces, so the room is square instead of rounded.
Tiles are created using decals since SnapMap doesn't provide nice tile floors. Making the decals wide first, then shrinking them makes it easy to evenly space the lines.
SnapMap doesn't provide sinks, but there are convenient decals and hand sanitizer dispensers that can be mashed into a substitute. It's also unfortunate that there are no mirrors.
This battle is to test your new abilities and practice with them. The boss is enclosed behind a force field, requiring you to unplug the power core to disable it. I put a hazard in front of the power station so that unplugging the core damages you by quite a bit as you unplug. A problem with animated interactions is that your character technically remains in the spot where you pressed the 'Use' key while the animation plays. This means that the hazard won't hurt you during the animation if you interact with the station without standing in the hazard first. The solution is to place the hazard far enough in front of the station so that it is not possible to interact with it without moving into the hazard.
To reset the station, I spawn a new core in the power station and remove the droppable from your inventory. While the 'Remove Droppable' command has an option to reset, this will not return the core to the station because the core technically doesn't exist until you interact with the station, so the core has no original location to reset to. Resetting is only effective when the core is pre-placed in the environment.
While the boss is stopped by the forcefield, his limbs can still clip through and be shot at. If the boss can be hit, the chainsaw can also hit him as well to instantly kill him. Therefore, it is necessary to prevent the boss from going up to the forcefield by putting a smaller, invisible box inside the forcefield, which keeps the boss from moving. Blocking boxes only block from the outside-in, not inside-out, so four walls are needed instead of a single box.
The arena itself has a fancy layout that makes it ideal for creating an executive foyer. The walls have indentations that are suitable for placing windows in, so placing lots of windows makes the place look fancier. Initially, I didn't use this module and just used a simple cross corridor that connected a Helix Stone arena leading to the president's office, the archive hall, and stairs to the prototype lab. The Helix Stone arena was used for this encounter, but it didn't have the forcefield mechanic and it was just a plain old kill the boss encounter. I decided to ditch the Helix Stone room and the cross corridor because I felt the executive section of the facility wasn't fancy enough (what kind of self-entitled executive wants to walk through the same kind of boring hallways as the plebs?). The problem with this module is that it is a straight-through connecting module, but I needed to connect up the prototype lab and archive hall, so I use teleporters.
The religious theme of this room makes it good for having the biggest fight here. This is another find-the-runes battle, but in a more confined space against stronger enemies. When it comes to rune placement, it's best to not be too devious so the battle doesn't drag for too long. However, you still have to search from different angles.
Because the main hall has no cover, I add pillars. They also allow you to reach the balconies on the second floor, giving you options for escape, but they need to have blocking boxes on them to make them climbable. I also added a bridge on the second floor to give you more cover from enemies on the ground and allow you to snipe them. However, this encounter gets quite thick with enemies. Glory killing for health is not totally reliable because the confined space makes it hard to do it without getting swarmed and losing whatever you gained and then some. To rectify this, I include siphon grenades.
All the tough enemies are set to spawn in close proximity in front of the podium, while the basic enemies spawn in proximity to the boss. I also place an explosive barrel to help you deal with them so you have time to search. Even after you reveal the gore nest, these guys may spawn before you get to it, making it hard to destroy without getting blasted to pieces in the process, so you have to deal with all the enemies first. Unfortunately, gore nests cannot be reset, so there isn't much penalty for dying.
Without the pillars, this would be a difficult room to fight in.
I find that a lot of weapons are rather underwhelming because most enemies take multiple shots to kill and you don't have some of the more powerful campaign weapon mods. The shotguns have awful spread, making them inconsistent beyond point-blank, and the automatic weapons are just pepper spray. Since SnapMap doesn't allow me to tweak individual weapon stats, I increased your damage all around. I created a simple test map that lets me vary the damage multiplier using the User Input keys and spawn different kinds of enemies using usable triggers. I base my tests on the shotguns because they shoot slowly and require carefully balanced risk-reward.
I found that 135% is a good buff. The principle behind determining how much to buff is to count how many shots it takes to stagger reliably and from what distance. A buff that's too high or too low can result in shots missing the stagger point and then killing in the next shot. I balance the damage so that the shotgun's effective range is increased, making it easier to stagger possessed soldiers in one shot from a short distance, allowing you to smoothly chain into a glory kill, which opens up chaining possibilities. Possessed soldiers take about 240 damage to stagger and 135% hits at least that in many short-range cases with some buffer. Because the shotguns have wide spread, it is most consistent when aiming for centre-mass instead of the head, although there is still significant variance and sometimes it will still fall short.
The default movement speed feels restrictive to me, so I increase it after the upgrade. I don't give the speed to you right off the bat to make the upgrade feel powerful by contrast, like you were growing beyond your human limits. With speed comes freedom, and 126% speed provides a good sense of responsiveness for shotgun charging and general evasion without feeling unbalanced. Movement should not be too fast because it makes movement slippery and hard to control, particularly for tight quarters. Going up stairs would get annoying because your own momentum causes you to fly into the air and lose control.
Grenade cooldown has been reduced to make it less tedious to deal with possessed security early on as well as make grenade throwing a staple for supplementing your guns. Grenade opportunities are quite common, so it is annoying to deal with a lengthy cooldown. I included most of the grenades for the sake of preference, and they respawn so you can change your mind later. The tesla rocket and shield wall have slower cooldowns than the frag grenade, yet their overall effectiveness is worse. The tesla rocket inflicts pitiful damage while the shield wall has the durability of paper. Since I can't specifically make them more effective, I change your grenade cooldown when you pick up a grenade box so you have faster cooldown on the weaker grenades. The tesla rocket can be used more often than the frag grenade, making it more reliable against mobs of weak enemies. While it does less damage, it can help you stagger groups of weak enemies at once. The shield wall can be used as portable cover you can deploy in a pinch to help you divide and conquer enemies. Siphon grenades have a much slower cooldown with respect to the other grenades to prevent you from becoming immortal with them, but it's still faster than default to make them more reliable.
I would have preferred if you could carry all grenades at once, but there's no built-in way to do so. It is possible to mimic grenade switching by using a sequencer to switch grenades with a button press, but it won't remember cooldowns, which makes it abusable. Enforcing cooldowns would require a complicated system of timers and booleans, and it would require cluttering the HUD with cooldown messages since there's no clean way of communicating why you can't switch grenades.
A single ammo pickup fully refills that ammo type. I also set ammo to respawn so you can fight as much as you want. The problem with the default ammo counts is that they don't carry you for long, especially when you're up against lots of enemies. Considering that you have to go out of your way to get them, there's no sense of confidence when picking them up because you know you'll just run out again before you've done any serious damage. Running out of ammo is still a concern because pickups are in out-of-the-way places, requiring you to take a risk and seek them out. Getting only eight shotgun shells out of a box is also disappointing when you can clearly see fifty in there. However, I avoid using ammo boxes so you are encouraged to run around the arenas and pick up the ammo you need. Ammo placement certainly affects how you move around the arenas, so placing different ammo in different locations helps make the arenas more dynamic.
Visiting the armoury lets you acquire regenerating armour. You might consider regenerating armour to be noobish, but I find that it gives greater confidence for taking risky, but awesome moves. Regenerating armour is complementary to glory kill healing because taking health damage while going in can negate the benefits, so you end up not doing it even though you need the health. I don't use the Halo standard of regenerating shields where it takes time to start regenerating, but it regenerates fast. I consider such mechanic to be pace-breaking because you have to sit behind cover and wait since taking damage, no matter how small, interrupts the regeneration. Instead, I just trickle armour steadily. You are still able to fight while you recover armour as long as you don't take more damage than you're recovering, so you are rewarded for dodging and defensive fighting rather than cowardice. If your armour is down, you can flee and buy time, but you are still encouraged to keep fighting because you can't get back to full by the time enemies rush your position, so you have to be good at dodging if you want to keep whatever armour you recovered.
I want encounters to spawn enemies infinitely until you kill the arena boss to prevent encounters from being too short and easy. Custom encounters are the staple method of spawning enemies since direct placement of enemies is resource expensive and cumbersome to manage. If I need precise placement of enemies, especially when spawning in a confined space, I use single demon encounters.
There are two different custom encounters that are on repeaters: basic and tough enemies. Basic enemies respawn faster to keep the pressure on while tough enemies are on a slower timer to prevent battles from getting too overwhelming and distracting you from objectives. Killing enemies buys you breathing room, but you have to focus down the boss or you will never finish the encounter. Once you kill the boss, the repeaters stop and you can just clean up the leftovers. To prevent encounters from being the same and to make them longer, I add some twist to each encounter, like making the boss immortal until you complete an objective.
Simply putting enemies on a repeater makes objective-based encounters either too easy or too difficult. Objectives are easy to do if enemies don't spawn frequently, but if they spawn frequently, it's hard to do objectives. There's no clear medium because enemies are either alive and pummeling you, or are dead and you have free reign. Stretching encounter length requires keeping you from doing the objectives, so the solution is to just stack on the enemies every time you progress on an objective so you spend a lot of time staying alive, but if you kill them all, the lengthy repeater duration will slow the flow of enemies and you have a time window to do the objective.
Each encounter is organized using custom events to avoid running lines all over the place. There are three different custom events for each encounter: start, end, and reset. The trigger for the encounter signals the event start, which then signals everything needed to start the encounter, such as locking the doors, playing music, and spawning enemies. All encounters are resolved when you eliminate all opposition, which triggers the encounter end to unlock the doors. If you die and respawn at the checkpoint before the encounter, it triggers the reset, which stops enemy spawns, removes enemies, respawns items, and reset objective progress.
Dynamically resolving encounters
Encounters need to end on killing all enemies. The simplest way to determine whether you have eliminated all opposition is to count the number of AIs spawned and compare it to how many have been killed. Rather than do a brute-force recount of every enemy every time you kill one, I set an AI Proxy to add to integers tracking the number spawned to the number killed while the Integer Compare object will perform a comparison after each kill to signal the end of the encounter once the integers are equal. Direct placement of enemies is problematic for deciding whether an encounter has ended because pre-placed enemies count as spawned enemies even if they are not visible, which complicates the counting process. Thus, when you see my map's resource meters, there are zero demons on the map.
A problem with enemy counting is that it doesn't know which encounter you are in. I don't want all encounters on the map to trigger at the same time, so to distinguish encounters, it signals a switch using an encounter index set by each encounter start. Some encounters have objectives and should not end prematurely in the event you kill all the enemies before new ones spawn, so I have a boolean to prevent encounter ends and then disable it when you complete the objectives. If there are enemies outside of a lockdown after it starts, it will affect the spawn count, so they must be cleaned up and the counters reset before starting an encounter.
Encounters are resolved by counting the number of enemies spawned and comparing it to the number of enemies killed. There may be times when you don't want this to trigger or other times when you want it to double-check, so extra logic is needed. Notice that there are zero demons pre-placed in the map, which is necessary for this to work properly.
Sometimes, the number of enemies is miscounted, usually when the spawn limit is over-saturated, which results in encounters either ending early or not ending after killing everything. To work around this, I built a failsafe into the objective toggling to do a recount of enemies and end the current encounter if there are no more enemies. I also perform a recount after each encounter boss is killed to reduce the chance of misfire if you don't check your objectives during encounters. However, you have to remember to press the objective toggle for it to work.
I also noticed that on rare occasions, there will be a glitched enemy, usually a hell knight, that jumped through the ceiling and got stuck outside, preventing the associated encounter from being finished. It's hard to build a robust failsafe because there's no way to detect whether an enemy is stuck or how many, but it is usually just one enemy. So, I check if there is only one enemy left and set a countdown until that enemy is auto-killed. I use an AI iterator and cached object to select and save the last enemy during the recount process and then kill him when the timer expires. To prevent abuse, the countdown is long enough so that it's faster to kill the last guy yourself. It is canceled when new enemies spawn, and every encounter will spawn enemies before the timer runs out.
There's also an issue when resetting encounters. It is necessary to reset the spawned/killed counters after dying so that they are accurate when you restart an encounter. However, the 'Finish Encounters' command sometimes won't fully clean up encounters. Some enemies get spawned in anyways after you respawn, which will affect the spawn count, and some enemies won't get cleaned up at all, which will affect the kill count when you kill them. This most likely happens when over-saturating the spawn limit. To combat this, I use a boolean that turns on after you die, which sets new AIs to be removed on spawn, and then I turn off the boolean when an encounter starts. I also use an AI Iterator to clean up enemies when you respawn.
Enemy compositions matter a lot to the overall feel of an encounter and it is best to use a mix of enemies to fulfill different roles. Hell knights and pinkies are intended to flush you from cover while everything else suppresses or flanks you from a distance. I always use the full twelve-enemy limit because battles are too bland otherwise. To make battles easier or harder, I vary the number and type of big enemies and fill the rest with regulars, but I don't go above twelve enemies total to reduce spawn hold-ups unless it's a horde imp rush. I use possessed soldiers as the mainstay enemy because I prefer fighting more tactical enemies that are better at frontal assault than imps. I consider imps to be flankers and fire support because of their low health and high agility, but they can be a source of pressure in large numbers.
In encounters where you need to destroy runes to progress, the runes are decals. However, decals are not dynamic and cannot be shown or hidden. To work around this, I use a blocking box to create a tile, put the decal on that tile, wrap the tile in a shootable trigger, and showing or hiding the tile while disabling or enabling the trigger. Unfortunately, the tile cannot be invisible because that would also make the decal invisible. The decal also needs to be set to dynamic only because decals are applied to surfaces in an area according to its depth property. If I place the tile on a wall, the decal will be placed on both the tile and the wall. If the tile is hidden, the decal will still be visible. This also means the tile cannot be placed on a dynamic surface like a box. Having multiple runes to shoot to make progress is just a matter of counting the number of runes shot, since you can shoot them in any order, although it is necessary to disable the shootable triggers after shooting them so you can't shoot the same rune multiple times.
A single action music track is a mash-up of many short segments randomly played one after the other so the track can loop indefinitely. However, SnapMap does not provide a way to select which segment plays first when you start the action music. For the Archive Hall battle, I use Hell 01, Combat Variant B because the dramatic choir is a good fit for the location, but the choir is just one of the segments chosen at random and I can't do anything about it. Another thing about music is that there are additional transition tracks when going from action music to idle music. However, which transition track plays is also random and they only play if you are using the same set. For example, going from UAC 02 action music to UAC 01 idle music results in instant cutoff, so you need to use UAC 02 idle instead. Thus, it is necessary to switch idle music according to the encounter.
While encounters are dynamically ended, I use a separate switch to signal the appropriate idle music for each encounter rather than tie them in to the encounter end switch. If you die, the music should go back to idle, but the encounter end should not be triggered. For the survival sequence, I created a music playlist using a sequencer and timer. I also have the playlist change the encounter index for the sake of changing the idle music for when you die or finish the level.
I implemented a checkpoint system since there is no built-in way to save your game. By default, if you die, you just respawn at an active player start while everything continues, which cheapens death. The idea of a checkpoint system is to respawn your character before an encounter while resetting the encounter so it can be triggered again. To create a checkpoint, you disable all other player starts and enable the one you want to respawn at. SnapMap provides an easy way to save your current inventory through the Vitals object. When you trigger the checkpoint, it sets your restore point. When you respawn, it applies your restore point.
Setting restore points requires using the player as the activator, but the trigger for the checkpoint may not be a player. For example, using a boolean change to trigger the checkpoint will provide no activator, which will prevent saving. Therefore, saving uses a cached object set to the player. Cached objects are useful for activator conversion, ensuring that the correct activator triggers the appropriate action no matter what signals the event.
Restore points do not apply to keycards and other droppables. What happens to them when you die is handled separately by the droppable itself. For a checkpoint to be correct, you need to keep all keycards you had up to the checkpoint. I use booleans to track what keycards you picked up, set each keycard to be removed on death, then go through each boolean to see what keycards to give on respawn. Encounters that reward you with keycards are responsible for setting the booleans to false before the respawn check so you don't have them when you respawn. A short delay needs to be added before doing the check in order to reset the booleans if the keycard is picked up as part of an encounter.
Keycards that are pre-placed in the map have to be reset to their original location because removing them on death deletes them, which prevents them from being reset and you can't pick them up again. This has the side effect of creating a duplicate card when you die after triggering a new checkpoint, but there is an option to limit to carrying one.
Resetting environments can get messy because there are many possibilities. For example, if you pick up armour, then trigger a checkpoint, your current armour will be saved and you will have that armour when you respawn. But resetting everything will result in the armour being respawned, which allows you to pick up a second set. It is better to set up checkpoints so that it is hard to usefully abuse than to try creating logic to account for everything. Not everything can be reset because there is no command to so. Computer interfaces allow reuse and I can just disable/enable them, but gore nests cannot be reused or reset while power core stations allow spawning new cores, but not removing them. So, I just let you get a head start in encounters with non-resettable interactables if you have already interacted with them. It is also important to not delete objects, only disable them so I can reuse them in the event of loading a checkpoint.
Rather than try to make up some cheesy back-story about an evil corporation turning everyone into demons, I communicate only information relevant to the gameplay so you don't get lost or have no idea what you should be doing. Whenever you "read" story items, you are provided with only the information relevant to the gameplay.
I wish objectives could be referenced objects because where you receive an objective is often nowhere near where it gets completed. So, I have to use custom events to signal objective progress across the map for each objective.
Since it is easy for enemies to hide in some arenas, I give you the ability to find enemies on your compass by viewing your objectives. SnapMap does not allow you to have more than five point-of-interest icons, and it forces you to manually manage each icon through individual POI Settings objects instead of allowing you to create and assign one where needed. A single icon cannot be applied to more than one thing and it will apply the icon to the latest object it is assigned to. Finding one enemy is as simple as using an AI Iterator because only the last iterated enemy will be marked.
Creating an objective toggle requires using a boolean to track the state of the toggle, showing or hiding the objectives depending on the state of the boolean, and then setting the boolean to reflect the current state. Incorrect assignments can result in the objectives not showing or require an extra key press to get to the state you want. It is necessary to directly test the boolean instead of using boolean filters in parallel for each state because both filters are not tested simultaneously, which results in the boolean being switched for the first test and then it tests the other boolean, causing it to switch back.
Custom AI Conductor
The final sequence is the culmination of what Headquarters was meant to do: fight in a never-ending battle across an entire open map. However, layout is only half the picture and there needs to be a constant stream of enemies. The given AI conductor does not provide the level of control I want, so I made my own. I didn't like how the default AI conductor turned into baron spam and always spawned in close proximity to you rather than distributed throughout a module. I wanted a more balanced composition of enemies so you can get some easy glory kills for healing and allow you to chain glory kills among a group of weak enemies. The basic principle of an AI conductor is to repeatedly spawn enemies in the area you are in while cleaning up enemies everywhere else.
Custom encounters provide the options to spawn enemies in your vicinity. You need to have the player be the activator, but the trigger for signalling the AI conductor may not be a player. That's where the cached object comes into play to convert the activator to the player. I have different encounters that define a different composition of enemies to make encounters less repetitive. Every cycle of the repeater signals a random relay to spawn one of the encounters at random. The cycle time should be long enough so that killing enemies is productive, but it should be short enough so that a new batch spawns just as you finish off the current batch to keep idle time to a minimum.
I discovered that spawning large encounters is bad for performance. Spawning an encounter often causes hitching, which is bad when you're in the middle of fighting the last cycle of enemies. To mitigate this, I divide each encounter into three smaller encounters of four enemies, and then spawn them one at a time with a second of delay between each. As a result, the hitching has been drastically reduced.
Cleaning up enemies
Enemies do not automatically remove themselves if you get too far away from them. Because you can have only twelve enemies spawned at a time and running away can cause enemies to be unable to follow, you can't get endless combat wherever you go. Blanket deleting will cause enemies to vanish in front of you, so it is necessary to track your location and delete all enemies not in your location for each spawn cycle. It is also necessary to call the encounters to finish early because any enemies that have not yet spawned due to the limit will continue spawning in at the old location unless you stop the encounters.
I divide the map into regions of modules and assign each region an integer. This region index is set every time you enter a region by placing on-entered triggers in modules where you can enter the region. The index is used to signal a switch that filters out all enemies not in the same region as you. Each region is defined by a custom filter that specifies the modules the region contains. I don't want individual corridor sections to be treated as a separate area, so multiple modules are included in one region. I also don't want enemies to be cleaned up in front of you while you are at region borders, so I put some overlap by having border modules be included in more than one filter. Of course, it's not perfect because enemies farther in will be cleaned up and there is no way to check for visibility before removing an enemy, but smart region overlap will minimize this.
To allow you to reduce pressure by killing enemies, the conductor cycles infrequently. However, if you leave the area while there are still lots of enemies, they may have a hard time following and you end up with empty rooms until the next cycle because existing enemies hog the spawn limit. Therefore, I set the AI conductor to cycle whenever the region index changes. Cycling the conductor on region change creates a constant stream of enemies no matter where you go. Of course, this means enemies will vanish from the region you were in.
I ease transitions between regions by slowly deleting enemies one at a time. The AI iterator does not provide a way to delay each iteration, so a separate repeater is needed as a workaround. Each cycle of the repeater signals the AI iterator. Each enemy iterated over is passed through the region filter to ensure only enemies outside the current region get selected, and the filtered enemy is saved into a cached object. The saved enemy is deleted and the process is repeated. The AI iterator does not provide a signal for when it is finished, so deleting is signaled by the repeater after a slight delay. Because deleting enemies is only a concern when you leave a region, the clean-up is signaled only when the region index changes rather than when the conductor cycles itself.
Each encounter has a second variant that spawns enemies in connecting modules instead of inside the player's module. Because corridor sections are tiny and considered a separate module, spawning twelve enemies inside that one corridor section while you are in it is just crazy. The solution is to use a switch to check what region the player is in and decide which encounter type to spawn. Whenever the repeater cycles, it signals the switch to set a boolean based on the region index. This boolean is used as a filter to decide which encounter to spawn.
I discovered a bug where spawning in connected modules while in the elbow corridor between the research lab and the decontamination coupler would cause a massive frame rate drop. Since the coupler is considered a separate module and that spawning enemies incurs significant performance hits, I suspect it has something to do with attempting to spawn large numbers of enemies inside a confined space. All I can do is not trigger a region change in that elbow corridor and do it in the main room.
I don't want enemies spawning in certain areas, like the armoury and the president's office. The armoury is a confined space and it is annoying to have enemies attack you while you are loading up, which is also why I included security lasers. The president's office is just a small section of the grid room, so enemies can spawn outside the office and potentially clip through the windows. However, SnapMap doesn't give fine control over where enemies are allowed to spawn. I use a custom filter of modules where I don't want enemies to spawn. This filter prevents the conductor from doing anything when the repeater cycles. Since the player is the activator for the repeater, the custom filter matches your current location. However, this does not stop the repeater. If you clear an area, then go into a no-spawn module, the repeater will cycle, nothing happens, and when you leave, there will be no enemies because the repeater is on a new cycle. To combat this, I define such modules to be a separate region, which will trigger a region change when you leave.
This method of inhibiting spawns is not compatible with the divided spawn optimization because you can't cancel delays, so enemies can still spawn in no-spawn zones if you enter them immediately after the conductor starts to spawn a new group. Cancelling spawns requires using a repeater and sequencer to do the spawning for each group of encounters, which gets messy, so I don't really bother.
The custom AI conductor. The custom filters on the left define modules in each region, the logic in the middle cycles the conductor and spawns enemies, and the logic on the right define regional enemy spawning and cleanup.