Last weekend, Regicide Rumble 3 happened. Regicide Rumble 3 (abbreviated RR3) was an Age of Empires 2 event organized and casted by T90Official on Twitch that had players of different skill levels compete for some prize money over three days in Regicide games1 of Age of Empires 2.
In this blog post, we will not focus too much on the (amazing) event, but on how we built the maps that have been used in that event.
About one month ago T90Official gathered a bunch of channel moderators and map makers and laid out his plans for the event: There should be multiple games over three days on different maps. Since the focus should be on Regicide, anything that makes sniping Kings easier should be done. That included removing HP bonuses and technologies for buildings that could protect the King, as well as disabling alternative win conditions like Relic or Wonder victory.
In the Regicide Rumble 1 and 2 events, there have been matches that took multiple hours to finish and that blew up the planned schedule for the games. To prevent this from happening again this time, a weakened version of a Battle Royale storm should push out players from the edges of the maps towards the center after a certain amount of time.
Battle Royale in Age of Empires 2 is not a novel idea, a few maps have been created already (mainly by HenkDeSuperNerd) that contain an area effect which damages units and buildings and grows over time, forcing players towards the middle of the map. We will not go into the details of the actual mechanism that creates the “storm” in this article – the relavant part is that the mechanic is based on chains of decaying animals which are placed around the edges of the map. Once the initial animal fully decays, it spawns a loop of other units that repeatedly cause blast damage to an area around them. Having all the edges full of these animals with different decay times creates “storm zones” that get activated one after another.
Those existing Battle Royale map scripts are highly specialized and a lot of work went into their creation. Creating and testing ten new map scripts with a similar but weaker version of the storm within a month and out of thin air2 would be totally unfeasible, as much was clear from the beginning.
Now we had to decide between two alternatives: Create a generic snippet that we can add to an existing random map script which turns it into a Battle Royale like map (with our custom weak storm). Or create ZR maps and place the animals that spawn the storm directly in the scenario file.
Excursus: ZR maps
Regular random map scripts are just text – in a strange markup format, yes, but still just text. They define everything about how the map shall look in vague generic terms. Their goal is to procedurally generate maps that look similar, but not the same.
ZR maps are different: They usually contain a scenario file that exactly defines the positions of terrains, possibly some other objects, and the starting positions of players, and a slimmed down random map script that defines all the rest in the usual vague terms, bringing variation onto the scenario. Unlike in regular scenarios, Triggers are ignored in ZR maps, so we can’t do shenanigans with those unfortunately.
Automation Is King3
The first option we had was tempting: The grandmaster of Age of Empires 2 Battle Royale maps, HenkDeSuperNerd, already had a snippet ready that could supposedly add the Battle Royale ring of decaying animals around any map. It was largely untested though and the effects it could have on the map generation were not investigated yet.
In contrast, the mechanics of ZR maps are well understood and controllable. For this reason, we went with the second option, which seemed to be the safer one regarding the time frame of less than a month.
Our process looked as follows:
- Select maps
- Adapt maps (like removing some spawn conditions for certain resources or removing animals)
- Add features: Custom regicide, Nerfs (for defensive buildings and civilisations), Storm constants
- Split script into Scenario and RMS part (one script to generate the scenario file from, one script to put into the final ZR map)
- Generate scenario file
- Add storm to scenario
- Create ZR map from scenario and RMS part
Having to do that for ten or more maps is tedious work. Also, at the time we did not even know which maps would be selected in the end. Without automation, that would have been a few unhappy hours of monotonous error-prone work for a few of us. And if we wanted to change something, we would have to do many of the steps again.
We had to change a lot of things. More on that in a bit.
Going through the above steps with each new iteration would have been an absolute pain. Luckily, we were able to automate some steps:
I created a python script that automatically patched the code for our desired custom features into the maps that we had selected and adapted manually beforehand. The script also separated the sections of the rms that were needed for the scenario generation from the sections that were to be put into the ZR map.
The scenario generation had to be done by hand unfortunately. Since the scenario files are static and we desire fair starting positions for all players, a manual review would be needed anyway before continuing. Luckily, the rms part for generating scenarios does usually not change at all while testing custom map features, so we could generate those once and then use them again and again in the final steps.
Adding the storm spawners to the scenarios was a bit tricky. Our first approach was to manually “paint” a zone of terrain around the generated scenarios on which the map script would then place the storm spawners, like it is done in the existing Battle Royale maps. That would have meant monotonous repetetive work.
Luckily though, we had a breakthrough after a few days: I was able to use the agescx python library to modify the generated scenario programmatically! What would have taken at least multiple minutes per map before was now a matter of seconds. And not only that: We were suddenly able to place arbitrary objects at arbitrary positions on the map. Something that can hardly be achieved in random map scripts alone. We basically had full control. I wrote a second python script that created copies of our scenarios and modified them to our specifications. Mainly: placing the storm spawners precisely at the edges, with a specific distance to each other.
The last part, combining the scenario and the RMS file, is also easily automated, since ZR maps are just zip files without compression that contain the scenario file and the rms file.
We finally had a semi-automated pipeline in which we could tweak a script and and produce 17 new versions of our maps within seconds. Now it was just a matter of making it all work.
It Would Be A Lot Of Fun, Were It Not For The Players
During testing on community games friday, we found out that the features we initially used had some issues. For example we tried to disable Relic victory by spawning in the Relics after the game start, by placing Priests with 0 HP that hold a relic. When the priest dies (which he does immediately), he drops the relic, and it looks like it has been there all along. Turns out that Burmese can see the locations of the relics on the map, and they converted the dead priests while they died. Due to a bug in the game, this led to the dead priests taking up population space for the Burmese player, who had to build a few extra houses at the start of the game in order to produce own units. A similar thing happened when the storm was active and players converted some of the Gaia units on the edge which we used to deal the damage. The dead units took up population space, and CaptureAge showed us the discrepancy between units and used up population space clear as day, making us wonder if we were suddenly unable to do simple maths.
The relic issue was simply fixed by disabling Relic victory in a differenty way: By just placing 504 relics into an inaccessible corner. Can’t get all relics if you can’t get to all relics! The storm however we had to revamp multiple times until we had a conversion resistant version that worked reliably.
Tweaks, Tweaks, Tweaks
To understand why the final maps look like they look, we have to dive into the issues we had along the way. Our biggest concern has always been players messing with the storm mechanic. They could disable or speed up storm spawners, intentionally or unintentionally, or break a whole lot of other things. So we put a ring on it. Or rather around it. To prevent players from accessing the storm spawners with units.
The first iteration of our storm had six waves, but did unfortunately not reliably damage units. So we switched up the storm units a bit and got it to do damage reliably, but only with three waves.
This also brought a new issue: Players were suddenly able to convert one of the gaia towers (wtf) used in the storm cycle. This led to deforestation on the map and annoying “Tower built!” noises and chat spam, making it impossible for the unfortunate player to read chat messages from the other players. That would be deadly in a game mode where communication with the players to plot against others is essential for success.
Our first attempt to solve this new issue was to move the barrier further away from the storm spawners, so that players could not get close enough to convert them anymore. Taking away more space from the already crammed map would shrink it way to much, so instead we increased the map size by ten tiles in each direction and moved the storm spawners outwards, effectively turning a “Large”map (220×220 tiles) into a “Giant” map (240×240 tiles). The resulting empty space was filled with deep water (to prevent resources like gold, stone, wood, or relics from spawning there) and stone heads on top (just for looks). I also had to fix “holes” in the map that resulted from moving the whole map down and to the right by ten tiles after increasing the map size. Looking at those “holes” for more than one second would reliably crash the game – not a good thing in general, and especially if you want to stream to an audience of 2000+ people around the world. The fix led to hills in the water, but it worked. Good enough for me!
Now that we had that “dead space” around the outside, we could also have some fun with it, and write the name of the event with haystacks or something silly like that. We also placed three rings of torches on the map that indicated how far each “storm wave” would reach, since the storm currently lacks a visual indicator (apart from slowly killing units I guess).
Sadly, ten tiles is not enough to prevent for example a Cannon Galleon with a line of sight of 15 or a Trebuchet with a line of sight of 18 from converting those damn towers. In the end, we managed to fix the issue by replacing the bear in the damage cycle with a macaw. Nobody knows exactly why that worked, but it worked! Good enough for me.
The last week before the event was spent fixing small issues in the map scripts (our step 2), like resources not spawning in the center island on Migration, and adding a large patch of wood in the center of the scenario files of Grand Bara, Chaos Pit, Steppe, and King of Kings, to prevent players from running out of wood if the game goes long. We even managed to fit Pilgrims with a custom storm layout that only storms from two of the four sides, and has storm spawners on the eight starting islands in the corner of the map.
The morning before the start of the event, somebody noticed that players start with five out of five population after we had removed the spawns of extra villagers and houses the night before. This meant that players cannot start creating villagers right away, but would have to build a house first – a big deviation from the usual build order at the start of the game. That issue could luckily be fixed manually by replacing the scout with a horse, which does not take up population space, bringing it down to four out of five with room for one more. That was a (preventable) close call, but it worked out in the end.
Before we go into what could be done better I would like to thank the whole team of Regicide Rumble 3. It was a pleasure working with so many motivated people of different skill levels across time zones. It’s quite funny when you wake up at eight in the morning to a new storm mechanic suggestion and reply with “I will try that out after work in about 10-12 hours, thanks”. Together, we did things I think had never been done before. Certainly, somebody will prove me wrong about that in the comments.
Light And Shadow
I used a git repository on GitHub for managing the scripts that were used. Sadly, not many in the map making community are proficient with git, so we could not use that directly to collaborate. Also, the python scripts were probably not usable for non-programmers. Making the whole process more accessible for non-programmers would be desirable.
During the whole time, I was the only one to have access to the unpatched scenario files since they were in the non-versioned output folder. That turned out to bite us when we added the clumps of wood in the centers of some scenario files. Motivated team members edited what they had access to – the patched scenario files from the ZR maps. Those were then not suitable to use in the pipeline because they already contained the storm spawners and torches. The work and had to be redone with the plain scenario files. The unpatched scenarios should definitely have been versioned and published in the git repository.
Also, due to my shallow understanding of what to look for when generating the scenario files, some maps had generations that were let’s say not ideal in the end.
Finally, it would probably have been sufficient to only place the storm spawners and torches in the scenario files, since we got the storm pretty tamper-proof. No stone barriers needed, no extra large maps with pixelated text on the border necessary.
You live and you learn. The Kings are dead, long live the Snipers!
- Regicide is the game mode where you lose the game if your King unit dies. See also. ↩
- not thin air exactly, since the base scripts are there already, but still a lot of work to be done ↩
- An obvious pun to make when writing about Regicide. ↩
- In this case, 50 was an arbitrarily selected “high enough” number. ↩