1548 lines
52 KiB
Ucode
1548 lines
52 KiB
Ucode
//=============================================================================
|
|
// KFAISpawnManager_Versus
|
|
//=============================================================================
|
|
// The KFAISpawnManager for Versus Survival mode
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2016 Tripwire Interactive LLC
|
|
// - Matt "Squirrlz" Farber
|
|
//=============================================================================
|
|
class KFAISpawnManager_Versus extends KFAISpawnManager
|
|
within KFGameInfo_VersusSurvival;
|
|
|
|
/** Player zed spawn groups for this wave */
|
|
var protected array<KFAISpawnSquad> PlayerZedSquads;
|
|
|
|
/** Player zed wave archetypes */
|
|
var const array<KFAIWaveInfo> PlayerZedWaves;
|
|
|
|
/** Time between player zed group spawns, set per-wave */
|
|
var array<float> PlayerZedSpawnInterval;
|
|
|
|
/** How long to wait at the start of a wave before spawning player zeds */
|
|
var float FirstWaveStartSpawnWaitTime;
|
|
var float LaterWaveStartSpawnWaitTime;
|
|
|
|
/** When TRUE, special squad for this wave has been spawned */
|
|
var bool bSpawnedSpecialSquad;
|
|
|
|
/** How much of a chance the spawner has of choosing the special squad this spawn cycle */
|
|
var float SpecialSquadRandomChance;
|
|
|
|
/** MAXIMUM number times the special squad can be spawned per wave */
|
|
var array<int> MaxPlayerSpecialSquadSpawns;
|
|
|
|
/** How many times the special squad has been spawned this wave */
|
|
var int NumPlayerSpecialSquadSpawns;
|
|
|
|
/** How much to add to the random chance of spawning the special squad each spawn cycle */
|
|
var float AdditionalSpecialSquadChancePerSpawn;
|
|
|
|
/** Array of zeds reserved for players to spawn as */
|
|
var protected array<class<KFPawn_Monster> > ReservedPlayerZeds;
|
|
|
|
/** The reserved zed queue limit */
|
|
var const int ReservedZedQueueLimit;
|
|
|
|
/** Maximum number of player-controlled Scrakes allowed at once */
|
|
var const int MaxActivePlayerScrakes;
|
|
|
|
/** Maximum number of player-controlled Fleshpounds allowed at once */
|
|
var const int MaxActivePlayerFleshpounds;
|
|
|
|
/** Number of player Scrakes that have been queued up to spawn this cycle */
|
|
var protected int NumScrakesThisSpawnCycle;
|
|
|
|
/** Number of player Fleshpounds that have been queued up to spawn this cycle */
|
|
var protected int NumFleshpoundsThisSpawnCycle;
|
|
|
|
/** TRUE when boss has been spawned in Versus */
|
|
var protected bool bBossSpawned;
|
|
|
|
/** How long to wait between spawn cycles during the boss round */
|
|
var protected float BossSpawnPlayerInterval;
|
|
|
|
/** The interval between spawn attempts when we're down to only one survivor */
|
|
var protected float FinalSurvivorBossSpawnPlayerInterval;
|
|
|
|
/** The largest squad size in this wave */
|
|
var protected int LargestSquadSize;
|
|
`if(`notdefined(ShippingPC))
|
|
/** TRUE if we are using simulated players */
|
|
var bool bUsingAISimulatedPlayers;
|
|
|
|
/** Number of players we're simulating */
|
|
var int AISimulatedPlayerNum;
|
|
`endif
|
|
|
|
/** We need to add our reserved zeds to the alive count */
|
|
function int GetAIAliveCount()
|
|
{
|
|
return super.GetAIAliveCount() + ReservedPlayerZeds.Length;
|
|
}
|
|
|
|
/** Next wave's basic setup */
|
|
function SetupNextWave(byte NextWaveIndex, int TimeToNextWaveBuffer = 0)
|
|
{
|
|
local int i, j, SquadZedCount;
|
|
local array<KFAISpawnSquad> SquadList;
|
|
local KFPlayerControllerVersus KFPCV;
|
|
local float SpawnWaitTime;
|
|
|
|
super.SetupNextWave( NextWaveIndex, TimeToNextWaveBuffer );
|
|
|
|
// Get the maximum squad size count
|
|
LargestSquadSize = 0;
|
|
WaveSettings.Waves[NextWaveIndex].GetNewSquadList( SquadList );
|
|
for( i = 0; i < AvailableSquads.Length; ++i )
|
|
{
|
|
SquadZedCount = 0;
|
|
for( j = 0; j < AvailableSquads[i].MonsterList.Length; j++ )
|
|
{
|
|
SquadZedCount += AvailableSquads[i].MonsterList[j].Num;
|
|
}
|
|
|
|
if( SquadZedCount > LargestSquadSize )
|
|
{
|
|
LargestSquadSize = SquadZedCount;
|
|
}
|
|
}
|
|
|
|
// Set up player zed squad list
|
|
if( NextWaveIndex < PlayerZedWaves.Length )
|
|
{
|
|
PlayerZedWaves[NextWaveIndex].GetNewSquadList( PlayerZedSquads );
|
|
|
|
// Set initial spawn timer
|
|
SpawnWaitTime = NextWaveIndex == 0 ? FirstWaveStartSpawnWaitTime : LaterWaveStartSpawnWaitTime;
|
|
SetTimer( SpawnWaitTime, false, nameOf(Timer_SpawnPlayerZeds), self );
|
|
MyKFGRIV.SetPlayerZedSpawnTime( SpawnWaitTime, false );
|
|
}
|
|
else
|
|
{
|
|
// Set our boss pawn spawn timer
|
|
SetTimer( 5.f, false, nameOf(Timer_SpawnPlayerZeds), self );
|
|
|
|
// Disable the spawn timer on clients
|
|
MyKFGRIV.SetPlayerZedSpawnTime( 255, false );
|
|
}
|
|
|
|
// Clear out any pending zed spawn info
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
KFPCV.PlayerZedSpawnInfo.PendingZedSpawnLocation = vect(0,0,0);
|
|
}
|
|
|
|
// Reset special squad spawn properties
|
|
bSpawnedSpecialSquad = false;
|
|
SpecialSquadRandomChance = 0.1f;
|
|
NumPlayerSpecialSquadSpawns = 0;
|
|
}
|
|
|
|
/** Find best spawn location and spawn a squad there */
|
|
function int SpawnSquad( out array< class<KFPawn_Monster> > AIToSpawn, optional bool bSkipHumanZedSpawning=false )
|
|
{
|
|
local int NumSpawned;
|
|
|
|
NumSpawned = super.SpawnSquad( AIToSpawn, bSkipHumanZedSpawning );
|
|
|
|
// If an AI boss spawned, we shouldn't allow a player boss to
|
|
if( NumSpawned > 0 && DesiredSquadType == EST_Boss )
|
|
{
|
|
bBossSpawned = true;
|
|
}
|
|
|
|
return NumSpawned;
|
|
}
|
|
|
|
/** Spawns the next squad of players, if possible */
|
|
function Timer_SpawnPlayerZeds()
|
|
{
|
|
`if(`notdefined(ShippingPC))
|
|
local array<class<KFPawn_Monster> > AISquad;
|
|
local KFSpawnVolume KFSV;
|
|
local KFAIController KFAIC;
|
|
local int ReservedIndex, OtherRandNum, i, j;
|
|
local bool bRemoveFromSquad;
|
|
`endif
|
|
local int RandNum;
|
|
local array<class<KFPawn_Monster> > NewSquad;
|
|
local float SpawnTimer;
|
|
|
|
// Early out if takeover timer is active or spawning isn't allowed
|
|
if( IsTimerActive(nameOf(Timer_CheckForZedTakeovers), self) || !IsPlayerZedSpawnAllowed() || CheckForTakeoverTimer() )
|
|
{
|
|
// Spawn our boss pawn
|
|
if( MyKFGRI.WaveNum == MyKFGRI.WaveMax )
|
|
{
|
|
RespawnZedHumanPlayers( none );
|
|
}
|
|
else
|
|
{
|
|
MyKFGRIV.SetPlayerZedSpawnTime( 255, true );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If we have no zed players, spawn in the remaining reserved zeds
|
|
if( !HaveZedPlayers() )
|
|
{
|
|
SpawnRemainingReservedZeds();
|
|
return;
|
|
}
|
|
|
|
// Reset our big zed counts
|
|
NumScrakesThisSpawnCycle = 0;
|
|
NumFleshpoundsThisSpawnCycle = 0;
|
|
|
|
// Only do spawning if the wave isn't paused
|
|
if( WorldInfo.Game.IsInState('PlayingWave') )
|
|
{
|
|
// Repopulate squad list if we don't have any squads available to spawn (or need to spawn our special squad)
|
|
CheckForSpecialSquadSpawn();
|
|
|
|
// Generate list of classes to spawn and remove squad from possible squad list
|
|
RandNum = Rand( PlayerZedSquads.Length );
|
|
GetSpawnListFromSquad( RandNum, PlayerZedSquads, NewSquad );
|
|
PlayerZedSquads.Remove( RandNum, 1 );
|
|
`if(`notdefined(ShippingPC))
|
|
// Fake players using AI
|
|
if( bUsingAISimulatedPlayers )
|
|
{
|
|
i = AISimulatedPlayerNum;
|
|
|
|
// Subtract the number of alive simulated AI from the number we want to spawn
|
|
foreach WorldInfo.AllControllers( class'KFAIController', KFAIC )
|
|
{
|
|
if( KFAIC.bIsSimulatedPlayerController && KFAIC.Pawn != none && KFAIC.Pawn.IsAliveAndWell() )
|
|
{
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Add our reserved zeds first
|
|
for( j = 0; j < ReservedPlayerZeds.Length && i > 0; ++j )
|
|
{
|
|
AISquad.AddItem( ReservedPlayerZeds[j] );
|
|
ReservedPlayerZeds.Remove( j, 1 );
|
|
--j;
|
|
--i;
|
|
}
|
|
|
|
// Build our simulated squad
|
|
while( i > 0 && NewSquad.Length > 0 )
|
|
{
|
|
OtherRandNum = Rand( NewSquad.Length );
|
|
AISquad.AddItem( NewSquad[OtherRandNum] );
|
|
NewSquad.Remove( OtherRandNum, 1 );
|
|
--i;
|
|
}
|
|
|
|
// If we have simulated players to spawn, do it immediately
|
|
if( AISquad.Length > 0 )
|
|
{
|
|
// Change squad classes to player zed versions
|
|
for( i = 0; i < AISquad.Length; ++i )
|
|
{
|
|
// Make sure we're not going over our active Scrake/FP limit
|
|
bRemoveFromSquad = false;
|
|
if( AISquad[i] == AIClassList[AT_Scrake] )
|
|
{
|
|
if( NumScrakesThisSpawnCycle >= MaxActivePlayerScrakes || GetNumActiveZedsOfClass(class'KFPawn_ZedScrake') >= MaxActivePlayerScrakes )
|
|
{
|
|
bRemoveFromSquad = true;
|
|
}
|
|
else
|
|
{
|
|
++NumScrakesThisSpawnCycle;
|
|
}
|
|
}
|
|
else if( AISquad[i] == AIClassList[AT_Fleshpound] )
|
|
{
|
|
if( NumFleshpoundsThisSpawnCycle >= MaxActivePlayerFleshpounds || GetNumActiveZedsOfClass(class'KFPawn_ZedFleshpound') >= MaxActivePlayerFleshpounds )
|
|
{
|
|
bRemoveFromSquad = true;
|
|
}
|
|
else
|
|
{
|
|
++NumFleshpoundsThisSpawnCycle;
|
|
}
|
|
}
|
|
|
|
if( bRemoveFromSquad )
|
|
{
|
|
NewSquad.AddItem( AISquad[i] );
|
|
AISquad.Remove( i, 1 );
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
for( j = 0; j < PlayerZedClasses.Length; ++j )
|
|
{
|
|
if( ClassIsChildOf(PlayerZedClasses[j], AISquad[i]) )
|
|
{
|
|
// Make sure we're clearing our reserved array
|
|
ReservedIndex = ReservedPlayerZeds.Find( AISquad[i] );
|
|
if( ReservedIndex != INDEX_NONE )
|
|
{
|
|
ReservedPlayerZeds.Remove( ReservedIndex, 1 );
|
|
}
|
|
|
|
// Assign our player zed class to the AI squad
|
|
AISquad[i] = PlayerZedClasses[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spawn our simulated AI squad
|
|
KFSV = GetBestSpawnVolume( AISquad );
|
|
KFSV.SpawnWave( AISquad, true, true );
|
|
|
|
// Add our new simulated AI players to the spawned AI count
|
|
foreach WorldInfo.AllControllers( class'KFAIController', KFAIC )
|
|
{
|
|
if( KFAIC.bIsSimulatedPlayerController && KFAIC.CreationTime == WorldInfo.TimeSeconds )
|
|
{
|
|
++NumAISpawnsQueued;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`endif
|
|
|
|
// Assign zed classes to players
|
|
AssignZedsToPlayers( NewSquad );
|
|
|
|
// Respawn all pending zed players
|
|
RespawnZedHumanPlayers( LastAISpawnVolume );
|
|
}
|
|
|
|
// Set timer for next spawn
|
|
SpawnTimer = PlayerZedSpawnInterval[MyKFGRI.WaveNum-1];
|
|
SetTimer( SpawnTimer, false, nameOf(Timer_SpawnPlayerZeds), self );
|
|
MyKFGRIV.SetPlayerZedSpawnTime( SpawnTimer, false );
|
|
}
|
|
|
|
/** See if we can spawn the special squad yet */
|
|
function CheckForSpecialSquadSpawn()
|
|
{
|
|
local int WaveArrayNum;
|
|
local bool bRequireEndWaveSpecialSquadSpawn;
|
|
|
|
WaveArrayNum = MyKFGRI.WaveNum-1;
|
|
|
|
// If we can't spawn any special squads, early out
|
|
if( MaxPlayerSpecialSquadSpawns[WaveArrayNum] == 0 || NumPlayerSpecialSquadSpawns >= MaxPlayerSpecialSquadSpawns[WaveArrayNum] )
|
|
{
|
|
if( PlayerZedSquads.Length == 0 )
|
|
{
|
|
PlayerZedWaves[WaveArrayNum].GetNewSquadList( PlayerZedSquads );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// First, make sure that we've spawned in our special squad if we've reached the middle/end of the wave
|
|
bRequireEndWaveSpecialSquadSpawn = (WaveTotalAI - NumAISpawnsQueued <= LargestSquadSize*3);
|
|
if( bRequireEndWaveSpecialSquadSpawn || (NumPlayerSpecialSquadSpawns == 0 && NumAISpawnsQueued >= WaveTotalAI * 0.5f) )
|
|
{
|
|
GetSpecialSquad( WaveArrayNum );
|
|
}
|
|
|
|
// Next, deal with the squad array being empty
|
|
if( PlayerZedSquads.Length == 0 )
|
|
{
|
|
// If we've already spawned our special squad, reset for next cycle but don't add to squads array
|
|
if( bSpawnedSpecialSquad )
|
|
{
|
|
bSpawnedSpecialSquad = false;
|
|
SpecialSquadRandomChance = 0.f;
|
|
}
|
|
else
|
|
{
|
|
// We haven't spawned our special squad yet, so do it now
|
|
GetSpecialSquad( WaveArrayNum );
|
|
}
|
|
}
|
|
|
|
// Check against our random number to see if we can spawn a special squad
|
|
if( !bSpawnedSpecialSquad )
|
|
{
|
|
SpecialSquadRandomChance += AdditionalSpecialSquadChancePerSpawn;
|
|
if( fRand() < SpecialSquadRandomChance )
|
|
{
|
|
GetSpecialSquad( WaveArrayNum );
|
|
}
|
|
}
|
|
|
|
// Make sure we still fill the squad array in case there was no special squad
|
|
if( PlayerZedSquads.Length == 0 )
|
|
{
|
|
PlayerZedWaves[WaveArrayNum].GetNewSquadList( PlayerZedSquads );
|
|
}
|
|
}
|
|
|
|
/** Clears the squad array and assigns the special squad */
|
|
function GetSpecialSquad( int WaveArrayNum )
|
|
{
|
|
PlayerZedSquads.Length = 0;
|
|
PlayerZedWaves[WaveArrayNum].GetSpecialSquad( PlayerZedSquads );
|
|
bSpawnedSpecialSquad = true;
|
|
SpecialSquadRandomChance = 0.f;
|
|
++NumPlayerSpecialSquadSpawns;
|
|
}
|
|
|
|
/** Assign and reserve zed squad members for human players if this is a versus game */
|
|
function AssignZedsToPlayers( out array<class<KFPawn_Monster> > NewZeds )
|
|
{
|
|
local KFPlayerControllerVersus KFPCV;
|
|
|
|
// Always refresh the alive count when handing out zed classes
|
|
RefreshMonsterAliveCount();
|
|
|
|
// First, give players that are waiting an immediate spawn
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( KFPCV.GetTeamNum() == 255
|
|
&& (KFPCV.Pawn == none || !KFPCV.Pawn.IsAliveAndWell())
|
|
&& KFPCV.CanRestartPlayer()
|
|
&& KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass == none )
|
|
{
|
|
if( ReservedPlayerZeds.Length > 0 )
|
|
{
|
|
GiveAvailableZedClass( KFPCV, ReservedPlayerZeds,, true );
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass != none )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( NewZeds.Length == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GiveAvailableZedClass( KFPCV, NewZeds );
|
|
}
|
|
}
|
|
|
|
// Now reserve any remaining strong zeds for players
|
|
ReserveStrongZedsForPlayers( NewZeds );
|
|
}
|
|
|
|
/** Gives a dead player a zed class to spawn with */
|
|
function GiveAvailableZedClass( KFPlayerControllerVersus KFPCV, out array<class<KFPawn_Monster> > AvailableZeds, optional bool bSecondPass, optional bool bReservedCheck )
|
|
{
|
|
local int i, j;
|
|
local int ZedIndex;
|
|
local array<int> PassedOnZeds;
|
|
local array<class<KFPawn_Monster> > PossibleZeds;
|
|
|
|
// Clear our spawned zeds array if we've spawned as most of the zed types already
|
|
if( KFPCV.HasSpawnedZeds.Length >= PlayerZedClasses.Length - 3 )
|
|
{
|
|
KFPCV.HasSpawnedZeds.Length = 0;
|
|
}
|
|
|
|
// Iterate through all available zeds and populate array with classes we haven't spawned as yet
|
|
for( i = 0; i < AvailableZeds.Length; ++i )
|
|
{
|
|
// Make sure we're not spawning more than are allowed to be active
|
|
if( AvailableZeds[i] == AIClassList[AT_Scrake] )
|
|
{
|
|
if( NumScrakesThisSpawnCycle >= MaxActivePlayerScrakes || GetNumActiveZedsOfClass(class'KFPawn_ZedScrake') >= MaxActivePlayerScrakes )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if( AvailableZeds[i] == AIClassList[AT_Fleshpound] )
|
|
{
|
|
if( NumFleshpoundsThisSpawnCycle >= MaxActivePlayerFleshpounds || GetNumActiveZedsOfClass(class'KFPawn_ZedFleshpound') >= MaxActivePlayerFleshpounds )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for( j = 0; j < PlayerZedClasses.Length; ++j )
|
|
{
|
|
// Check our spawnzed zeds array to see if we've spawned as this class yet
|
|
if( ClassIsChildOf(PlayerZedClasses[j], AvailableZeds[i]) )
|
|
{
|
|
if( j < KFPCV.HasSpawnedZeds.Length && KFPCV.HasSpawnedZeds[j] )
|
|
{
|
|
PassedOnZeds.AddItem( j );
|
|
break;
|
|
}
|
|
|
|
PossibleZeds.AddItem( AvailableZeds[i] );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine if we should check the list again or take over a zed instead
|
|
if( PossibleZeds.Length == 0 )
|
|
{
|
|
// If this isn't a reserved zed check, either process second pass or find a takeover zed
|
|
if( !bReservedCheck )
|
|
{
|
|
// See if we need to trim our spawned types array and start fresh
|
|
if( !bSecondPass && PassedOnZeds.Length > 1 )
|
|
{
|
|
// Remove one of the zed types we passed on
|
|
KFPCV.HasSpawnedZeds.Remove( PassedOnZeds[Rand(PassedOnZeds.Length)], 1 );
|
|
|
|
// Do our second pass, with trimming
|
|
GiveAvailableZedClass( KFPCV, AvailableZeds, true );
|
|
return;
|
|
}
|
|
|
|
// Try to take over an existing zed if we have no available squad slots
|
|
FindTakeoverZed( KFPCV );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Pick a zed at random
|
|
ZedIndex = Rand( PossibleZeds.Length );
|
|
for( i = 0; i < PlayerZedClasses.Length; ++i )
|
|
{
|
|
if( ClassIsChildOf(PlayerZedClasses[i], PossibleZeds[ZedIndex]) )
|
|
{
|
|
if( PossibleZeds[ZedIndex] == AIClassList[AT_Scrake] )
|
|
{
|
|
++NumScrakesThisSpawnCycle;
|
|
}
|
|
else if( PossibleZeds[ZedIndex] == AIClassList[AT_Fleshpound] )
|
|
{
|
|
++NumFleshpoundsThisSpawnCycle;
|
|
}
|
|
|
|
KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[i];
|
|
AvailableZeds.Remove( AvailableZeds.Find(PossibleZeds[ZedIndex]), 1 );
|
|
KFPCV.HasSpawnedZeds[i] = true;
|
|
|
|
// Only increment this if we are not taking a reserved zed
|
|
// NOTE: The queue counter is already incremented when a reserved zed is taken out of the squad
|
|
if( !bReservedCheck )
|
|
{
|
|
++NumAISpawnsQueued;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Reserves strong zeds for players */
|
|
function ReserveStrongZedsForPlayers( out array<class<KFPawn_Monster> > LeftoverZeds )
|
|
{
|
|
local int i;
|
|
local class<KFPawn_Monster> LeftoverZedClass;
|
|
|
|
for( i = 0; i < LeftoverZeds.Length; ++i )
|
|
{
|
|
LeftoverZedClass = LeftoverZeds[i];
|
|
|
|
// Always reserve Scrake or Fleshpound, and always at the front of the queue
|
|
if( ClassIsChildOf(PlayerZedClasses[AT_Fleshpound], LeftoverZedClass)
|
|
|| ClassIsChildOf(PlayerZedClasses[AT_Scrake], LeftoverZedClass) )
|
|
{
|
|
LeftoverZeds.Remove( i, 1 );
|
|
ReservedPlayerZeds.Insert( 0, 1 );
|
|
ReservedPlayerZeds[0] = LeftoverZedClass;
|
|
++NumAISpawnsQueued;
|
|
--i;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Pings the spawn volume to get the best location to spawn a player zed */
|
|
function vector GetSpawnLocation( class<KFPawn_Monster> MonsterPawnClass, KFSpawnVolume SpawnVolume )
|
|
{
|
|
SpawnVolume.SetLastSpawnTime( WorldInfo.TimeSeconds );
|
|
return SpawnVolume.FindSpawnLocation( MonsterPawnClass );
|
|
}
|
|
|
|
/** Attempt to respawn all player zeds */
|
|
protected function RespawnZedHumanPlayers( KFSpawnVolume SpawnVolume, optional bool bIsTakeOverSpawn )
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local vector SpawnLocation;
|
|
local ESquadType SquadType;
|
|
local array<KFPlayerController> CrawlerPlayers, MediumPlayers, LargePlayers, BossPlayers;
|
|
local int NumSpawned, NumSquadMembers;
|
|
local array<class<KFPawn_Monster> > TempPawnClasses, CrawlerPawnClasses, MediumPawnClasses, LargePawnClasses, BossPawnClasses;
|
|
local int i;
|
|
local bool bStopSpawning;
|
|
|
|
// If match is over or trader is open, do nothing
|
|
if( !IsWaveActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Spawn a boss pawn
|
|
if( MyKFGRI.WaveNum == MyKFGRI.WaveMax && !bBossSpawned )
|
|
{
|
|
// Make sure the zed takeover timer isn't running
|
|
if( IsTimerActive(nameOf(Timer_CheckForZedTakeovers), self) )
|
|
{
|
|
ClearTimer( nameOf(Timer_CheckForZedTakeovers), self );
|
|
}
|
|
|
|
// Clear reserved list
|
|
ReservedPlayerZeds.Length = 0;
|
|
|
|
// Count how many player zeds we have
|
|
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
|
|
{
|
|
// Clear out any pending zed spawn info
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
KFPC.PlayerZedSpawnInfo.PendingZedSpawnLocation = vect(0,0,0);
|
|
|
|
if( KFPC.CanRestartPlayer() && KFPC.GetTeamNum() == 255 )
|
|
{
|
|
BossPlayers.AddItem( KFPC );
|
|
}
|
|
}
|
|
|
|
KFPC = BossPlayers[Rand(BossPlayers.Length)];
|
|
|
|
if( KFPC != none )
|
|
{
|
|
// Set our boss pawn class
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerBossClassList[Rand(PlayerBossClassList.Length)];
|
|
BossPawnClasses[0] = KFPC.PlayerZedSpawnInfo.PendingZedPawnClass;
|
|
|
|
// Make sure we get a valid spawn volume to start
|
|
if( SpawnVolume == none || SpawnVolume.bNoPlayers )
|
|
{
|
|
SetDesiredSquadTypeForZedList( BossPawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( BossPawnClasses,, KFPC );
|
|
}
|
|
|
|
// Make sure we kill the previous pawn
|
|
if( KFPC.Pawn != none )
|
|
{
|
|
KFPC.Pawn.Destroy();
|
|
}
|
|
|
|
RestartPlayer( KFPC );
|
|
if( KFPC.Pawn != none )
|
|
{
|
|
bBossSpawned = true;
|
|
SpawnLocation = GetSpawnLocation( class<KFPawn_Monster>(KFPC.Pawn.Class), SpawnVolume );
|
|
KFPC.SetLocation( SpawnLocation );
|
|
KFPC.Pawn.SetLocation( SpawnLocation );
|
|
KFPC.InitGameplayPostProcessFX();
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
KFPC.PlayerZedSpawnInfo.PendingZedSpawnLocation = vect(0,0,0);
|
|
++NumAISpawnsQueued;
|
|
|
|
RefreshMonsterAliveCount();
|
|
}
|
|
else
|
|
{
|
|
// Try to find a different player
|
|
RespawnZedHumanPlayers( none );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Set the player zed spawn timer
|
|
SetTimer( BossSpawnPlayerInterval, false, nameOf(Timer_SpawnBossPlayerZeds), self );
|
|
return;
|
|
}
|
|
|
|
// Collect squads
|
|
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
|
|
{
|
|
if( KFPC.Pawn != none && KFPC.Pawn.IsAliveAndWell() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Separate pending zed players out into squads
|
|
if( KFPC.PlayerZedSpawnInfo.PendingZedPawnClass != none && KFPC.CanRestartPlayer() && KFPC.GetTeamNum() == 255 )
|
|
{
|
|
TempPawnClasses[0] = KFPC.PlayerZedSpawnInfo.PendingZedPawnClass;
|
|
SquadType = GetDesiredSquadTypeForZedList( TempPawnClasses );
|
|
|
|
if( SquadType == EST_Crawler )
|
|
{
|
|
CrawlerPlayers.AddItem( KFPC );
|
|
CrawlerPawnClasses.AddItem( KFPC.PlayerZedSpawnInfo.PendingZedPawnClass );
|
|
}
|
|
else if( SquadType == EST_Small || SquadType == EST_Medium )
|
|
{
|
|
MediumPlayers.AddItem( KFPC );
|
|
MediumPawnClasses.AddItem( KFPC.PlayerZedSpawnInfo.PendingZedPawnClass );
|
|
}
|
|
else
|
|
{
|
|
LargePlayers.AddItem( KFPC );
|
|
LargePawnClasses.AddItem( KFPC.PlayerZedSpawnInfo.PendingZedPawnClass );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Early out if we have no players to spawn
|
|
if( CrawlerPlayers.Length == 0 && MediumPlayers.Length == 0 && LargePlayers.Length == 0 )
|
|
{
|
|
CheckForTakeoverTimer();
|
|
return;
|
|
}
|
|
|
|
// Refresh our alive count
|
|
RefreshMonsterAliveCount();
|
|
|
|
// Crawlers
|
|
if( CrawlerPlayers.Length > 0 )
|
|
{
|
|
SetDesiredSquadTypeForZedList( CrawlerPawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( CrawlerPawnClasses,, CrawlerPlayers[0] );
|
|
for( i = 0; i < CrawlerPlayers.Length; ++i )
|
|
{
|
|
if( !IsWaveActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure we have enough slots
|
|
if( MyKFGRI.WaveNum < MyKFGRI.WaveMax && AIAliveCount + NumSpawned + 1 > MyKFGRI.AIRemaining )
|
|
{
|
|
bStopSpawning = true;
|
|
break;
|
|
}
|
|
|
|
// Only allow 3 spawns per volume
|
|
if( NumSquadMembers % 3 == 0 )
|
|
{
|
|
SetDesiredSquadTypeForZedList( CrawlerPawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( CrawlerPawnClasses,, CrawlerPlayers[i] );
|
|
}
|
|
|
|
// Spawn player
|
|
if( RestartPlayerZed(CrawlerPlayers[i], SpawnVolume) )
|
|
{
|
|
// Count the number of spawns, if we have more than 2 then choose a new spawn volume
|
|
++NumSpawned;
|
|
++NumSquadMembers;
|
|
}
|
|
|
|
// Clear these zeds out of the array in case we need to get another spawn volume
|
|
CrawlerPlayers.Remove( i, 1 );
|
|
CrawlerPawnClasses.Remove( i, 1 );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Small-medium zeds
|
|
if( !bStopSpawning && MediumPlayers.Length > 0 )
|
|
{
|
|
NumSquadMembers = 0;
|
|
SetDesiredSquadTypeForZedList( MediumPawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( MediumPawnClasses,, MediumPlayers[0] );
|
|
for( i = 0; i < MediumPlayers.Length; ++i )
|
|
{
|
|
if( !IsWaveActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure we have enough slots
|
|
if( MyKFGRI.WaveNum < MyKFGRI.WaveMax && AIAliveCount + NumSpawned + 1 > MyKFGRI.AIRemaining )
|
|
{
|
|
bStopSpawning = true;
|
|
break;
|
|
}
|
|
|
|
// Only allow 3 spawns per volume
|
|
if( NumSquadMembers % 3 == 0 )
|
|
{
|
|
SetDesiredSquadTypeForZedList( MediumPawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( MediumPawnClasses,, MediumPlayers[i] );
|
|
}
|
|
|
|
// Spawn player
|
|
if( RestartPlayerZed(MediumPlayers[i], SpawnVolume) )
|
|
{
|
|
// Count the number of spawns, if we have more than 2 then choose a new spawn volume
|
|
++NumSpawned;
|
|
++NumSquadMembers;
|
|
}
|
|
|
|
// Clear these zeds out of the array in case we need to get another spawn volume
|
|
MediumPlayers.Remove( i, 1 );
|
|
MediumPawnClasses.Remove( i, 1 );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Large zeds
|
|
if( !bStopSpawning && LargePlayers.Length > 0 )
|
|
{
|
|
NumSquadMembers = 0;
|
|
SetDesiredSquadTypeForZedList( LargePawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( LargePawnClasses,, LargePlayers[0] );
|
|
for( i = 0; i < LargePlayers.Length; ++i )
|
|
{
|
|
if( !IsWaveActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure we have enough slots
|
|
if( MyKFGRI.WaveNum < MyKFGRI.WaveMax && AIAliveCount + NumSpawned + 1 > MyKFGRI.AIRemaining )
|
|
{
|
|
bStopSpawning = true;
|
|
break;
|
|
}
|
|
|
|
// Only allow 3 spawns per volume
|
|
if( NumSquadMembers % 3 == 0 )
|
|
{
|
|
SetDesiredSquadTypeForZedList( LargePawnClasses );
|
|
SpawnVolume = GetBestSpawnVolume( LargePawnClasses,, LargePlayers[i] );
|
|
}
|
|
|
|
// Spawn player
|
|
if( RestartPlayerZed(LargePlayers[i], SpawnVolume) )
|
|
{
|
|
// Count the number of spawns, if we have more than 2 then choose a new spawn volume
|
|
++NumSpawned;
|
|
++NumSquadMembers;
|
|
}
|
|
|
|
// Clear these zeds out of the array in case we need to get another spawn volume
|
|
LargePlayers.Remove( i, 1 );
|
|
LargePawnClasses.Remove( i, 1 );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Add spawned to monster alive count
|
|
AIAliveCount += NumSpawned;
|
|
NumAIFinishedSpawning += NumSpawned;
|
|
UpdateAIRemaining();
|
|
|
|
// If we need to stop spawning, clear all pending zed spawns
|
|
if( bStopSpawning )
|
|
{
|
|
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
|
|
{
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
}
|
|
}
|
|
|
|
// Check if our takeover timer should be started
|
|
CheckForTakeoverTimer();
|
|
}
|
|
|
|
/** Checks the wave status to see if we can start our zed takeover timer */
|
|
function bool CheckForTakeoverTimer()
|
|
{
|
|
local KFPlayerControllerVersus KFPCV;
|
|
local int ZedPlayers;
|
|
|
|
// Don't allow if this is not a normal wave
|
|
if( !IsPlayerZedSpawnAllowed() )
|
|
{
|
|
if( IsTimerActive(nameOf(Timer_CheckForZedTakeovers), self) )
|
|
{
|
|
ClearTimer( nameOf(Timer_CheckForZedTakeovers), self );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Count up number of active zed players
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( !KFPCV.CanRestartPlayer() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( KFPCV.GetTeamNum() == 255 )
|
|
{
|
|
++ZedPlayers;
|
|
}
|
|
}
|
|
|
|
// Set a timer to take over zeds right before the last squad
|
|
if( WaveTotalAI - NumAISpawnsQueued <= LargestSquadSize+ZedPlayers )
|
|
{
|
|
if( !IsTimerActive(nameOf(Timer_CheckForZedTakeovers), self) )
|
|
{
|
|
MyKFGRIV.SetPlayerZedSpawnTime( 255, true );
|
|
SetTimer( 1.f, true, nameOf(Timer_CheckForZedTakeovers), self );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Searches for AI zeds to take over */
|
|
function Timer_CheckForZedTakeovers()
|
|
{
|
|
local KFPlayerControllerVersus KFPCV;
|
|
local bool bNeedRespawn;
|
|
local array<KFPlayerControllerVersus> ZedPlayers;
|
|
local int i, LivingPlayerCount, DesiredTakeovers;
|
|
|
|
// Always refresh the alive count when this timer is active
|
|
RefreshMonsterAliveCount();
|
|
|
|
// Don't allow if this is not a normal wave
|
|
if( !IsPlayerZedSpawnAllowed() )
|
|
{
|
|
ClearTimer( nameOf(Timer_CheckForZedTakeovers), self );
|
|
return;
|
|
}
|
|
|
|
// Make sure we're putting in our remaining reserved zeds
|
|
SpawnRemainingReservedZeds();
|
|
|
|
// Set to zero
|
|
DesiredTakeovers = 0;
|
|
bNeedRespawn = false;
|
|
|
|
// If the survivors are down to 1 person, don't allow more than 3 player zeds at once
|
|
LivingPlayerCount = GetLivingPlayerCount();
|
|
if( LivingPlayerCount < 3 )
|
|
{
|
|
DesiredTakeovers = LivingPlayerCount == 2 ? 4 : 3;
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( KFPCV.GetTeamNum() == 255 )
|
|
{
|
|
if( KFPCV.Pawn != none && KFPCV.Pawn.IsAliveAndWell() )
|
|
{
|
|
--DesiredTakeovers;
|
|
|
|
// Early out if we have no available spawns left
|
|
if( DesiredTakeovers == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if( KFPCV.CanRestartPlayer() )
|
|
{
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass != none )
|
|
{
|
|
// We had a pending zed class already, so we'll need at least one respawn
|
|
bNeedRespawn = true;
|
|
continue;
|
|
}
|
|
|
|
// Make sure we clear out our reserved zeds first
|
|
if( ReservedPlayerZeds.Length > 0 )
|
|
{
|
|
GiveAvailableZedClass( KFPCV, ReservedPlayerZeds,, true );
|
|
}
|
|
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass == none )
|
|
{
|
|
ZedPlayers.AddItem( KFPCV );
|
|
}
|
|
else
|
|
{
|
|
// We had a pending zed class already, so we'll need at least one respawn
|
|
bNeedRespawn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Collect our pending zed players
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( KFPCV.GetTeamNum() != 255 || !KFPCV.CanRestartPlayer() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass != none )
|
|
{
|
|
// We had a pending zed class already, so we'll need at least one respawn
|
|
bNeedRespawn = true;
|
|
continue;
|
|
}
|
|
|
|
if( KFPCV.Pawn == none || !KFPCV.Pawn.IsAliveAndWell() )
|
|
{
|
|
// Make sure we clear out our reserved zeds first
|
|
if( ReservedPlayerZeds.Length > 0 )
|
|
{
|
|
GiveAvailableZedClass( KFPCV, ReservedPlayerZeds,, true );
|
|
}
|
|
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass == none )
|
|
{
|
|
ZedPlayers.AddItem( KFPCV );
|
|
}
|
|
else
|
|
{
|
|
// We had a pending zed class already, so we'll need at least one respawn
|
|
bNeedRespawn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Early out if we have no players waiting for spawn
|
|
if( ZedPlayers.Length == 0 && !bNeedRespawn )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only attempt takeovers if we have players waiting for a spawn
|
|
if( ZedPlayers.Length > 0 )
|
|
{
|
|
// Remove enough pending zed players to fit within our available spawn slots
|
|
if( DesiredTakeovers > 0 )
|
|
{
|
|
while( ZedPlayers.Length > DesiredTakeovers )
|
|
{
|
|
ZedPlayers.Remove( Rand(ZedPlayers.Length), 1 );
|
|
}
|
|
}
|
|
|
|
// Attempt to find zeds for takeover
|
|
for( i = 0; i < ZedPlayers.Length; ++i )
|
|
{
|
|
KFPCV = ZedPlayers[i];
|
|
FindTakeoverZed( KFPCV );
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass != none )
|
|
{
|
|
bNeedRespawn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Respawn all pending controllers
|
|
if( bNeedRespawn )
|
|
{
|
|
RespawnZedHumanPlayers( none );
|
|
}
|
|
}
|
|
|
|
function bool NeedPlayerSpawnVolume()
|
|
{
|
|
return IsTimerActive( nameOf(Timer_CheckForZedTakeovers), self );
|
|
}
|
|
|
|
/** Spawns in our remaining reserved zeds */
|
|
function SpawnRemainingReservedZeds( optional bool bSpawnAllReservedZeds )
|
|
{
|
|
local int NumWaitingZedPlayers;
|
|
local array<class<KFPawn_Monster> > TempSquad;
|
|
|
|
// Early out if this is not a normal wave or there are no reserved zeds remaining
|
|
if( ReservedPlayerZeds.Length == 0 || !IsPlayerZedSpawnAllowed() )
|
|
{
|
|
// Make sure this stays clear
|
|
ReservedPlayerZeds.Length = 0;
|
|
return;
|
|
}
|
|
|
|
// Refresh our alive count
|
|
RefreshMonsterAliveCount();
|
|
|
|
// Don't spawn in any of our leftover reserved zeds if it would exceed the zed counter
|
|
if( AIAliveCount + 1 > MyKFGRI.AIRemaining )
|
|
{
|
|
ReservedPlayerZeds.Length = 0;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Prune to spawn limits
|
|
while( ReservedPlayerZeds.Length > 0 && AIAliveCount + ReservedPlayerZeds.Length > MyKFGRI.AIRemaining )
|
|
{
|
|
ReservedPlayerZeds.Remove( 0, 1 );
|
|
}
|
|
|
|
// No reserved zeds left!
|
|
if( ReservedPlayerZeds.Length == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// See how many are still waiting to spawn. If we don't have any, spawn them in as AI
|
|
NumWaitingZedPlayers = GetNumWaitingZedPlayers();
|
|
if( NumWaitingZedPlayers == 0 )
|
|
{
|
|
TempSquad = ReservedPlayerZeds;
|
|
ReservedPlayerZeds.Length = 0;
|
|
}
|
|
else
|
|
{
|
|
while( ReservedPlayerZeds.Length > NumWaitingZedPlayers )
|
|
{
|
|
TempSquad.AddItem( ReservedPlayerZeds[0] );
|
|
ReservedPlayerZeds.Remove( 0, 1 );
|
|
}
|
|
}
|
|
|
|
// No need to modify NumAISpawnsQueued as these zeds were already queued
|
|
if( TempSquad.Length > 0 )
|
|
{
|
|
// Make sure squad doesn't exceed remaining AI
|
|
while( TempSquad.Length > 0 && AIAliveCount + ReservedPlayerZeds.Length + TempSquad.Length > MyKFGRI.AIRemaining )
|
|
{
|
|
TempSquad.Remove( 0, 1 );
|
|
}
|
|
|
|
if( TempSquad.Length > 0 )
|
|
{
|
|
SpawnSquad( TempSquad, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Finds an AI zed to take over */
|
|
function FindTakeoverZed( KFPlayerControllerVersus KFPCV )
|
|
{
|
|
local KFPawn_Monster KFPM;
|
|
local Controller C;
|
|
local bool bNextZed;
|
|
local class<KFPawn_Monster> AliveClass;
|
|
local int i;
|
|
|
|
// Don't allow if this is not a normal wave
|
|
if( !IsPlayerZedSpawnAllowed() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach WorldInfo.AllPawns( class'KFPawn_Monster', KFPM )
|
|
{
|
|
// Only try to take over zeds that are still valid for play
|
|
if( !KFPM.CanTakeOver() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only choose zeds that are not relatively close to players
|
|
bNextZed = false;
|
|
foreach WorldInfo.AllControllers( class'Controller', C )
|
|
{
|
|
if( !C.bIsPlayer || C.GetTeamNum() == 255 || C.Pawn == none || !C.Pawn.IsAliveAndWell() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Dist >= 8 meters
|
|
if( VSizeSQ(C.Pawn.Location - KFPM.Location) < 640000.f )
|
|
{
|
|
bNextZed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Skip if this zed was too close to a player
|
|
if( bNextZed )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Cache off class
|
|
AliveClass = KFPM.class;
|
|
|
|
for( i = 0; i < PlayerZedClasses.Length; ++i )
|
|
{
|
|
if( ClassIsChildOf(PlayerZedClasses[i], AliveClass) )
|
|
{
|
|
// Deal explosive damage, make them burst apart
|
|
KFPM.TakeRadiusDamage( none, 100000, 1000, class'KFDT_Explosive_PlayerZedTakeover', 1, KFPM.Location, true, none );
|
|
|
|
// Make sure this AI died
|
|
if( KFPM == none || KFPM.Health <= 0 || KFPM.bPlayedDeath || KFPM.bPendingDelete || KFPM.bDeleteMe )
|
|
{
|
|
KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[i];
|
|
KFPCV.PlayerZedSpawnInfo.PendingZedSpawnLocation = KFPM.Location;
|
|
KFPCV.HasSpawnedZeds[i] = true;
|
|
|
|
// Need to increment AI remaining, because killing the AI subtracted a zed
|
|
MyKFGRI.AIRemaining += 1;
|
|
NumAIFinishedSpawning -= 1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Spawn players into low-level zeds every set number of seconds */
|
|
function Timer_SpawnBossPlayerZeds()
|
|
{
|
|
local KFPlayerController KFPC;
|
|
local KFPlayerController BestPlayer, SecondBestPlayer;
|
|
local int LivingPlayerCount;
|
|
local float LongestSpawnTime, TimeSinceSpawn;
|
|
local float RandomFloat;
|
|
local bool bNeedRespawn;
|
|
|
|
if( MyKFGRI.bMatchIsOver )
|
|
{
|
|
ClearTimer( nameOf(Timer_SpawnBossPlayerZeds), self );
|
|
return;
|
|
}
|
|
|
|
LivingPlayerCount = GetLivingPlayerCount();
|
|
|
|
// If there is already at least one player zed minion with 1 survivor left, early out
|
|
if( LivingPlayerCount == 1 && GetMonsterAliveCount() > 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Respawn the dead player zeds
|
|
BestPlayer = none;
|
|
bNeedRespawn = false;
|
|
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
|
|
{
|
|
if( KFPC.GetTeamNum() != 255 || !KFPC.CanRestartPlayer() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Clear any pending zed class here
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
|
|
if( KFPC.Pawn == none || !KFPC.Pawn.IsAliveAndWell() )
|
|
{
|
|
// If we're down to just one survivor, only spawn in two player zeds each boss spawn interval
|
|
if( LivingPlayerCount == 1 )
|
|
{
|
|
// Pick the players that haven't spawned in the longest time
|
|
TimeSinceSpawn = `TimeSince(KFPC.PlayerZedSpawnInfo.LastSpawnedTime);
|
|
if( LongestSpawnTime == 0 || TimeSinceSpawn > LongestSpawnTime )
|
|
{
|
|
LongestSpawnTime = TimeSinceSpawn;
|
|
|
|
// Keep the two players with the longest wait time since last spawn
|
|
SecondBestPlayer = BestPlayer;
|
|
BestPlayer = KFPC;
|
|
|
|
bNeedRespawn = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RandomFloat = fRand();
|
|
if( RandomFloat < 0.33f )
|
|
{
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_Crawler];
|
|
}
|
|
else if( RandomFloat < 0.66f )
|
|
{
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_SlasherClot];
|
|
}
|
|
else
|
|
{
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_GoreFast];
|
|
}
|
|
bNeedRespawn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Give our chosen player (if any) a zed class
|
|
if( BestPlayer != none )
|
|
{
|
|
if( fRand() < 0.5f )
|
|
{
|
|
BestPlayer.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_SlasherClot];
|
|
}
|
|
else
|
|
{
|
|
BestPlayer.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_GoreFast];
|
|
}
|
|
|
|
if( SecondBestPlayer != none )
|
|
{
|
|
if( fRand() < 0.5f )
|
|
{
|
|
SecondBestPlayer.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_SlasherClot];
|
|
}
|
|
else
|
|
{
|
|
SecondBestPlayer.PlayerZedSpawnInfo.PendingZedPawnClass = PlayerZedClasses[AT_GoreFast];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Respawn all of our player zeds
|
|
if( bNeedRespawn )
|
|
{
|
|
RespawnZedHumanPlayers( none );
|
|
}
|
|
|
|
// Use our adjusted spawn time when only one survivor remains
|
|
if( LivingPlayerCount == 1 )
|
|
{
|
|
SetTimer( FinalSurvivorBossSpawnPlayerInterval, false, nameOf(Timer_SpawnBossPlayerZeds), self );
|
|
}
|
|
else
|
|
{
|
|
SetTimer( BossSpawnPlayerInterval, false, nameOf(Timer_SpawnBossPlayerZeds), self );
|
|
}
|
|
}
|
|
|
|
/** Recycles a player's pending zed pawn class back into the spawn rotation */
|
|
function RecyclePendingZedPawnClass( KFPlayerController KFPC )
|
|
{
|
|
local int i;
|
|
|
|
// Early out if there is no pending zed pawn class
|
|
if( KFPC.PlayerZedSpawnInfo.PendingZedPawnClass == none )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find matching AI zed pawn class
|
|
for( i = 0; i < AIClassList.Length; ++i )
|
|
{
|
|
if( ClassIsChildOf(KFPC.PlayerZedSpawnInfo.PendingZedPawnClass, AIClassList[i]) )
|
|
{
|
|
ReservedPlayerZeds.AddItem( AIClassList[i] );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Spawns a player zed */
|
|
function bool RestartPlayerZed( KFPlayerController KFPC, KFSpawnVolume SpawnVolume )
|
|
{
|
|
local vector SpawnLocation;
|
|
local rotator SpawnRotation;
|
|
|
|
// No spawning if the trader is open or the match is over
|
|
if( !IsWaveActive() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
RestartPlayer( KFPC );
|
|
if( KFPC.Pawn != none )
|
|
{
|
|
// Set spawn location
|
|
SpawnLocation = ( !IsZero(KFPC.PlayerZedSpawnInfo.PendingZedSpawnLocation) )
|
|
? KFPC.PlayerZedSpawnInfo.PendingZedSpawnLocation
|
|
: GetSpawnLocation( class<KFPawn_Monster>(KFPC.Pawn.Class), SpawnVolume );
|
|
KFPC.SetLocation( SpawnLocation );
|
|
KFPC.Pawn.SetLocation( SpawnLocation );
|
|
|
|
// Set spawn rotation
|
|
SpawnRotation = RotRand( false );
|
|
SpawnRotation.Roll = 0;
|
|
KFPC.SetRotation( SpawnRotation );
|
|
SpawnRotation.Pitch = 0;
|
|
KFPC.Pawn.SetRotation( SpawnRotation );
|
|
|
|
// Init
|
|
KFPC.ServerCamera( 'ThirdPerson' );
|
|
KFPC.InitGameplayPostProcessFX();
|
|
|
|
// Start fading in the camera
|
|
KFPC.ClientSetCameraFade( true, MakeColor(255,255,255,255), vect2d(1.f, 0.f), 0.6f, true );
|
|
|
|
// Clear pending spawn values
|
|
KFPC.PlayerZedSpawnInfo.PendingZedPawnClass = none;
|
|
KFPC.PlayerZedSpawnInfo.PendingZedSpawnLocation = vect(0,0,0);
|
|
|
|
// Set our last spawned time
|
|
KFPC.PlayerZedSpawnInfo.LastSpawnedTime = WorldInfo.TimeSeconds;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** General function to indicate whether normal wave player zed spawning is allowed */
|
|
protected function bool IsPlayerZedSpawnAllowed()
|
|
{
|
|
return !MyKFGRIV.bTraderIsOpen && !MyKFGRIV.bMatchIsOver && !MyKFGRIV.bRoundIsOver && MyKFGRIV.WaveNum < MyKFGRIV.WaveMax;
|
|
}
|
|
|
|
/** Returns true if there is at least one zed player */
|
|
protected function bool HaveZedPlayers()
|
|
{
|
|
local int i;
|
|
|
|
// Iterating through PRIArray should be faster than AllControllers() since it doesn't need to go in and out of native,
|
|
// and the PRI list is shorter by default due to AI not being included.
|
|
for( i = 0; i < MyKFGRI.PRIArray.Length; ++i )
|
|
{
|
|
if( !MyKFGRI.PRIArray[i].bOnlySpectator && MyKFGRI.PRIArray[i].GetTeamNum() == 255 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
`if(`notdefined(ShippingPC))
|
|
// If we have simulated zed players, allow spawning to continue
|
|
return AISimulatedPlayerNum > 0;
|
|
`endif
|
|
return false;
|
|
}
|
|
|
|
/** Retrieves the number of zed players */
|
|
protected function int GetNumWaitingZedPlayers()
|
|
{
|
|
local KFPlayerControllerVersus KFPCV;
|
|
local int NumWaiting;
|
|
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( KFPCV.PlayerZedSpawnInfo.PendingZedPawnClass == none
|
|
&& KFPCV.GetTeamNum() == 255 && KFPCV.CanRestartPlayer()
|
|
&& (KFPCV.Pawn == none || !KFPCV.Pawn.IsAliveAndWell()) )
|
|
{
|
|
++NumWaiting;
|
|
}
|
|
}
|
|
|
|
return NumWaiting;
|
|
}
|
|
|
|
/** Retrieves the number of active (alive and well) zeds of class ZedClass */
|
|
protected function int GetNumActiveZedsOfClass( class<KFPawn_Monster> ZedClass )
|
|
{
|
|
local KFPawn_Monster MonsterPawn;
|
|
local int NumZeds;
|
|
|
|
foreach WorldInfo.AllPawns( class'KFPawn_Monster', MonsterPawn )
|
|
{
|
|
if( !MonsterPawn.IsAliveAndWell() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( ClassIsChildOf(MonsterPawn.class, ZedClass) )
|
|
{
|
|
NumZeds++;
|
|
}
|
|
}
|
|
|
|
return NumZeds;
|
|
}
|
|
|
|
/** Determines whether we have any zed players that can play as the boss */
|
|
protected function bool CanSpawnPlayerBoss()
|
|
{
|
|
local KFPlayerControllerVersus KFPCV;
|
|
|
|
// If we've already spawned in the boss, we already know that a boss player can spawn
|
|
if( bBossSpawned )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach WorldInfo.AllControllers( class'KFPlayerControllerVersus', KFPCV )
|
|
{
|
|
if( KFPCV.GetTeamNum() == 255 && KFPCV.CanRestartPlayer()
|
|
&& (KFPCV.Pawn == none || !KFPCV.Pawn.IsAliveAndWell()) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function ResetSpawnManager()
|
|
{
|
|
bBossSpawned = false;
|
|
ReservedPlayerZeds.Length = 0;
|
|
}
|
|
|
|
DefaultProperties
|
|
{
|
|
FinalSurvivorBossSpawnPlayerInterval=20.f
|
|
EarlyWaveIndex=2
|
|
MaxSpecialSquadRecycles=1
|
|
// Slow down the early wave spawning in versus
|
|
EarlyWaveSpawnRateModifier(0)=2.10 // 1.65 -> 2.10
|
|
|
|
RecycleSpecialSquad.Empty
|
|
RecycleSpecialSquad(0)=true
|
|
bRecycleSpecialSquad=true
|
|
bSpawnedSpecialSquad=false
|
|
AdditionalSpecialSquadChancePerSpawn=0.1f
|
|
|
|
MaxActivePlayerScrakes=2
|
|
MaxActivePlayerFleshpounds=2
|
|
|
|
// Added to slow stuff down for the late waves in VS kb
|
|
LateWavesSpawnTimeModByPlayers(0)=1.8 // 1 player 1.2 //1.2
|
|
LateWavesSpawnTimeModByPlayers(1)=1.7 // 2 players 1.2 //1.2
|
|
LateWavesSpawnTimeModByPlayers(2)=1.6 // 3 players 1.2 //1.2
|
|
LateWavesSpawnTimeModByPlayers(3)=1.5 // 4 players //1.09 //1.2
|
|
LateWavesSpawnTimeModByPlayers(4)=1.17 // 5 players //0.82 //0.94
|
|
LateWavesSpawnTimeModByPlayers(5)=1 // 6 players //0.65 //0.8
|
|
|
|
// ---------------------------------------------
|
|
// Wave settings
|
|
DifficultyWaveSettings(0)={(Waves[0]=KFAIWaveInfo'GP_Spawning_ARCH.Versus.ZED_Wave1',
|
|
Waves[1]=KFAIWaveInfo'GP_Spawning_ARCH.Versus.ZED_Wave2',
|
|
Waves[2]=KFAIWaveInfo'GP_Spawning_ARCH.Versus.ZED_Wave3',
|
|
Waves[3]=KFAIWaveInfo'GP_Spawning_ARCH.Versus.ZED_Wave4',
|
|
Waves[4]=KFAIWaveInfo'GP_Spawning_ARCH.Versus.ZED_Boss')}
|
|
PlayerZedWaves(0)=KFAIWaveInfo'GP_Spawning_ARCH.Versus.PlayerZED_Wave1'
|
|
PlayerZedWaves(1)=KFAIWaveInfo'GP_Spawning_ARCH.Versus.PlayerZED_Wave2'
|
|
PlayerZedWaves(2)=KFAIWaveInfo'GP_Spawning_ARCH.Versus.PlayerZED_Wave3'
|
|
PlayerZedWaves(3)=KFAIWaveInfo'GP_Spawning_ARCH.Versus.PlayerZED_Wave4'
|
|
|
|
// This is the maximum number of times special squads can be spawned in each wave
|
|
MaxPlayerSpecialSquadSpawns(0)=0
|
|
MaxPlayerSpecialSquadSpawns(1)=0
|
|
MaxPlayerSpecialSquadSpawns(2)=2
|
|
MaxPlayerSpecialSquadSpawns(3)=2
|
|
|
|
// This is the fixed amount of time between player zed spawns
|
|
PlayerZedSpawnInterval(0)=20.f //40 //25
|
|
PlayerZedSpawnInterval(1)=27.f //40 //30
|
|
PlayerZedSpawnInterval(2)=30.f //40 //35
|
|
PlayerZedSpawnInterval(3)=35.f //40 //40
|
|
BossSpawnPlayerInterval=40.f
|
|
FirstWaveStartSpawnWaitTime=15.f // 20
|
|
LaterWaveStartSpawnWaitTime=10.f //5
|
|
|
|
// Normal
|
|
SoloWaveSpawnRateModifier(0)={(RateModifier[0]=1.5, // Wave 1
|
|
RateModifier[1]=1.5, // Wave 2
|
|
RateModifier[2]=1.5, // Wave 3
|
|
RateModifier[3]=1.5)} // Wave 4
|
|
|
|
// Hard
|
|
SoloWaveSpawnRateModifier(1)={(RateModifier[0]=1.5, // Wave 1
|
|
RateModifier[1]=1.5, // Wave 2
|
|
RateModifier[2]=1.5, // Wave 3
|
|
RateModifier[3]=1.5)} // Wave 4
|
|
|
|
// Suicidal
|
|
SoloWaveSpawnRateModifier(2)={(RateModifier[0]=1.5, // Wave 1
|
|
RateModifier[1]=1.5, // Wave 2
|
|
RateModifier[2]=1.5, // Wave 3
|
|
RateModifier[3]=1.5)} // Wave 4
|
|
|
|
// Hell On Earth
|
|
SoloWaveSpawnRateModifier(3)={(RateModifier[0]=1.0, // Wave 1
|
|
RateModifier[1]=1.0, // Wave 2
|
|
RateModifier[2]=1.0, // Wave 3
|
|
RateModifier[3]=1.0)} // Wave 4
|
|
|
|
// Reserved queue
|
|
ReservedZedQueueLimit=5
|
|
}
|