Running a Server

Killing Floor: Zed Count per Wave, MaxZombiesOnce and Squad Spawn Delay

A server owner on killingfloor.ru asked in 2016 how to make zeds spawn more and faster without touching the sandbox settings: a higher MaxZombiesOnce and a shorter delay between squads. Flame answered with a walk-through of KFGameType.SetupWave() and the spawn timer. This page summarises it.

Killing Floor specimens from Clot to Patriarch, shown in two texture versions The Killing Floor specimen roster, from Clot to Patriarch, in two texture versions. Every one of them counts towards the limits below.

Two different limits

VariableMeaning
TotalMaxMonstersHow many zeds the wave contains in total (not at once)
MaxMonstersHow many zeds can be alive at the same time. In practice it equals MaxZombiesOnce

During a wave the game keeps spawning until the number of live zeds reaches MaxMonsters. You kill a zed, the count drops, a new one spawns. This continues until the wave's total is used up.

How SetupWave counts zeds

  1. It takes WaveMaxMonsters for the current wave from the Waves array:
Waves[0]=(WaveMask=2013265920,WaveMaxMonsters=20,WaveDuration=180,WaveDifficulty=3.000000)
  1. It multiplies that by a difficulty modifier:
DifficultyGameDifficultyModifier
Beginner10.7
Normal2+1.0
Hard, Suicidal4, 51.3
Hell on Earth71.7

The code comment still labels the 7+ branch "Suicidal": it predates Hell on Earth.

  1. And by a player-count modifier (players plus bots):
Players123456more
Modifier122.753.544.5players × 0.8
  1. The result is clamped:
NewMaxMonsters = NewMaxMonsters * DifficultyMod * NumPlayersMod;
TotalMaxMonsters = Clamp(NewMaxMonsters, 5, 800);
MaxMonsters = Clamp(TotalMaxMonsters, 5, MaxZombiesOnce);

Clamp(x, min, max) keeps x inside the range. Two conclusions:

  • Stock KF never puts more than 800 zeds in one wave, whatever WaveMaxMonsters says. To lift that you have to change the constant in your own game type's SetupWave(). The SRGameType from the thread used Clamp(NewMaxMonsters,5,1100).
  • TotalMaxMonsters is almost always larger than MaxZombiesOnce, so MaxMonsters ends up equal to MaxZombiesOnce.

Example: 6 players on Hell on Earth with WaveMaxMonsters=20 give 20 × 1.7 × 4.5 = 153 zeds in the wave, at most MaxZombiesOnce of them alive at once.

Where each setting must go

  • Waves is a config array declared in Invasion. Every game type derived from it reads its own section, so if KillingFloor.ini has a [ServerPerks.SRGameType] section with Waves lines, those win.
  • MaxZombiesOnce is globalconfig in KFGameType. It is read only from [KFMod.KFGameType]. Setting it in [ServerPerks.SRGameType] has no effect.
[KFMod.KFGameType]
MaxZombiesOnce=64

The sandbox UI does not let you go above 64, but the ini value can be higher. Think twice before you do: players with weak PCs start to lag, and by wave 6 or so the load builds up into all kinds of desync (missing weapons, invisible zeds). 64 is a sensible ceiling, and faster squad spawns are usually the better lever.

The delay between squads

WaveStartSpawnPeriod comes up in every such discussion, but according to Flame's reading of the code it is not used anywhere. The real delay sits in Timer() of the MatchInProgress state, in the bWaveInProgress branch:

if ( nextSpawnSquad.length > 0 )
    NextMonsterTime = Level.TimeSeconds + 0.2;
else
    NextMonsterTime = Level.TimeSeconds + CalcNextSquadSpawnTime();

While a squad is still being placed, zeds come every 0.2 s. Between squads the pause comes from CalcNextSquadSpawnTime(), about 1.5 s on average on Hell on Earth. To make spawning almost continuous, override the state in your game type and use a constant:

state MatchInProgress
{
    function Timer()
    {
        // copy the whole stock KFGameType.MatchInProgress.Timer() body here,
        // then replace the NextMonsterTime branch with:
        NextMonsterTime = Level.TimeSeconds + 0.2;
    }
}

Copy the stock Timer() body in full and change only that line. You do not need to copy the other functions of the state.

What CalcNextSquadSpawnTime() does

Flame posted the stock function KFMod.KFGameType.MatchInProgress.CalcNextSquadSpawnTime() in the killingfloor.ru forum thread "Популяция монстров" (how to make zeds come out of the spawners faster when MaxZombiesOnce is raised to 64). The base value is KFLRules.WaveSpawnPeriod, which is then multiplied by player-count factors:

PlayersEarly wavesLater waves
3 or fewer1.0 (unchanged)1.1
40.851.0
50.650.75
6 or more0.30.60

"Early waves" are WaveNum < 2 for a short game, WaveNum < 4 for a normal one and WaveNum < 7 for a long one. For a custom-length game (GL_Custom) the early-wave factors (0.85 / 0.65 / 0.3) apply to all waves. On Hard and above (GameDifficulty >= 4.0) the time is additionally multiplied by 0.85. Finally a sine wave adds "peaks and valleys" to the spawn intensity:

SineMod = 1.0 - Abs(sin(WaveTimeElapsed * SineWaveFreq));
...
NextSpawnTime += SineMod * (NextSpawnTime * 2);

So the variable to change is var() float SineWaveFreq;, which controls the frequency of this sine wave and with it the speed and intensity of zed spawning.

WaveCountDown is something else entirely: it is the trader-time countdown between waves, not the spawn delay.

Summary

GoalWhere
Zeds alive at onceMaxZombiesOnce in [KFMod.KFGameType]
Zeds per waveWaveMaxMonsters in Waves, in your game type's section
More than 800 per waveChange the clamp in your own SetupWave()
Faster squadsOverride MatchInProgress.Timer() in your game type
Time between wavesTrader time, not the spawn code

FAQ

Why does MaxZombiesOnce in [ServerPerks.SRGameType] do nothing?
MaxZombiesOnce is declared globalconfig in KFGameType, so it is only read from the [KFMod.KFGameType] section of KillingFloor.ini, whatever game type the server runs.
Does WaveStartSpawnPeriod change the delay between zed spawns?
According to the code review on killingfloor.ru, no: the variable is not used anywhere in the game code. The delay between squads comes from CalcNextSquadSpawnTime().