1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFGameInfo_Survival.uc
2020-12-13 18:01:13 +03:00

1906 lines
53 KiB
Ucode

//=============================================================================
// KFGameInfo_Survival
//=============================================================================
// Classic Killing Floor wave based game type
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Christian "schneidzekk" Schneider
//=============================================================================
class KFGameInfo_Survival extends KFGameInfo;
`include(KFGame\KFGameAnalytics.uci);
`include(KFGame\KFMatchStats.uci);
enum EWaveEndCondition
{
WEC_WaveWon,
WEC_TeamWipedOut,
WEC_GameWon
};
/************************************************************************************
* Game Config
***********************************************************************************/
/** Trader */
var int TimeBetweenWaves;
var const float EndCinematicDelay; // time between the game ending, and the final camera change activating
/************************************************************************************
* AAR
***********************************************************************************/
var const float AARDisplayDelay;
var Array<AARAWard> TeamAwardList;
/************************************************************************************
* Waves
***********************************************************************************/
var byte WaveMax; // The "end" wave
var int WaveNum; // The wave we are currently in
var bool bHumanDeathsLastWave; //Track this separate from player count in case someone dies and leaves
var int ObjectiveSpawnDelay; // How long should the first wave be delayed if there is an active objective.
/** Whether this game mode should play music from the get-go (lobby) */
static function bool ShouldPlayMusicAtStart()
{
return true;
}
/** Whether an action or ambient track should be played */
static function bool ShouldPlayActionMusicTrack(KFGameReplicationInfo GRI)
{
return GRI.bMatchHasBegun && !GRI.bTraderIsOpen;
}
event PreBeginPlay()
{
super.PreBeginPlay();
InitSpawnManager();
UpdateGameSettings();
}
event PostBeginPlay()
{
super.PostBeginPlay();
TimeBetweenWaves = GetTraderTime();
}
/** Set up the spawning */
function InitSpawnManager()
{
SpawnManager = new(self) SpawnManagerClasses[GameLength];
SpawnManager.Initialize();
WaveMax = SpawnManager.WaveSettings.Waves.Length;
MyKFGRI.WaveMax = WaveMax;
}
/* StartMatch()
Start the game - inform all actors that the match is starting, and spawn player pawns
*/
function StartMatch()
{
local KFPlayerController KFPC;
WaveNum = 0;
super.StartMatch();
if( class'KFGameEngine'.static.CheckNoAutoStart() || class'KFGameEngine'.static.IsEditor() )
{
GotoState('DebugSuspendWave');
}
else
{
GotoState('PlayingWave');
}
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.ClientMatchStarted();
}
}
function PlayWaveStartDialog()
{
`DialogManager.PlayWaveStartDialog(MyKFGRI.IsBossWave());
if (WaveNum == 1)
{
class'KFTraderDialogManager'.static.PlayFirstWaveStartDialog(WorldInfo);
}
}
/** Custom logic to determine what the game's current intensity is */
function byte GetGameIntensityForMusic()
{
switch( GameLength )
{
case GL_Short:
if( WaveNum <= 0 )
return 1;
else if( WaveNum <= 1 )
return 4;
else if( WaveNum <= 2 )
return 7;
else
return 10;
case GL_Normal:
if( WaveNum <= 1 )
return 1;
else if( WaveNum <= 3 )
return 4;
else if( WaveNum <= 5 )
return 7;
else
return 10;
case GL_Long:
if( WaveNum <= 2 )
return 1;
else if( WaveNum <= 5 )
return 4;
else if( WaveNum <= 8 )
return 7;
else
return 10;
};
return 255;
}
function bool IsPlayerReady( KFPlayerReplicationInfo PRI )
{
local KFPlayerController KFPC;
// Spawn our player even if we don't have a perk while using the editor or skip lobby
if (class'KFGameEngine'.static.CheckSkipLobby() || class'Engine'.static.IsEditor())
{
return true;
}
if( super.IsPlayerReady(PRI) )
{
KFPC = KFPlayerController(PRI.Owner);
if ( WorldInfo.NetMode == NM_StandAlone && KFPC != None && (KFPC.CurrentPerk == None || !KFPC.CurrentPerk.bInitialized) )
{
// HSL - BWJ - 3-16-16 - console doesn't read stats yet, so no perk support. Adding this hack in for now so we can spawn in
if( WorldInfo.IsConsoleDedicatedServer() || WorldInfo.IsConsoleBuild() )
{
`warn("TODO: Need perk support for console");
return true;
}
else
{
`log("ERROR: Failed to load perk for:"@KFPC@KFPC.CurrentPerk);
//ForceKickPlayer(KFPC, "Failed to find perk");
return false; // critical error
}
}
return true;
}
return false;
}
function bool PlayerCanRestart( PlayerController aPlayer )
{
return (!IsWaveActive() && MyKFGRI.bMatchHasBegun);
}
function RestartPlayer(Controller NewPlayer)
{
local KFPlayerController KFPC;
local KFPlayerReplicationInfo KFPRI;
local bool bWasWaitingForClientPerkData;
KFPC = KFPlayerController(NewPlayer);
KFPRI = KFPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo);
if( KFPC != None && KFPRI != None )
{
if( IsPlayerReady( KFPRI ) )
{
bWasWaitingForClientPerkData = KFPC.bWaitingForClientPerkData;
/** If we have rejoined the match more than once, delay our respawn by some amount of time */
if( MyKFGRI.bMatchHasBegun && KFPRI.NumTimesReconnected > 1 && `TimeSince(KFPRI.LastQuitTime) < ReconnectRespawnTime )
{
KFPC.StartSpectate();
KFPC.SetTimer(ReconnectRespawnTime - `TimeSince(KFPRI.LastQuitTime), false, nameof(KFPC.SpawnReconnectedPlayer));
}
//If a wave is active, we spectate until the end of the wave
else if( IsWaveActive() && !bWasWaitingForClientPerkData )
{
KFPC.StartSpectate();
}
else
{
Super.RestartPlayer(NewPlayer);
// Already gone through one RestartPlayer() cycle, don't process again
if( bWasWaitingForClientPerkData )
{
return;
}
if( KFPRI.Deaths == 0 )
{
if( WaveNum < 1 )
{
KFPRI.Score = DifficultyInfo.GetAdjustedStartingCash();
}
else
{
KFPRI.Score = GetAdjustedDeathPenalty( KFPRI, true );
}
`log("SCORING: Player" @ KFPRI.PlayerName @ "received" @ KFPRI.Score @ "starting cash", bLogScoring);
}
}
`BalanceLog(GBE_Respawn, KFPRI, "$"$KFPRI.Score);
`AnalyticsLog(("player_respawn",
KFPRI,
"#"$MyKFGRI.WaveNum,
"#"$KFPRI.Score ));
}
}
}
function Killed(Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> damageType)
{
local Sequence GameSeq;
local array<SequenceObject> AllWaveProgressEvents;
local KFSeqEvent_WaveProgress WaveProgressEvt;
local int i;
local KFInterface_MapObjective MapObj;
super.Killed(Killer, KilledPlayer, KilledPawn, damageType);
// tell objectives (ie dosh hold and exterminate) when something dies
if (KilledPawn.IsA('KFPawn_Monster'))
{
MapObj = KFInterface_MapObjective(MyKFGRI.CurrentObjective);
if (MapObj != none)
{
MapObj.NotifyZedKilled(Killer, KilledPawn, KFInterface_MonsterBoss(KilledPawn) != none);
}
}
// if not boss wave or endless wave, play progress update trader dialog
if( !MyKFGRI.IsBossWave() && !MyKFGRI.IsEndlessWave() && KilledPawn.IsA('KFPawn_Monster') )
{
// no KFTraderDialogManager object on dedicated server, so use static function
class'KFTraderDialogManager'.static.PlayGlobalWaveProgressDialog( MyKFGRI.AIRemaining, MyKFGRI.WaveTotalAICount, WorldInfo );
// Get the gameplay sequence.
GameSeq = WorldInfo.GetGameSequence();
if (GameSeq != none)
{
GameSeq.FindSeqObjectsByClass(class'KFSeqEvent_WaveProgress', TRUE, AllWaveProgressEvents);
for (i = 0; i < AllWaveProgressEvents.Length; i++)
{
WaveProgressEvt = KFSeqEvent_WaveProgress(AllWaveProgressEvents[i]);
if (WaveProgressEvt != None)
{
WaveProgressEvt.SetWaveProgress(MyKFGRI.AIRemaining, MyKFGRI.WaveTotalAICount, self);
}
}
}
}
//If a human died to a non-suicide
if (KilledPawn.IsA('KFPawn_Human') && DamageType != class'DmgType_Suicided')
{
bHumanDeathsLastWave = true;
}
// BossDied will handle the end of wave.
if(!(KFPawn_Monster(KilledPawn) != none && KFPawn_Monster(KilledPawn).IsABoss()))
{
CheckWaveEnd();
}
}
/* Use reduce damage for friendly fire, etc. */
function ReduceDamage(out int Damage, Pawn Injured, Controller InstigatedBy, vector HitLocation, out vector Momentum, class<DamageType> DamageType, Actor DamageCauser, TraceHitInfo HitInfo)
{
if( Injured.Controller != none && Injured.Controller.bIsPlayer && !MyKFGRI.bMatchHasBegun )
{
Damage = 0;
Momentum = vect(0,0,0);
}
Super.ReduceDamage(Damage, Injured, InstigatedBy, HitLocation, Momentum, DamageType, DamageCauser, HitInfo);
}
function BossDied(Controller Killer, optional bool bCheckWaveEnded = true)
{
local KFPawn_Monster AIP;
local KFGameReplicationInfo KFGRI;
local KFPlayerController KFPC;
super.BossDied(Killer, bCheckWaveEnded);
KFPC = KFPlayerController(Killer);
`RecordBossMurderer(KFPC);
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if( KFGRI != none && !KFGRI.IsBossWave() )
{
return;
}
// Extended zed time for an extra dramatic event
DramaticEvent( 1, 6.f );
// Kill all zeds active zeds when the game ends
foreach WorldInfo.AllPawns(class'KFPawn_Monster', AIP)
{
if( AIP.Health > 0 )
{
AIP.Died(none , none, AIP.Location);
}
}
if(bCheckWaveEnded)
{
CheckWaveEnd( true );
}
}
function UpdateGameSettings()
{
local name SessionName;
local KFOnlineGameSettings KFGameSettings;
local int NumHumanPlayers, i;
local KFGameEngine KFEngine;
if (WorldInfo.NetMode == NM_DedicatedServer || WorldInfo.NetMode == NM_ListenServer)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo_Survival.UpdateGameSettings 1 - GameInterface: "$GameInterface);
if (GameInterface != None)
{
KFEngine = KFGameEngine(class'Engine'.static.GetEngine());
SessionName = PlayerReplicationInfoClass.default.SessionName;
if( PlayfabInter != none && PlayfabInter.GetGameSettings() != none )
{
KFGameSettings = KFOnlineGameSettings(PlayfabInter.GetGameSettings());
KFGameSettings.bAvailableForTakeover = KFEngine.bAvailableForTakeover;
}
else
{
KFGameSettings = KFOnlineGameSettings(GameInterface.GetGameSettings(SessionName));
}
//Ensure bug-for-bug compatibility with KF1
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo_Survival.UpdateGameSettings 2 - KFGameSettings: "$KFGameSettings);
if (KFGameSettings != None)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo_Survival.UpdateGameSettings 3 - KFGameSettings.bAvailableForTakeover: "$KFGameSettings.bAvailableForTakeover);
KFGameSettings.Mode = GetGameModeNum();
KFGameSettings.Difficulty = GameDifficulty;
//Ensure bug-for-bug compatibility with KF1
if (WaveNum == 0)
{
KFGameSettings.bInProgress = false;
KFGameSettings.CurrentWave = 1;
}
else
{
KFGameSettings.bInProgress = true;
KFGameSettings.CurrentWave = WaveNum;
}
//Also from KF1
if(MyKFGRI != none)
{
KFGameSettings.NumWaves = MyKFGRI.GetFinalWaveNum();
}
else
{
KFGameSettings.NumWaves = WaveMax - 1;
}
KFGameSettings.OwningPlayerName = class'GameReplicationInfo'.default.ServerName;
KFGameSettings.NumPublicConnections = MaxPlayersAllowed;
KFGameSettings.bRequiresPassword = RequiresPassword();
KFGameSettings.bCustom = bIsCustomGame;
KFGameSettings.bUsesStats = !IsUnrankedGame();
KFGameSettings.NumSpectators = NumSpectators;
if(MyKFGRI != none)
{
MyKFGRI.bCustom = bIsCustomGame;
}
// Set the map name
//@SABER_EGS IsEOSDedicatedServer() case added
if( WorldInfo.IsConsoleDedicatedServer() || WorldInfo.IsEOSDedicatedServer() )
{
KFGameSettings.MapName = WorldInfo.GetMapName(true);
if( GameReplicationInfo != none )
{
for( i = 0; i < GameReplicationInfo.PRIArray.Length; i++ )
{
if( !GameReplicationInfo.PRIArray[i].bBot )
{
NumHumanPlayers++;
}
}
}
KFGameSettings.NumOpenPublicConnections = KFGameSettings.NumPublicConnections - NumHumanPlayers;
}
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo_Survival.UpdateGameSettings 4 - PlayfabInter: "$PlayfabInter);
if (PlayfabInter != none)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo_Survival.UpdateGameSettings 4.1 - IsRegisteredWithPlayfab: "$PlayfabInter.IsRegisteredWithPlayfab());
}
if( PlayfabInter != none && PlayfabInter.IsRegisteredWithPlayfab() )
{
PlayfabInter.ServerUpdateOnlineGame();
//@SABER_EGS_BEGIN Crossplay support
if (WorldInfo.IsEOSDedicatedServer()) {
GameInterface.UpdateOnlineGame(SessionName, KFGameSettings, true);
}
//@SABER_EGS_END
}
else
{
//Trigger re-broadcast of game settings
GameInterface.UpdateOnlineGame(SessionName, KFGameSettings, true);
}
}
}
}
}
function OnServerTitleDataRead()
{
super.OnServerTitleDataRead();
class'KFGameEngine'.static.RefreshEventContent();
//set boss index again here - this fixes the case of seasonal events like christmas setting krampus the only boss
//to spawn on krampuses lair
SetBossIndex();
}
/**
* Return whether Viewer is allowed to spectate from ViewTarget's PoV.
* Used to prevent players from spectating zeds when the DummyPRI is active.
*
*/
function bool CanSpectate( PlayerController Viewer, PlayerReplicationInfo ViewTarget )
{
// Normal PRI's should be replicatable, DummyPRI is not, indicating a zed
return ( super.CanSpectate(Viewer, ViewTarget)
&& (Viewer.PlayerReplicationInfo.bOnlySpectator || Viewer.GetTeamNum() == ViewTarget.GetTeamNum() || MyKFGRI.bTraderIsOpen) );
}
/************************************************************************************
* Timers
***********************************************************************************/
/** Default timer, called from native */
event Timer()
{
super.Timer();
if( SpawnManager != none )
{
SpawnManager.Update();
}
if( GameConductor != none )
{
GameConductor.TimerUpdate();
}
}
/**
* @brief Checks if we are playing coop online with other people
*
* @return true if hosting and more than 1 player
*/
function byte IsMultiplayerGame()
{
return (WorldInfo.NetMode != NM_Standalone && GetNumPlayers() > 1) ? 1 : 0;
}
function UpdateWaveEndDialogInfo()
{
local int PlayersAlive, PlayersTotal, MostZedsKilled, MostDoshEarned, BestTeammateScore;
local KFPlayerController KFPC, KilledMostZeds, EarnedMostDosh, BestTeammate;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( KFPC.bDemoOwner )
{
// don't count demorecspectator
continue;
}
PlayersTotal++;
if( KFPC.Pawn != none && KFPC.Pawn.IsAliveAndWell() )
{
PlayersAlive++;
}
if( KFPC.MatchStats.ZedsKilledLastWave > MostZedsKilled )
{
KilledMostZeds = KFPC;
MostZedsKilled = KFPC.MatchStats.ZedsKilledLastWave;
}
if( KFPC.MatchStats.GetDoshEarnedInWave() > MostDoshEarned )
{
EarnedMostDosh = KFPC;
MostDoshEarned = KFPC.MatchStats.GetDoshEarnedInWave();
}
if( KFPC.MatchStats.ZedsKilledLastWave + KFPC.MatchStats.GetDoshEarnedInWave() > BestTeammateScore )
{
BestTeammate = KFPC;
BestTeammateScore = KFPC.MatchStats.ZedsKilledLastWave + KFPC.MatchStats.GetDoshEarnedInWave();
}
}
if( PlayersTotal > 1 )
{
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( KFPC.Pawn != none && KFPC.Pawn.IsAliveAndWell() )
{
if( !bHumanDeathsLastWave )
{
// no teammates died
KFPC.PWRI.bAllSurvivedLastWave = true;
}
else if( PlayersAlive == 1 )
{
// only survivor
KFPC.PWRI.bOneSurvivedLastWave = true;
}
else
{
// more than one teammate died
KFPC.PWRI.bSomeSurvivedLastWave = true;
}
}
}
}
if( KilledMostZeds != none )
{
KilledMostZeds.PWRI.bKilledMostZeds = true;
}
if( EarnedMostDosh != none )
{
EarnedMostDosh.PWRI.bEarnedMostDosh = true;
}
if( BestTeammate != none )
{
BestTeammate.PWRI.bBestTeammate = true;
}
bHumanDeathsLastWave = false;
}
/************************************************************************************
* Do$h
***********************************************************************************/
/* Add up the team's earned money and give it to the surviving players */
function RewardSurvivingPlayers()
{
local int PlayerCut;
local int PlayerCount;
local KFPlayerController KFPC;
Local KFTeamInfo_Human T;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( KFPC.Pawn != none && KFPC.Pawn.IsAliveAndWell() )
{
PlayerCount++;
// Find the player's team
if( T == none && KFPC.PlayerReplicationInfo != none && KFPC.PlayerReplicationInfo.Team != none )
{
T = KFTeamInfo_Human(KFPC.PlayerReplicationInfo.Team);
}
}
}
// No dosh to distribute if there is no team or score
if( T == none || T.Score <= 0 )
{
return;
}
PlayerCut = Round(T.Score / PlayerCount);
`log("SCORING: Team dosh earned this round:" @ T.Score, bLogScoring);
`log("SCORING: Number of surviving players:" @ PlayerCount, bLogScoring);
`log("SCORING: Dosh/survivng player:" @ PlayerCut, bLogScoring);
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( KFPC.Pawn != none && KFPC.Pawn.IsAliveAndWell() )
{
KFPlayerReplicationInfo(KFPC.PlayerReplicationInfo).AddDosh(PlayerCut, true);
T.AddScore( -PlayerCut );
`log("Player" @ KFPC.PlayerReplicationInfo.PlayerName @ "got" @ PlayerCut @ "for surviving the wave", bLogScoring);
}
}
// Reset team score afte the wave ends
T.AddScore( 0, true );
}
function int CalculateMinimumRespawnDosh(float UsedMaxRespawnDosh)
{
return Round(UsedMaxRespawnDosh * (float(WaveNum) / float(MyKFGRI.GetFinalWaveNum())));
}
/**
* @brief Calculates the dosh penalty or minimum dosh spawning amount
*
* @param KilledPlayerPRI The killed player's PRI
* @param bLateJoiner If true, give late joiners money without penalty
* @return Rounded amount of dosh
*/
function int GetAdjustedDeathPenalty( KFPlayerReplicationInfo KilledPlayerPRI, optional bool bLateJoiner=false )
{
local float MinimumRespawnDosh, PlayerRespawnDosh;
local float UsedMaxRespawnDosh;
//pointless to give respawn money in the last wave
if( WaveNum >= WaveMax )
{
return 0;
}
if( GameDifficulty < MaxRespawnDosh.Length )
{
UsedMaxRespawnDosh = MaxRespawnDosh[GameDifficulty];
}
else
{
UsedMaxRespawnDosh = MaxRespawnDosh[MaxRespawnDosh.Length - 1];
}
MinimumRespawnDosh = CalculateMinimumRespawnDosh(UsedMaxRespawnDosh);
if( bLateJoiner )
{
return CalculateLateJoinerStartingDosh(MinimumRespawnDosh);
}
`log( "SCORING: Player" @ KilledPlayerPRI.PlayerName @ "predicted minimum respawn dosh:" @ MinimumRespawnDosh, bLogScoring );
PlayerRespawnDosh = KilledPlayerPRI.Score - (KilledPlayerPRI.Score * DeathPenaltyModifiers[GameDifficulty]);
`log( "SCORING: Player" @ KilledPlayerPRI.PlayerName @ "current respawn dosh:" @ PlayerRespawnDosh, bLogScoring );
if( MinimumRespawnDosh > PlayerRespawnDosh )
{
`log( "SCORING: Player" @ KilledPlayerPRI.PlayerName @ "MinimumRespawnDosh > PlayerRespawnDosh, returning MinimumRespawnDosh - KilledPlayerPRI.Score =" @ MinimumRespawnDosh - KilledPlayerPRI.Score, bLogScoring );
return MinimumRespawnDosh - KilledPlayerPRI.Score;
}
`log( "SCORING: Player" @ KilledPlayerPRI.PlayerName @ "PlayerRespawnDosh > MinimumRespawnDosh, returning KilledPlayerPRI.Score * DeathPenaltyModifiers[GameDifficulty] =" @ -Round( KilledPlayerPRI.Score * DeathPenaltyModifiers[GameDifficulty] ), bLogScoring );
return -Round( KilledPlayerPRI.Score * DeathPenaltyModifiers[GameDifficulty] );
}
function int CalculateLateJoinerStartingDosh(int MinimumRespawnDosh)
{
if (default.LateArrivalStarts.Length > 0 && GameLength >= 0 && GameLength < default.LateArrivalStarts.Length)
{
if (default.LateArrivalStarts[GameLength].StartingDosh.Length > 0 && WaveNum - 1 >= 0 && WaveNum - 1 < default.LateArrivalStarts[GameLength].StartingDosh.Length)
{
`log("SCORING: Late joiner received" @ LateArrivalStarts[GameLength].StartingDosh[WaveNum - 1]);
return default.LateArrivalStarts[GameLength].StartingDosh[WaveNum - 1];
}
}
`log("SCORING: Late joiner - invalid parameters to properly award late joiner dosh. Will instead receive Minimum Respawn Dosh of" @ MinimumRespawnDosh, bLogScoring);
return MinimumRespawnDosh;
}
/************************************************************************************
* Wave Mode Cheats
***********************************************************************************/
function bool AllowWaveCheats()
{
`if(`notdefined(ShippingPC))
return true;
`else
return class'KFGameEngine'.static.IsEditor();
`endif
}
function FindCollectibles()
{
`if(`notdefined(ShippingPC))
local KFPlayerController KFPC;
local KFCollectibleActor Collectible;
local Vector EmptyVector;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC.IsLocalPlayerController())
{
foreach WorldInfo.AllActors(class'KFCollectibleActor', Collectible)
{
Collectible.TakeDamage(100, KFPC, EmptyVector, EmptyVector, class'KFDT_Fire_Mac10');
}
}
}
`endif
}
exec function ToggleSpawning( optional string ZedTypeString )
{
if( AllowWaveCheats() && GameReplicationInfo.bMatchHasBegun )
{
if( ZedTypeString != "" )
{
//OverrideZedSpawnList( ZedTypeString );
}
if( GetStateName() == 'DebugSuspendWave' )
{
GotoState('PlayingWave');
}
else
{
GotoState('DebugSuspendWave');
}
}
}
exec function EndCurrentWave ( )
{
if( AllowWaveCheats() )
{
DebugKillZeds();
WaveEnded(WEC_WaveWon);
}
}
exec function SetWave( byte NewWaveNum )
{
if( AllowWaveCheats() )
{
if( NewWaveNum <= WaveMax )
{
WaveNum = NewWaveNum - 1;
// stop, then restart
GotoState('DebugSuspendWave');
GotoState('PlayingWave');
ResetAllPickups();
}
else
{
`log("SetWave: new wave num must be <= "$WaveMax);
}
}
}
exec function WinMatch()
{
if( AllowWaveCheats() )
{
WaveNum = SpawnManager.WaveSettings.Waves.Length;
WaveEnded(WEC_WaveWon);
}
}
/*********************************************************************************************
* state PlayingWave
*********************************************************************************************/
/** Returns true if wave gameplay is active */
function bool IsWaveActive();
State PlayingWave
{
function BeginState( Name PreviousStateName )
{
MyKFGRI.SetWaveActive(TRUE, GetGameIntensityForMusic());
MyKFGRI.VoteCollector.ResetSkipTraderVote();
StartWave();
if ( AllowBalanceLogging() )
{
LogPlayersDosh(GBE_WaveStart);
}
}
function bool IsWaveActive()
{
return true;
}
}
/** Starts a new Wave */
function StartWave()
{
local int WaveBuffer;
local KFPlayerController KFPC;
//closes trader on server
if (MyKFGRI.OpenedTrader != none)
{
MyKFGRI.CloseTrader();
NotifyTraderClosed();
}
WaveBuffer = 0;
WaveNum++;
MyKFGRI.WaveNum = WaveNum;
if (IsMapObjectiveEnabled())
{
MyKFGRI.ClearPreviousObjective();
if (MyKFGRI.StartNextObjective())
{
WaveBuffer = ObjectiveSpawnDelay;
}
}
SetupNextWave(WaveBuffer);
NumAIFinishedSpawning = 0;
NumAISpawnsQueued = 0;
AIAliveCount = 0;
MyKFGRI.bForceNextObjective = false;
if( WorldInfo.NetMode != NM_DedicatedServer && Role == ROLE_Authority )
{
MyKFGRI.UpdateHUDWaveCount();
}
WaveStarted();
MyKFGRI.NotifyWaveStart();
MyKFGRI.AIRemaining = SpawnManager.WaveTotalAI;
MyKFGRI.WaveTotalAICount = SpawnManager.WaveTotalAI;
BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GetWaveStartMessage());
SetupNextTrader();
ResetAllPickups();
`DialogManager.SetTraderTime( false );
// first spawn and music are delayed 5 seconds (KFAISpawnManager.TimeUntilNextSpawn == 5 initially), so line up dialog with them;
// fixes problem of clients not being ready to receive dialog at the instant the match starts;
SetTimer( 5.f, false, nameof(PlayWaveStartDialog) );
//Reset Supplier perks here
ForEach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC.GetPerk() != none)
{
KFPC.GetPerk().OnWaveStart();
}
}
}
function SetupNextWave(int WaveBuffer)
{
SpawnManager.SetupNextWave(WaveNum-1, WaveBuffer);
}
function byte GetWaveStartMessage()
{
if (MyKFGRI.IsBossWave())
{
return GMT_WaveSBoss;
}
return GMT_WaveStart;
}
/** Called to reset all the types of pickups */
function ResetAllPickups()
{
// for the boss wave request all ammo pickups
if (MyKFGRI.IsBossWave())
{
// -1, so that we always have a different pickup to activate
NumAmmoPickups = Max(AmmoPickups.Length - 1, 0);
}
Super.ResetAllPickups();
}
/** Overridden to scale the number of active pickups by wave */
function ResetPickups( array<KFPickupFactory> PickupList, int NumPickups )
{
NumPickups *= (float(WaveNum) / float(WaveMax));
// make sure to have at least 1 ammo pickup in the level, and if it's wave 2 or later make sure there's
// at least one weapon pickup
if( NumPickups == 0 && PickupList.Length > 0 && (WaveNum > 1 || KFPickupFactory_Ammo(PickupList[0]) != none) )
{
NumPickups = 1;
}
super.ResetPickups( PickupList, NumPickups );
}
/** Sets NextTrader and re-inits trader list if necessary */
function SetupNextTrader()
{
local byte NextTraderIndex;
// Try to set the scripted trader first
if( ScriptedTrader != none )
{
MyKFGRI.NextTrader = ScriptedTrader;
return;
}
if( TraderList.Length > 0 )
{
NextTraderIndex = DetermineNextTraderIndex();
if( NextTraderIndex >= 0 && NextTraderIndex < TraderList.Length )
{
MyKFGRI.NextTrader = TraderList[ NextTraderIndex ];
TraderList.Remove( NextTraderIndex, 1 );
}
}
if( TraderList.Length <= 0 )
{
InitTraderList();
}
}
/** Picks next trader index and allows for mutator hook */
function byte DetermineNextTraderIndex()
{
local byte NextTraderIndex;
NextTraderIndex = Rand( TraderList.Length );
`if(`__TW_SDK_)
if( BaseMutator != none )
{
BaseMutator.ModifyNextTraderIndex( NextTraderIndex );
}
`endif
return NextTraderIndex;
}
function WaveStarted()
{
local array<SequenceObject> AllWaveStartEvents, AllWaveProgressEvents;
local array<int> OutputLinksToActivate;
local KFSeqEvent_WaveStart WaveStartEvt;
local KFSeqEvent_WaveProgress WaveProgressEvt;
local Sequence GameSeq;
local int i;
local KFPlayerController KFPC;
`AnalyticsLog(("wave_start",
None,
"#"$WaveNum,
"#"$GetLivingPlayerCount()));
GameConductor.ResetWaveStats();
ForEach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( !KFPC.bDemoOwner )
{
`AnalyticsLog(("pc_wave_start",
KFPC.PlayerReplicationInfo,
"#"$WaveNum,
KFPC.GetPerk().Class.Name,
KFPC.GetPerk().GetLevel(),
"#"$KFPC.PlayerReplicationInfo.Score,
KFPC.Pawn != none ? KFInventoryManager(KFPC.Pawn.InvManager).DumpInventory() : "",
KFPC.GetPerk().DumpPerkLoadout(),
KFPC.PlayerReplicationInfo.GetTeamNum()
));
}
`QALog("Player Name:" @ KFPC.PlayerReplicationInfo.PlayerName @ "Dosh" @ KFPC.PlayerReplicationInfo.Score, bLogScoring);
}
// Get the gameplay sequence.
GameSeq = WorldInfo.GetGameSequence();
if( GameSeq != none )
{
GameSeq.FindSeqObjectsByClass(class'KFSeqEvent_WaveStart', TRUE, AllWaveStartEvents);
for( i = 0; i < AllWaveStartEvents.Length; i++ )
{
WaveStartEvt = KFSeqEvent_WaveStart(AllWaveStartEvents[i]);
if( WaveStartEvt != None )
{
WaveStartEvt.Reset();
WaveStartEvt.SetWaveNum( WaveNum, WaveMax );
if( MyKFGRI.IsBossWave() && WaveStartEvt.OutputLinks.Length > 1 )
{
OutputLinksToActivate.AddItem( 1 );
}
else
{
OutputLinksToActivate.AddItem( 0 );
}
WaveStartEvt.CheckActivate( self, self,, OutputLinksToActivate );
}
}
GameSeq.FindSeqObjectsByClass(class'KFSeqEvent_WaveProgress', TRUE, AllWaveProgressEvents);
for( i = 0; i < AllWaveProgressEvents.Length; i++ )
{
WaveProgressEvt = KFSeqEvent_WaveProgress(AllWaveProgressEvents[i]);
if( WaveProgressEvt != None )
{
WaveProgressEvt.Reset();
}
}
}
//So the server browser can have our new wave information
UpdateGameSettings();
}
/** Do something when there are no AIs left */
function CheckWaveEnd( optional bool bForceWaveEnd = false )
{
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ZombieServerLog("KFGameInfo_Survival.CheckWaveEnd - bForceWaveEnd: "$bForceWaveEnd$"; bMatchHasBegun: "$MyKFGRI.bMatchHasBegun$"; GetLivingPlayerCount(): "$GetLivingPlayerCount()$"; AIAliveCount: "$AIAliveCount$"; IsWaveActive(): "$IsWaveActive()$"; IsFinishedSpawning(): "$SpawnManager.IsFinishedSpawning());
}
if( !MyKFGRI.bMatchHasBegun )
{
`log("KFGameInfo - CheckWaveEnd - Cannot check if wave has ended since match has not begun. ");
return;
}
`log("KFGameInfo.CheckWaveEnd() AIAliveCount:" @ AIAliveCount, SpawnManager.bLogAISpawning);
if( GetLivingPlayerCount() <= 0 )
{
// `log("KFGameInfo.CheckWaveEnd() - Call Wave Ended - WEC_TeamWipedOut");
WaveEnded(WEC_TeamWipedOut);
}
else if( (AIAliveCount <= 0 && IsWaveActive() && SpawnManager.IsFinishedSpawning()) || bForceWaveEnd )
{
//`log("KFGameInfo.CheckWaveEnd() - Call Wave Ended - WEC_WaveWon");
WaveEnded(WEC_WaveWon);
}
}
/** The wave ended */
function WaveEnded(EWaveEndCondition WinCondition)
{
local array<SequenceObject> AllWaveEndEvents;
local array<int> OutputLinksToActivate;
local KFSeqEvent_WaveEnd WaveEndEvt;
local Sequence GameSeq;
local int i;
local KFPlayerController KFPC;
if (WorldInfo.NetMode == NM_DedicatedServer)
{
scripttrace();
//`REMOVEMESOON_ZombieServerLog("KFGameInfo_Survival.WaveEnded - WinCondition: "$WinCondition$"; WaveNum: "$WaveNum$"; WaveMax: "$WaveMax);
}
// Get the gameplay sequence.
GameSeq = WorldInfo.GetGameSequence();
if( GameSeq != none )
{
GameSeq.FindSeqObjectsByClass( class'KFSeqEvent_WaveEnd', TRUE, AllWaveEndEvents );
for( i = 0; i < AllWaveEndEvents.Length; ++i )
{
WaveEndEvt = KFSeqEvent_WaveEnd( AllWaveEndEvents[i] );
if( WaveEndEvt != None )
{
WaveEndEvt.Reset();
WaveEndEvt.SetWaveNum( WaveNum, WaveMax );
if( MyKFGRI.IsBossWave() && WaveEndEvt.OutputLinks.Length > 1 )
{
OutputLinksToActivate.AddItem( 1 );
}
else
{
OutputLinksToActivate.AddItem( 0 );
}
WaveEndEvt.CheckActivate( self, self,, OutputLinksToActivate );
}
}
}
BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GMT_WaveEnd);
MyKFGRI.DeactivateObjective();
MyKFGRI.NotifyWaveEnded();
`DialogManager.SetTraderTime( !MyKFGRI.IsBossWave() );
`AnalyticsLog(("wave_end", None, "#"$WaveNum, GetEnum(enum'EWaveEndCondition',WinCondition), "#"$GameConductor.CurrentWaveZedVisibleAverageLifeSpan));
// IsPlayInEditor check was added to fix a scaleform crash that would call an actionscript function
// as scaleform was being destroyed. This issue only occurs when playing in the editor
if( WinCondition == WEC_TeamWipedOut && !class'WorldInfo'.static.IsPlayInEditor())
{
EndOfMatch(false);
}
else if( WinCondition == WEC_WaveWon )
{
RewardSurvivingPlayers();
UpdateWaveEndDialogInfo();
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC != none)
{
KFPC.OnWaveComplete(WaveNum);
}
}
if( WaveNum < WaveMax )
{
GotoState( 'TraderOpen', 'Begin' );
}
else
{
EndOfMatch(true);
}
}
// To allow any statistics that are recorded on the very last zed killed at the end of the wave,
// wait a single frame to allow them to finalize.
SetTimer( WorldInfo.DeltaSeconds, false, nameOf(Timer_FinalizeEndOfWaveStats) );
}
/** All stats should be finalized here */
function Timer_FinalizeEndOfWaveStats()
{
local bool bOpeningTrader;
local KFPlayerController KFPC;
bOpeningTrader = MyKFGRI.bTraderIsOpen && !IsInState( 'MatchEnded' ) && !IsInState( 'RoundEnded ');
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
{
// submit online player analytics
LogWaveEndAnalyticsFor( KFPC );
// submit aar/dialog stats
KFPC.SubmitPostWaveStats( bOpeningTrader );
`QALog( "Player Name:" @ KFPC.PlayerReplicationInfo.PlayerName @ "Dosh" @ KFPC.PlayerReplicationInfo.Score, bLogScoring );
}
}
/** Game Analytics */
function LogWaveEndAnalyticsFor(KFPlayerController KFPC)
{
local int i;
local array<WeaponDamage> Weapons;
if( KFPC.bDemoOwner )
{
return;
}
`AnalyticsLog(("pc_wave_stats",
KFPC.PlayerReplicationInfo,
"#"$WaveNum,
"#"$KFPC.MatchStats.GetHealGivenInWave(),
"#"$KFPC.MatchStats.GetHeadShotsInWave(),
"#"$KFPC.MatchStats.GetDoshEarnedInWave(),
"#"$KFPC.MatchStats.GetDamageTakenInWave(),
"#"$KFPC.MatchStats.GetDamageDealtInWave(),
"#"$KFPC.ShotsFired,
"#"$KFPC.ShotsHit,
"#"$KFPC.ShotsHitHeadshot ));
`AnalyticsLog(("pc_wave_end",
KFPC.PlayerReplicationInfo,
"#"$WaveNum,
KFPC.GetPerk().Class.Name,
"#"$KFPC.GetPerk().GetLevel(),
"#"$KFPC.PlayerReplicationInfo.Score,
"#"$KFPC.PlayerReplicationInfo.Kills,
(KFPC.Pawn != none && KFPC.Pawn.InvManager != none) ? KFInventoryManager(KFPC.Pawn.InvManager).DumpInventory() : ""));
KFPC.MatchStats.GetTopWeapons( 3, Weapons );
for ( i = 0; i < Weapons.Length; ++i )
{
`AnalyticsLog(("pc_weapon_stats",
KFPC.PlayerReplicationInfo,
"#"$WaveNum,
Weapons[i].WeaponDef.Name,
"#"$Weapons[i].DamageAmount,
"#"$Weapons[i].HeadShots, // really head kills
"#"$Weapons[i].LargeZedKills ));
}
}
/** Allow specific map to override the spawn class. Default implementation returns from the AI class list. */
function class<KFPawn_Monster> GetAISpawnType(EAIType AIType)
{
local KFMapInfo KFMI;
local KFGameReplicationInfo KFGRI;
local array<SpawnReplacement> SpawnReplacements;
local int i;
KFMI = KFMapInfo(WorldInfo.GetMapInfo());
if (KFMI != none)
{
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if (KFMI.bUsePresetObjectives &&
((!KFMI.WaveHasPresetObjectives(WaveNum, GameLength)) ||
(KFGRI != none && KFGRI.ObjectiveInterface != none && KFGRI.ObjectiveInterface.CanActivateObjective())))
{
switch (GameLength)
{
case GL_Short:
SpawnReplacements = KFMI.PresetWaveObjectives.ShortObjectives[WaveNum-1].SpawnReplacements;
break;
case GL_Normal:
SpawnReplacements = KFMI.PresetWaveObjectives.MediumObjectives[WaveNum-1].SpawnReplacements;
break;
case GL_Long:
SpawnReplacements = KFMI.PresetWaveObjectives.LongObjectives[WaveNum-1].SpawnReplacements;
break;
};
if (SpawnReplacements.Length > 0)
{
for (i = 0; i < SpawnReplacements.Length; ++i)
{
if (SpawnReplacements[i].SpawnEntry == AIType && FRand() < SpawnReplacements[i].PercentChance)
{
if (SpawnReplacements[i].NewClass.Length > 0)
{
return SpawnReplacements[i].NewClass[Rand(SpawnReplacements[i].NewClass.Length)];
}
}
}
}
}
}
return AIClassList[AIType];
}
/** Scale to use against WaveTotalAI to determine full wave size */
function float GetTotalWaveCountScale()
{
local float WaveScale;
local KFMapInfo KFMI;
local KFGameReplicationInfo KFGRI;
WaveScale = super.GetTotalWaveCountScale();
KFMI = KFMapInfo(WorldInfo.GetMapInfo());
if (KFMI != none)
{
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if (KFMI.bUsePresetObjectives &&
((!KFMI.WaveHasPresetObjectives(WaveNum, GameLength)) ||
(KFGRI != none && KFGRI.ObjectiveInterface != none && KFGRI.ObjectiveInterface.CanActivateObjective())))
{
switch (GameLength)
{
case GL_Short:
WaveScale *= KFMI.PresetWaveObjectives.ShortObjectives[WaveNum-1].WaveScale;
break;
case GL_Normal:
WaveScale *= KFMI.PresetWaveObjectives.MediumObjectives[WaveNum-1].WaveScale;
break;
case GL_Long:
WaveScale *= KFMI.PresetWaveObjectives.LongObjectives[WaveNum-1].WaveScale;
break;
};
}
}
return WaveScale;
}
/** Allow specific game types to modify the spawn rate at a global level */
function float GetGameInfoSpawnRateMod()
{
local float SpawnRateMod;
local KFGameReplicationInfo KFGRI;
local KFMapInfo KFMI;
local int NumPlayersAlive;
SpawnRateMod = super.GetGameInfoSpawnRateMod();
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if (KFGRI == none)
{
return SpawnRateMod;
}
KFMI = KFMapInfo(WorldInfo.GetMapInfo());
if (KFMI == none)
{
return SpawnRateMod;
}
if (KFMI.bUsePresetObjectives &&
((!KFMI.WaveHasPresetObjectives(WaveNum, GameLength)) ||
(KFGRI != none && KFGRI.ObjectiveInterface != none && KFGRI.ObjectiveInterface.CanActivateObjective())))
{
switch (GameLength)
{
case GL_Short:
NumPlayersAlive = Clamp(KFGRI.GetNumPlayersAlive(), 1, KFMI.PresetWaveObjectives.ShortObjectives[WaveNum-1].PerPlayerSpawnRateMod.Length) - 1;
SpawnRateMod *= KFMI.PresetWaveObjectives.ShortObjectives[WaveNum-1].PerPlayerSpawnRateMod[NumPlayersAlive];
break;
case GL_Normal:
NumPlayersAlive = Clamp(KFGRI.GetNumPlayersAlive(), 1, KFMI.PresetWaveObjectives.MediumObjectives[WaveNum-1].PerPlayerSpawnRateMod.Length) - 1;
SpawnRateMod *= KFMI.PresetWaveObjectives.MediumObjectives[WaveNum-1].PerPlayerSpawnRateMod[NumPlayersAlive];
break;
case GL_Long:
NumPlayersAlive = Clamp(KFGRI.GetNumPlayersAlive(), 1, KFMI.PresetWaveObjectives.LongObjectives[WaveNum-1].PerPlayerSpawnRateMod.Length) - 1;
SpawnRateMod *= KFMI.PresetWaveObjectives.LongObjectives[WaveNum-1].PerPlayerSpawnRateMod[NumPlayersAlive];
break;
};
}
return SpawnRateMod;
}
/*********************************************************************************************
* state TraderOpen
*********************************************************************************************/
/** Called when TimeBetweenWaves expires */
function CloseTraderTimer();
function SkipTrader(int TimeAfterSkipTrader)
{
SetTimer(TimeAfterSkipTrader, false, nameof(CloseTraderTimer));
}
/** Cleans up anything from the previous wave that needs to be removed for trader time */
function DoTraderTimeCleanup();
/** Handle functionality for opening trader */
function OpenTrader()
{
MyKFGRI.OpenTrader(TimeBetweenWaves);
NotifyTraderOpened();
}
State TraderOpen
{
function BeginState( Name PreviousStateName )
{
local KFPlayerController KFPC;
MyKFGRI.SetWaveActive(FALSE, GetGameIntensityForMusic());
ForEach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if( KFPC.GetPerk() != none )
{
KFPC.GetPerk().OnWaveEnded();
}
KFPC.ApplyPendingPerks();
}
// Restart players
StartHumans();
OpenTrader();
if ( AllowBalanceLogging() )
{
LogPlayersDosh(GBE_TraderOpen);
}
SetTimer(TimeBetweenWaves, false, nameof(CloseTraderTimer));
}
/** Cleans up anything from the previous wave that needs to be removed for trader time */
function DoTraderTimeCleanup()
{
local KFProj_BloatPukeMine PukeMine;
// Destroy all lingering explosions
MyKFGRI.FadeOutLingeringExplosions();
// Destroy all puke mine projectiles
foreach DynamicActors( class'KFProj_BloatPukeMine', PukeMine )
{
PukeMine.FadeOut();
}
}
/** Called when TimeBetweenWaves expires */
function CloseTraderTimer()
{
GotoState('PlayingWave');
}
function EndState( Name NextStateName )
{
if ( AllowBalanceLogging() )
{
LogPlayersInventory();
}
}
/** Special handling for survival/wave mode. Reduces health to 1 */
function bool PreventDeath(Pawn KilledPawn, Controller Killer, class<DamageType> DamageType, vector HitLocation)
{
// (player-only) Prevent enemy team kills during trader time to fix players
// missing the respawn and then dying from certain attacks that can do damage
// just after the last zed dies (e.g. explosives/husk suicide, damage over time)
if ( KilledPawn.Controller != None && KilledPawn.Controller.bIsPlayer
&& (Killer == none || (KilledPawn.GetTeamNum() != Killer.GetTeamNum()))
// @hack: Somehow we can get a suicide where Killer!=Victim?
&& DamageType != class'DmgType_Suicided' )
{
// sanity check - The killer pawn should be dead or are detached by now
if ( Killer == none || Killer.Pawn == None || !Killer.Pawn.IsAliveAndWell() )
{
return true;
}
}
return Global.PreventDeath(KilledPawn, Killer, DamageType, HitLocation);
}
Begin:
Sleep(0.1f);
DoTraderTimeCleanup();
}
/** Tell Kismet a trader opened */
function NotifyTraderOpened()
{
local array<SequenceObject> AllTraderOpenedEvents;
local array<int> OutputLinksToActivate;
local KFSeqEvent_TraderOpened TraderOpenedEvt;
local Sequence GameSeq;
local int i;
// Get the gameplay sequence.
GameSeq = WorldInfo.GetGameSequence();
if( GameSeq != none )
{
GameSeq.FindSeqObjectsByClass(class'KFSeqEvent_TraderOpened', TRUE, AllTraderOpenedEvents);
for( i = 0; i < AllTraderOpenedEvents.Length; i++ )
{
TraderOpenedEvt = KFSeqEvent_TraderOpened(AllTraderOpenedEvents[i]);
if( TraderOpenedEvt != None )
{
TraderOpenedEvt.Reset();
TraderOpenedEvt.SetWaveNum( WaveNum, WaveMax );
if( MyKFGRI.IsFinalWave() && TraderOpenedEvt.OutputLinks.Length > 1 )
{
OutputLinksToActivate.AddItem( 1 );
}
else
{
OutputLinksToActivate.AddItem( 0 );
}
TraderOpenedEvt.CheckActivate( self, self,, OutputLinksToActivate );
}
}
}
}
/** Tell Kismet a Trader closed */
function NotifyTraderClosed()
{
local KFSeqEvent_TraderClosed TraderClosedEvt;
local array<SequenceObject> AllTraderClosedEvents;
local Sequence GameSeq;
local int i;
GameSeq = WorldInfo.GetGameSequence();
if (GameSeq != none)
{
GameSeq.FindSeqObjectsByClass(class'KFSeqEvent_TraderClosed', true, AllTraderClosedEvents);
for (i = 0; i < AllTraderClosedEvents.Length; ++i)
{
TraderClosedEvt = KFSeqEvent_TraderClosed(AllTraderClosedEvents[i]);
if (TraderClosedEvt != none)
{
TraderClosedEvt.Reset();
TraderClosedEvt.SetWaveNum(WaveNum, WaveMax);
TraderClosedEvt.CheckActivate(self, self);
}
}
}
}
final function ForceChangeLevel()
{
bGameRestarted = false;
bChangeLevels = true;
RestartGame();
}
/*********************************************************************************************
* state MatchEnded
*********************************************************************************************/
State MatchEnded
{
function BeginState( Name PreviousStateName )
{
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ZombieServerLog("KFGameInfo_Survival:MatchEnded.BeginState - PreviousStateName: "$PreviousStateName);
}
`log("KFGameInfo_Survival - MatchEnded.BeginState - AARDisplayDelay:" @ AARDisplayDelay);
MyKFGRI.EndGame();
MyKFGRI.bWaitingForAAR = true; //@HSL - JRO - 6/15/2016 - Make sure we're still at full speed before the end of game menu shows up
if ( AllowBalanceLogging() )
{
LogPlayersKillCount();
}
SetTimer(90.f, false, 'ForceChangeLevel');
SetTimer(1.f, false, nameof(ProcessAwards));
SetTimer(AARDisplayDelay, false, nameof(ShowPostGameMenu));
}
event Timer()
{
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ZombieServerLog("KFGameInfo_Survival:MatchEnded.Timer - NumPlayers: "$NumPlayers);
}
global.Timer();
if (NumPlayers == 0)
{
RestartGame();
}
}
}
function EndOfMatch(bool bVictory)
{
local KFPlayerController KFPC;
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ZombieServerLog("KFGameInfo_Survival.EndOfMatch - bVictory: "$bVictory);
}
`AnalyticsLog(("match_end", None, "#"$WaveNum, "#"$(bVictory ? "1" : "0"), "#"$GameConductor.ZedVisibleAverageLifespan));
if(bVictory)
{
SetTimer(EndCinematicDelay, false, nameof(SetWonGameCamera));
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.ClientWonGame( WorldInfo.GetMapName( true ), GameDifficulty, GameLength, IsMultiplayerGame() );
}
BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GMT_MatchWon);
}
else
{
BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GMT_MatchLost);
SetZedsToVictoryState();
}
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.ClientGameOver( WorldInfo.GetMapName(true), GameDifficulty, GameLength, IsMultiplayerGame(), WaveNum );
}
GotoState('MatchEnded');
}
//Get Top voted map
function string GetNextMap()
{
local KFGameReplicationInfo KFGRI;
local int NextMapIndex;
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if( KFGRI != none )
{
NextMapIndex = KFGRI.VoteCollector.GetNextMap();
}
if( NextMapIndex != INDEX_NONE )
{
if(WorldInfo.NetMode == NM_Standalone)
{
return KFGRI.VoteCollector.Maplist[NextMapIndex];
}
else
{
return GameMapCycles[ActiveMapCycle].Maps[NextMapIndex];
}
}
return super.GetNextMap();
}
function SetWonGameCamera()
{
local KFPlayerController KFPC;
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
{
KFPC.ServerCamera( 'ThirdPerson' );
}
}
function SetZedsToVictoryState()
{
local KFAIController KFAIC;
foreach WorldInfo.AllControllers( class'KFAIController', KFAIC )
{
// Have the zeds enter their victory state
KFAIC.EnterZedVictoryState();
}
}
function ShowPostGameMenu()
{
local KFGameReplicationInfo KFGRI;
`log("KFGameInfo_Survival - ShowPostGameMenu");
MyKFGRI.bWaitingForAAR = false; //@HSL - JRO - 6/15/2016 - Make sure we're still at full speed before the end of game menu shows up
bEnableDeadToVOIP=true; //Being dead at this point is irrelevant. Allow players to talk about AAR -ZG
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
if(KFGRI != none)
{
KFGRI.OnOpenAfterActionReport( GetEndOfMatchTime() );
}
class'EphemeralMatchStats'.Static.SendMapOptionsAndOpenAARMenu();
UpdateCurrentMapVoteTime( GetEndOfMatchTime(), true);
WorldInfo.TWPushLogs();
}
function float GetEndOfMatchTime()
{
return MapVoteDuration;
}
function ProcessAwards()
{
class'EphemeralMatchStats'.Static.ProcessPostGameStats();
}
function UpdateCurrentMapVoteTime(byte NewTime, optional bool bStartTime)
{
if(WorldInfo.GRI.RemainingTime > NewTime || bStartTime)
{
ClearTimer(nameof(RestartGame));
SetTimer(NewTime, false, nameof(TryRestartGame));
WorldInfo.GRI.RemainingMinute = NewTime;
WorldInfo.GRI.RemainingTime = NewTime;
}
//in the case that the server has a 0 for the time we still want to be able to trigger a server travel.
if(NewTime <= 0 || WorldInfo.GRI.RemainingTime <= 0)
{
TryRestartGame();
}
}
function TryRestartGame()
{
RestartGame();
}
/************************************************************************************
* @name Objectives
***********************************************************************************/
function bool IsMapObjectiveEnabled()
{
return bEnableMapObjectives;
}
function ObjectiveFailed()
{
MyKFGRI.DeactivateObjective();
}
function OnEndlessSpawningObjectiveDeactivated()
{
CheckWaveEnd();
}
/*********************************************************************************************
* state DebugSuspendWave
*********************************************************************************************/
state DebugSuspendWave
{
ignores CheckWaveEnd;
function BeginState( Name PreviousStateName )
{
local PlayerController PC;
DebugKillZeds();
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
PC.ClientMessage("Survival: Spawn suspended");
}
}
function EndState( Name NextStateName )
{
local PlayerController PC;
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
PC.ClientMessage("Survival: Spawn resumed");
}
}
}
/**
* Invoke the CheatManager function. For some reason calling
* ConsoleCommand directly on the GI does not follow through, so
* as a workaround just find a PC and assume they have cheats on
*/
function DebugKillZeds()
{
local PlayerController PC;
if( AllowWaveCheats() )
{
foreach WorldInfo.AllControllers(class'PlayerController', PC)
{
if (KFDemoRecSpectator(PC) == none)
{
PC.ConsoleCommand("KillZeds");
return;
}
}
}
}
DefaultProperties
{
TimeBetweenWaves=60 //This is going to be a difficulty setting later
EndCinematicDelay=4
AARDisplayDelay=15
bCanPerkAlwaysChange=false
MaxGameDifficulty=3
ObjectiveSpawnDelay=5
SpawnManagerClasses(0)=class'KFGame.KFAISpawnManager_Short'
SpawnManagerClasses(1)=class'KFGame.KFAISpawnManager_Normal'
SpawnManagerClasses(2)=class'KFGame.KFAISpawnManager_Long'
MaxRespawnDosh(0)=1750.f // Normal
MaxRespawnDosh(1)=1550.f // Hard
MaxRespawnDosh(2)=1700.f // Suicidal //1550
MaxRespawnDosh(3)=1550.f // Hell On Earth //1000.0
GameplayEventsWriterClass=class'KFGame.KFGameplayEventsWriter'
TraderVoiceGroupClass=class'KFGameContent.KFTraderVoiceGroup_Default'
DifficultyInfoClass=class'KFGameDifficulty_Survival'
DifficultyInfoConsoleClass=class'KFGameDifficulty_Survival_Console'
// Preload content classes (by reference) to prevent load time hitches during gameplay
// and keeps the GC happy. This will also load client content -- via GRI.GameClass
AIClassList(AT_Clot)=class'KFGameContent.KFPawn_ZedClot_Cyst'
AIClassList(AT_AlphaClot)=class'KFGameContent.KFPawn_ZedClot_Alpha'
AIClassList(AT_SlasherClot)=class'KFGameContent.KFPawn_ZedClot_Slasher'
AIClassList(AT_Crawler)=class'KFGameContent.KFPawn_ZedCrawler'
AIClassList(AT_GoreFast)=class'KFGameContent.KFPawn_ZedGorefast'
AIClassList(AT_Stalker)=class'KFGameContent.KFPawn_ZedStalker'
AIClassList(AT_Scrake)=class'KFGameContent.KFPawn_ZedScrake'
AIClassList(AT_FleshPound)=class'KFGameContent.KFPawn_ZedFleshpound'
AIClassList(AT_FleshpoundMini)=class'KFGameContent.KFPawn_ZedFleshpoundMini'
AIClassList(AT_Bloat)=class'KFGameContent.KFPawn_ZedBloat'
AIClassList(AT_Siren)=class'KFGameContent.KFPawn_ZedSiren'
AIClassList(AT_Husk)=class'KFGameContent.KFPawn_ZedHusk'
AIClassList(AT_EliteClot)=class'KFGameContent.KFPawn_ZedClot_AlphaKing'
AIClassList(AT_EliteCrawler)=class'KFGameContent.KFPawn_ZedCrawlerKing'
AIClassList(AT_EliteGoreFast)=class'KFGameContent.KFPawn_ZedGorefastDualBlade'
AIClassList(AT_EDAR_EMP)=class'KFGameContent.KFPawn_ZedDAR_Emp'
AIClassList(AT_EDAR_Laser)=class'KFGameContent.KFPawn_ZedDAR_Laser'
AIClassList(AT_EDAR_Rocket)=class'KFGameContent.KFPawn_ZedDAR_Rocket'
NonSpawnAIClassList(0)=class'KFGameContent.KFPawn_ZedBloatKingSubspawn'
AIBossClassList(BAT_Hans)=class'KFGameContent.KFPawn_ZedHans'
AIBossClassList(BAT_Patriarch)=class'KFGameContent.KFPawn_ZedPatriarch'
AIBossClassList(BAT_KingFleshpound)=class'KFGameContent.KFPawn_ZedFleshpoundKing'
AIBossClassList(BAT_KingBloat)=class'KFGameContent.KFPawn_ZedBloatKing'
AIBossClassList(BAT_Matriarch)=class'KFGameContent.KFPawn_ZedMatriarch'
// Short Wave
LateArrivalStarts(0)={(
StartingDosh[0]=550,
StartingDosh[1]=650,
StartingDosh[2]=1200,
StartingDosh[3]=1500
)}
// Normal Wave
LateArrivalStarts(1)={(
StartingDosh[0]=450,
StartingDosh[1]=600,
StartingDosh[2]=750,
StartingDosh[3]=800,
StartingDosh[4]=1100,
StartingDosh[5]=1400,
StartingDosh[6]=1500,
StartingDosh[7]=1600
)}
// Long Wave
LateArrivalStarts(2)={(
StartingDosh[0]=450,
StartingDosh[1]=550,
StartingDosh[2]=750,
StartingDosh[3]=1000,
StartingDosh[4]=1200,
StartingDosh[5]=1300,
StartingDosh[6]=1400,
StartingDosh[7]=1500,
StartingDosh[8]=1600,
StartingDosh[9]=1600
)}
}