Somebody Set Up Us The Invasion

I like Invasion as a gametype, in which you and your team are deposited on some arbitrary DeathMap to fend off wave after wave of unreal-style monsters intending to gut you. There’s a brief respite between waves to let you catch your breath and glug a well-needed slug from the nerve-calming beercan until the next salvo begins in earnest, increasing in difficulty (either in monster breed, their intelligence/toughness or simply the sheer quantity of the damn things) until level 16 kicks in and everything turns batshit hatstand crazy.

It’s a liberating experience. Really.

There are various guides discussing game strategies and player etiquette for Invasion, so I won’t bore you with those – other than reveal we use InstaGib so it’s a one-shot kill, but ya still gots ter be accurate there, Officer Dahn. However, I sneaked a look at the configuration file that determines what monsters should appear and when, as well as how the game ramps up the difficulty to leave you shaking, sweating and swearing (not necessarily in that order).

The key lines in the UT2004.ini file are:

[Skaarjpack.Invasion]
InitialWave=8
FinalWave=16
Waves[0]=(WaveMask=20491,WaveMaxMonsters=16,WaveDuration=90,WaveDifficulty=0.000000)
Waves[1]=(WaveMask=60,WaveMaxMonsters=12,WaveDuration=90,WaveDifficulty=0.000000)
Waves[2]=(WaveMask=105,WaveMaxMonsters=12,WaveDuration=90,WaveDifficulty=0.000000)
Waves[3]=(WaveMask=186,WaveMaxMonsters=12,WaveDuration=90,WaveDifficulty=0.500000)
Waves[4]=(WaveMask=225,WaveMaxMonsters=12,WaveDuration=90,WaveDifficulty=0.500000)
Waves[5]=(WaveMask=966,WaveMaxMonsters=12,WaveDuration=90,WaveDifficulty=0.500000)
Waves[6]=(WaveMask=4771,WaveMaxMonsters=12,WaveDuration=120,WaveDifficulty=1.000000)
Waves[7]=(WaveMask=917,WaveMaxMonsters=12,WaveDuration=120,WaveDifficulty=1.000000)
Waves[8]=(WaveMask=1689,WaveMaxMonsters=12,WaveDuration=120,WaveDifficulty=1.000000)
Waves[9]=(WaveMask=18260,WaveMaxMonsters=12,WaveDuration=120,WaveDifficulty=1.000000)
Waves[10]=(WaveMask=14340,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=1.500000)
Waves[11]=(WaveMask=4021,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=1.500000)
Waves[12]=(WaveMask=3729,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=1.500000)
Waves[13]=(WaveMask=3972,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=2.000000)
Waves[14]=(WaveMask=3712,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=2.000000)
Waves[15]=(WaveMask=2048,WaveMaxMonsters=8,WaveDuration=255,WaveDifficulty=2.000000)

Some of those settings are fairly obvious: InitialWave is the starting wave, but is zero-based whereas the players see a one-based count.  Hence a value here of 0 means Waves[0], but the players are told “Wave 1” – consequently 15 is the maximum value (players seeing “Wave 16” as the last wave).

Playing that many waves on the same map grew boring after a bit, so initially the starting wave was set to 4 on the server (I normally play from 9-16 when practising, skipping the easier levels and just doing the last 8) but now the game has been reduced to just 7 waves: players start from “Wave 1” and must survive “Wave 7“, i.e. the lines Wave[0]Wave[6] being the important ones.

But what are those Wave[ ] things? They contain magic values that determine – for each wave – just how hard and long the wave is, and what monsters attack. Or at least it appears to be, but appearances can be deceptive.  Let’s deconstruct those values:

  • WaveMask – a decimal representation of what monsters appear
  • WaveMaxMonsters – influences how many monsters can be on the screen at any one time
  • WaveDuration – modifier that influences the overall time for this wave
  • WaveDifficulty – modifies just how tough the monsters are

WaveMask

This is a 16-bit binary value, each bit representing a specific monster. The Unreal Wiki tries to explain it, but it’s sadly lacking – it’s been created from the (undocumented/poorly-commented) source code so is somewhat terse, and the bitmask values were actually incorrect (I’ve updated the Wiki now.. or rather, I tried to).

So, a deeper search into the source code reveals the information about the bitmapped fields corresponding to each monster class:

MonsterClass(0)=Class'SkaarjPack.SkaarjPupae'
MonsterClass(1)=Class'SkaarjPack.Razorfly'
MonsterClass(2)=Class'SkaarjPack.Manta'
MonsterClass(3)=Class'SkaarjPack.Krall'
MonsterClass(4)=Class'SkaarjPack.EliteKrall'
MonsterClass(5)=Class'SkaarjPack.Gasbag'
MonsterClass(6)=Class'SkaarjPack.Brute'
MonsterClass(7)=Class'SkaarjPack.Skaarj'
MonsterClass(8)=Class'SkaarjPack.Behemoth'
MonsterClass(9)=Class'SkaarjPack.IceSkaarj'
MonsterClass(10)=Class'SkaarjPack.FireSkaarj'
MonsterClass(11)=Class'SkaarjPack.WarLord'
MonsterClass(12)=Class'SkaarjPack.SkaarjPupae'
MonsterClass(13)=Class'SkaarjPack.SkaarjPupae'
MonsterClass(14)=Class'SkaarjPack.Razorfly'
MonsterClass(15)=Class'SkaarjPack.Razorfly'

From there, we can deduce that WaveMask is a 16-bit value, but really only 12 are used. So let’s convert them to binary values:

Wave number WaveMask (decimal) WaveMask (binary) Monsters
1 20491 (11) 0101 0000 0000 1011 Pupae, RazorFly, Krall
2 60 0000 0000 0011 1100 Manta, Krall, EliteKrall, Gasbag
3 105 0000 0000 0110 1001 Pupae, Krall, Gasbag, Brute
4 186 0000 0000 1011 1010 Razorfly, Krall, EliteKrall, Gasbag, Skaarj
5 225 0000 0000 1110 0001 Pupae, Gasbag, Brute, Skaarj
6 966 0000 0011 1100 0110 Razorfly, Manta, Brute, Skaarj, Behemoth, IceSkaarj
7 4771 (675) 0001 0010 1010 0011 Pupae, Razorfly, Gasbag, Skaarj, IceSkaarj
8 917 0000 0011 1001 0101 Pupae, Manta, EliteKrall, Skaarj, Behemoth, IceSkaarj
9 1689 0000 0110 1001 1001 Pupae, Krall, EliteKrall, Skaarj, IceSkaarj, FireSkaarj
10 18260 (1878) 0100 0111 0101 0100 RazorFly, Manta, EliteKrall, Brute, Behemoth, IceSkaarj, FireSkaarj
11 14340 (2053) 0011 1000 0000 0100 Pupae, Manta, WarLord
12 4021 0000 1111 1011 0101 Pupae, Manta, EliteKrall, Gasbag, Skaarj, Behemoth, IceSkaarj, FireSkaarj, WarLord
13 3729 0000 1110 1001 0001 Pupae, EliteKrall, Skaarj, IceSkaarj,
FireSkaarj, WarLord
14 3972 0000 1111 1000 0100 Manta, Skaarj, Behemoth, IceSkaarj, FireSkaarj, Warlord
15 3712 0000 1110 1000 0000 Skaarj, IceSkaarj, FireSkaarj, WarLord
16 2048 0000 1000 0000 0000 Warlords only

So, how is this decoded? Let’s take an example…. suppose a level had WaveMask=5;

  • as a binary mask, this would be 0101 (binary 100 + binary 1)…
  • this means bits number 2 and 0 are on (not bits 1 and 3 – it’s zero-based, not 1-based)…
  • so you’d be up against MonsterClass(2) and MonsterClass(0),
  • meaning you’d expect the wave to consist of Manta and Pupae (actually SkaarjPupae, but let’s refrain from discussing their disturbing reproductive cycles)

Simples when you know how, really!

Alternatively, if you wanted to go up against loads of Skaarj, you’d want bits 10, 9 and 7 on – a binary mask of 110 1000 0000, or in decimal: 2^10 + 2^9 + 2^7 = 1024 + 512 + 128, meaning WaveMask=1664 ought to do the trick.  We’ve added this to the server, known as the “Skarrj Dance”, discussed later…

Given that there are only 12 monster types, the most significant 4 bits (2^12 – 2^15, or bits 13, 14, 15 and 16) appear unnecessary, so the maximum value you’d expect would be if all 12 monsters were in play. If all bits were on, it’d simply be one less than the next bitmask value up, i.e.: 2^12 – 1 = 4096 – 1 = 4095. So what’s with the higher values?

Because Pupae are bit 0, 12 and 13, there are some redundant settings:

  • a binary mask of 0000 0000 0000 0001, 0001 0000 0000 0000 and 0010 0000 0000 0000 have an identical affect, as are combinations of those bits, for example: 0011 0000 0000 0001
  • hence WaveMasks of 4097, 8192, and 12289 are all identical to WaveMask=1, death by hopping chomping bugs.

A similar situation holds for RazorFly: bits 1, 14 and 15 all have the same meaning, so buzzy gnat death can be achieved via a WaveMask of 32768, 16384, 49152 .. or just “2”.

And this is where the Invasion level editor unnecessarily over-complicates things. The first wave contains Pupae, Flies and Krall: 1011 binary, or in decimal: 8 + 2 + 1 = 11… but the editor also decides Pupae should have bit 12 also set, meaning it writes WaveMask=20491 instead of the more simple WaveMask=11. It’s also the same for RazorFlies: bit 1 has the same effect of flicking on bit 14.

Go figure. Oh, you’ll work it out eventually.

 

WaveMaxMonsters

It sounds like this parameter sets the upper limit of monsters appearing in the wave, but actually the code uses this to determine how many monsters can be on the screen at any one time:

NewMaxMonsters = Waves[WaveNum].WaveMaxMonsters;

if ( NumPlayers + NumBots <= 2 )
         NewMaxMonsters = NewMaxMonsters * (FMin(GameDifficulty,7) + 3)/10;
if ( NumPlayers > 4 )
         NewMaxMonsters *= FMin(NumPlayers/4,2);

MaxMonsters = NewMaxMonsters;

The important note here is that the maximum monster limit is also dependent upon the number of players, too:

  • two or less players (including bots) means the number of monsters is scaled down, depending upon the difficulty – down to a possible 30% of total monsters. This means smaller parties won’t be completely overwhelmed.
  • above four (human) players, the total amount of on-screen monsters is scaled up – to a possible 200% – depending upon the number of players.

This is actually a nice tweak – it means there won’t be a large amount of players all vying to kill the single legless Krall, nor will the total membership of Skanky Shockball Steve And His Supportive Skaarj Posse suddenly descend en-mass upon a single bemused player.

The flipside is that skilled players find they’ve got more work on their hands if novice players die early – someone needs to take up the slack.  But that’s what you signed on for – right, soldier?

Check the Source code, for those nerds who are bored of Star Trek re-runs.

WaveDuration

Sounds like it relates to how long each wave is, but again this is a modifier that increases the duration from the base setting. Geeks can take a look at the source code here:

WaveEndTime = Level.TimeSeconds + Waves[WaveNum].WaveDuration;

So each level takes the same amount of minimum time; this value simply pads it out longer to draw out the agony as waves progress. Wait – duration of what? Seems it doesn’t mean the duration of the wave itself but refers to how long monsters will keep respawning for, so canny players can ride out the storm by hiding for some time then picking off the remainders at leisure.

WaveDifficulty

A value from 0 – 3, but again, this is simply an influencer: the core difficulty is set by the Monster skill level, and WaveDifficulty is a small multiplier that makes the same monsters appear much tougher in later levels:

AdjustedDifficulty = GameDifficulty + Waves[WaveNum].WaveDifficulty;

Generally, these last values increase for successive levels simply to make them appear to go on longer and be tougher, adding on another 50%, then 100% (doubling the difficulty), then 150% and finally 200%!  Oh, go look for yourself.

 

Custom Wave Settings

So, with this in mind… let’s look at how to improve Invasion gameplay a bit.

The first – and most obvious tweak – is the duration: reduce the number of levels.  Originally I set InitialWave to 4, meaning 12 levels, but this still made for a long game (compared to other gametypes).  I then shifted the InitialWave to 9, so players encountered the “second half” only, but earlier (and easier) waves allowed players to get into their zone and build up some adrenaline ready for a health boost when needed; jumping in at such a late point meant too many wipe-outs before any chance of settling into the game.

That’s where the second tweak came in: starting at wave 9 doesn’t mean wave 9’s difficulty, so the WaveMask needs adjusting.  So I designed 8 waves but after initial feedback that the game began very languid then ramped up quickly, I decided to rethink this slightly, and arrived at a different approach: pick 7 levels, in which each level is memorable for a specific reason.  And came up with:

[Skaarjpack.Invasion]
InitialWave=0
FinalWave=6
Waves[0]=(WaveMask=7,WaveMaxMonsters=16,WaveDuration=90,WaveDifficulty=0.700000)
Waves[1]=(WaveMask=102,WaveMaxMonsters=12,WaveDuration=100,WaveDifficulty=1.000000)
Waves[2]=(WaveMask=200,WaveMaxMonsters=12,WaveDuration=120,WaveDifficulty=1.000000)
Waves[3]=(WaveMask=1664,WaveMaxMonsters=12,WaveDuration=140,WaveDifficulty=1.500000)
Waves[4]=(WaveMask=2864,WaveMaxMonsters=12,WaveDuration=160,WaveDifficulty=1.500000)
Waves[5]=(WaveMask=4095,WaveMaxMonsters=12,WaveDuration=180,WaveDifficulty=2.000000)
Waves[6]=(WaveMask=2048,WaveMaxMonsters=12,WaveDuration=200,WaveDifficulty=2.000000)

Okay, it doesn’t completely match that…. but you get the idea. The waves are simply:

  1. “bite me” – pupae, razorflies, mantas… irritating things that chase after you but nothing that shoots – you can simply run away.  This allows you to get your bearings and navigate the map, but don’t expect to stand still taking in the sights else you’ll be stung, bitten and leg-humped.  The difficulty is substandard, making it an easy level.
  2. same as the earlier wave but with a couple of easy shooters:  I opted for gasbags (their slow-moving flaming shots are easy to dodge) and krall, then replaced krall with brutes as they’re slow-walking and don’t run after the player.  I then dropped the pupae as it was too much like the earlier level with standard difficulty.
  3. “shooters” – I had two shooter levels, one with simpler beasts (krall, brutes, skaarj) and the latter with their more superior counterparts (elite krall, behemouth, ice skaarj). I dropped the non-shooters from here (pupae/razorflies/mantas) as those return later, then decided to drop the gasbags as they were encountered on the last level.
  4. “Skaarj Dance” – Skaarj, ice skarrj and fire skarrj.  The playfield is pretty much lit up with shock balls which are not too difficult to dodge, but permit too many skarrj to spawn and it’s “Run, Forrest! Run!” time.
  5. “return of the shooters” – elite krall, behemouth, ice skaarj so it’s a notch up from the original “shooters” level… but also added gasbags.  This is also the first apparance of warlords, so anything that can shoot at you will.
  6. “mayhem”.  This is every monster, so dangers arrive not only from the more obvious shooters but also the biters that players are likely to run into (or get stung whilst they’re trying to concentrate on hitting a distant shooter).
  7. “warlord death” – the standard ending level, nothing but warlords giving you a rocket to the knee or a swat to the face.  You can run, you can hide… but you’ll soon hear their mocking laughter.

So, why not have mayhem as the last level? Well… what would you rather face: 10 warlords, or 4 warlords and 6 pupae?  Mayhem is easier as there is a range of monsters to pick when choosing which to spawn; in the last level the game will always pick a warlord to spawn.  I wanted a mayhem level with everything thrown at the player, and it being the penultimate means (a) a chance to rack up kills, and (b) if you don’t survive mayhem, you’ve no chance when it’s just warlords on the scene.

I also added warlords to level 5, as having them on the last two levels only doesn’t give the player time to prepare.  Plus, they reward the most adrenaline and are the largest target to hit, so adding them earlier benefits the player somewhat.

In keeping with the original settings, I also biased the durations and difficulty so each wave gets slightly longer and creeps up in difficulty: once you’re past the first three levels, it gets to be a challenge.

So.. can you survive? Make your time!

Leave a Reply

Your email address will not be published. Required fields are marked *