2020-12-13 15:01:13 +00:00
//=============================================================================
// KFGameInfo_VersusSurvival
//=============================================================================
// The game mode class for players vs zeds survival game type
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - John "Ramm-Jaeger" Gibson
//=============================================================================
class KFGameInfo _VersusSurvival extends KFGameInfo _Survival ;
/** The maximum global number of puke mines that can be active in play */
const MAX _ACTIVE _PUKE _MINES = 30 ;
/** Anti-griefing system */
var protected const float ANTI _GRIEF _DELAY ;
var protected const float ANTI _GRIEF _INTERVAL ;
var protected const float ANTI _GRIEF _DAMAGE _PERCENTAGE ;
/** Zed pawn classes used by players */
var protected const array < class < KFPawn _Monster > > PlayerZedClasses ;
var protected const array < class < KFPawn _Monster > > PlayerBossClassList ;
/** Cached reference to versus gamereplicationinfo */
var KFGameReplicationInfoVersus MyKFGRIV ;
/** Damagetype used for anti-griefing system */
var class < KFDamageType > AntiGriefDamageTypeClass ;
/** Whether auto team balance is enabled */
var config bool bTeamBalanceEnabled ;
/** Radius around a damaged zed to award score to player zeds */
var config float ScoreRadius ;
/** The delay at the end of a game before auto-switching to the next round */
var int TimeUntilNextRound ;
/** The delay after a round is over before opening the round results screen */
var float RoundEndCinematicDelay ;
/** Cooldown period after the round results menu is closed but before spawning starts */
var float PostRoundWaitTime ;
/** Stat tracking for scoring */
var protected int WaveBonus ;
var protected int BossDamageDone ;
var protected int BossSurvivorDamageTaken ;
var protected float PercentOfZedsKilledBeforeWipe ;
/ * *
* DLO ( by string ) content classes specific to this game type
* - Use when direct ref loading is not possible ( e . g . unused GameInfo is loaded )
* - Class refs can be cached using the supplied GRI
* /
static function PreloadGlobalContentClasses ( )
{
local class < KFPawn _Monster > PawnClass ;
super . PreloadGlobalContentClasses ( ) ;
foreach default . PlayerZedClasses ( PawnClass )
{
PawnClass . static . PreloadContent ( ) ;
}
}
event PreBeginPlay ( )
{
super . PreBeginPlay ( ) ;
// Create the zed team
CreateTeam ( 1 ) ;
}
function InitGRIVariables ( )
{
super . InitGRIVariables ( ) ;
MyKFGRIV = KFGameReplicationInfoVersus ( MyKFGRI ) ;
MyKFGRIV . bTeamBalanceEnabled = bTeamBalanceEnabled ;
}
function bool IsPlayerReady ( KFPlayerReplicationInfo PRI )
{
// 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 ;
}
return super ( KFGameInfo ) . IsPlayerReady ( PRI ) ;
}
function StartMatch ( )
{
local KFPlayerController KFPC ;
local array < KFPlayerController > PlayerControllers ;
// Get a list of players
foreach WorldInfo . AllControllers ( class 'KFPlayerController' , KFPC )
{
PlayerControllers [ PlayerControllers . Length ] = KFPC ;
if ( KFPC . GetTeamNum ( ) == 255 && KFPC . CanRestartPlayer ( ) && ( KFPC . Pawn == none || KFPawn _Customization ( KFPC . Pawn ) != none ) )
{
if ( KFPC . Pawn != none )
{
KFPC . Pawn . Destroy ( ) ;
}
KFPC . StartSpectate ( ) ;
}
}
// If there's only one player, we must spawn them in as human
if ( PlayerControllers . Length == 1 )
{
SetTeam ( PlayerControllers [ 0 ] , Teams [ 0 ] ) ;
}
// Clear round over flag
MyKFGRIV . bRoundIsOver = false ;
MyKFGRIV . bNetDirty = true ;
MyKFGRIV . bForceNetUpdate = true ;
super . StartMatch ( ) ;
}
function bool ShouldStartMatch ( )
{
` if( ` notdefined ( ShippingPC ) )
if ( bWaitForNetPlayers && WorldInfo . NetMode != NM _Standalone )
{
return NumPlayers >= 1 ;
}
return true ;
` endif
return ( WorldInfo . NetMode == NM _StandAlone || ( Teams [ 0 ] . Size > 0 && Teams [ 1 ] . Size > 0 ) ) ;
}
function StripAbsentPlayers ( )
{
local int GroupIndex , PlayerIndex ;
local UniqueNetId StupidUnrealBS ;
` log("StripAbsentPlayers: Stripping players who are no longer logged in from matchmaking groups.", bLogGroupTeamBalance);
for ( GroupIndex = PlayerGroups . length - 1 ; GroupIndex >= 0 ; -- GroupIndex )
{
for ( PlayerIndex = PlayerGroups [ GroupIndex ] . PlayerGroup . Length - 1 ; PlayerIndex >= 0 ; -- PlayerIndex )
{
if ( GetPRIById ( PlayerGroups [ GroupIndex ] . PlayerGroup [ PlayerIndex ] ) == none )
{
if ( bLogGroupTeamBalance )
{
StupidUnrealBS = PlayerGroups [ GroupIndex ] . PlayerGroup [ PlayerIndex ] ;
` log("StripAbsentPlayers: Removing player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(StupidUnrealBS) @ "from group" @ GroupIndex @ "because they are no longer logged in.", bLogGroupTeamBalance );
}
PlayerGroups [ GroupIndex ] . PlayerGroup . Remove ( PlayerIndex , 1 ) ;
}
}
if ( PlayerGroups [ GroupIndex ] . PlayerGroup . length == 0 )
{
` log("StripAbsentPlayers: Removing empty matchmaking group " @ GroupIndex @ "from group list because it is empty.", bLogGroupTeamBalance );
PlayerGroups . Remove ( GroupIndex , 1 ) ;
}
}
}
function SplitArrayByTeam ( out PlayerGroupStruct Group , out PlayerGroupStruct Other )
{
local int i ;
local PlayerReplicationInfo PRI ;
local UniqueNetId StupidUnrealscriptBS ;
//Clear out the Other struct
Other . PlayerGroup . length = 0 ;
Other . Team = Group . Team == 1 ? 0 : 1 ;
//The group should have a team by now, but doublecheck and clear it out
//if it doesn't and quit
if ( Group . Team == 128 )
{
` log("SplitArrayByTeam: Aborting and removing group because it hasn't yet been assigned a team.");
Other . Team = 128 ;
Group . PlayerGroup . length = 0 ;
return ;
}
//Iterate backwards because that makes the logic of removing an item simpler
for ( i = Group . PlayerGroup . length - 1 ; i >= 0 ; -- i )
{
PRI = GetPRIById ( Group . PlayerGroup [ i ] ) ;
//Check again that the player in the group is still connected
if ( PRI == none )
{
if ( bLogGroupTeamBalance )
{
StupidUnrealscriptBS = Group . PlayerGroup [ i ] ;
` log("SplitArrayByTeam: Removing player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(StupidUnrealscriptBS) @ "from group because they are not logged in.");
}
Group . PlayerGroup . Remove ( i , 1 ) ;
continue ;
}
//If the team they're currently in doesn't match the one they were assigned, put them into
//the other player group with the other team
if ( PRI . Team != Teams [ Group . Team ] )
{
if ( bLogGroupTeamBalance )
{
StupidUnrealscriptBS = Group . PlayerGroup [ i ] ;
` log("SplitArrayByTeam: Moving player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(StupidUnrealscriptBS) @ "from group in team" @ Group.Team @ "to group in team" @ Other.Team @ "because they chose that team");
}
Other . PlayerGroup . AddItem ( Group . PlayerGroup [ i ] ) ;
Group . PlayerGroup . Remove ( i , 1 ) ;
}
}
}
function SplitGroups ( )
{
//if players have chosen to be in different teams, consider them as singles or separate groups
//for balancing purposes
local int i ;
local PlayerGroupStruct NewGroup ;
local PlayerGroupStruct StupidUnrealscriptBS ;
` log("SplitGroups: Splitting groups with players on separate teams.", bLogGroupTeamBalance);
//iterate from end to make adding and removing elements logic simpler
for ( i = PlayerGroups . length - 1 ; i >= 0 ; -- i )
{
StupidUnrealscriptBS = PlayerGroups [ i ] ;
SplitArrayByTeam ( StupidUnrealscriptBS , NewGroup ) ;
PlayerGroups [ i ] = StupidUnrealscriptBS ;
if ( PlayerGroups [ i ] . PlayerGroup . length < 2 )
{
if ( NewGroup . PlayerGroup . length > 1 )
{
` log("SplitGroups: Changing group number" @ i @ "from team" @ PlayerGroups[i].Team @ "to team" @ NewGroup.Team @ "(now has" @ NewGroup.PlayerGroup.length @ "members).", bLogGroupTeamBalance);
PlayerGroups [ i ] = NewGroup ;
}
else
{
` log("SplitGroups: Removing empty group number" @ i, bLogGroupTeamBalance);
PlayerGroups . Remove ( i , 1 ) ;
}
}
else
{
if ( NewGroup . PlayerGroup . length > 0 )
{
` log("SplitGroups: Adding a new group of team" @ NewGroup.Team @ "at position" @ i+1 @ "with" @ NewGroup.PlayerGroup.length @ "members.", bLogGroupTeamBalance);
PlayerGroups . Insert ( i + 1 , 1 ) ;
PlayerGroups [ i + 1 ] = NewGroup ;
}
}
}
}
//Returns human team size - zed team size
function int GetDelta ( )
{
return Teams [ 0 ] . Size - Teams [ 1 ] . Size ;
}
//Returns true if the game is playable (neither team over 6) by only moving players not in a group
//AdditionalDelta is number moved from Zed to Human team, negative for the other direction
function bool IsBalanceable ( int NumHumanSingles , int NumZedSingles , optional int AdditionalDelta = 0 )
{
if ( Teams [ 0 ] . Size > 0 && Teams [ 0 ] . Size <= ` KF_MAX_PLAYERS && Teams[1].Size <= ` KF _MAX _PLAYERS )
{
return true ;
}
if ( Teams [ 0 ] . Size - NumHumanSingles + AdditionalDelta > ` KF_MAX_PLAYERS || Teams[1].Size - NumZedSingles - AdditionalDelta > ` KF _MAX _PLAYERS )
{
return false ;
}
return true ;
}
//Returns true if it's possible to play a Versus match with the current team balance
//Does not mean balance is optimal (Delta within
function bool IsLegal ( )
{
return Teams [ 0 ] . Size > 0 && Teams [ 0 ] . Size <= ` KF_MAX_PLAYERS && Teams[1].Size <= ` KF _MAX _PLAYERS ;
}
//Moves single players from team to team until they are equal or within one of each other
//If bTryAnyway is false, will do nothing if they can't be balanced within 1
//If bTryAnyway is true, will get as close as possible
//Returns true if any players were moved or the teams already within 1
function bool BalanceSingles ( out array < PlayerReplicationInfo > HumanSingles , out array < PlayerReplicationInfo > ZedSingles , optional bool bTryAnyway = false )
{
local int Delta ;
local bool rc ;
if ( ! IsBalanceable ( HumanSingles . length , ZedSingles . length ) && ! bTryAnyway )
{
return false ;
}
Delta = GetDelta ( ) ;
if ( abs ( Delta ) <= 1 )
{
return true ;
}
rc = false ;
if ( Delta < 0 )
{
while ( ZedSingles . length > 0 && Delta < 0 )
{
SwapTeamFor ( ZedSingles [ ZedSingles . length - 1 ] ) ;
ZedSingles . Remove ( ZedSingles . length - 1 , 1 ) ;
Delta = GetDelta ( ) ;
rc = true ;
}
}
else
{
while ( HumanSingles . length > 0 && Delta > 0 )
{
SwapTeamFor ( HumanSingles [ HumanSingles . length - 1 ] ) ;
HumanSingles . Remove ( HumanSingles . length - 1 , 1 ) ;
Delta = GetDelta ( ) ;
rc = true ;
}
}
return rc ;
}
function BalanceTeams ( )
{
local int Delta ;
local PlayerReplicationInfo PRI ;
local array < PlayerReplicationInfo > HumanSingles , ZedSingles ;
StripAbsentPlayers ( ) ;
SplitGroups ( ) ;
Delta = Teams [ 0 ] . Size - Teams [ 1 ] . Size ;
if ( Delta == 0 )
return ;
foreach MyKFGRIV . PRIArray ( PRI )
{
if ( GetPRIById ( PRI . UniqueId ) == none )
{
if ( PRI . Team == Teams [ 0 ] )
{
HumanSingles . AddItem ( PRI ) ;
}
else
{
ZedSingles . AddItem ( PRI ) ;
}
}
}
if ( BalanceSingles ( HumanSingles , ZedSingles ) )
{
return ;
}
else
{
BalanceTeamsOld ( ) ;
}
}
function BalanceTeamsOld ( )
{
local int Delta , AutoBalanceRemaining , i ;
local TeamInfo TI ;
local Array < PlayerReplicationInfo > AutoBalanceList ;
local PlayerReplicationInfo PRI ;
Delta = Teams [ 1 ] . Size - Teams [ 0 ] . Size ;
if ( Delta == 0 )
return ;
TI = ( Delta > 0 ) ? Teams [ 1 ] : Teams [ 0 ] ;
for ( i = 0 ; i < MyKFGRIV . PRIArray . Length ; i ++ )
{
PRI = MyKFGRIV . PRIArray [ i ] ;
if ( PRI . Team == TI )
{
AutoBalanceList . AddItem ( PRI ) ;
}
}
AutoBalanceRemaining = Min ( abs ( Delta ) , MyKFGRIV . TeamBalanceDelta ) ;
while ( AutoBalanceRemaining > 0 || TI . Size > ( MaxPlayersAllowed / 2 ) )
{
i = Rand ( AutoBalanceList . Length ) ;
SwapTeamFor ( AutoBalanceList [ i ] ) ;
AutoBalanceList . Remove ( i , 1 ) ;
AutoBalanceRemaining -- ;
}
}
function SwapTeamFor ( PlayerReplicationInfo PRI )
{
local KFPlayerControllerVersus KFPCV ;
KFPCV = KFPlayerControllerVersus ( PRI . Owner ) ;
if ( KFPCV != none )
{
KFPCV . NotifyOfAutoBalance ( ) ;
SetTeam ( KFPCV , PRI . GetTeamNum ( ) == 255 ? Teams [ 0 ] : Teams [ 1 ] ) ;
}
}
/ * c r e a t e a p l a y e r t e a m , a n d f i l l f r o m t h e t e a m r o s t e r
* /
function CreateTeam ( int TeamIndex )
{
switch ( TeamIndex )
{
case 0 :
Teams [ TeamIndex ] = spawn ( class 'KFGame.KFTeamInfo_Human' ) ;
GameReplicationInfo . SetTeam ( TeamIndex , Teams [ TeamIndex ] ) ;
break ;
case 1 :
Teams [ TeamIndex ] = spawn ( class 'KFGameContent.KFTeamInfo_Zeds' ) ;
GameReplicationInfo . SetTeam ( TeamIndex , Teams [ TeamIndex ] ) ;
break ;
}
}
function int GetPlayerGroup ( const out UniqueNetId PlayerId , optional out int IdIndex )
{
local PlayerGroupStruct Group ;
local UniqueNetId Id ;
local int GroupIndex , Index ;
IdIndex = INDEX _NONE ;
foreach PlayerGroups ( Group , GroupIndex )
{
foreach Group . PlayerGroup ( Id , Index )
{
if ( id == PlayerId )
{
IdIndex = Index ;
return GroupIndex ;
}
}
}
return INDEX _NONE ;
}
function byte PickGroupTeam ( optional int GroupSize = 1 )
{
local int Human , Zed ;
GetReservedTotals ( Human , Zed ) ;
//Keep the human team ahead of Zeds, since there will always be AI Zeds to fill in
if ( Zed + GroupSize <= Human )
{
` log("PickGroupTeam: Team totals are Humans" @ Human @ "Zeds" @ Zed @ ", adding new group to Zed team", bLogGroupTeamBalance);
//Zed team
return 1 ;
}
else
{
` log("PickGroupTeam: Team totals are Humans" @ Human @ "Zeds" @ Zed @ ", adding new group to Human team", bLogGroupTeamBalance);
//Human team
return 0 ;
}
}
function PlayerReplicationInfo GetPRIById ( const UniqueNetId Id )
{
local PlayerReplicationInfo PRI ;
foreach MyKFGRIV . PRIArray ( PRI )
{
if ( PRI . UniqueId == Id )
{
return PRI ;
}
}
return none ;
}
/ * A d d s u p t h e p l a y e r s o n e a c h t e a m , i n c l u d i n g t h o s e w h o a r e i n a g r o u p
assigned to a team but not yet logged in * /
function GetReservedTotals ( out int Human , out int Zed )
{
local PlayerGroupStruct Group ;
local UniqueNetId Id ;
//First total who is logged in
Human = Teams [ 0 ] . Size ;
Zed = Teams [ 1 ] . Size ;
foreach PlayerGroups ( Group )
{
foreach Group . PlayerGroup ( Id )
{
//If they they have a PRI they have already logged in and were counted in the
//Team[].Size values above
if ( GetPRIById ( Id ) == none )
{
//If they haven't logged in but their group has been assigned a team, count them
//in that team. If Team is something other than 0 or 1 they haven't been assigned a
//team and so don't count them yet
if ( Group . Team == 0 )
{
++ Human ;
}
else if ( Group . Team == 1 )
{
++ Zed ;
}
}
}
}
}
/** Called by Gameinfo::Login(), initial team pick */
function byte PickTeam ( byte Current , Controller C , const out UniqueNetId PlayerId )
{
local int Group ;
` if( ` notdefined ( ShippingPC ) )
// Allow us to force join as the zed team if we pass 128 as our team ID to the command line
if ( Current == 128 )
{
` log( "[DEBUGVERSUS] Force joining zed team using ?Team=128 from command line!" );
return 1 ;
}
` endif
Group = GetPlayerGroup ( PlayerId ) ;
if ( Group == INDEX _NONE )
{
//If they're not in a group, consider them as a group of one
return PickGroupTeam ( ) ;
}
else if ( PlayerGroups [ Group ] . Team != 0 && PlayerGroups [ Group ] . Team != 1 )
{
//If their group hasn't been assigned a team yet (should only happen for the first member
//who connects), pick a team for them based on their group size
PlayerGroups [ Group ] . Team = PickGroupTeam ( PlayerGroups [ Group ] . PlayerGroup . length ) ;
}
//If we make it here, they should have a team, so return it
return PlayerGroups [ Group ] . Team ;
}
/** Return whether a team change is allowed. */
function bool ChangeTeam ( Controller Other , int N , bool bNewTeam )
{
2022-11-27 21:49:25 +00:00
if ( ArrayCount ( Teams ) > N )
2020-12-13 15:01:13 +00:00
{
2022-11-27 21:49:25 +00:00
if ( Other . PlayerReplicationInfo == none
|| Other . PlayerReplicationInfo . bBot
|| ( ! Other . PlayerReplicationInfo . bOnlySpectator
&& Other . PlayerReplicationInfo . Team != Teams [ N ] ) )
{
SetTeam ( Other , Teams [ N ] ) ;
return true ;
}
2020-12-13 15:01:13 +00:00
}
return false ;
}
/ * * S e t T e a m ( )
* Change Other ' s team to NewTeam .
* @ param Other : the controller which wants to change teams
* @ param NewTeam : the desired team .
* @ param bNewTeam : if true , broadcast team change notification
* /
function SetTeam ( Controller Other , KFTeamInfo _Human NewTeam )
{
local KFPlayerControllerVersus KFPCV ;
local TeamInfo OldTeam ;
if ( Other == none || Other . PlayerReplicationInfo == none || Other . PlayerReplicationInfo . bBot || Other . PlayerReplicationInfo . bOnlySpectator )
{
super . SetTeam ( Other , NewTeam ) ;
return ;
}
OldTeam = Other . PlayerReplicationInfo . Team ;
super . SetTeam ( Other , NewTeam ) ;
if ( OldTeam != none && NewTeam != OldTeam )
{
KFPCV = KFPlayerControllerVersus ( Other ) ;
KFPCV . ServerNotifyTeamChanged ( ) ;
if ( ! IsPlayerReady ( KFPlayerReplicationInfo ( Other . PlayerReplicationInfo ) ) )
{
OnWaitingPlayerTeamSwapped ( Other ) ;
}
else
{
if ( OldTeam != none && OldTeam . TeamIndex == 255 && KFPCV != none && KFPCV . PlayerZedSpawnInfo . PendingZedPawnClass != none )
{
// If this player switched teams, we need to recycle their zed pawn class back into rotation
if ( SpawnManager != none )
{
KFAISpawnManager _Versus ( SpawnManager ) . RecyclePendingZedPawnClass ( KFPCV ) ;
}
}
}
}
}
/** Overridden to handle team changes right after login */
event PostLogin ( PlayerController NewPlayer )
{
super . PostLogin ( NewPlayer ) ;
if ( ! NewPlayer . CanRestartPlayer ( ) )
{
OnWaitingPlayerTeamSwapped ( NewPlayer ) ;
}
}
/** Initializes player state on team swap */
function OnWaitingPlayerTeamSwapped ( Controller C )
{
local KFPlayerControllerVersus KFPCV ;
local NavigationPoint Start ;
KFPCV = KFPlayerControllerVersus ( C ) ;
if ( KFPCV != none )
{
if ( KFPCV . GetTeamNum ( ) == 255 )
{
if ( KFPCV . Pawn != none && KFPawn _Customization ( KFPCV . Pawn ) != none )
{
// Hide customization pawn
KFPawn _Customization ( KFPCV . Pawn ) . SetServerHidden ( true ) ;
}
KFPCV . SetCameraMode ( 'PlayerZedWaiting' ) ;
}
else if ( KFPCV . Pawn != none )
{
// Unhide/relocate customization pawn
KFPawn _Customization ( KFPCV . Pawn ) . SetServerHidden ( false ) ;
if ( ! KFPawn _Customization ( KFPCV . Pawn ) . MoveToCustomizationPoint ( ) )
{
Start = KFPCV . GetBestCustomizationStart ( self ) ;
if ( Start != none )
{
KFPawn _Customization ( KFPCV . Pawn ) . SetUpdatedMovementData ( Start . Location , Start . Rotation ) ;
}
}
// Restore view target and camera mode
KFPCV . SetViewTarget ( KFPCV . Pawn ) ;
KFPCV . SetCameraMode ( 'Customization' ) ;
}
KFPCV . ServerNotifyTeamChanged ( ) ;
}
}
/** Need to recycle any exiting players' pending zed pawn classes back into the spawn rotation */
function Logout ( Controller Exiting )
{
local KFPlayerControllerVersus KFPCV ;
if ( Exiting != none )
{
KFPCV = KFPlayerControllerVersus ( Exiting ) ;
if ( KFPCV != none && KFPCV . GetTeamNum ( ) == 255 && KFPCV . PlayerZedSpawnInfo . PendingZedPawnClass != none )
{
if ( SpawnManager != none )
{
KFAISpawnManager _Versus ( SpawnManager ) . RecyclePendingZedPawnClass ( KFPCV ) ;
}
}
}
super . Logout ( Exiting ) ;
}
function RestartPlayer ( Controller NewPlayer )
{
local int PlayerTeamIndex ;
local KFPawn _Monster MonsterPawn ;
local KFPlayerController KFPC ;
// Check bOnlySpectator. Can be called sometimes without calling CanRestartPlayer()?!?
if ( NewPlayer . PlayerReplicationInfo == none || NewPlayer . PlayerReplicationInfo . bOnlySpectator )
{
return ;
}
PlayerTeamIndex = NewPlayer . GetTeamNum ( ) ;
KFPC = KFPlayerController ( NewPlayer ) ;
if ( KFPC != none
&& ( MyKFGRI . bMatchIsOver || ( PlayerTeamIndex == 255 && ( MyKFGRI . bTraderIsOpen || KFPC . PlayerZedSpawnInfo . PendingZedPawnClass == none ) ) ) )
{
if ( IsPlayerReady ( KFPlayerReplicationInfo ( KFPC . PlayerReplicationInfo ) ) )
{
KFPC . StartSpectate ( ) ;
}
return ;
}
if ( NewPlayer . Pawn != none && KFPawn _Customization ( NewPlayer . Pawn ) != none )
{
// If we have a customization pawn, destroy it before spawning a new pawn with super.RestartPlayer
NewPlayer . Pawn . Destroy ( ) ;
}
// Restart human
if ( PlayerTeamIndex != 255 )
{
super . RestartPlayer ( NewPlayer ) ;
}
else if ( NewPlayer . Pawn == none && PlayerTeamIndex == 255 )
{
super ( GameInfo ) . RestartPlayer ( NewPlayer ) ;
}
MonsterPawn = KFPawn _Monster ( NewPlayer . Pawn ) ;
if ( MonsterPawn != none && MonsterPawn . IsHumanControlled ( ) )
{
MonsterPawn . UpdateLastTimeDamageHappened ( ) ;
}
}
/* @see GameInfo::SetPlayerDefaults */
function SetPlayerDefaults ( Pawn PlayerPawn )
{
local KFPawn _Human P ;
Super . SetPlayerDefaults ( PlayerPawn ) ;
// humans get full armor for the first wave
if ( WaveNum == 0 )
{
P = KFPawn _Human ( PlayerPawn ) ;
if ( P != none )
{
P . GiveMaxArmor ( ) ;
}
}
}
function int GetAIControlledMonsterAliveCount ( )
{
local AIController AIP ;
local int UsedLivingAIMonsterCount ;
foreach WorldInfo . AllControllers ( class 'AIController' , AIP )
{
if ( AIP != none && AIP . Pawn != none && AIP . Pawn . IsAliveAndWell ( ) )
{
if ( KFPawn _Monster ( AIP . Pawn ) != none )
{
UsedLivingAIMonsterCount ++ ;
}
}
}
return UsedLivingAIMonsterCount ;
}
/ * *
* Returns the default pawn class for the specified controller ,
*
* @ param C - controller to figure out pawn class for
*
* @ return default pawn class
* /
function class < Pawn > GetDefaultPlayerClass ( Controller C )
{
local KFPlayerController KFPC ;
KFPC = KFPlayerController ( C ) ;
if ( KFPC . GetTeamNum ( ) == 255 )
{
if ( KFPC . PlayerZedSpawnInfo . PendingZedPawnClass != none )
{
return KFPC . PlayerZedSpawnInfo . PendingZedPawnClass ;
}
return none ;
}
// default to the game specified pawn class
return super . GetDefaultPlayerClass ( C ) ;
}
// Overridden because the native implementation will count human controlled zeds
function int GetLivingPlayerCount ( )
{
local Controller P ;
local int UsedLivingHumanPlayersCount ;
` if( ` notdefined ( ShippingPC ) )
if ( ForcedNumLivingPlayers != 0 )
{
return ForcedNumLivingPlayers ;
}
` endif
foreach WorldInfo . AllControllers ( class 'Controller' , P )
{
if ( P != none && P . Pawn != none && P . Pawn . IsAliveAndWell ( ) )
{
if ( P . GetTeamNum ( ) != 255 )
{
//`log(P$" TeamIndex = "$P.PlayerReplicationInfo.Team.TeamIndex);
UsedLivingHumanPlayersCount ++ ;
}
}
}
//`log(GetFuncName()$" Player alive count: "$UsedLivingHumanPlayersCount);
return UsedLivingHumanPlayersCount ;
}
/* 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 )
{
local KFPawn InstigatorPawn , InjuredPawn ;
if ( InstigatedBy != none )
{
InstigatorPawn = KFPawn ( InstigatedBy . Pawn ) ;
InjuredPawn = KFPawn ( Injured ) ;
if ( DamageType != AntiGriefDamageTypeClass )
{
if ( InstigatorPawn != none && InjuredPawn != none &&
InstigatorPawn . GetTeamNum ( ) != InjuredPawn . GetTeamNum ( ) )
{
InstigatorPawn . UpdateLastTimeDamageHappened ( ) ;
InjuredPawn . UpdateLastTimeDamageHappened ( ) ;
}
}
// Don't let player zeds damage AI zeds
if ( InstigatedBy . GetTeamNum ( ) == 255
&& Injured . GetTeamNum ( ) == 255
&& InstigatedBy . bIsPlayer
&& ! Injured . IsHumanControlled ( ) )
{
Momentum = vect ( 0 , 0 , 0 ) ;
Damage = 0 ;
return ;
}
}
super . ReduceDamage ( Damage , Injured , InstigatedBy , HitLocation , Momentum , DamageType , DamageCauser , HitInfo ) ;
}
function ScoreDamage ( int DamageAmount , int HealthBeforeDamage , Controller InstigatedBy , Pawn DamagedPawn , class < DamageType > damageType )
{
local KFInterface _MonsterBoss BossRef ;
if ( InstigatedBy == none
|| ! InstigatedBy . bIsPlayer
|| InstigatedBy . PlayerReplicationInfo == none
|| InstigatedBy . GetTeamNum ( ) == DamagedPawn . GetTeamNum ( ) )
{
return ;
}
DamageAmount = Min ( DamageAmount , HealthBeforeDamage ) ;
KFPlayerReplicationInfo ( InstigatedBy . PlayerReplicationInfo ) . DamageDealtOnTeam += DamageAmount ;
KFPlayerController ( InstigatedBy ) . AddTrackedDamage ( DamageAmount , damageType , InstigatedBy . Pawn . Class , DamagedPawn . Class ) ;
if ( InstigatedBy . PlayerReplicationInfo . GetTeamNum ( ) == 255 )
{
BossRef = KFInterface _MonsterBoss ( InstigatedBy . Pawn ) ;
if ( BossRef != none )
{
if ( DamagedPawn . IsA ( 'KFPawn_Human' ) )
{
//damage taken from boss
BossSurvivorDamageTaken += DamageAmount ;
}
}
}
else
{
BossRef = KFInterface _MonsterBoss ( DamagedPawn ) ;
if ( BossRef != none )
{
//damage done to boss
BossDamageDone += DamageAmount ;
}
}
}
function ScoreKill ( Controller Killer , Controller Other )
{
local KFPawn KFP ;
local KFPlayerReplicationInfo DamagerKFPRI ;
local int i ;
// Add player zed assists
if ( Other . GetTeamNum ( ) == 0 )
{
KFP = KFPawn ( Other . Pawn ) ;
for ( i = 0 ; i < KFP . DamageHistory . Length ; ++ i )
{
if ( KFP . DamageHistory [ i ] . DamagerController != none
&& KFP . DamageHistory [ i ] . DamagerController . bIsPlayer
&& KFP . DamageHistory [ i ] . DamagerPRI != none
&& KFP . DamageHistory [ i ] . DamagerPRI . GetTeamNum ( ) == 255 )
{
if ( Killer . PlayerReplicationInfo != KFP . DamageHistory [ i ] . DamagerPRI )
{
DamagerKFPRI = KFPlayerReplicationInfo ( KFP . DamageHistory [ i ] . DamagerPRI ) ;
if ( DamagerKFPRI != none )
{
++ DamagerKFPRI . Assists ;
}
}
}
}
}
else if ( WaveNum == WaveMax && KFInterface _MonsterBoss ( Other . Pawn ) != none )
{
// Add our boss kill points
BossDamageDone = POINTS _FOR _BOSS _KILL ;
}
super . ScoreKill ( Killer , Other ) ;
}
//override from survival to send correct message
function EndOfMatch ( bool bVictory )
{
local KFPlayerController KFPC ;
local int TempScore ;
local KFPlayerReplicationInfoVersus KFPRIV ;
` AnalyticsLog(("match_end", None, "#" $ WaveNum, "#" $ (bVictory ? "1" : "0"), "#" $ GameConductor.ZedVisibleAverageLifespan));
if ( bVictory )
{
SetTimer ( EndCinematicDelay , false , nameof ( SetWonGameCamera ) ) ;
BroadcastLocalizedMessage ( class 'KFLocalMessage_Priority' , GMT _HumansWin ) ;
}
else
{
BroadcastLocalizedMessage ( class 'KFLocalMessage_Priority' , GMT _ZedsWin ) ;
SetZedsToVictoryState ( ) ;
}
foreach WorldInfo . AllControllers ( class 'KFPlayerController' , KFPC )
{
if ( bVictory )
{
KFPC . ClientWonGame ( WorldInfo . GetMapName ( true ) , GameDifficulty , GameLength , IsMultiplayerGame ( ) ) ;
}
KFPRIV = KFPlayerReplicationInfoVersus ( KFPC . PlayerReplicationInfo ) ;
if ( KFPRIV != none )
{
KFPRIV . RecordEndGameInfo ( ) ;
}
KFPC . ClientGameOver ( WorldInfo . GetMapName ( true ) , GameDifficulty , GameLength , IsMultiplayerGame ( ) , WaveNum ) ;
}
WorldInfo . TWPushLogs ( ) ;
// Calculate final score
WaveBonus = Max ( ( WaveNum - 1 ) , 0 ) * POINTS _FOR _WAVE _COMPLETION ;
if ( bVictory )
{
CheckRoundEndAchievements ( 0 ) ;
TempScore += POINTS _FOR _BOSS _KILL ;
TempScore -= BossSurvivorDamageTaken ;
}
else
{
CheckRoundEndAchievements ( 255 ) ;
if ( WaveNum != WaveMax )
{
WaveBonus += Max ( int ( float ( POINTS _FOR _WAVE _COMPLETION ) * PercentOfZedsKilledBeforeWipe ) , 0 ) ;
}
}
TempScore += WaveBonus ;
TempScore -= POINTS _PENALTY _FOR _DEATH * HumanDeaths ;
Teams [ 0 ] . AddRoundScore ( TempScore , true ) ;
// Update team scores
if ( MyKFGRIV . CurrentRound == 0 )
{
UpdateFirstRoundTeamScore ( ) ;
}
else
{
UpdateSecondRoundTeamScore ( ) ;
}
GotoState ( 'RoundEnded' , 'Begin' ) ;
}
function Killed ( Controller Killer , Controller KilledPlayer , Pawn KilledPawn , class < DamageType > damageType )
{
super . Killed ( Killer , KilledPlayer , KilledPawn , damageType ) ;
if ( IsWaveActive ( ) && GetAIControlledMonsterAliveCount ( ) <= 0 && SpawnManager . IsFinishedSpawning ( ) )
{
CheckPawnsForGriefing ( true ) ;
}
}
function WaveEnded ( EWaveEndCondition WinCondition )
{
local KFPlayerReplicationInfoVersus KFPRIV ;
local int WaveKills ;
local int i , j ;
MyKFGRIV . SetPlayerZedSpawnTime ( 255 , false ) ;
ClearTimer ( nameOf ( CheckPawnsForGriefing ) ) ;
// If game ended on a wipe, record how many zeds were killed as well as wave reached
if ( WinCondition == WEC _TeamWipedOut && SpawnManager != none && WaveNum != WaveMax )
{
PercentOfZedsKilledBeforeWipe = fClamp ( 1. f - ( float ( MyKFGRI . AIRemaining ) / float ( SpawnManager . WaveTotalAI ) ) , 0. f , 1. f ) ;
}
// Tabulate the number of kills this wave
for ( i = 0 ; i < WorldInfo . GRI . PRIArray . Length ; ++ i )
{
// No kills to tabulate
if ( WorldInfo . GRI . PRIArray [ i ] . Kills == 0 )
{
continue ;
}
KFPRIV = KFPlayerReplicationInfoVersus ( WorldInfo . GRI . PRIArray [ i ] ) ;
if ( KFPRIV != none && ! KFPRIV . bBot && KFPRIV . GetTeamNum ( ) == 255 )
{
WaveKills = KFPRIV . Kills ;
for ( j = 0 ; j < KFPRIV . WaveKills . Length ; ++ j )
{
WaveKills -= KFPRIV . WaveKills [ j ] ;
}
KFPRIV . WaveKills [ WaveNum ] = WaveKills ;
}
}
super . WaveEnded ( WinCondition ) ;
}
function BossDied ( Controller Killer , optional bool bCheckWaveEnded = true )
{
super . BossDied ( Killer , bCheckWaveEnded ) ;
}
/ * *
* @ brief Checks if a player zed has dealt or received damage
*
* @ param bool bInitial Set ' s the LastTimeDamageHappened to the current time to
* give the players some time to get to the humans
* /
protected function CheckPawnsForGriefing ( optional bool bInitial = false )
{
local Pawn TestPawn ;
local KFPawn _Monster MonsterTestPawn ;
foreach WorldInfo . AllPawns ( class 'Pawn' , TestPawn )
{
if ( TestPawn . IsAliveAndWell ( ) && TestPawn . Controller != none &&
TestPawn . Controller . bIsPlayer && TestPawn . GetTeamNum ( ) == 255 )
{
MonsterTestPawn = KFPawn _Monster ( TestPawn ) ;
if ( MonsterTestPawn != none )
{
if ( bInitial )
{
MonsterTestPawn . LastTimeDamageHappened = WorldInfo . TimeSeconds ;
}
else if ( WorldInfo . TimeSeconds - MonsterTestPawn . LastTimeDamageHappened >= ANTI _GRIEF _DELAY &&
MonsterTestPawn . LastTimeDamageHappened != 0 )
{
MonsterTestPawn . MotivatePlayerToAttack ( ANTI _GRIEF _DAMAGE _PERCENTAGE , AntiGriefDamageTypeClass ) ;
}
}
}
}
if ( IsWaveActive ( ) )
{
SetTimer ( ANTI _GRIEF _INTERVAL , false , nameOf ( CheckPawnsForGriefing ) ) ;
}
}
/** Called to reset all the types of pickups */
function ResetAllPickups ( )
{
if ( ! bDisablePickups )
{
// Reset ALL pickups each wave (ignoring NumPickups)
// -1, so that we always have a different pickup to activate
NumWeaponPickups = Max ( ItemPickups . Length - 1 , 0 ) ;
NumAmmoPickups = Max ( AmmoPickups . Length - 1 , 0 ) ;
}
Super . ResetAllPickups ( ) ;
}
function OpenPostRoundMenu ( )
{
local KFPlayerController KFPC ;
foreach WorldInfo . AllControllers ( class 'KFPlayerController' , KFPC )
{
if ( IsPlayerReady ( KFPlayerReplicationInfo ( KFPC . PlayerReplicationInfo ) ) )
{
KFPC . ClientOpenRoundSummary ( ) ;
}
}
}
/** Updates team data for use in the next game round */
function UpdateFirstRoundTeamScore ( )
{
// Cache team score
Teams [ 1 ] . TeamScoreDataPacket . RoundScore = Teams [ 0 ] . TeamScoreDataPacket . RoundScore ;
// Cache wave reached
Teams [ 1 ] . TeamScoreDataPacket . WaveBonus = WaveBonus ;
// Cache survivor deaths
Teams [ 1 ] . TeamScoreDataPacket . Deaths = HumanDeaths ;
// Cache boss values if relevant
if ( WaveNum == WaveMax )
{
// Boss damage done
Teams [ 1 ] . TeamScoreDataPacket . BossDamageDone = BossDamageDone ;
// Boss survivor damage taken
Teams [ 1 ] . TeamScoreDataPacket . BossDamageTaken = BossSurvivorDamageTaken ;
}
else
{
// Clear any boss values
Teams [ 1 ] . TeamScoreDataPacket . BossDamageDone = 0 ;
Teams [ 1 ] . TeamScoreDataPacket . BossDamageTaken = 0 ;
}
// Reset merc team stats for second round
Teams [ 0 ] . TeamScoreDataPacket . RoundScore = 0 ;
Teams [ 0 ] . TeamScoreDataPacket . WaveBonus = - 1 ;
Teams [ 0 ] . TeamScoreDataPacket . Deaths = 0 ;
Teams [ 0 ] . TeamScoreDataPacket . BossDamageDone = 0 ;
Teams [ 0 ] . TeamScoreDataPacket . BossDamageTaken = 0 ;
// Force network update
Teams [ 0 ] . bForceNetUpdate = true ;
Teams [ 0 ] . bNetDirty = true ;
Teams [ 1 ] . bForceNetUpdate = true ;
Teams [ 1 ] . bNetDirty = true ;
}
/** Updates team data for use in the final game round */
function UpdateSecondRoundTeamScore ( )
{
// Cache wave reached
Teams [ 0 ] . TeamScoreDataPacket . WaveBonus = WaveBonus ;
// Cache survivor deaths
Teams [ 0 ] . TeamScoreDataPacket . Deaths = HumanDeaths ;
// Cache boss values if relevant
if ( WaveNum == WaveMax )
{
// Boss damage done
Teams [ 0 ] . TeamScoreDataPacket . BossDamageDone = BossDamageDone ;
// Boss survivor damage taken
Teams [ 0 ] . TeamScoreDataPacket . BossDamageTaken = BossSurvivorDamageTaken ;
}
else
{
// Clear any boss values
Teams [ 0 ] . TeamScoreDataPacket . BossDamageDone = 0 ;
Teams [ 0 ] . TeamScoreDataPacket . BossDamageTaken = 0 ;
}
// Force network update
Teams [ 0 ] . bNetDirty = true ;
Teams [ 0 ] . bForceNetUpdate = true ;
Teams [ 1 ] . bNetDirty = true ;
Teams [ 1 ] . bForceNetUpdate = true ;
}
/** Perform gametype-specific resets */
function Reset ( )
{
local KFPlayerControllerVersus KFPCV ;
super . Reset ( ) ;
// Swap teams
foreach WorldInfo . AllControllers ( class 'KFPlayerControllerVersus' , KFPCV )
{
if ( KFPCV . CanRestartPlayer ( ) )
{
SetTeam ( KFPCV , KFPCV . PlayerReplicationInfo . Team == Teams [ 0 ] ? Teams [ 1 ] : Teams [ 0 ] ) ;
KFPCV . ServerNotifyTeamChanged ( ) ;
// Put everyone into spectator state
if ( ! KFPCV . IsInState ( 'Spectating' ) )
{
KFPCV . StartSpectate ( ) ;
}
}
}
if ( SpawnManager != none )
{
SpawnManager . ResetSpawnManager ( ) ;
}
// Reset score statistics
WaveBonus = - 1 ;
HumanDeaths = 0 ;
BossDamageDone = 0 ;
BossSurvivorDamageTaken = 0 ;
PercentOfZedsKilledBeforeWipe = 0. f ;
// Reset pool manager (clear puke mines, c4, etc)
class 'KFGameplayPoolManager' . static . GetPoolManager ( ) . Reset ( ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* state RoundEnded
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/** Close the team results screen */
protected function ClosePostRoundMenu ( optional bool bMatchOver = false )
{
local KFPlayerControllerVersus KFPCV ;
foreach WorldInfo . AllControllers ( class 'KFPlayerControllerVersus' , KFPCV )
{
KFPCV . ClientShowPostGameMenu ( false ) ;
}
// Wait a tiny bit to display the next round message
if ( ! bMatchOver )
{
SetTimer ( 0.1 , false , nameOf ( Timer _AnnounceNextRound ) ) ;
}
}
/** Announces the next round to players */
protected function Timer _AnnounceNextRound ( )
{
local KFPlayerController KFPC ;
foreach WorldInfo . AllControllers ( class 'KFPlayerController' , KFPC )
{
// Only process players that are ready to play
if ( KFPC . CanRestartPlayer ( ) )
{
KFPC . ReceiveLocalizedMessage ( class 'KFLocalMessage_Priority' , GMT _NextRoundBegin , , , KFPC . PlayerReplicationInfo . Team ) ;
}
}
}
/** Checks to ensure a game is actually playable and balances accordingly */
protected function CheckTeamNumbers ( )
{
local KFPlayerControllerVersus KFPCV ;
local int HumanPlayers , ZedPlayers ;
// If there aren't at least 2 players, end the match
if ( NumPlayers < 2 || MyKFGRIV . CurrentRound > 1 )
{
GotoState ( 'MatchEnded' ) ;
return ;
}
// If there are no players on one of the teams, force one of them to switch
foreach WorldInfo . AllControllers ( class 'KFPlayerControllerVersus' , KFPCV )
{
if ( ! KFPCV . CanRestartPlayer ( ) )
{
continue ;
}
if ( KFPCV . GetTeamNum ( ) == 255 )
{
++ ZedPlayers ;
}
else
{
++ HumanPlayers ;
}
}
if ( HumanPlayers == 0 && ZedPlayers > 1 )
{
SwitchOnePlayerToTeam ( 0 ) ;
}
else if ( ZedPlayers == 0 && HumanPlayers > 1 )
{
SwitchOnePlayerToTeam ( 255 ) ;
}
}
/** Resets the level and announces a round change */
protected function BeginNextRound ( )
{
// Reset everything
ResetLevel ( ) ;
// Init next round
MyKFGRIV . bStopCountDown = true ;
MyKFGRIV . SetPlayerZedSpawnTime ( PostRoundWaitTime , false ) ;
ClosePostRoundMenu ( ) ;
}
static function bool IsInitialSpawnPointSelection ( WorldInfo WI )
{
local KFGameReplicationInfo KFGRI ;
KFGRI = KFGameReplicationInfo ( WI . GRI ) ;
if ( KFGRI != none )
{
return KFGRI . bRoundIsOver || super . IsInitialSpawnPointSelection ( WI ) ;
}
return false ;
}
/** Attempts to pre-select player starts and begin texture streaming */
protected function PreSelectPlayerStarts ( )
{
local KFPlayerController KFPC ;
local byte TeamNum ;
foreach WorldInfo . AllControllers ( class 'KFPlayerController' , KFPC )
{
KFPC . StartSpot = none ;
TeamNum = KFPC . GetTeamNum ( ) ;
if ( KFPC . GetTeamNum ( ) == 0 )
{
KFPC . StartSpot = FindPlayerStart ( KFPC , TeamNum ) ;
}
else
{
KFPC . StartSpot = none ;
continue ;
}
if ( KFPC . StartSpot != none )
{
KFPC . ClientAddTextureStreamingLoc ( KFPC . StartSpot . Location , 0. f , false ) ;
}
}
}
/** Starts spawning */
function StartSpawning ( )
{
// Reset round over flag
MyKFGRIV . bRoundIsOver = false ;
MyKFGRIV . bNetDirty = true ;
MyKFGRIV . bForceNetUpdate = true ;
// Off we go!
GotoState ( '' ) ;
StartMatch ( ) ;
}
/** Swaps one player to the opposite team */
protected function SwitchOnePlayerToTeam ( byte TeamNum )
{
local KFPlayerControllerVersus KFPCV ;
foreach WorldInfo . AllControllers ( class 'KFPlayerControllerVersus' , KFPCV )
{
if ( KFPCV . CanRestartPlayer ( )
&& KFPCV . GetTeamNum ( ) != TeamNum )
{
SetTeam ( KFPCV , TeamNum == 0 ? Teams [ 0 ] : Teams [ 1 ] ) ;
break ;
}
}
}
state RoundEnded
{
event BeginState ( name PrevStateName )
{
local int i ;
// Set round over flag (allows perk changes etc)
MyKFGRIV . bRoundIsOver = true ;
// Increment round
MyKFGRIV . CurrentRound += 1 ;
// BWJ - 10-3-16 - Clear the spawned in flag when round end begins
for ( i = 0 ; i < GameReplicationInfo . PRIArray . Length ; i ++ )
{
KFPlayerReplicationInfo ( GameReplicationInfo . PRIArray [ i ] ) . bHasSpawnedIn = false ;
}
// Turn off respawn timer
MyKFGRIV . SetPlayerZedSpawnTime ( 255 , false ) ;
}
function bool MatchIsInProgress ( )
{
return false ;
}
ForceEnded :
Sleep ( 1. f ) ;
BeginNextRound ( ) ;
Goto ( 'End' ) ;
Begin :
// Wait a short time and bring up the results screen
Sleep ( RoundEndCinematicDelay ) ;
OpenPostRoundMenu ( ) ;
// Wait for the results screen duration and start the next round
Sleep ( GetEndOfMatchTime ( ) ) ;
if ( MyKFGRIV . CurrentRound > 1 )
{
GotoState ( 'MatchEnded' ) ;
Sleep ( 0. f ) ;
}
CheckTeamNumbers ( ) ;
BeginNextRound ( ) ;
// Wait a short time after closing the results screen (for perk changes) before spawning
Sleep ( fMax ( PostRoundWaitTime - 3. f , 0. f ) ) ;
PreSelectPlayerStarts ( ) ;
Sleep ( 3. f ) ;
End :
StartSpawning ( ) ;
}
protected function CheckRoundEndAchievements ( byte WinningTeam )
{
local KFPlayerControllerVersus KFPCV ;
foreach WorldInfo . AllControllers ( class 'KFPlayerControllerVersus' , KFPCV )
{
KFPCV . ClientRoundEnded ( WinningTeam ) ;
}
}
function TryRestartGame ( )
{
if ( IsInState ( 'RoundEnded' ) )
{
BeginNextRound ( ) ;
}
else
{
super . TryRestartGame ( ) ;
}
}
function ShowPostGameMenu ( )
{
ClosePostRoundMenu ( true ) ;
super . ShowPostGameMenu ( ) ;
}
function float GetEndOfMatchTime ( )
{
if ( IsInState ( 'RoundEnded' ) )
{
return TimeUntilNextRound ;
}
return super . GetEndOfMatchTime ( ) ;
}
function bool IsMapObjectiveEnabled ( )
{
return false ;
}
DefaultProperties
{
bIsVersusGame = true
TimeUntilNextRound = 12
RoundEndCinematicDelay = 4
PostRoundWaitTime = 15
MaxGameDifficulty = 0
GameLengthDoshScale ( 0 ) = 1.0 // Short but already scaled in other ways for versus
MaxRespawnDosh ( 0 ) = 1950 // Max amount of dosh that a player can respawn with after dying //1750
DeathPenaltyModifiers ( 0 ) = 0.03 // How much of a penalty to apply to dosh after dying. Smaller means lower penalty //0.05
OnlineGameSettingsClass = class 'KFGame.KFOnlineGameSettingsVersus'
HUDType = class 'KFGameContent.KFGFXHudWrapper_Versus'
KFGFxManagerClass = class 'KFGame.KFGFxMoviePlayer_Manager_Versus'
PlayerControllerClass = class 'KFGameContent.KFPlayerControllerVersus'
PlayerReplicationInfoClass = class 'KFGame.KFPlayerReplicationInfoVersus'
GameReplicationInfoClass = class 'KFGameContent.KFGameReplicationInfoVersus'
MaxPlayersAllowed = ` KF_MAX_PLAYERS_VERSUS
DefaultPawnClass = class 'KFGameContent.KFPawn_Human_Versus'
GameConductorClass = class 'KFGameContent.KFGameConductorVersus'
DifficultyInfoClass = class 'KFGameDifficulty_Versus'
DifficultyInfoConsoleClass = class 'KFGameDifficulty_Versus_Console'
PlayerZedClasses ( AT _AlphaClot ) = class 'KFGameContent.KFPawn_ZedClot_Alpha_Versus'
PlayerZedClasses ( AT _SlasherClot ) = class 'KFGameContent.KFPawn_ZedClot_Slasher_Versus'
PlayerZedClasses ( AT _Crawler ) = class 'KFGameContent.KFPawn_ZedCrawler_Versus'
PlayerZedClasses ( AT _GoreFast ) = class 'KFGameContent.KFPawn_ZedGorefast_Versus'
PlayerZedClasses ( AT _Stalker ) = class 'KFGameContent.KFPawn_ZedStalker_Versus'
PlayerZedClasses ( AT _Scrake ) = class 'KFGameContent.KFPawn_ZedScrake_Versus'
PlayerZedClasses ( AT _FleshPound ) = class 'KFGameContent.KFPawn_ZedFleshpound_Versus'
PlayerZedClasses ( AT _Bloat ) = class 'KFGameContent.KFPawn_ZedBloat_Versus'
PlayerZedClasses ( AT _Siren ) = class 'KFGameContent.KFPawn_ZedSiren_Versus'
PlayerZedClasses ( AT _Husk ) = class 'KFGameContent.KFPawn_ZedHusk_Versus'
PlayerBossClassList . Add ( class 'KFGameContent.KFPawn_ZedPatriarch_Versus' )
SpawnManagerClasses . Empty
SpawnManagerClasses ( 0 ) = class 'KFAISpawnManager_Versus'
AntiGriefDamageTypeClass = class 'KFDT_NoGoVolume'
/** Anti-griefing system */
ANTI _GRIEF _DELAY = 30. f
ANTI _GRIEF _INTERVAL = 2. f
ANTI _GRIEF _DAMAGE _PERCENTAGE = 10. f
}