As your player levels up and gets stronger and stronger, enemies will naturally get easier and easier to beat.
Increasing the enemy difficulty dynamically based on the player's level can be a delicate thing to balance, but if pulled off can be used to help maintain the pace of the game.
There's a lot that can be done to increase difficulty this way. We'll be doing three different changes:
- Make it so that enemies will deal slightly increased damage depending on the player's current level.
- Enemies will be more accurate as the player's level increases.
- If the player enters a map when level 5 or higher, all Dog enemies will be replaced with Guard enemies.
Enemy accuracy and damage
Two of our changes will occur in WL_ACT2, in the T_Shoot() function. This function activates when the enemy attempts to shoot a player. It calculates distance from the player and then uses that to work out if the player is hit and for how much. We can see a reference to accuracy in:
if (ob->obclass == ssobj || ob->obclass == bossobj)
dist = dist*2/3; // ss are better shots
This small snippet of code is used to modify the value determined when calculating distance, to make the SS Enemy and Hans Grosse boss more accurate when shooting (Gretel isn't as good at shooting as her brother!)
We can use this as a base for calculating our own modification. We want all shooting enemies to be slightly more accurate with each level the player attains. So, under the above snippet, we'll add a further modifier:
dist = dist*2/gamestate.lvl+1;
You might want to tweak the values to balance the game (You wouldn't want it to get too hard!), but this is an example of a way to directly affect the accuracy of enemies.
Down further in the same function, we can see where damage is being calculated:
// see if the shot was a hit
if (US_RndT()<hitchance)
{
if (dist<2)
damage = US_RndT()>>2;
else if (dist<4)
damage = US_RndT()>>3;
else
damage = US_RndT()>>4;
TakeDamage (damage,ob);
}
A random number is calculated based on how far away the enemy is and assigned to the damage variable, and then that number is being sent to the TakeDamage() function. We'll modify that value before it gets sent:
// see if the shot was a hit
if (US_RndT()<hitchance)
{
if (dist<2)
damage = US_RndT()>>2;
else if (dist<4)
damage = US_RndT()>>3;
else
damage = US_RndT()>>4;
damage += gamestate.lvl+1;
TakeDamage (damage,ob);
}
In that line, we're adding the player's level as a straight damage modifier. So at level 1, we're seeing an extra damage. At level 10, the player will take an extra 10! When taking multiple shots from an SS enemy this could be deadly!
Enemy mutation
Our last change will be enemy mutation - at higher levels, we want to replace the "easy" enemies with harder ones. In our case, the plan is:
- While the player is under level 5, enemies will spawn normally.
- When the player reaches level 5, Guard enemies will be replaced with Officers.
- At max level (Currently 10), Guard enemies will instead all be replaced with Mutants.
To create this change, we'll want to go to the ScanInfoPlane() function in WL_GAME.
When the game loads a level, ScanInfoPlane() runs through all the tiles on the map and depending on the value of that tile, will place an object or enemy.
For example, we can see that tiles of value 19 through 22 will spawn the Player:
case 19:
case 20:
case 21:
case 22:
SpawnPlayer(x,y,NORTH+tile-19);
break;
Down further, we can see the groups of values that make up the Guard enemy spawns:
//
// guard
//
case 180:
case 181:
case 182:
case 183:
if (gamestate.difficulty<gd_hard)
break;
tile -= 36;
case 144:
case 145:
case 146:
case 147:
if (gamestate.difficulty<gd_medium)
break;
tile -= 36;
case 108:
case 109:
case 110:
case 111:
SpawnStand(en_guard,x,y,tile-108);
break;
case 184:
case 185:
case 186:
case 187:
if (gamestate.difficulty<gd_hard)
break;
tile -= 36;
case 148:
case 149:
case 150:
case 151:
if (gamestate.difficulty<gd_medium)
break;
tile -= 36;
case 112:
case 113:
case 114:
case 115:
SpawnPatrol(en_guard,x,y,tile-112);
break;
These are the tile values for both the standing guard and patroling guard for each difficulty and direction they could face.
We want the game to take into account the level of the player before choosing which enemy to spawn. So, we will make it do a check:
case 108:
case 109:
case 110:
case 111:
if (gamestate.lvl >= 4 && gamestate.lvl < 9)
SpawnStand(en_officer,x,y,tile-108);
else if (gamestate.lvl == 9)
SpawnStand(en_mutant,x,y,tile-108);
else
SpawnStand(en_guard,x,y,tile-108);
break;
Remember, the in-game level displayed is one greater than what it is in the code (Level 1 is '0', Level 8 is '7'). So what we're doing here is saying:
- If the player is a level between 5 and 9, spawn an Officer instead of a guard.
- If instead the player's level is equal to 10 (Which in the code is '9', since the first level is '0'), spawn mutants.
- If neither of those are true (So, our player is a level below 5), spawn a regular Guard.
You'll want to make similar changes to the Patrol spawns for consistency.
There are many more ways you could scale difficulty - maybe you could have enemy health increase too (Though, you'd want to balance it to ensure things don't get too hard), or with some more changes you could have enemies start firing projectiles!