1
0
KF2-Dev-Scripts/KFGame/Classes/KFGameInfo.uc
2021-03-02 14:56:51 +03:00

3856 lines
114 KiB
Ucode

//=============================================================================
// KFGameInfo
//=============================================================================
// Base GameInfo for KFGame
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFGameInfo extends FrameworkGame
native;
`define GAMEINFO(dummy)
`include(KFGame\KFGameAnalytics.uci);
`undefine(GAMEINFO)
`include(KFGame\KFMatchStats.uci);
/** Scoring */
var const int POINTS_FOR_BOSS_KILL;
var const int POINTS_FOR_WAVE_COMPLETION;
var const int POINTS_PENALTY_FOR_DEATH;
struct native sGameMode
{
var string FriendlyName;
var string ClassNameAndPath;
var bool bSoloPlaySupported;
var int DifficultyLevels;
var int Lengths;
var int LocalizeID; // This corresponds to an element in the localized ModeStrings array in KFCommon_LocalizedStrings
};
var KFGameReplicationInfo MyKFGRI;
/************************************************************************************
* @name Customization
***********************************************************************************/
var class<KFPawn_Customization> CustomizationPawnClass;
/************************************************************************************
* @name Team Settings
***********************************************************************************/
var globalconfig float FriendlyFireScale; // scale friendly fire damage by this value
var KFTeamInfo_Human Teams[2];
/** True if there was only one active player pawn at last respawn time */
var bool bOnePlayerAtStart;
/** The number of human deaths this match */
var int HumanDeaths;
var bool bSplitBossDoshReward;
/************************************************************************************
* @name User Interface
***********************************************************************************/
var class<KFGFxMoviePlayer_Manager> KFGFxManagerClass;
var bool bStartFinalCount;
/************************************************************************************
* @name Game Config
***********************************************************************************/
var globalconfig int GameLength; // EGameLength
var config int MinNetPlayers;
var config bool bWaitForNetPlayers; // wait until more than MinNetPlayers players have joined before starting match
var globalconfig int ReadyUpDelay;
var globalconfig int GameStartDelay; // The default amount of time before the match begins
var config int EndOfGameDelay; // time between maps
var config array<sGameMode>GameModes; // Gamemode definitions, used by menus/filters
var globalconfig bool bDisableKickVote; // If set, players will be able to vote to kick a person out(if they get VoteKickPercentage of players to vote to kick the same person)
var globalconfig float KickVotePercentage; // Percentage of players that must vote to kick a person before they are kicked
var globalconfig float TimeBetweenFailedVotes; // How much time must pass after a failed vote before another one can be initiated
var globalconfig bool bDisablePublicTextChat; // Whether or not this server allows Public Text Chat
var globalconfig bool bDisableVOIP; // Whether or not this server allows VOIP
var globalconfig bool bPartitionSpectators; // Whether or not Spectators are limited to only Text and VOIP Chatting with other spectators
var globalconfig bool bDisablePickups; // Whether or not this server allows pickups
var globalconfig bool bDisableMapVote; // Turn off map voting
var globalconfig float MapVotePercentage; // Percentage of players that must vote for this map
var globalconfig float MapVoteDuration; // time till the map vote ends
var config bool bEnableMapObjectives; // Whether or not maps that support objectives should use them
var globalconfig string ServerMOTD; // Message of the day to be displayed on the server welcome screen
var globalconfig Color ServerMOTDColor; // The color of the server MOTD text (set in the form ServerMOTDColor=(R=0,G=0,B=0)
var globalconfig string BannerLink; // The url to the banner to be loaded from the magical internet
var globalconfig string WebsiteLink; // The url to the clan website
var globalconfig Color WebLinkColor; // The color of the web link text (set in the form WebLinkColor=(R=0,G=0,B=0)
var globalconfig string ClanMotto; // The server's motto of the day
var globalconfig Color ClanMottoColor; // The color of the clan motto text (set in the form ClanMottoColor=(R=0,G=0,B=0)
/** If set, ignore blocking collision of teammates */
var globalconfig bool bDisableTeamCollision;
var globalconfig bool bEnableDeadToVOIP;
var globalconfig bool bDisablePublicVOIPChannel;
/** The time a server should stay up when empty before it dies if no one joins. Used for console dedicated servers only */
var const config float ServerExpirationForKillWhenEmpty;
/************************************************************************************
* @name Other (non-config) Game Settings
***********************************************************************************/
/** If true we can change our perks at any point in this game mode */
var const bool bCanPerkAlwaysChange;
/** The time it takes to respawn when we have reconnected to the same server multiple times */
var const int ReconnectRespawnTime;
var private const float XPMultiplier;
/************************************************************************************
* @name Game Difficulty
***********************************************************************************/
/** Difficulty settings are accessible through the DifficultyInfo */
var KFGameDifficultyInfo DifficultyInfo;
/** Difficulty class templates */
var class<KFGameDifficultyInfo> DifficultyInfoClass;
var class<KFGameDifficultyInfo> DifficultyInfoConsoleClass;
/** Trader */
var array<KFTraderTrigger> TraderList;
var transient KFTraderTrigger ScriptedTrader;
/** Pickups */
var array<KFPickupFactory> ItemPickups;
var array<KFPickupFactory> AmmoPickups;
/** Keep track off all pickup factories so we don't have to use all actors */
var array<KFPickupFactory> AllPickupFactories;
/** The max number of pickups we can have on this difficulty mode and map */
var byte NumWeaponPickups;
var byte NumAmmoPickups;
/** Dosh Penalty for dying mid wave */
var array<float> DeathPenaltyModifiers;
/** Dosh Penalty for dying mid wave by difficulty*/
var array<float> MaxRespawnDosh;
const TeamDeathPenaltyPerc = 0.05f;
/** Maximum value (aka NumDifficulties - 1) for the GameDifficulty */
var int MaxGameDifficulty;
/** Minimum value for the GameDifficulty. Paired with above, allows game type to
* clamp difficulty at InitGame time.
*/
var int MinGameDifficulty;
/************************************************************************************
* @name Game Length
***********************************************************************************/
enum EGameLength
{
GL_Short,
GL_Normal,
GL_Long,
};
/** Amount to scale dosh reward based on game length */
var array<float> GameLengthDoshScale;
/** Available spawn managers (game length) */
var array< class<KFAISpawnManager> > SpawnManagerClasses;
struct native LateArrivalStart
{
var() array< int > StartingDosh;
};
var array< LateArrivalStart > LateArrivalStarts;
/************************************************************************************
* @name Game balance
***********************************************************************************/
/** Game conductor which dynamically manages the difficulty and fun of the game */
var KFGameConductor GameConductor;
/** The class of GameConductor to use for this gametype */
var class<KFGameConductor> GameConductorClass;
/************************************************************************************
* @name AI
***********************************************************************************/
/** Active AI Manager */
var KFAIDirector AIDirector;
/** Keeps track of healthy AI*/
var int AIAliveCount;
/** Counter for keeping track of AI spawned */
var int NumAISpawnsQueued;
/** Counter for AI spawned, increanses when the zeds are actually spawned */
var int NumAIFinishedSpawning;
/** Number of zeds remainining before setting their pawns to bAlwaysRelevant=TRUE */
var private const int NumAlwaysRelevantZeds;
/** Active spawn manager - None if N/A to this game type */
var KFAISpawnManager SpawnManager;
/** Content class references loaded by this game type */
var protected const array< class<KFPawn_Monster> > AIClassList;
var protected const array< class<KFPawn_Monster> > NonSpawnAIClassList;
var protected const array< class<KFPawn_Monster> > AIBossClassList;
var protected const array< class<KFPawn_Monster> > AITestBossClassList; //List of non-rotation bosses that can be forced
var protected int BossIndex; //Index into boss array, only preload content for the boss we want - PC builds can handle DLO cost much better
/** Class replacements for each zed type */
struct native SpawnReplacement
{
/** Entry to replace, some form of AT_* */
var() EAIType SpawnEntry;
/** Class(es) to use instead of default */
var() array<class<KFPawn_Monster> > NewClass;
/** Percent chance that the spawn will be replaced by NewClass */
var() float PercentChance;
structdefaultproperties
{
PercentChance = 1.f;
}
};
/** Class replacements for each zed type (same as SpawnReplacement, but with EBossAIType for clarity */
struct native BossSpawnReplacement
{
/** Entry to replace, some form of AT_* */
var() EBossAIType SpawnEntry;
/** Class to use instead of default */
var() class<KFPawn_Monster> NewClass;
/** Percent chance that the spawn will be replaced by NewClass */
var() float PercentChance;
structdefaultproperties
{
PercentChance = 1.f;
}
};
/************************************************************************************
* @name ZEDTime - slomo system
***********************************************************************************/
/** What percentage of normal game speed to slow down the game during ZedTime */
var() float ZedTimeSlomoScale;
/** How long to blend out zed time dilation for */
var() float ZedTimeBlendOutTime;
/** Time left in zed time */
var float ZedTimeRemaining;
/** True if zed time blend out has already started */
var bool bZedTimeBlendingOut;
/** Number of Zed Time extensions used(Chaining effect for killing other Zeds while Zed Time is active) */
var int ZedTimeExtensionsUsed;
/** The last time we had a Zed Time event */
var float LastZedTimeEvent;
/************************************************************************************
* @name Event Tracking (aka Analytics)
***********************************************************************************/
var config bool bEnableGameAnalytics;
var config bool bRecordGameStatsFile;
var class<KFGameplayEventsWriter> GameplayEventsWriterClass;
var transient KFGameplayEventsWriter GameplayEventsWriter;
/************************************************************************************
* @name Map rotation
***********************************************************************************/
/** Whether we should use a Map Cycle List or just continuously reload the same map */
var globalconfig bool bUseMapList;
/** Game Map Cycles, there is a map cycle per game mode */
struct native GameMapCycle
{
var array<string> Maps;
// var array<int> RoundLimits;
};
var globalconfig array<GameMapCycle> GameMapCycles;
/** The currently active GameMapCycle */
var globalconfig int ActiveMapCycle;
/** index of current map in the cycle */
var globalconfig int MapCycleIndex;
/** Maps that are not valid for this game mode. If the array is empty, all are valid
Make sure to add the map names ALL UPPERCASE in the config*/
var config array<string> InValidMaps;
/************************************************************************************
* @name Dialog
***********************************************************************************/
var KFDialogManager DialogManager;
var class<KFDialogManager> DialogManagerClass;
var class<KFTraderVoiceGroupBase> TraderVoiceGroupClass;
/************************************************************************************
* @name Music
***********************************************************************************/
/** Delay between start of "action" phase and action music */
var float ActionMusicDelay;
enum EForcedMusicType
{
EFM_MainMenu,
EFM_Credits,
EFM_Boss1,
EFM_Boss2,
EFM_Boss3,
EFM_Boss4,
EFM_Boss5
};
/** Tracks that are not selected randomly but rather "forced" by the server (or standalone client) at specific times */
var array< KFMusicTrackInfo > ForcedMusicTracks;
/************************************************************************************
* @name Debugging
***********************************************************************************/
var config bool bLogScoring;
var config bool bLogAIDefaults;
var bool bLogReservations;
var bool bLogAnalytics;
//var config bool bLogGameBalance;
var config bool bLogAICount;
var bool bForceOutbreakWave;
var bool bForceSpecialWave;
/** The number of players the game thinks are alive when ForceLivingPlayerCount is called */
var const byte ForcedNumLivingPlayers;
/** The outbreak index if an outbreak wave. */
var int DebugForcedOutbreakIdx;
/** The zed type if there is a forced special wave. */
var int DebugForceSpecialWaveZedType;
/** Event names for game balance logging */
const GBE_Inventory = 'Inventory';
const GBE_TraderOpen = 'TraderOpen';
const GBE_WaveStart = 'WaveStart';
const GBE_Buy = 'Buy';
const GBE_Sell = 'Sell';
const GBE_Pickup = 'Pickup';
const GBE_Respawn = 'Respawn';
const GBE_Kills = 'Kills';
const GBE_Deaths = 'Deaths';
/************************************************************************************
* @name Reservations
***********************************************************************************/
struct native KFPlayerReservation
{
var UniqueNetId PlayerId;
var int Timer;
};
var transient array<KFPlayerReservation> PlayerReservations;
var int ReservationTimeout;
struct native PlayerGroupStruct
{
var byte Team;
var array<UniqueNetId> PlayerGroup;
structdefaultproperties
{
Team=128
}
};
var transient array<PlayerGroupStruct> PlayerGroups;
/************************************************************************************
* @name Server type
***********************************************************************************/
/** Whether the current game is a custom game (as opposed to standard) */
var protected const bool bIsCustomGame;
/** Whether the current game is unranked (as opposed to, you guessed it, ranked) */
var private const bool bIsUnrankedGame;
/** Whether we should check if this server is out of date and shut it down. Set in native PreBeginPlay */
var const bool bEnableServerVersionCheck;
/** Time since we last checked for updates */
var const float LastUpToDateCheckTime;
/** Tickble object to manage UpToDate webapi */
var transient KFSteamWebUpToDateCheck UpToDateChecker;
/** If this is a versus game */
var bool bIsVersusGame;
var bool bIsEndlessGame;
// NVCHANGE_BEGIN - RLS - Debugging Effects
/************************************************************************************
* @name NVIDIA
***********************************************************************************/
/** Debug dramatic kills. */
var bool bNVAlwaysDramatic;
var bool bNVBlockDramatic; // rls - has priority
var bool bNVAlwaysHeadshot;
var bool bNVDebugDamage;
// NVCHANGE_END - RLS - Debugging Effects
var bool bLogGroupTeamBalance;
/************************************************************************************
* @name Outbreak
***********************************************************************************/
/** Zed adjustments used in outbreak events. */
var KFOutbreakEvent OutbreakEvent;
/** Type of outbreak event to be used. */
var class<KFOutbreakEvent> OutbreakEventClass;
/************************************************************************************
* @name Kismet Monster Properties
***********************************************************************************/
enum EMonsterProperties
{
EMonsterProperties_Enraged,
EMonsterProperties_Sprinting
};
var int SpawnedMonsterProperties[EMonsterProperties];
/************************************************************************************
* @name Native
***********************************************************************************/
cpptext
{
// static members (doesn't contribute to class size)
static UBOOL bServerNeedsRestart;
virtual void PreBeginPlay();
virtual void TickSpecial( FLOAT DeltaSeconds );
// -autoupdate commandline
virtual void CheckGameUpToDate();
virtual void SendUpToDateCheck();
virtual void ServerCleanExit();
virtual UBOOL IsStandardGame();
}
// Returns num of alive AI
native function int GetMonsterAliveCount();
// Returns num of living human team members
native function int GetLivingPlayerCount();
// Force the game to think we have this many players alive
native function ForceLivingPlayerCount( byte NewPlayerNum );
// Set the num of alive AI (after a spawn ect)
native function RefreshMonsterAliveCount();
// Returns the AIDirector
native function KFAIDirector GetAIDirector();
// Skip a single map that is not allowed in the current server settings.
native function bool IsMapAllowedInCycle(string MapName);
// Returns if the server was just restarted
native function bool MapCycleNeedsInit();
// Sets game to ranked/unranked
native final function SetGameUnranked( bool bUnranked );
native protected function bool IsUnrankedGame();
/************************************************************************************
* @name Static functions
***********************************************************************************/
/**
* Allows overriding of which gameinfo class to use.
* Called on the DefaultGameType from the ini, or the one specified on the command line (?game=xxx)
*/
static event class<GameInfo> SetGameType(string MapName, string Options, string Portal)
{
// if we're in the menu level, use the menu gametype
if ( class'WorldInfo'.static.IsMenuLevel(MapName) )
{
return class'KFGameInfo_Entry';
}
return Default.class;
}
/**
* Strips the "play on X" prefixes added to autosaved maps.
*/
static function string StripPlayOnPrefix( String MapName )
{
if (Left(MapName, 6) ~= "UEDPIE")
{
return Right(MapName, Len(MapName) - 6);
}
else if ( Left(MapName, 5) ~= "UEDPC" )
{
return Right(MapName, Len(MapName) - 5);
}
else if (Left(MapName, 6) ~= "UEDPS3")
{
return Right(MapName, Len(MapName) - 6);
}
else if (Left(MapName, 6) ~= "UED360")
{
return Right(MapName, Len(MapName) - 6);
}
else if (Left(MapName, 6) ~= "UEDIOS")
{
return Right(MapName, Len(MapName) - 6);
}
return MapName;
}
/**
* 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;
class'KFGameEngine'.static.RefreshEventContent();
foreach default.AIClassList(PawnClass)
{
PawnClass.static.PreloadContent();
}
//TODO: Revisit this list for better preloading, since it's tied to specific situations
foreach default.NonSpawnAIClassList(PawnClass)
{
PawnClass.static.PreloadContent();
}
}
/** Various functions used by UI when setting game mode */
static function bool GetShouldShowLength()
{
return true;
}
static function string GetGameModeFriendlyNameFromNum( int GameModeNum )
{
return default.GameModes[Max(GameModeNum, 0)].FriendlyName;
}
static function string GetGameModeFriendlyNameFromClass( string GameModeClassString )
{
return default.GameModes[Max(default.GameModes.Find('ClassNameAndPath', GameModeClassString), 0)].FriendlyName;
}
static function int GetGameModeNumFromClass( string GameModeClassString )
{
return default.GameModes.Find('ClassNameAndPath', GameModeClassString);
}
static function string GetGameModeClassFromNum( int GameModeNum )
{
return default.GameModes[Max(GameModeNum, 0)].ClassNameAndPath;
}
static function int GetLocalizeIDFromFriendlyName( string FriendlyNameString )
{
return default.GameModes[Max( default.GameModes.Find('FriendlyName', FriendlyNameString), 0 )].LocalizeID;
}
static function bool IsGameModeSoloPlayAllowed( int GameModeNum )
{
return default.GameModes[GameModeNum].bSoloPlaySupported;
}
static function int GetGameModeIndexFromName(string FriendlyName)
{
local int index;
local array<string> LocalizedGameModes;
LocalizedGameModes = class'KFCommon_LocalizedStrings'.static.GetGameModeStringsArray();
for (index = 0; index < LocalizedGameModes.length; index++)
{
if (LocalizedGameModes[index] == FriendlyName)
{
return index;
}
}
return INDEX_NONE;
}
function string GetFriendlyNameForCurrentGameMode()
{
return GetGameModeFriendlyNameFromClass( "KFGameContent."$Class );
}
function int GetGameModeNum()
{
return GetGameModeNumFromClass( PathName(default.class) );
}
function string GetFullGameModePath()
{
return GetGameModeClassFromNum( GetGameModeNum() );
}
/**
* @brief Marks the game as running an out-of-date version of the engine or workshop content. Designed to be callable on default object
*/
native function SetNeedsRestart();
native final function DisableServerTakeover();
/**
* @brief Marks the game as needing to reload the map. If no one is playing, will reload the map immediately, otherwise will do nothing because the map
* will reload when the player win/lose/all leave
*/
native function SetNeedsReload();
native static function StaticSetNeedsRestart();
static native final function bool GameModeSupportsMap(int GameMode, string MapName);
/************************************************************************************
* @name InitGame
***********************************************************************************/
/* Initialize the game.
The GameInfo's InitGame() function is called before any other scripts (including
PreBeginPlay() ), and is used by the GameInfo to initialize parameters and spawn
its helper classes.
Warning: this is called before actors' PreBeginPlay.
*/
event InitGame( string Options, out string ErrorMessage )
{
Super.InitGame( Options, ErrorMessage );
if (UsesModifiedDifficulty())
{
SetModifiedGameDifficulty();
}
else
{
GameDifficulty = Max(0, GetIntOption(Options, "Difficulty", GameDifficulty));
GameDifficulty = Clamp(GameDifficulty, MinGameDifficulty, MaxGameDifficulty);
}
if (UsesModifiedLength())
{
SetModifiedGameLength();
}
else
{
GameLength = Clamp(GetIntOption(Options, "GameLength", GameLength), 0, SpawnManagerClasses.Length - 1);
}
if( OnlineSub != none && OnlineSub.GetLobbyInterface() != none )
{
OnlineSub.GetLobbyInterface().LobbyJoinGame();
}
if( Role == ROLE_Authority )
{
if( DialogManagerClass != None )
{
DialogManager = Spawn(DialogManagerClass);
}
}
if( ParseOption( Options, "Private" ) ~= "1" )
{
GameEngine(class'Engine'.static.GetEngine()).bPrivateServer = true;
UpdateGameSettings();
}
if ( MapCycleNeedsInit() )
{
MapCycleIndex = INDEX_NONE;
SaveConfig();
}
GameStartDelay = Clamp( GetIntOption( Options, "GameStartDelay", GameStartDelay ), 0, 60 );
ReadyUpDelay = Clamp( GetIntOption( Options, "ReadyUpDelay", ReadyUpDelay ), 0, 300 );
EndOfGameDelay = Clamp( GetIntOption( Options, "EndOfGameDelay", EndOfGameDelay ), 0, 120 );
FriendlyFireScale = FClamp( GetFloatOption( Options, "FriendlyFireScale", FriendlyFireScale ), 0, 1.f );
CreateOutbreakEvent();
CheckForCustomSettings();
CreateDifficultyInfo(Options);
}
//for difficulty override
function bool UsesModifiedLength()
{
return false;
}
function SetModifiedGameLength();
function bool UsesModifiedDifficulty()
{
return false;
}
function SetModifiedGameDifficulty();
/** Called once by InitGame to select DifficultyInfo Class */
function CreateDifficultyInfo(string Options)
{
if ( DifficultyInfoConsoleClass != None && (WorldInfo.IsConsoleBuild() || WorldInfo.IsConsoleDedicatedServer()
`if(`notdefined(ShippingPC))
// ?ConsoleBalance=1 for testing
|| ParseOption( Options, "ConsoleBalance" ) ~= "1"
`endif
))
{
`log("Using difficulty override class:"@DifficultyInfoConsoleClass);
DifficultyInfo = new(self) DifficultyInfoConsoleClass;
}
else
{
DifficultyInfo = new(self) DifficultyInfoClass;
}
DifficultyInfo.SetDifficultySettings( GameDifficulty );
}
function CreateOutbreakEvent()
{
if (OutbreakEventClass != none)
{
OutbreakEvent = new(self) OutbreakEventClass;
}
}
/**
* @brief Get's a floatfrom the config if available (Nobody tried to grab a float option yet? Only int?)
*
* @param Options Passed in optiions
* @param ParseString The variable we are looking for
* @param CurrentValue the current value
* @return float value of the option we are looking for
*/
static function float GetFloatOption( string Options, string ParseString, float CurrentValue )
{
local string InOpt;
InOpt = ParseOption( Options, ParseString );
if ( InOpt != "" )
{
return float(InOpt);
}
return CurrentValue;
}
/**
* @brief Checks if certain config settings have been changed or modified that make
* this a custom server
* @return true if custom
*/
protected native function CheckForCustomSettings();
event PreBeginPlay()
{
class'KFGameEngine'.static.ApplyTweaks(WorldInfo.GetMapName());
super.PreBeginPlay();
MyKFGRI = KFGameReplicationInfo(GameReplicationInfo);
InitGRIVariables();
CreateTeam(0);
InitGameConductor();
InitAIDirector();
InitTraderList();
ReplicateWelcomeScreen();
WorldInfo.TWLogsInit();
`if(`notdefined(ShippingPC))
if ( WorldInfo.NetMode == NM_ListenServer )
{
WorldInfo.AddOnScreenDebugMessage(-1, 60, MakeColor(255,0,0,255), "NM_ListenServer");
}
`endif
}
event PostBeginPlay()
{
InitAllPickups();
InitGameplayEventWriter();
super.PostBeginPlay();
// BWJ - 4-11-16 - Dedicated server builds launched by playfab kill themselves when the last player leaves or after x seconds of first person not joining
if( WasLaunchedByPlayfab() )
{
SetTimer( ServerExpirationForKillWhenEmpty, true, nameof(CheckPopulation) );
}
}
// Creates the GamePlayEventWriter and begins logging all expected information
function InitGameplayEventWriter()
{
if( bRecordGameStatsFile && GameplayEventsWriterClass != None )
{
`log("Recording game events with"@GameplayEventsWriterClass);
GameplayEventsWriter = new(self) GameplayEventsWriterClass;
}
else
{
`log("Gameplay events will not be recorded.");
}
}
function ReplicateWelcomeScreen()
{
local WorldInfo WI;
WI = class'WorldInfo'.static.GetWorldInfo();
if(WI.NetMode != NM_DedicatedServer)
{
return;
}
if(MyKFGRI != none)
{
MyKFGRI.ServerAdInfo.BannerLink = BannerLink;
MyKFGRI.ServerAdInfo.ServerMOTD = Repl(ServerMOTD, "@nl@", Chr(10));
MyKFGRI.ServerAdInfo.WebsiteLink= WebsiteLink;
MyKFGRI.ServerAdInfo.ClanMotto = ClanMotto;
}
}
//
// Accept or reject a player on the server.
// Fails login if you set the Error to a non-empty string.
// NOTE: UniqueId should not be trusted at this stage, it requires authentication
//
// TWI: Overriden from GameInfo.uc so we can use a custom ErrorMessage for bans -MattF
event PreLogin(string Options, string Address, const UniqueNetId UniqueId, bool bSupportsAuth, out string ErrorMessage)
{
local bool bSpectator;
local bool bPerfTesting;
local string DesiredDifficulty, DesiredWaveLength, DesiredGameMode;
// Check for an arbitrated match in progress and kick if needed
if (WorldInfo.NetMode != NM_Standalone && bUsingArbitration && bHasArbitratedHandshakeBegun)
{
ErrorMessage = PathName(WorldInfo.Game.GameMessageClass) $ ".ArbitrationMessage";
return;
}
// If this player is banned, reject him
if (AccessControl != none && AccessControl.IsIDBanned(UniqueId))
{
`log(Address@"is banned, rejecting...");
ErrorMessage = "<Strings:KFGame.KFLocalMessage.BannedFromServerString>";
return;
}
// Check against what is expected from the client in the case of quick join/server browser. The server settings can change from the time the server gets the properties from the backend
// This code is used to avoid collisions. A player may think they are joining a certain server with the options they are expecting, but its possible for the server to change
// before this happens. ServerTakeover for instance. These options should only be provided when joining from quick match or server browser. They get removed after successfully joining a server
if( WorldInfo.NetMode == NM_DedicatedServer && !HasOption( Options, "bJoinViaInvite" ) )
{
DesiredDifficulty = ParseOption( Options, "Difficulty" );
if( !bIsVersusGame && GametypeChecksDifficulty() && DesiredDifficulty != "" && int(DesiredDifficulty) != GameDifficulty )
{
`log("Got bad difficulty"@DesiredDifficulty@"expected"@GameDifficulty);
//ErrorMessage = "<Strings:KFGame.KFLocalMessage.ServerNoLongerAvailableString>";
ErrorMessage = "Server No longer available. Mismatch difficulty.";
return;
}
DesiredWaveLength = ParseOption( Options, "GameLength" );
if(!bIsEndlessGame && !bIsVersusGame && GametypeChecksWaveLength() && DesiredWaveLength != "" && int(DesiredWaveLength) != GameLength && int(DesiredWaveLength) != 127 && int(DesiredWaveLength) != INDEX_NONE)
{
`log("Got bad wave length"@DesiredWaveLength@"expected"@GameLength);
//ErrorMessage = "<Strings:KFGame.KFLocalMessage.ServerNoLongerAvailableString>";
ErrorMessage = "Server No longer available. Mismatch GameLength.";
return;
}
DesiredGameMode = ParseOption( Options, "Game" );
if( DesiredGameMode != "" && !(DesiredGameMode ~= GetFullGameModePath()) )
{
`log("Got bad game mode"@DesiredGameMode@"expected"@GetFullGameModePath());
ErrorMessage = "Server No longer available. Mismatch DesiredGameMode.";
return;
}
}
bPerfTesting = ( ParseOption( Options, "AutomatedPerfTesting" ) ~= "1" );
bSpectator = bPerfTesting || ( ParseOption( Options, "SpectatorOnly" ) ~= "1" ) || ( ParseOption( Options, "CauseEvent" ) ~= "FlyThrough" );
if (AccessControl != None)
{
AccessControl.PreLogin(Options, Address, UniqueId, bSupportsAuth, ErrorMessage, bSpectator);
}
}
static function bool GametypeChecksDifficulty()
{
return true;
}
static function bool GametypeChecksWaveLength()
{
return true;
}
event PostLogin( PlayerController NewPlayer )
{
local KFPlayerController KFPC;
local int OldNumSpectators;
OldNumSpectators = NumSpectators;
super.PostLogin( NewPlayer );
// Delay this by 1.5 seconds so there aren't multiple calls if everyone joins/leaves at the same time
if( PlayfabInter != None && PlayfabInter.IsRegisteredWithPlayfab() )
{
SetTimer( 1.5, false, nameof(UpdateGameSettings) );
}
//We need to keep number of spectators as absolutely up-to-date as possible for the server browser
else if (OldNumSpectators != NumSpectators)
{
UpdateGameSettings();
}
KFPC = KFPlayerController(NewPlayer);
if( KFPC != None )
{
if( KFPC.PlayerReplicationInfo.bOnlySpectator )
{
// if we're initially spectating, initialize front-end but skip lobby menu
KFPC.ClientSetFrontEnd( KFGFxManagerClass, true );
}
else if( KFPC.GetTeamNum() == 255 )
{
// if we're initially spectating, initialize front-end but skip lobby menu
if( !class'Engine'.static.IsEditor() && !class'KFGameEngine'.static.CheckSkipLobby() )
{
KFPC.CreateCustomizationPawn();
}
if( KFPC.Pawn != none && KFPawn_Customization(KFPC.Pawn) != none )
{
KFPawn_Customization(KFPC.Pawn).SetServerHidden( true );
KFPC.SetCameraMode( 'PlayerZedWaiting' );
}
KFPC.ClientSetFrontEnd( KFGFxManagerClass, false );
}
else
{
KFPC.ClientSetFrontEnd( KFGFxManagerClass, false );
// Only create customization pawns for a real game
if( !class'Engine'.static.IsEditor() && !class'KFGameEngine'.static.CheckSkipLobby() )
{
KFPC.CreateCustomizationPawn();
}
}
}
}
function Logout( Controller Exiting )
{
local int OldNumSpectators;
OldNumSpectators = NumSpectators;
Super.Logout( Exiting );
// Delay this by 1.5 seconds so there aren't multiple calls if everyone joins/leaves at the same time
if( PlayfabInter != None && PlayfabInter.IsRegisteredWithPlayfab() )
{
SetTimer( 1.5, false, nameof(UpdateGameSettings) );
}
else if( OldNumSpectators != NumSpectators )
{
UpdateGameSettings();
}
if( PlayfabInter != none && PlayfabInter.IsRegisteredWithPlayfab() )
{
if( PlayerController(Exiting) != none )
{
// Check the current population to see if we need to shut down the next frame
SetTimer( 0.01, false, nameof(CheckPopulation) );
// Notify playfab that a player has left
if( Exiting.PlayerReplicationInfo != None && Exiting.PlayerReplicationInfo.PlayfabPlayerId != "" )
{
PlayfabInter.ServerNotifyPlayerLeft( Exiting.PlayerReplicationInfo.PlayfabPlayerId );
}
}
}
}
/* ProcessServerTravel()
Optional handling of ServerTravel for network games.
*/
function ProcessServerTravel(string URL, optional bool bAbsolute)
{
super.ProcessServerTravel( URL, bAbsolute );
// Set the last travel time in our persistent game data
class'KFMapTravelData'.static.SetLastTravelTime( WorldInfo.RealTimeSeconds );
class'KFMapTravelData'.static.SetLastGameMap( WorldInfo.GetMapName(true) );
//@HSL_BEGIN - JRO - 4/28/2016 - Recreate the session after each match
`log("SESSIONS - Clearing ConsoleGameSessionGuid for a new map"@URL);
KFGameEngine(Class'Engine'.static.GetEngine()).ConsoleGameSessionGuid = "";
//@HSL_END
}
function CheckPopulation()
{
local bool bNeedsShutdown;
local int i;
// We check if we need to shut down only if not doing a level transition and the game was launched by playfab services
if( !bLevelChange && WasLaunchedByPlayfab() )
{
bNeedsShutdown = true;
for( i = 0; i < GameReplicationInfo.PRIArray.Length; i++ )
{
if( !GameReplicationInfo.PRIArray[i].bBot )
{
bNeedsShutdown = false;
break;
}
}
}
if( bNeedsShutdown )
{
`log("Server needs shutdown! Exiting now");
ConsoleCommand("Exit");
}
}
event InitAIDirector()
{
AIDirector = new(self) class'KFAIDirector';
AIDirector.Initialize();
}
/* Initialize the Game Conductor */
function InitGameConductor()
{
GameConductor = new(self) GameConductorClass;
GameConductor.Initialize();
}
/* Initialize the GRI varaibles */
function InitGRIVariables()
{
MyKFGRI.GameDifficulty = GameDifficulty;
MyKFGRI.GameLength = GameLength;
MyKFGRI.ReceivedGameLength();
MyKFGRI.bVersusGame = bIsVersusGame;
MyKFGRI.MaxHumanCount = MaxPlayers;
SetBossIndex();
}
//Event or maps-specific override functionality
native function bool UseSpecificBossIndex(out int ForcedIndex);
//Find and preload content for the chosen boss index
function SetBossIndex()
{
if (!UseSpecificBossIndex(BossIndex))
{
BossIndex = Rand(default.AIBossClassList.Length);
}
MyKFGRI.CacheSelectedBoss(BossIndex);
}
/**
* AllowCheats - Allow cheating in single player games and coop games.
*/
function bool AllowCheats(PlayerController P)
{
`if(`notdefined(ShippingPC))
return true;
`else
return false; // see KFPlayerController.EnableCheats
`endif
}
/** Adds all available traders to the list */
function InitTraderList()
{
local KFTraderTrigger MyTrader;
TraderList.Remove(0, TraderList.Length);
foreach DynamicActors(class'KFTraderTrigger', MyTrader)
{
if (MyTrader.bEnabled)
{
TraderList.AddItem(MyTrader);
}
}
}
/** Stub, implement in subclasses */
function SetupNextTrader();
/** Sets the number of possible pickups on this difficulty and activates that many around the map */
function InitAllPickups()
{
if(bDisablePickups || DifficultyInfo == none)
{
NumWeaponPickups = 0;
NumAmmoPickups = 0;
}
else
{
NumWeaponPickups = ItemPickups.Length * DifficultyInfo.GetItemPickupModifier();
NumAmmoPickups = AmmoPickups.Length * DifficultyInfo.GetAmmoPickupModifier();
}
`if(`__TW_SDK_)
if( BaseMutator != none )
{
BaseMutator.ModifyPickupFactories();
}
`endif
ResetAllPickups();
}
/** Called to reset all the types of pickups */
function ResetAllPickups()
{
local int i;
AllPickupFactories.Remove( 0, AllPickupFactories.Length );
// Add all the item pickups
for ( i = 0; i < ItemPickups.Length; i++ )
{
AllPickupFactories.AddItem( ItemPickups[i] );
}
// Add all the ammo pickups
for ( i = 0; i < AmmoPickups.Length; i++ )
{
AllPickupFactories.AddItem( AmmoPickups[i] );
}
if(NumWeaponPickups > 0 )
{
ResetPickups( ItemPickups, NumWeaponPickups );
}
if(NumAmmoPickups > 0)
{
ResetPickups( AmmoPickups, NumAmmoPickups );
}
}
/** Pick random pickup items to enable and put all others to sleep */
function ResetPickups( array<KFPickupFactory> PickupList, int NumPickups )
{
local byte i, ChosenIndex;
local array<KFPickupFactory> PossiblePickups;
PossiblePickups = PickupList;
for ( i = 0; i < NumPickups; i++ )
{
ChosenIndex = Rand( PossiblePickups.Length );
PossiblePickups[ChosenIndex].Reset();
PossiblePickups.Remove( ChosenIndex, 1 );
}
// Put any pickup factories that weren't enabled to sleep
for ( i = 0; i < PossiblePickups.Length; i++ )
{
PossiblePickups[i].StartSleeping();
}
}
/** Find a new item pickup factory to enable after a certain amount of time */
function EnableNewPickup( array<KFPickupFactory> PickupList, float RespawnDelay, KFPickupFactory LastPickup )
{
local KFPickupFactory ActiveFactory;
ActiveFactory = DetermineNextPickup( PickupList, LastPickup );
`if(`__TW_SDK_)
if( BaseMutator != none )
{
BaseMutator.ModifyActivatedPickupFactory( ActiveFactory, RespawnDelay );
}
`endif
ActivateNextPickup( ActiveFactory, RespawnDelay );
if (ActiveFactory != LastPickup)
{
LastPickup.StartSleeping();
}
}
/** Pick a new factory from provided list */
function KFPickupFactory DetermineNextPickup( array<KFPickupFactory> PickupList, KFPickupFactory LastPickup )
{
local byte i;
local array<KFPickupFactory> PossibleFactories;
for ( i = 0; i < PickupList.length; i++ )
{
if ( PickupList[i] != LastPickup && PickupList[i].IsInState('Sleeping') && !PickupList[i].bToBeActivated )
{
PossibleFactories.AddItem( PickupList[i] );
}
}
if ( PossibleFactories.Length > 0 )
{
return PossibleFactories[ Rand( PossibleFactories.Length ) ];
}
else
{
return LastPickup;
}
}
/** Activate selected pickup factory */
function ActivateNextPickup( KFPickupFactory NextFactory, int RespawnDelay )
{
if( NextFactory != none )
{
NextFactory.bToBeActivated = true;
NextFactory.SetTimer( RespawnDelay, , nameof(NextFactory.Reset) );
}
}
/************************************************************************************
* @name Spawning
***********************************************************************************/
/** This is called when the generic customization points have been streamed in */
function CustomizationPointsInitialized()
{
local KFPlayerController KFPC;
local KFPawn_Customization KFPCustom;
foreach LocalPlayerControllers(class'KFPlayerController', KFPC)
{
if ( KFPC.Pawn != none )
{
KFPCustom = KFPawn_Customization( KFPC.Pawn );
if ( KFPCustom != none && !KFPCustom.bUsingCustomizationPoint )
{
KFPCustom.MoveToCustomizationPoint();
}
}
}
}
/** Overridden to set bNoCollisionFail in spawn to true */
function Pawn SpawnDefaultPawnFor(Controller NewPlayer, NavigationPoint StartSpot)
{
local class<Pawn> DefaultPlayerClass;
local Rotator StartRotation;
local Pawn ResultPawn;
DefaultPlayerClass = GetDefaultPlayerClass(NewPlayer);
// don't allow pawn to be spawned with any pitch or roll
StartRotation.Yaw = StartSpot.Rotation.Yaw;
ResultPawn = Spawn(DefaultPlayerClass,,,StartSpot.Location,StartRotation,,true);
if ( ResultPawn == None )
{
`log("Couldn't spawn player of type "$DefaultPlayerClass$" at "$StartSpot);
}
return ResultPawn;
}
/** Exists to create a customization pawn rather than the default pawn */
function KFPawn SpawnCustomizationPawn( NavigationPoint StartSpot )
{
local Rotator StartRotation;
local KFPawn_Customization ResultPawn;
// don't allow pawn to be spawned with any pitch or roll
StartRotation.Yaw = StartSpot.Rotation.Yaw;
ResultPawn = Spawn(CustomizationPawnClass,,,StartSpot.Location,StartRotation,,true);
if ( ResultPawn == None )
{
`log("Couldn't spawn player of type "$CustomizationPawnClass$" at "$StartSpot);
}
// Init location and rotation
ResultPawn.SetUpdatedMovementData( StartSpot.Location, StartRotation );
return ResultPawn;
}
/** Overwritten to restart the player even if we are using a customization pawn*/
function StartHumans()
{
local KFPlayerController KFPC;
local int NumPlayerPawns;
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
{
if( bGameEnded )
{
return; // telefrag ended the game with ridiculous frag limit
}
if( KFPC.Pawn == None || KFPawn_Customization(KFPC.Pawn) != none )
{
if( KFPC.CanRestartPlayer() && KFPC.GetTeamNum() != 255 )
{
RestartPlayer( KFPC );
}
}
if( KFPC.Pawn != None || KFPC.bWaitingForClientPerkData )
{
NumPlayerPawns++;
}
}
bOnePlayerAtStart = (NumPlayerPawns == 1);
}
function RestartPlayer(Controller NewPlayer)
{
local KFPlayerController KFPC;
local KFPerk MyPerk;
`if(`notdefined(ShippingPC))
local bool bIsBenchmark;
`endif
KFPC = KFPlayerController( NewPlayer );
// Make sure the perk is initialized before spawning in, if not, wait for it
// @NOTE: We still do this in standalone games because we may need to wait for Steam -MattF
`if(`notdefined(ShippingPC))
// Benchmark silliness. If we allow this code to run, it takes control away from the benchmark
// matinee director and the command fails. -MattF
if( !class'WorldInfo'.Static.IsConsoleBuild()
&& !class'WorldInfo'.Static.IsConsoleDedicatedServer()
&& InStr(WorldInfo.GetLocalURL(), "?CAUSEEVENT=BENCHMARK",, true) > -1 )
{
bIsBenchmark = true;
}
if( KFPC != none && KFPC.GetTeamNum() != 255 && !bIsBenchmark )
`else
if( KFPC != none && KFPC.GetTeamNum() != 255 )
`endif
{
MyPerk = KFPC.GetPerk();
if( MyPerk == none || !MyPerk.bInitialized )
{
KFPC.WaitForPerkAndRespawn();
return;
}
}
// If we have a customization pawn, destroy it before spawning a new pawn with super.RestartPlayer
if( NewPlayer.Pawn != none && KFPawn_Customization(NewPlayer.Pawn) != none )
{
NewPlayer.Pawn.Destroy();
}
super.RestartPlayer( NewPlayer );
if( NewPlayer.Pawn != none )
{
if( KFPC != none )
{
// Initialize game play post process effects such as damage, low health, etc.
KFPC.InitGameplayPostProcessFX();
// Start fading in the camera
KFPC.ClientSetCameraFade( true, MakeColor(255,255,255,255), vect2d(1.f, 0.f), 0.6f, true );
}
}
}
/** Returns a low rating if other players are nearby (spawn will fail) */
function float RatePlayerStart(PlayerStart P, byte Team, Controller Player)
{
local float Rating;
`if(`notdefined(ShippingPC))
// Random starts are done by disabling starts in kismet. It's not ideal,
// but we can assume all disabled starts are disabled this way.
if ( !P.bEnabled && class'KFGameEngine'.static.CheckNoRandomStart() )
{
P.bEnabled = TRUE;
}
`endif
Rating = Super.RatePlayerStart(P, Team, Player);
// If bEnabled (Rating == 5.f), check proximity
if ( P.bEnabled )
{
if ( !CheckSpawnProximity( P, Player, Team ) )
{
return 5.1f; // Higher than disabled, but lower than default
}
}
return Rating;
}
/** returns whether the given Controller StartSpot property should be used as the spawn location for its Pawn */
function bool ShouldSpawnAtStartSpot(Controller Player)
{
local PlayerStart StartSpot;
// Without NM_Standalone (see super) for online predicted spawn location / texture streaming
if ( Player != None && Player.StartSpot != None &&
(IsInitialSpawnPointSelection(WorldInfo) || (Player.PlayerReplicationInfo != None && Player.PlayerReplicationInfo.bWaitingPlayer)) )
{
StartSpot = PlayerStart(Player.StartSpot);
if ( StartSpot == None )
{
// Not a PlayerStart class (e.g. Teleporter / Play From Here)
return true;
}
// 5 == disabled, 5.1 == collision, 6 == okay
return RatePlayerStart(StartSpot, Player.GetTeamNum(), Player) >= 6.0;
}
return false;
}
/** Find a predefined spot for PlayerCustomization */
function KFCustomizationPoint FindCustomizationStart( Controller Player )
{
local KFCustomizationPoint CP;
ForEach AllActors( class 'KFCustomizationPoint', CP )
{
if( CheckSpawnProximity(CP, Player, Player.GetTeamNum(), true) )
{
return CP;
}
}
return CP;
}
/** Returns false if this is near another player or in-use spawn point */
static function bool CheckSpawnProximity( NavigationPoint P, Controller Player, byte TeamNum, optional bool bCustomizationPoint )
{
local PlayerController PC;
local KFPawn_Customization CPawn;
local WorldInfo WI;
WI = class'WorldInfo'.static.GetWorldInfo();
foreach WI.AllControllers( class'PlayerController', PC )
{
if( PC == Player )
{
continue;
}
// During initial StartSpot selection, before 1st spawn, choose a unique spawn for each player
if( IsInitialSpawnPointSelection(WI) && !bCustomizationPoint )
{
if ( PC.StartSpot == P && PC.GetTeamNum() == TeamNum )
{
return false;
}
}
// During gameplay, or using customization starts, ignore StartSpot and use distance
else if( PC.Pawn != None && !PC.Pawn.bHidden )
{
if( bCustomizationPoint )
{
// invisible customization pawns are okay
CPawn = KFPawn_Customization(PC.Pawn);
if( CPawn != None && CPawn.bServerHidden )
{
continue;
}
}
if( VSizeSq(PC.Pawn.Location - P.Location) < Square(2.1 * PC.Pawn.GetCollisionRadius()) )
{
return false;
}
}
}
return true;
}
static function bool IsInitialSpawnPointSelection( WorldInfo WI )
{
return WI.Game != none ? WI.Game.bWaitingToStartMatch : !WI.GRI.bMatchHasBegun;
}
/* @see GameInfo::SetPlayerDefaults */
function SetPlayerDefaults(Pawn PlayerPawn)
{
local KFPawn KFP;
KFP = KFPawn(PlayerPawn);
if ( KFP != none && KFP.GetPerk() != none )
{
if ( KFP.GetPerk() != none )
{
KFP.GetPerk().SetPlayerDefaults(KFP);
}
KFP.bIgnoreTeamCollision = bDisableTeamCollision;
}
if ( BaseMutator != None )
{
BaseMutator.ModifyPlayer(PlayerPawn);
}
PlayerPawn.PhysicsVolume.ModifyPlayer(PlayerPawn);
}
function ModifyGroundSpeed(KFPawn PlayerPawn, out float GroundSpeed)
{
if(OutbreakEvent != none)
{
OutbreakEvent.ModifyGroundSpeed(PlayerPawn, GroundSpeed);
}
}
function ModifySprintSpeed(KFPawn PlayerPawn, out float SprintSpeed);
/** Used by wave based game types */
function bool IsWaveActive();
/** Scale to use against WaveTotalAI to determine full wave size */
function float GetTotalWaveCountScale()
{
//Boss wave, don't scale it.
if (MyKFGRI.IsBossWave())
{
return 1.0f;
}
if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.WaveAICountScale.Length > 0)
{
return GetLivingPlayerCount() > OutbreakEvent.ActiveEvent.WaveAICountScale.Length ? OutbreakEvent.ActiveEvent.WaveAICountScale[OutbreakEvent.ActiveEvent.WaveAICountScale.Length - 1] : OutbreakEvent.ActiveEvent.WaveAICountScale[GetLivingPlayerCount() - 1];
}
return 1.0f;
}
/** Returns NumAlwaysRelevantZeds, the number of remaining zeds before they become bAlwaysRelevant=true */
static function float GetNumAlwaysRelevantZeds()
{
return default.NumAlwaysRelevantZeds;
}
/** Allow specific game type to override the spawn class. Default implementation returns from the AI class list. */
function class<KFPawn_Monster> GetAISpawnType(EAIType AIType)
{
return AIClassList[AIType];
}
/** Allow specific game type to override the boss spawn class. Default implementation returns from the AI class list. */
function class<KFPawn_Monster> GetBossAISpawnType()
{
return GetSpecificBossClass(BossIndex, KFMapInfo(WorldInfo.GetMapInfo()));
}
static function class<KFPawn_Monster> GetSpecificBossClass(int Index, KFMapInfo MapInfo = none)
{
local array< class<KFPawn_Monster> > ClassList;
ClassList = default.AIBossClassList;
if (MapInfo != none)
{
MapInfo.ModifyGameClassBossAIClassList(ClassList);
}
if (Index < 0 || Index >= ClassList.Length)
{
return none;
}
return ClassList[Index];
}
/** Allow gametype to do any adjustments to the spawned AI pawn */
event PrePossessAdjustments(KFPawn NewSpawn);
/** Allow specific game types to modify the spawn rate at a global level */
function float GetGameInfoSpawnRateMod()
{
local float SpawnRateMod;
SpawnRateMod = 1.f;
if (OutbreakEvent != none)
{
SpawnRateMod *= 1.f / OutbreakEvent.ActiveEvent.SpawnRateMultiplier;
}
if (MyKFGRI != none)
{
SpawnRateMod *= MyKFGRI.GetMapObjectiveSpawnRateMod();
}
return SpawnRateMod;
}
/** Whether or not a specific primary weapon is allowed. Called at player spawn time while setting inventory. */
function bool AllowPrimaryWeapon(string ClassPath)
{
return true;
}
/** Whether or not a specific secondary weapon is allowed. Called at player spawn time while setting inventory. */
function bool AllowSecondaryWeapon(string ClassPath)
{
return true;
}
/** Allows gametype to adjust starting grenade count. Called at player spawn time from GiveInitialGrenadeCount in the inventory. */
function int AdjustStartingGrenadeCount(int CurrentCount)
{
return CurrentCount;
}
/** Allows gametype to validate a perk for the current match */
function bool IsPerkAllowed(class<KFPerk> PerkClass)
{
return true;
}
/************************************************************************************
* @name Difficulty Scaling
***********************************************************************************/
/** Adjusts AI pawn default settings by game difficulty and player count */
function SetMonsterDefaults( KFPawn_Monster P )
{
local float HealthMod;
local float HeadHealthMod;
local float TotalSpeedMod, StartingSpeedMod;
local float DamageMod;
local int LivingPlayerCount;
local int i;
LivingPlayerCount = GetLivingPlayerCount();
DamageMod = 1.0;
HealthMod = 1.0;
HeadHealthMod = 1.0;
// Scale health and damage by game conductor values for versus zeds
if( P.bVersusZed )
{
DifficultyInfo.GetVersusHealthModifier(P, LivingPlayerCount, HealthMod, HeadHealthMod);
HealthMod *= GameConductor.CurrentVersusZedHealthMod;
HeadHealthMod *= GameConductor.CurrentVersusZedHealthMod;
// scale damage
P.DifficultyDamageMod = DamageMod * GameConductor.CurrentVersusZedDamageMod;
StartingSpeedMod = 1.f;
TotalSpeedMod = 1.f;
}
else
{
DifficultyInfo.GetAIHealthModifier(P, GameDifficulty, LivingPlayerCount, HealthMod, HeadHealthMod);
DamageMod = DifficultyInfo.GetAIDamageModifier(P, GameDifficulty,bOnePlayerAtStart);
// scale damage
P.DifficultyDamageMod = DamageMod;
StartingSpeedMod = DifficultyInfo.GetAISpeedMod(P, GameDifficulty);
TotalSpeedMod = GameConductor.CurrentAIMovementSpeedMod * StartingSpeedMod;
}
//`log("Start P.GroundSpeed = "$P.GroundSpeed$" GroundSpeedMod = "$GroundSpeedMod$" percent of default = "$(P.default.GroundSpeed * GroundSpeedMod)/P.default.GroundSpeed$" RandomSpeedMod= "$RandomSpeedMod);
// scale movement speed
P.GroundSpeed = P.default.GroundSpeed * TotalSpeedMod;
P.SprintSpeed = P.default.SprintSpeed * TotalSpeedMod;
// Store the difficulty adjusted ground speed to restore if we change it elsewhere
P.NormalGroundSpeed = P.GroundSpeed;
P.NormalSprintSpeed = P.SprintSpeed;
P.InitialGroundSpeedModifier = StartingSpeedMod;
//`log(P$" GroundSpeed = "$P.GroundSpeed$" P.NormalGroundSpeed = "$P.NormalGroundSpeed);
// Scale health by difficulty
P.Health = P.default.Health * HealthMod;
if( P.default.HealthMax == 0 )
{
P.HealthMax = P.default.Health * HealthMod;
}
else
{
P.HealthMax = P.default.HealthMax * HealthMod;
}
P.ApplySpecialZoneHealthMod(HeadHealthMod);
P.GameResistancePct = DifficultyInfo.GetDamageResistanceModifier(LivingPlayerCount);
// look for special monster properties that have been enabled by the kismet node
for (i = 0; i < ArrayCount(SpawnedMonsterProperties); i++)
{
// this property is currently enabled
if (SpawnedMonsterProperties[i] != 0)
{
// do the action associated with that property
switch (EMonsterProperties(i))
{
case EMonsterProperties_Enraged:
P.SetEnraged(true);
break;
case EMonsterProperties_Sprinting:
P.bSprintOverride=true;
break;
}
}
}
if (OutbreakEvent != none)
{
OutbreakEvent.AdjustMonsterDefaults(P);
}
// debug logging
`log("==== SetMonsterDefaults for pawn: " @P @"====",bLogAIDefaults);
`log("HealthMod: " @HealthMod @ "Original Health: " @P.default.Health @" Final Health = " @P.Health, bLogAIDefaults);
`log("HeadHealthMod: " @HeadHealthMod @ "Original Head Health: " @P.default.HitZones[HZI_HEAD].GoreHealth @" Final Head Health = " @P.HitZones[HZI_HEAD].GoreHealth, bLogAIDefaults);
`log("GroundSpeedMod: " @TotalSpeedMod @" Final Ground Speed = " @P.GroundSpeed, bLogAIDefaults);
//`log("HiddenSpeedMod: " @HiddenSpeedMod @" Final Hidden Speed = " @P.HiddenGroundSpeed, bLogAIDefaults);
`log("SprintSpeedMod: " @TotalSpeedMod @" Final Sprint Speed = " @P.SprintSpeed, bLogAIDefaults);
`log("DamageMod: " @DamageMod @" Final Melee Damage = " @P.MeleeAttackHelper.BaseDamage * DamageMod, bLogAIDefaults);
//`log("bCanSprint: " @P.bCanSprint @ " from SprintChance: " @SprintChance, bLogAIDefaults);
}
/************************************************************************************
* @name Team
***********************************************************************************/
/** SetTeam()
* 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)
{
if( Other.PlayerReplicationInfo == None || Other.PlayerReplicationInfo.bOnlySpectator || NewTeam == Other.PlayerReplicationInfo.Team )
{
return;
}
// remove the controller from his old team
if ( Other.PlayerReplicationInfo.Team != None )
{
Other.PlayerReplicationInfo.Team.RemoveFromTeam(Other);
Other.PlayerReplicationInfo.Team = none;
}
if ( NewTeam == None || (NewTeam != none && NewTeam.AddToTeam(Other)) )
{
if ( (NewTeam!=None) && ((WorldInfo.NetMode != NM_Standalone) || (PlayerController(Other) == None) || (PlayerController(Other).Player != None)) )
BroadcastLocalizedMessage( GameMessageClass, 3, Other.PlayerReplicationInfo, None, NewTeam );
GameConductor.HandlePlayerChangedTeam();
}
// Force net update for UI snappyness
Other.PlayerReplicationInfo.bNetDirty = true;
Other.PlayerReplicationInfo.bForceNetUpdate = true;
}
/* create a player team, and fill from the team roster
*/
function CreateTeam(int TeamIndex)
{
Teams[TeamIndex] = spawn(class'KFGame.KFTeamInfo_Human');
GameReplicationInfo.SetTeam(TeamIndex, Teams[TeamIndex]);
}
/** Called by Gameinfo::Login(), initial team pick */
function byte PickTeam(byte Current, Controller C, const out UniqueNetId PlayerId)
{
return 0;
}
/** Return whether a team change is allowed. */
function bool ChangeTeam(Controller Other, int N, bool bNewTeam)
{
// AI
if( PlayerController(Other) == none )
{
SetTeam( Other, Teams[N] );
return true;
}
// Human
if( Other.PlayerReplicationInfo != none
&& !Other.PlayerReplicationInfo.bOnlySpectator
&& Other.PlayerReplicationInfo.Team != Teams[0] )
{
SetTeam( Other, Teams[0] );
return true;
}
return false;
}
/**
* 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 )
{
local PlayerController TargetController;
// Normal PRI's should be replicatable, DummyPRI is not, indicating a zed
if( ViewTarget.RemoteRole == ROLE_None )
{
return false;
}
// We only want players with valid pawns
TargetController = PlayerController( ViewTarget.Owner );
return TargetController != none && TargetController.Pawn != none && TargetController.Pawn.IsAliveAndWell();
}
/************************************************************************************
* @name Damage
***********************************************************************************/
/* 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 class<KFDamageType> KFDT;
KFDT = class<KFDamageType>(DamageType);
// Don't damage the instigator if the flag is set
if ( KFDT != None && InstigatedBy != None && InstigatedBy == Injured.Controller )
{
if( KFDT.default.bNoInstigatorDamage )
{
Damage = 0;
}
else if( !KFDT.default.bIgnoreSelfInflictedScale )
{
Damage *= DifficultyInfo.GetSelfInflictedDamageMod();
}
// No momentum if no damage
Momentum = vect(0,0,0);
}
if ( Damage > 0 && InstigatedBy != None && InstigatedBy != Injured.Controller )
{
// Friendly fire is scaled for all humans
if ( Injured.IsHumanControlled() && Injured.GetTeamNum() == InstigatedBy.GetTeamNum() )
{
if (KFDT.default.bNoFriendlyFire)
{
Damage = 0;
}
else
{
Damage *= FriendlyFireScale;
}
// For now, don't apply momentum transfer to friendly pawns
Momentum = vect(0,0,0);
}
}
// checks neutral zone and god mode and calls mutator hook
Super.ReduceDamage(Damage, Injured, InstigatedBy, HitLocation, Momentum, DamageType, DamageCauser, HitInfo);
if (OutbreakEvent != none)
{
OutbreakEvent.ReduceDamage(Damage, Injured, InstigatedBy, DamageType, HitInfo);
}
}
function NotifyTakeHit(KFPawn Pawn, Controller InstigatedBy, vector HitLocation, int Damage,
class<DamageType> DamageType, vector Momentum, Actor DamageCauser);
function Killed(Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> DT)
{
local KFPlayerReplicationInfo KilledPRI;
local KFPlayerController KFPC;
local int PlayerScoreDelta, TeamPenalty;
local KFPerk KFPCP;
local KFPawn_Monster MonsterPawn;
local string KillerLabel;
local class<DamageType> LastHitByDamageType;
local int i;
if( KilledPlayer != None && KilledPlayer.bIsPlayer )
{
// Let the game conductor know a human team player died
if( KilledPlayer.GetTeamNum() == 0 && Killer != none && Killer.GetTeamNum() == 255 )
{
GameConductor.NotifyHumanTeamPlayerDeath();
}
KilledPRI = KFPlayerReplicationInfo( KilledPlayer.PlayerReplicationInfo );
if( KilledPRI != none )
{
if ( KilledPlayer == Killer )
{
KillerLabel = "self";
}
else if( Killer != none )
{
if( Killer.Pawn != none )
{
KillerLabel = string(Killer.Pawn.Class.Name);
}
else
{
KillerLabel = string(Killer.Class.Name); // well SOMETHING killed me (vs), no pawn, log the controller
}
}
else
{
KillerLabel = "Player"; // Need to always have some label
}
// Dosh penalty on death
PlayerScoreDelta = GetAdjustedDeathPenalty( KilledPRI );
`log("SCORING: Player" @ KilledPRI.PlayerName @ "next starting dosh =" @ PlayerScoreDelta + KilledPRI.Score, bLogScoring);
KilledPRI.AddDosh( PlayerScoreDelta );
TeamPenalty = GetAdjustedTeamDeathPenalty( KilledPRI );
if( KilledPRI.Team != none )
{
KFTeamInfo_Human(KilledPRI.Team).AddScore( -TeamPenalty );
`log("SCORING: Team lost" @ TeamPenalty @ "dosh for a player dying", bLogScoring);
}
KilledPRI.PlayerHealth = 0;
KilledPRI.PlayerHealthPercent = 0;
}
KFPC = KFPlayerController( KilledPlayer );
`log("KFGameInfo - Killed - Current Killed Player is not a valid KFPlayerController. KilledPlayer:" @ KilledPlayer, KFPC == none);
`AnalyticsLog(("player_death",
KilledPRI,
KillerLabel,
DT.Name,
"#"$MyKFGRI.WaveNum,
KFPC != none ? KFPC.GetPerk().PerkName : "",
KFPC != none ? KFPC.GetPerk().GetLevel() : byte(0),
(KilledPawn != none && KilledPawn.InvManager != none) ? KFInventoryManager(KilledPawn.InvManager).DumpInventory() : ""));
}
Super.Killed( Killer, KilledPlayer, KilledPawn, DT );
/* __TW_ANALYTICS_ */
`RecordKillEvent( NORMAL, Killer, DT, KilledPlayer );
// Maybe do a DramaticEvent that may trigger Zedtime when someone is killed
if( Killer != KilledPlayer )
{
CheckZedTimeOnKill( Killer, KilledPlayer, KilledPawn, DT );
}
// Update pawn counters
if( KilledPawn != none && KilledPawn.GetTeamNum() == 255 )
{
MonsterPawn = KFPawn_Monster(KilledPawn);
// Add Assists.
if (MonsterPawn != none)
{
for(i = 0; i < MonsterPawn.DamageHistory.length; i++)
{
// The killer will receive dosh later.
if (MonsterPawn.DamageHistory[i].DamagerController == Killer)
{
continue;
}
if (MonsterPawn.DamageHistory[i].TotalDamage > 0 && MonsterPawn.DamageHistory[i].DamagerController != none && KFPlayerController(MonsterPawn.DamageHistory[i].DamagerController) != none)
{
KFPlayerController(MonsterPawn.DamageHistory[i].DamagerController).AddZedAssist(MonsterPawn.class);
}
}
}
if( Killer != none )
{
KFPC = KFPlayerController( Killer );
if( KFPC != none )
{
if( MonsterPawn != none )
{
LastHitByDamageType = GetLastHitByDamageType( DT, MonsterPawn, Killer );
//Chris: We have to do it earlier here because we need a damage type
KFPC.AddZedKill( MonsterPawn.class, GameDifficulty, LastHitByDamageType, true );
KFPCP = KFPC.GetPerk();
if( KFPCP != none )
{
if( KFPCP.CanEarnSmallRadiusKillXP( LastHitByDamageType ) )
{
if(KFPCP.GetPerkClass() == class'KFPerk_Berserker'.static.GetPerkClass())
{
CheckForSmallRadiusKill( MonsterPawn, KFPC, class'KFPerk_Berserker' );
}
else if(KFPCP.GetPerkClass() == class'KFPerk_Survivalist'.static.GetPerkClass())
{
CheckForSmallRadiusKill( MonsterPawn, KFPC, class'KFPerk_Survivalist' );
}
}
KFPCP.AddVampireHealth( KFPC, LastHitByDamageType );
}
}
}
}
else if (class<KFDamageType>(DT) != none && class<KFDamageType>(DT).default.bIsTrapDamage)
{
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.AddZedKill(MonsterPawn.Class, GameDifficulty, DT, false);
}
}
RefreshMonsterAliveCount();
if( SpawnManager != None && MyKFGRI != none )
{
UpdateAIRemaining();
if( !MyKFGRI.IsEndlessWave() && MyKFGRI.AIRemaining <= class'KFGameInfo'.static.GetNumAlwaysRelevantZeds() )
{
//Tell the remaining pawns to set themselves relevant.
foreach WorldInfo.AllPawns( class'KFPawn_Monster', MonsterPawn )
{
MonsterPawn.CheckShouldAlwaysBeRelevant();
}
}
`log("@@@@ ZED COUNT DEBUG: MyKFGRI.AIRemaining =" @ MyKFGRI.AIRemaining, bLogAICount);
`log("@@@@ ZED COUNT DEBUG: AIAliveCount =" @ AIAliveCount, bLogAICount);
}
}
//Non-neutral kill, send through PC for other tracking
else if (KilledPawn != none)
{
if (Killer != none)
{
KFPC = KFPlayerController(Killer);
if (KFPC != none)
{
KFPC.AddNonZedKill(KilledPawn.class, GameDifficulty);
}
}
}
}
event UpdateAIRemaining()
{
if (Role == ROLE_AUTHORITY)
{
if (MyKFGRI != none && SpawnManager != none)
{
RefreshMonsterAliveCount();
MyKFGRI.AIRemaining = Max(0.0f, SpawnManager.WaveTotalAI - NumAIFinishedSpawning) + AIAliveCount;
}
}
}
/** Get Last Damage type current monster was hit by, just in case passed in damage has no definition. */
function class<DamageType> GetLastHitByDamageType(class<DamageType> DT, KFPawn_Monster P, Controller Killer)
{
local class<KFDamageType> RealDT;
// Null check
if( DT == none )
{
return none;
}
RealDT = class<KFDamageType>(DT);
// Some damage types don't have weapon defs (Bleeding) in this case, give the kill to the last damage type done. Make sure we were the last one to hit them with this weapon.
if( RealDT != none )
{
if( RealDT.default.WeaponDef == none && P.HitFxInfo.DamageType != none && Killer == P.LastHitBy )
{
if(!RealDT.default.bAnyPerk && !RealDT.default.bCausedByWorld && RealDT.default.DoT_Type != DOT_Bleeding)
{
`warn("Damage Type "@RealDT.Name@" has not had its weapon definition initialized");
}
RealDT = P.HitFxInfo.DamageType;
}
}
else
{
`warn( "GetLastHitByDamageType() Received non-KFDamageType damagetype:"@DT);
}
return RealDT;
}
function BroadCastLastManStanding()
{
//if multiplayer
BroadcastLocalized(self, class'KFLocalMessage_Priority', GMT_LastPlayerStanding, none );
}
function BroadcastDeathMessage(Controller Killer, Controller Other, class<DamageType> damageType)
{
if( Killer == none )
{
// If a zed died from no killer, it's very likely that they killed themselves. Skip the death message.
if( Other.GetTeamNum() != 255 )
{
// Probably killed by an AI that no longer exists or a player that left the game
BroadcastLocalized(self, class'KFLocalMessage_Game', KMT_Killed, none, Other.PlayerReplicationInfo, damageType );
}
}
else if( Killer == Other )
{
// Suicide
BroadcastLocalized( self, class'KFLocalMessage_Game', KMT_Suicide, none, Other.PlayerReplicationInfo );
}
else
{
// Optimization, try to use IsHumanControlled() before we cast
if( (Killer.Pawn != none && !Killer.Pawn.IsHumanControlled()) || KFAIController(Killer) != none )
{
BroadcastLocalized( self, class'KFLocalMessage_Game', KMT_Killed, none, Other.PlayerReplicationInfo, Killer.Pawn != none ? Killer.Pawn.Class : class'KFPawn_Human' );
}
else
{
BroadcastLocalized( self, class'KFLocalMessage_PlayerKills', KMT_PlayerKillPlayer, Killer.PlayerReplicationInfo, Other.PlayerReplicationInfo );
}
}
if (Other != none && Other.GetTeamNum() != 255)
{
//do timer to double check group didnt get wasted by hans' nade spam or something like that
SetTimer(1.f, false, nameOf(CheckShouldBroadcastLastManStanding));
}
}
function CheckShouldBroadcastLastManStanding()
{
if(WorldInfo.NetMode != NM_Standalone && GetLivingPlayerCount() == 1 && !MyKFGRI.bTraderIsOpen && MyKFGRI.bMatchHasBegun && !MyKFGRI.bMatchIsOver)
{
BroadCastLastManStanding();
`TraderDialogManager.PlayLastManStandingDialog(WorldInfo);
}
}
/**
* @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 );
/**
* @brief Calculates the dosh penalty for the team's jackpot
*
* @param KilledPlayerPRI The killed player's PRI
* @return Rounded amount of dosh to be substracted
*/
function int GetAdjustedTeamDeathPenalty( KFPlayerReplicationInfo KilledPlayerPRI )
{
return Round( KilledPlayerPRI.Score * GameDifficulty * TeamDeathPenaltyPerc );
}
function BossDied(Controller Killer, optional bool bCheckWaveEnded = true)
{
local KFPlayerController KFPC;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC != none)
{
KFPC.ClientOnBossDied();
}
}
}
/************************************************************************************
* @name Scoring/DO$H
***********************************************************************************/
static function int GetBossKillScore()
{
return default.POINTS_FOR_BOSS_KILL;
}
function ScoreDamage( int DamageAmount, int HealthBeforeDamage, Controller InstigatedBy, Pawn DamagedPawn, class<DamageType> damageType )
{
if( InstigatedBy == none
|| InstigatedBy.PlayerReplicationInfo == none
|| InstigatedBy.GetTeamNum() == DamagedPawn.GetTeamNum() )
{
return;
}
if (InstigatedBy.bIsPlayer)
{
DamageAmount = Min(DamageAmount, HealthBeforeDamage);
KFPlayerReplicationInfo(InstigatedBy.PlayerReplicationInfo).DamageDealtOnTeam += DamageAmount;
if (InstigatedBy.Pawn != none)
{
KFPlayerController(InstigatedBy).AddTrackedDamage(DamageAmount, damageType, InstigatedBy.Pawn.Class, DamagedPawn.Class);
}
}
if (OutbreakEvent != none)
{
OutbreakEvent.AdjustScoreDamage(InstigatedBy, DamagedPawn, damageType);
}
}
function ScoreHeal(int HealAmount, int HealthBeforeHeal, Controller InstigatedBy, Pawn HealedPawn, class<DamageType> DamageType)
{
super.ScoreHeal(HealAmount, HealthBeforeHeal, InstigatedBy, HealedPawn, DamageType);
if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.bScaleOnHealth)
{
OutbreakEvent.AdjustPawnScale(HealedPawn);
}
}
function PassiveHeal(int HealAmount, int HealthBeforeHeal, Controller InstigatedBy, Pawn HealedPawn)
{
if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.bScaleOnHealth)
{
OutbreakEvent.AdjustPawnScale(HealedPawn);
}
}
function ScoreKill(Controller Killer, Controller Other)
{
if ( Killer != none && Killer.PlayerReplicationInfo != None && Killer != Other )
{
Killer.PlayerReplicationInfo.Kills++;
Killer.bForceNetUpdate = TRUE;
if ( Other.Pawn.IsA('KFPawn_Monster') )
{
ScoreMonsterKill(Killer, Other, KFPawn_Monster(Other.Pawn));
}
}
if( Killer != none && Other != none && Killer == Other && Other.Pawn.IsA('KFPawn_Monster') )
{
ScoreMonsterKill(Killer, Other, KFPawn_Monster(Other.Pawn));
}
ModifyScoreKill(Killer, Other);
if ( Killer != None || MaxLives > 0 )
{
CheckScore(Killer.PlayerReplicationInfo);
}
if (OutbreakEvent != none && Role == ROLE_Authority && Other != none && Other.Pawn != none)
{
OutbreakEvent.OnScoreKill(Other.Pawn);
}
}
/* A Zed got killed, let's distribute its money money money value */
protected function ScoreMonsterKill( Controller Killer, Controller Monster, KFPawn_Monster MonsterPawn )
{
if( MonsterPawn != none )
{
if( MonsterPawn.DamageHistory.Length > 0 )
{
DistributeMoneyAndXP( MonsterPawn.class, MonsterPawn.DamageHistory, Killer );
if( MonsterPawn.IsStalkerClass() &&
MonsterPawn.LastStoredCC != none &&
MonsterPawn.bIsCloakingSpottedByTeam )
{
AddPlayerXP(MonsterPawn.LastStoredCC, MonsterPawn.static.GetXPValue(GameDifficulty), class'KFPerk_Commando');
}
}
}
if( WorldInfo.NetMode == NM_DedicatedServer && Killer.GetTeamNum() == 0 )
{
`RecordAARZedKill(KFPlayerController(Killer), MonsterPawn.Class, None );
}
}
/** Called from pawn when a rally occurs. Allows gametype to do any global behavior based on this. */
function NotifyRally(KFPawn RalliedPawn)
{
if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.bUseBeefcakeRules)
{
OutbreakEvent.AdjustForBeefcakeRules(RalliedPawn, EBT_Rally);
}
}
/** Called from explosion actor when a scream occurs on an ignored pawn. Allows gametype to do any global behavior based on this. */
function NotifyIgnoredScream(KFPawn ScreamPawn)
{
if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.bUseBeefcakeRules)
{
OutbreakEvent.AdjustForBeefcakeRules(ScreamPawn, EBT_Scream);
}
}
function CheckForSmallRadiusKill(KFPawn_Monster MonsterPawn, KFPlayerController KFPC, class<KFPerk> PerkClass)
{
local KFPawn_Human KFPH;
if( KFPC == none )
{
return;
}
foreach WorldInfo.Allpawns( class'KFPawn_Human', KFPH)
{
if( KFPH != none && KFPH.IsAliveAndWell() && KFPH.Controller != KFPC )
{
if( VSizeSq( KFPH.Location - MonsterPawn.Location ) <= class'KFPerk_Berserker'.static.GetSmallRadiusKillDistanceSQ() )
{
KFPC.AddSmallRadiusKill( GameDifficulty, PerkClass );
break;
}
}
}
}
/**
* @brief Adjusts the dosh you get based on difficulty and player count
*
* @param MonsterClass pawn class of thge killed zed
* @return Adjusted dosh amount
*/
function float GetAdjustedAIDoshValue( class<KFPawn_Monster> MonsterClass )
{
local float TempValue;
if(!ShouldOverrideDoshOnKill(MonsterClass, TempValue))
{
TempValue = MonsterClass.static.GetDoshValue();
}
TempValue *= DifficultyInfo.GetKillCashModifier();
ModifyAIDoshValueForPlayerCount( TempValue );
TempValue *= GameLengthDoshScale[GameLength];
KFMapInfo(WorldInfo.GetMapInfo()).ModifyAIDoshValue(TempValue);
return TempValue;
}
/**
* @brief Adjust monster's dosh value by player count
*
* @param ModifiedValue the amount modified
*/
function ModifyAIDoshValueForPlayerCount( out float ModifiedValue )
{
local float DoshMod;
// Scale dosh by the same rate we scale the zed spawning so we end up
// with the same amount of dosh per player in the world whether it's
// 1 player or 6;
DoshMod = GetNumPlayers() / DifficultyInfo.GetPlayerNumMaxAIModifier(GetNumPlayers());
//`log("Starting Dosh Bounty: "$ModifiedValue$" DoshMod: "$DoshMod);
ModifiedValue *= DoshMod;
//`log("Modified Dosh Bounty: "$ModifiedValue);
}
function int GetNumPlayers()
{
`if(`notdefined(ShippingPC))
if ( ForcedNumLivingPlayers != 0 )
{
return ForcedNumLivingPlayers;
}
`endif
return Super.GetNumPlayers();
}
/** Returns the number of human/mercenary players */
function int GetNumHumanTeamPlayers()
{
local Controller C;
local int HumanTeamPlayers;
foreach WorldInfo.AllControllers( class'Controller', C )
{
if( C.bIsPlayer
&& C.PlayerReplicationInfo != none
&& C.PlayerReplicationInfo.bReadyToPlay
&& !C.PlayerReplicationInfo.bOnlySpectator
&& C.GetTeamNum() == 0 )
{
++HumanTeamPlayers;
}
}
return HumanTeamPlayers;
}
/* Who needs some do$h? Give players money after a Zed was killed based on the percentage of damage they dealt*/
protected function DistributeMoneyAndXP(class<KFPawn_Monster> MonsterClass, const out array<DamageInfo> DamageHistory, Controller Killer)
{
local int i, j, TotalDamage, EarnedDosh;
local float AdjustedAIValue, ScoreDenominator, XP;
local KFPlayerController KFPC;
local KFPlayerReplicationInfo DamagerKFPRI;
local KFPerk InstigatorPerk;
local bool bIsBossKill;
for ( i = 0; i < DamageHistory.Length; i++ )
{
TotalDamage += DamageHistory[i].TotalDamage;
}
if ( TotalDamage <= 0 )
{
`warn("Total damage given to this zed is less or equal zero! This should never happen");
return;
}
bIsBossKill = MonsterClass.static.IsABoss();
// Scale value (via GameInfo) by difficulty and length & player count;
AdjustedAIValue = GetAdjustedAIDoshValue( MonsterClass );
ScoreDenominator = AdjustedAIValue / TotalDamage;
for ( i = 0; i < DamageHistory.Length; i++ )
{
if( DamageHistory[i].DamagerController != none
&& DamageHistory[i].DamagerController.bIsPlayer
&& DamageHistory[i].DamagerPRI.GetTeamNum() == 0
&& DamageHistory[i].DamagerPRI != none )
{
EarnedDosh = Round( DamageHistory[i].TotalDamage * ScoreDenominator );
//`log("SCORING: Player" @ DamageHistory[i].DamagerPRI.PlayerName @ "received" @ EarnedDosh @ "dosh for killing a" @ MonsterClass, bLogScoring);
DamagerKFPRI = KFPlayerReplicationInfo(DamageHistory[i].DamagerPRI);
if( DamagerKFPRI != none )
{
//Killer cannot receive assists.
if( Killer.PlayerReplicationInfo != DamagerKFPRI )
{
DamagerKFPRI.Assists++;
if( DamageHistory[i].DamagePerks.Length == 1 )
{
DamageHistory[i].DamagePerks[0].static.ModifyAssistDosh( EarnedDosh );
}
}
if (bIsBossKill && !bSplitBossDoshReward)
{
DamagerKFPRI.AddDosh(GetAdjustedAIDoshValue(MonsterClass), true);
}
else
{
DamagerKFPRI.AddDosh(EarnedDosh, true);
}
if( DamagerKFPRI.Team != none )
{
//Dosh
if (bIsBossKill && !bSplitBossDoshReward)
{
KFTeamInfo_Human(DamagerKFPRI.Team).AddScore(GetAdjustedAIDoshValue(MonsterClass));
}
else
{
KFTeamInfo_Human(DamagerKFPRI.Team).AddScore(EarnedDosh);
}
if( DamageHistory[i].DamagePerks.Length <= 0 )
{
continue;
}
if (!ValidateForXP(MonsterClass, DamageHistory[i].DamageCausers, DamageHistory[i].DamageTypes))
{
continue;
}
// Distribute experience points
KFPC = KFPlayerController(DamagerKFPRI.Owner);
if( KFPC != none )
{
InstigatorPerk = KFPC.GetPerk();
if( InstigatorPerk.ShouldGetAllTheXP() )
{
AddPlayerXP(KFPC, MonsterClass.static.GetXPValue(GameDifficulty), InstigatorPerk.Class, true);
continue;
}
XP = MonsterClass.static.GetXPValue(GameDifficulty) / DamageHistory[i].DamagePerks.Length;
for( j = 0; j < DamageHistory[i].DamagePerks.Length; j++ )
{
AddPlayerXP(KFPC, FCeil(XP), DamageHistory[i].DamagePerks[j], true);
}
}
}
}
}
}
}
/** Gives us a chance to validate things before calling AddPlayerXP */
native private final function bool ValidateForXP(class<KFPawn_Monster> MonsterClass,
const out array<class<Actor> > DamageCausers, const out array<class<KFDamageType> > DamageTypes);
/** Grant xp rewards */
native private function AddPlayerXP(KFPlayerController PC, INT XP, class<KFPerk> PerkClass, bool bApplyPrestigeBonus = false);
/************************************************************************************
* @name Zed time
***********************************************************************************/
// Called when a dramatic event happens that might cause slomo
// ZedTimeChance - the attempted probability of doing a slomo event
function DramaticEvent(float ZedTimeChance, optional float Duration=3.f)
{
//local float RandChance;
local float TimeSinceLastEvent;
local KFPlayerController KFPC;
// NVCHANGE_BEGIN - RLS - Debugging Effects & Performance
if (bNVBlockDramatic)
return;
// NVCHANGE_END - RLS - Debugging Effects & Performance
TimeSinceLastEvent = `TimeSince(LastZedTimeEvent);
`if(`__TW_SDK_)
if( BaseMutator != none )
{
BaseMutator.ModifyZedTime( TimeSinceLastEvent, ZedTimeChance, Duration );
}
`endif
// Don't go in slomo if we were just IN slomo
if( TimeSinceLastEvent < 10.0 && ZedTimeChance < 1.0 )
{
return;
}
// Increase the chance of zed time if it has been a while
if( TimeSinceLastEvent > 60 )
{
ZedTimeChance *= 4.0;
}
else if( TimeSinceLastEvent > 30 )
{
ZedTimeChance *= 2.0;
}
// Cause zed time if we pass the random chance check
if( FRand() <= ZedTimeChance && Duration > ZedTimeRemaining )
{
ZedTimeRemaining = Duration;
bZedTimeBlendingOut = false;
LastZedTimeEvent = WorldInfo.TimeSeconds;
SetZedTimeDilation(ZedTimeSlomoScale);
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC != none)
{
KFPC.EnterZedTime();
}
}
}
}
function Tick( float DeltaTime )
{
// Handle interpolating back from zed time
if( ZedTimeRemaining > 0.f )
{
TickZedTime(DeltaTime);
}
}
/** While zedtime is active, tick duration down */
function TickZedTime( float DeltaTime )
{
local float TrueTimeFactor;
local KFPlayerController KFPC;
// Get real world DeltaTime (undo zed time dilation)
TrueTimeFactor = 1.0 / (CustomTimeDilation * WorldInfo.TimeDilation);
ZedTimeRemaining -= DeltaTime * TrueTimeFactor;
// Reset zed time when this current zed time period ends
if( ZedTimeRemaining <= 0 )
{
bZedTimeBlendingOut = false;
SetZedTimeDilation(1.0);
ZedTimeExtensionsUsed = 0;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC != none)
{
KFPC.CompleteZedTime();
}
}
}
// Interpolate back from zed time if its almost done
else if ( ZedTimeRemaining < ZedTimeBlendOutTime )
{
if( !bZedTimeBlendingOut )
{
bZedTimeBlendingOut = true;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
if (KFPC != none)
{
KFPC.FadeOutZedTime();
}
}
}
SetZedTimeDilation(Lerp(1.0, ZedTimeSlomoScale, ZedTimeRemaining/ZEDTimeBlendOutTime));
}
}
/** Returns true if zed-time is currently active */
function bool IsZedTimeActive()
{
return ZedTimeRemaining > 0.f;
}
/** Changes the current TimeDilation value for zed-time */
function SetZedTimeDilation(float NewTimeDilation)
{
// Change this to swap between global/local zedtime systems
SetGameSpeed(NewTimeDilation);
}
/** Trigger DramaticEvent/ZedTime when pawn is killed */
function CheckZedTimeOnKill(Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> DamageType)
{
local bool bIsHuman;
local KFPlayerController KFPC;
local KFPerk KillersPerk;
local class<KFDamageType> KFDT;
// Skip if the damagetype is (or can apply) damage over time
KFDT = class<KFDamageType>(DamageType);
if ( KFDT != None && KFDT.default.DoT_Type != DOT_None )
{
return;
}
// If already in zed time, check for zed time extensions
if ( IsZedTimeActive() )
{
KFPC = KFPlayerController(Killer);
if ( KFPC != none )
{
KillersPerk = KFPC.GetPerk();
// Handle if someone has a perk with zed time extensions
if ( ZedTimeRemaining > 0.f && KillersPerk != none && KillersPerk.GetZedTimeExtension( KFPC.GetLevel() ) > ZedTimeExtensionsUsed )
{
// Force Zed Time extension for every kill as long as the Player's Perk has Extensions left
DramaticEvent(1.0);
ZedTimeExtensionsUsed++;
}
}
}
else
{
// NVCHANGE_BEGIN - RLS - Debugging Effects
if (bNVAlwaysDramatic)
DramaticEvent(1.0);
// NVCHANGE_END - RLS - Debugging Effects
// Handle human kills
bIsHuman = KilledPawn.IsA('KFPawn_Human');
if ( bIsHuman )
{
DramaticEvent(0.05f);
return;
}
if( KilledPawn.Controller == none )
{
// don't trigger dramatic event for brain-dead zeds
return;
}
// Handle monster/zed kills - increased probability if closer to the player
if( Killer != none && Killer.Pawn != none && VSizeSq(Killer.Pawn.Location - KilledPawn.Location) < 90000 ) // 3 meters
{
if(OutbreakEvent != none && Role == ROLE_Authority && OutbreakEvent.ActiveEvent.bModifyZedTimeOnANearZedKill)
{
DramaticEvent(OutbreakEvent.ActiveEvent.ZedTimeOnANearZedKill);
}
else
{
DramaticEvent(0.05);
}
}
else
{
DramaticEvent(0.025);
}
}
}
/** Called when pawn is killed by headshot */
function NotifyHeadshotKill(Controller Killer, Pawn KilledPawn)
{
// NVCHANGE_BEGIN - RLS - Debugging Effects
if (bNVAlwaysDramatic)
DramaticEvent(1.0);
// NVCHANGE_END - RLS - Debugging Effects
// increased chance for long range kills
if(Killer != none && Killer.Pawn != none && VSizeSq(Killer.Pawn.Location - KilledPawn.Location) > Square(2500)) // 25 meters
{
DramaticEvent(0.05);
}
else
{
DramaticEvent(0.03);
}
}
/** notification when a NavigationPoint becomes blocked or unblocked */
function NotifyNavigationChanged( NavigationPoint N )
{
local KFAIController AI;
local int Idx;
if( N.bBlocked )
{
foreach WorldInfo.AllControllers( class'KFAIController', AI )
{
`AILog_Ext( GetFuncName()$"() Notifying "$AI$" that navigation has changed for "$N, 'PathWarning', AI );
if( !AI.bMovingToGoal )
{
`AILog_Ext( GetFuncName()$"() ** Skipping notification for "$AI$" because bMovingToGoal was false. bPreparingMove? : "$AI.bPreparingMove, 'PathWarning', AI );
continue;
}
Idx = AI.RouteCache.Find( N );
if( Idx >= 0 )
{
`AILog_Ext( GetFuncName()$"() setting bReEvaluatePath to true for "$AI$" thanks to "$N, 'PathWarning', AI );
AI.bReevaluatePath = true;
AI.MoveTimer = -1.f;
AI.ForcePauseAndRepath();
}
else
{
`AILog_Ext( "** WARNING ** "$GetFuncName()$" for "$N$" not telling "$AI$" to reevaluate path because I couldn't find "$N$" in the routecache! bPreparingMove: "$AI.bPreparingMove, 'PathWarning', AI );
}
}
}
}
/************************************************************************************
* @name Music
***********************************************************************************/
/** Generally how intense the game is currently (MAX RETURN VALUE: 10, in line with TWMusicTrackStruct::MinGameIntensityLevel ClampMax) */
function byte GetGameIntensityForMusic();
/** Whether an action or ambient track should be played */
static function bool ShouldPlayActionMusicTrack(KFGameReplicationInfo GRI);
/** Whether this game mode should play music from the get-go */
static function bool ShouldPlayMusicAtStart();
simulated function ForceMenuMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_MainMenu] );
}
simulated function ForceCreditsMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Credits] );
}
simulated function ForceHansMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Boss1] );
}
simulated function ForcePatriarchMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Boss2] );
}
simulated function ForceMatriarchMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Boss3] );
}
simulated function ForceKingFPMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Boss4] );
}
simulated function ForceAbominationMusicTrack()
{
MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_Boss5] );
}
/*********************************************************************************************
* @name Map rotation
*********************************************************************************************/
/**
* Returns the next map to play.
*/
function string GetNextMap()
{
local array<string> MapList;
local int i;
if ( bUseMapList && GameMapCycles.Length > 0 )
{
if ( MapCycleIndex == INDEX_NONE )
{
MapList = GameMapCycles[ActiveMapCycle].Maps;
MapCycleIndex = GetCurrentMapCycleIndex(MapList);
if ( MapCycleIndex == INDEX_NONE )
{
// Assume current map is actually zero
MapCycleIndex = 0;
}
}
for (i = 0; i < GameMapCycles[ActiveMapCycle].Maps.length; ++i)
{
MapCycleIndex = MapCycleIndex + 1 < GameMapCycles[ActiveMapCycle].Maps.length ? (MapCycleIndex + 1) : 0;
if ( IsMapAllowedInCycle(GameMapCycles[ActiveMapCycle].Maps[MapCycleIndex]) )
{
SaveConfig();
return GameMapCycles[ActiveMapCycle].Maps[MapCycleIndex];
}
}
return string(WorldInfo.GetPackageName());
}
else
{
return string(WorldInfo.GetPackageName());
}
return "";
}
/** @return the index of the current map in the given list (used when starting up a server to start counting at the current map) */
function int GetCurrentMapCycleIndex(const out array<string> MapList)
{
return MapList.Find(string(WorldInfo.GetPackageName()));
}
/*********************************************************************************************
* @name Exec
*********************************************************************************************/
exec function SetFriendlyFireScale(float FFScale)
{
FriendlyFireScale = FClamp(FFScale, 0, 1.f);
SaveConfig();
}
exec function FindCollectibles();
exec function MaintenanceRestart()
{
SetNeedsRestart();
}
// Save player's stats on leave
function UnregisterPlayer(PlayerController PC)
{
local KFPlayerController KFPC;
KFPC = KFPlayerController(PC);
if( KFPC != none )
{
`AnalyticsLog(("player_disconnected",
KFPC.PlayerReplicationInfo,
"#"$MyKFGRI.WaveNum));
KFPC.ClientWriteAndFlushStats();
}
super.UnregisterPlayer(PC);
}
/** AddInactivePRI()
* Add PRI to the inactive list, remove from the active list
* Overrideen to include the time we quit
*/
function AddInactivePRI(PlayerReplicationInfo PRI, PlayerController PC)
{
local KFPlayerReplicationInfo NewKFPRI;
// don't store if it's an old PRI from the previous level or if it's a spectator
if (!PRI.bFromPreviousLevel && !PRI.bOnlySpectator)
{
NewKFPRI = KFPlayerReplicationInfo(PRI);
NewKFPRI.LastQuitTime = WorldInfo.TimeSeconds;
}
super.AddInactivePRI(PRI, PC);
}
/** FindInactivePRI()
* returns the PRI associated with this re-entering player
* Overrideen to increment the number of times we've reconnected to a server
*/
function bool FindInactivePRI(PlayerController PC)
{
local KFPlayerReplicationInfo NewKFPRI;
if( super.FindInactivePRI(PC) )
{
NewKFPRI = KFPlayerReplicationInfo(PC.PlayerReplicationInfo);
NewKFPRI.NumTimesReconnected++;
return true;
}
return false;
}
/************************************************************************************
* @name Reservations
***********************************************************************************/
function bool UniqueIdPresent(out array<UniqueNetId> PlayerIDs, UniqueNetId PlayerID)
{
local UniqueNetId i;
//array Find() won't work, for stupid Unrealscript reasons
foreach PlayerIDs(i)
{
if (i == PlayerID)
{
return true;
}
}
return false;
}
function bool ReservationPresent(UniqueNetId PlayerID)
{
local KFPlayerReservation i;
foreach PlayerReservations(i)
{
if (i.PlayerId == PlayerID)
{
return true;
}
}
return false;
}
function UniqueIdAdd(out array<UniqueNetId> PlayerIDs, UniqueNetId PlayerID)
{
if (!UniqueIdPresent(PlayerIDs, PlayerID))
{
PlayerIDs.AddItem(PlayerID);
}
}
function bool AtCapacity(bool bSpectator, optional const UniqueNetId PlayerID)
{
if (bSpectator)
{
return super.AtCapacity(bSpectator);
}
if ( GetAvailableReservations() <= 0 )
{
return !ReservationPresent(PlayerID);
}
return super.AtCapacity(bSpectator);
}
event MakeReservations(const string URLOptions, const UniqueNetId PlayerId, out string OutError)
{
local array<UniqueNetId> Reservations;
`log("KFGameInfo.MakeReservations", bLogReservations);
ReadReservations(URLOptions, PlayerId, Reservations);
if (Reservations.length > 0)
{
if (!AddPlayerReservations(Reservations))
{
`log("KFGameInfo.MakeReservations failed", bLogReservations);
OutError = "<Strings:"$PathName(WorldInfo.Game.GameMessageClass)$".MaxedOutMessage>";
}
}
}
event PlayerController Login(string Portal, string Options, const UniqueNetID UniqueID, out string ErrorMessage)
{
local PlayerController SpawnedPC;
local string PlayerfabPlayerId;
SeatPlayer(UniqueID);
SpawnedPC = super.Login(Portal, Options, UniqueID, ErrorMessage);
if( PlayfabInter != none && PlayfabInter.IsRegisteredWithPlayfab() && SpawnedPC != None )
{
`log("Player login with options"@Options);
PlayerfabPlayerId = ParseOption( Options, "PlayfabPlayerId" );
SpawnedPC.PlayerReplicationInfo.PlayfabPlayerId = PlayerfabPlayerId;
if( PlayerfabPlayerId != "")
{
PlayfabInter.ServerNotifyPlayerJoined(PlayerfabPlayerId);
}
}
return SpawnedPC;
}
/** Broadcast message when server needs restart/update. Called with commandline -autoupdate. */
event SendServerMaintenanceMessage()
{
BroadcastLocalizedMessage(class'KFLocalMessage', LMT_ServerMaintenance);
}
function UniqueReservationAdd(UniqueNetId PlayerID)
{
local KFPlayerReservation i;
local KFPlayerReservation Reservation;
local bool bFound;
`log("KFGameInfo.UniqueReservationAdd" @ class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerID), bLogReservations);
bFound = false;
foreach PlayerReservations(i)
{
if (i.PlayerId == PlayerID)
{
bFound = true;
break;
}
}
if (!bFound)
{
`log("KFGameInfo.UniqueReservationAdd adding" @ class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerID), bLogReservations);
Reservation.PlayerId = PlayerID;
Reservation.Timer = 0;
PlayerReservations.AddItem(Reservation);
}
}
function RemoveDuplicates(out array<UniqueNetId> PlayerIDs)
{
local int i;
i = 0;
while ( i < PlayerIDs.length )
{
if ( ReservationPresent(PlayerIDs[i]) )
{
PlayerIDs.Remove(i, 1);
}
else
{
++i;
}
}
}
function StripFromMatchmakingGroups(const out UniqueNetId PlayerID)
{
local int GroupIndex, MemberIndex;
`log("StripFromMatchmakingGroups: Checking if player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerID) @ "is already in a group", bLogGroupTeamBalance);
for (GroupIndex = PlayerGroups.length-1; GroupIndex >= 0; --GroupIndex)
{
for (MemberIndex = PlayerGroups[GroupIndex].PlayerGroup.length-1; MemberIndex >= 0; --MemberIndex)
{
if (PlayerGroups[GroupIndex].PlayerGroup[MemberIndex] == PlayerID)
{
`log("StripFromMatchmakingGroups: Removing player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerID) @ "from group" @ GroupIndex, bLogGroupTeamBalance);
PlayerGroups[GroupIndex].PlayerGroup.Remove(MemberIndex, 1);
}
}
if (PlayerGroups[GroupIndex].PlayerGroup.length == 0)
{
`log("StripFromMatchmakingGroups: Removing empty group" @ GroupIndex @ "from group list", bLogGroupTeamBalance);
PlayerGroups.Remove(GroupIndex, 1);
}
}
}
function AddPlayerMatchmakingGroup(const out array<UniqueNetId> PlayerIDs)
{
local UniqueNetId i;
local PlayerGroupStruct TempPlayerGroup;
local int LogIndex;
local UniqueNetId StupidUnrealscriptBS;
if (PlayerIDs.length < 2)
{
`log("AddPlayerMatchmakingGroup: Not creating a group for a single player.");
return;
}
foreach PlayerIDs(i)
{
StripFromMatchmakingGroups(i);
}
TempPlayerGroup.PlayerGroup = PlayerIDs;
PlayerGroups.Add(1);
PlayerGroups[PlayerGroups.length-1] = TempPlayerGroup;
if (bLogGroupTeamBalance)
{
`log("AddPlayerMatchmakingGroup: Adding new matchmaking group at position" @ PlayerGroups.length-1 @ "with members:");
for (LogIndex = 0; LogIndex < PlayerGroups[PlayerGroups.length-1].PlayerGroup.length; ++LogIndex)
{
StupidUnrealscriptBS = PlayerGroups[PlayerGroups.length-1].PlayerGroup[LogIndex];
`log(" "$class'OnlineSubsystem'.static.UniqueNetIdToString(StupidUnrealscriptBS));
}
}
}
function bool AddPlayerReservations(out array<UniqueNetId> PlayerIDs)
{
local UniqueNetId PlayerID;
local int OldCount;
RemoveDuplicates(PlayerIDs);
if (PlayerIDs.length == 0)
{
return true;
}
if (PlayerIDs.length > GetAvailableReservations())
{
return false;
}
AddPlayerMatchmakingGroup(PlayerIDs);
OldCount = PlayerReservations.length;
foreach PlayerIDs(PlayerID)
{
UniqueReservationAdd(PlayerID);
}
if (OldCount == 0 && PlayerReservations.length > 0)
{
SetTimer( 1.f, true, nameOf(TimeReservations) );
}
return true;
}
function TimeReservations()
{
local int i;
local UniqueNetId stupid;
i = 0;
while ( i < PlayerReservations.length )
{
stupid=PlayerReservations[i].PlayerId;
//`log("KFGameInfo.TimeReservations" @ i @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid) @ "timer is" @ PlayerReservations[i].Timer, bLogReservations);
if (PlayerReservations[i].Timer > ReservationTimeout)
{
`log("KFGameInfo.TimeReservations expiring player" @ i @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid), bLogReservations);
PlayerReservations.Remove(i, 1);
`log("TimeReservations: expiring player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid), bLogGroupTeamBalance);
StripFromMatchmakingGroups(stupid);
}
else
{
if (PlayerReservations[i].Timer >= 0)
{
++PlayerReservations[i].Timer;
if (bLogReservations)
{
`log("KFGameInfo.TimeReservations player" @ i @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid)@"="@string(PlayerReservations[i].Timer), bLogReservations);
}
}
++i;
}
}
if (PlayerReservations.length == 0)
{
ClearTimer( nameOf(TimeReservations) );
}
}
function ReadReservations(const string URLOptions, const UniqueNetId PlayerId, out array<UniqueNetId> Reservations)
{
local string OptionsCopy, Option, Key, Value;
local UniqueNetId Id;
`log("KFGameInfo.ReadReservations reading reservations from"@URLOptions, bLogReservations);
OptionsCopy = URLOptions;
Reservations.Remove(0, Reservations.length);
while ( GrabOption(OptionsCopy, Option) && Reservations.length < 5 )
{
GetKeyValue(Option, Key, Value);
if ( Len(Key) == 6 && Left(Key,5) ~= "party")
{
if ( class'OnlineSubsystem'.static.StringToUniqueNetId(Value, Id) )
{
`log("KFGameInfo.ReadReservations for player" @ Key, bLogReservations);
UniqueIdAdd(Reservations, Id);
}
}
else if (Key ~= "SpectatorOnly" && Value ~= "1")
{
//Make no reservation if we're spectating
`log("KFGameInfo.ReadReservations found" @ Key $ "=" $ Value @"in URL, making no reservations", bLogReservations);
Reservations.Remove(0, Reservations.length);
return;
}
}
`log("KFGameInfo.ReadReservations for player" @ class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerId), bLogReservations);
UniqueIdAdd(Reservations, PlayerId);
}
event bool ConfirmReservation(const UniqueNetId PlayerID)
{
local KFPlayerReservation i;
local UniqueNetId stupid;
local int index;
`log("KFGameInfo.ConfirmReservation", bLogReservations);
foreach PlayerReservations(i, index)
{
if (i.PlayerId == PlayerID)
{
stupid=i.PlayerId;
if (bLogReservations)
{
`log("KFGameInfo.ConfirmReservation for player"@ index @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid), bLogReservations);
}
PlayerReservations[index].Timer = -1;
`log("KFGameInfo.TimeReservations" @ index @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid) @ "timer is" @ string(PlayerReservations[index].Timer), bLogReservations);
return true;
}
}
return false;
}
event bool SeatPlayer(const UniqueNetId SeatedPlayerID)
{
local int i;
local UniqueNetId stupid;
`log("KFGameInfo.SeatPlayer", bLogReservations);
for (i = 0; i < PlayerReservations.length; ++i )
{
if (PlayerReservations[i].PlayerId == SeatedPlayerID)
{
if (bLogReservations)
{
stupid=PlayerReservations[i].PlayerId;
`log("KFGameInfo.SeatPlayer for player" @ i @ class'OnlineSubsystem'.static.UniqueNetIdToString(stupid), bLogReservations);
}
PlayerReservations.Remove(i, 1);
return true;
}
}
return false;
}
function int GetAvailableReservations()
{
return Maxplayers - GetNumPlayers() - PlayerReservations.length;
}
/*********************************************************************************************
* @name Debugging
*********************************************************************************************/
static native function bool AllowBalanceLogging();
static native function bool AllowAnalyticsLogging();
/** logs what each player is carrying */
function LogPlayersInventory()
{
local PlayerController PC;
local Inventory Item;
local string InvString;
if ( !AllowBalanceLogging() )
{
return;
}
ForEach WorldInfo.AllControllers(class'PlayerController', PC)
{
if ( PC.Pawn != None && PC.Pawn.InvManager != None )
{
InvString = "";
for (Item = PC.Pawn.InvManager.InventoryChain; Item != None; Item = Item.Inventory)
{
InvString $= Item.Class.Name $ ",";
}
`BalanceLog(GBE_Inventory, PC.PlayerReplicationInfo, InvString);
// @note: for `AnalyticsLog see "pc_wave_start"
}
}
}
/** logs how much dosh each player is carrying */
function LogPlayersDosh(name EventName)
{
local KFPlayerController PC;
if ( !AllowBalanceLogging() )
{
return;
}
ForEach WorldInfo.AllControllers(class'KFPlayerController', PC)
{
if ( PC.PlayerReplicationInfo != None && !PC.PlayerReplicationInfo.bIsSpectator )
{
`BalanceLog(EventName, PC.PlayerReplicationInfo, "$"$PC.PlayerReplicationInfo.Score$","$PC.GetPerk());
// @note: for `AnalyticsLog see "pc_wave_start"
}
}
}
/** logs how many total kills and deaths each player has at end of match */
function LogPlayersKillCount()
{
local KFPlayerController PC;
local PlayerReplicationInfo PRI;
if ( !AllowBalanceLogging() )
{
return;
}
ForEach WorldInfo.AllControllers(class'KFPlayerController', PC)
{
PRI = PC.PlayerReplicationInfo;
if ( PRI != None && !PRI.bIsSpectator )
{
`BalanceLog(GBE_Kills, PRI, PRI.Kills$","$PC.GetPerk());
`BalanceLog(GBE_Deaths, PRI, PRI.Deaths$","$PC.GetPerk());
// @note: for `AnalyticsLog see "player_death" and "pc_wave_end"
}
}
}
/*********************************************************************************************
* state PendingMatch
* This is the initial state for a kf game type.
*********************************************************************************************/
auto State PendingMatch
{
// REMOVEMESOON
function BeginState(Name PreviousStateName)
{
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ZombieServerLog("KFGameInfo:PendingMatch.BeginState - PreviousStateName: "$PreviousStateName);
}
}
// END REMOVEMESOON
event Timer()
{
global.Timer();
if (bDelayedStart)
{
if (ShouldStartMatch())
{
if (!IsTimerActive(nameof(LobbyCountdownComplete)))
{
if (CheckAllPlayersReady())
{
// Everyone is ready, start the final countdown
SetCountdown(true, GameStartDelay);
}
else if (MajorityPlayersReady())
{
// Majority of players are ready, start the lobby countdown
MyKFGRI.RemainingTime = ReadyUpDelay;
MyKFGRI.bStopCountDown = false;
SetCountdown(false, MyKFGRI.RemainingTime);
}
}
else
{
if (!MajorityPlayersReady())
{
// No more majority, stop the lobby countdown
ClearTimer(nameof(LobbyCountdownComplete));
MyKFGRI.bStopCountDown = true;
ResetCountDown();
}
else
{
if (MyKFGRI.RemainingTime <= GameStartDelay)
{
if (!bStartFinalCount)
{
// Lobby countdown is almost up, start the final countdown.
// Nothing can stop us now.
SetCountdown(true, GameStartDelay);
}
}
else
{
if (CheckAllPlayersReady())
{
if (!bStartFinalCount)
{
// Everyone is ready, start the final countdown
SetCountdown(true, GameStartDelay);
}
}
else
{
if (bStartFinalCount)
{
// Not everyone is ready, resume lobby countdown
// (unless the lobby countdown is almost up)
SetCountdown(false, MyKFGRI.RemainingTime);
}
}
}
}
}
}
else
{
if (IsTimerActive(nameof(LobbyCountdownComplete)))
{
ClearTimer(nameof(LobbyCountdownComplete));
MyKFGRI.bStopCountDown = true;
bStartFinalCount = false;
}
}
// If this is a dedicated server locked for use by players,
// check if anyone is connected after ready-up time expires,
// and unlock the server if no one is has connected
if (!IsTimerActive(nameof(CheckServerUnlock)))
{
SetTimer(ReadyUpDelay, false, nameof(CheckServerUnlock));
}
}
}
}
function SetCountdown(bool bFinalCountdown, byte CountdownTime)
{
local KFPlayerController PC;
local byte TeamNum;
bStartFinalCount = bFinalCountdown;
SetTimer(CountdownTime, false, nameof(LobbyCountdownComplete));
foreach WorldInfo.AllControllers(class'KFPlayerController', PC)
{
if ( bFinalCountdown )
{
// If spawn as been invalidated (e.g. kismet, team swap) choose again now,
// so that the client can stream textures at the correct location
TeamNum = PC.GetTeamNum();
if ( TeamNum == 0 && !ShouldSpawnAtStartSpot(PC) )
{
// clear old StartSpot so it's not re-evaluated by FindPlayerStart()
PC.StartSpot = None;
PC.StartSpot = FindPlayerStart(PC, TeamNum);
}
PC.ClientSetCountdown(bFinalCountdown, CountdownTime, PC.StartSpot);
}
else
{
PC.ClientSetCountdown(bFinalCountdown, CountdownTime);
}
}
}
/**
* @brief Stops the countdown when no player is readied up anymore
*/
function ResetCountDown()
{
local KFPlayerController KFPC;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.ClientResetCountdown();
}
}
function LobbyCountdownComplete()
{
local KFPlayerController KFPC;
foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC)
{
KFPC.PlayerReplicationInfo.bReadyToPlay = true;
}
StartMatch();
}
private function CheckServerUnlock()
{
local bool bWasAvailableForTakeover;
local KFGameEngine KFEngine;
local PlayfabInterface Playfab;
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo.CheckServerUnlock 1 - GetNumPlayers(): "$GetNumPlayers());
}
if ( GetNumPlayers() == 0 )
{
KFEngine = KFGameEngine(class'Engine'.static.GetEngine());
Playfab = KFEngine.GetPlayfabInterface();
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo.CheckServerUnlock 2 - Playfab: "$Playfab);
}
if( Playfab != none )
{
Playfab.ServerDeallocate();
}
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo.CheckServerUnlock 3 - IsLockedServer(): "$KFEngine.IsLockedServer()$" (bUsedForTakeover: "$KFEngine.bUsedForTakeover$", bAvailableForTakeover: "$KFEngine.bAvailableForTakeover$")");
}
// Won't unlock a server that's not lockable
if( KFEngine.IsLockedServer() )
{
bWasAvailableForTakeover = KFEngine.bAvailableForTakeover;
KFEngine.UnlockServer();
if (WorldInfo.NetMode == NM_DedicatedServer)
{
//`REMOVEMESOON_ServerTakeoverLog("KFGameInfo.CheckServerUnlock 4 - bWasAvailableForTakeover: "$bWasAvailableForTakeover$"; bAvailableForTakeover: "$KFEngine.bAvailableForTakeover);
}
if (!bWasAvailableForTakeover && KFEngine.bAvailableForTakeover)
{
AccessControl.SetGamePassword("");
StripPasswordFromLastURL(KFEngine);
}
// If the status changed, update game settings immediately
if( bWasAvailableForTakeover != KFEngine.bAvailableForTakeover )
{
UpdateGameSettings();
}
}
}
}
private native final function StripPasswordFromLastURL(KFGameEngine Engine);
function StartMatch()
{
// Clear bWaitingToStartMatch before super -- see CheckSpawnProximity
bWaitingToStartMatch = false;
super.StartMatch();
bDelayedStart = false;
MyKFGRI.RemainingTime = 0;
MyKFGRI.bStopCountDown = true;
ClearTimer(nameof(StartMatch));
ClearTimer(nameof(LobbyCountdownComplete));
if ( GameplayEventsWriter != None )
{
GameplayEventsWriter.StartLogging(0.0f);
}
}
function bool ShouldStartMatch()
{
if ( bWaitForNetPlayers && WorldInfo.NetMode != NM_Standalone )
{
return NumPlayers >= MinNetPlayers;
}
return true;
}
function bool CheckAllPlayersReady()
{
local int i;
local array<KFPlayerReplicationInfo> KFPRIArray;
MyKFGRI.GetKFPRIArray(KFPRIArray);
for ( i = 0; i < KFPRIArray.Length; i++ )
{
if ( !IsPlayerReady( KFPRIArray[i] ) )
{
return false;
}
}
return true;
}
/**
* @brief Checks if majority of players readied up yet
* @return true if a majority of the players have readied up
*/
function bool MajorityPlayersReady()
{
local int i, readyPlayers;
local array<KFPlayerReplicationInfo> KFPRIArray;
local float readyPercent;
MyKFGRI.GetKFPRIArray(KFPRIArray);
if( KFPRIArray.Length == 0 )
{
return false;
}
for( i = 0; i < KFPRIArray.Length; i++ )
{
if ( IsPlayerReady( KFPRIArray[i] ) )
{
readyPlayers++;
}
}
if( readyPlayers == 0 )
{
return false;
}
readyPercent = float(readyPlayers) / float(KFPRIArray.length);
return readyPercent >= 0.5f;
}
/**
* @brief Checks if any player has readied up yet
* @return true if at least one player has readied up
*/
function bool AnyPlayerReady()
{
local int i;
local array<KFPlayerReplicationInfo> KFPRIArray;
MyKFGRI.GetKFPRIArray(KFPRIArray);
for( i = 0; i < KFPRIArray.Length; i++ )
{
if ( IsPlayerReady( KFPRIArray[i] ) )
{
return true;
}
}
return false;
}
function bool IsPlayerReady( KFPlayerReplicationInfo PRI )
{
return PRI.bReadyToPlay;
}
function UpdateCurrentMapVoteTime(byte NewTime, optional bool bStartTime);
function float GetTraderTime()
{
local float MapOverride;
MapOverride = DifficultyInfo.GetTraderTimeByMap(WorldInfo.GetMapName(true));
if (MapOverride > 0.f)
{
return MapOverride;
}
return DifficultyInfo.GetTraderTimeByDifficulty();
}
function SkipTrader(int TimeAfterSkipTrader);
function bool ShouldOverrideDoshOnKill(class<KFPawn_Monster> KilledPawn, out float DoshGiven)
{
return false;
}
/**
* Checks if the next map exists.
* Network: Dedicated Server Only
* @param NextMap The map name to check
* @return NextMap if it exists, a default map name otherwise.
*/
native function string CheckNextMap(string NextMap);
/*********************************************************************************************
* @name AI
*********************************************************************************************/
function OnAIChangeEnemy( BaseAIController AI, Pawn Enemy )
{
`if(`__TW_SDK_)
if( BaseMutator != none )
{
BaseMutator.ModifyAIEnemy( AI, Enemy );
}
`endif
}
/************************************************************************************
* @name Objectives
***********************************************************************************/
function OnEndlessSpawningObjectiveDeactivated();
static function bool HasCustomTraderVoiceGroup()
{
return false;
}
defaultproperties
{
/** Scoring */
POINTS_FOR_BOSS_KILL=10000
POINTS_FOR_WAVE_COMPLETION=1000
POINTS_PENALTY_FOR_DEATH=100
bTeamGame=true
bCanPerkAlwaysChange=true
bRequiresPushToTalk=true
bPauseable=false
ZedTimeSlomoScale=0.2
ZedTimeBlendOutTime=0.5
BossIndex=-1
DeathPenaltyModifiers(0)=0.05
DeathPenaltyModifiers(1)=0.1f
DeathPenaltyModifiers(2)=0.2f
DeathPenaltyModifiers(3)=0.25
GameLengthDoshScale(0)=1.75 // Short
GameLengthDoshScale(1)=1.0 // Medium
GameLengthDoshScale(2)=0.8 // Long
XPMultiplier=1.0
AccessControlClass=class'KFGame.KFAccessControl'
OnlineGameSettingsClass=class'KFGame.KFOnlineGameSettings'
PlayerControllerClass=class'KFGame.KFPlayerController'
PlayerReplicationInfoClass=class'KFGame.KFPlayerReplicationInfo'
GameReplicationInfoClass=class'KFGame.KFGameReplicationInfo'
DefaultPawnClass=class'KFGame.KFPawn_Human'
CustomizationPawnClass=class'KFGame.KFPawn_Customization'
PopulationManagerClass=class'GameFramework.GameCrowdPopulationManager'
DialogManagerClass=class'KFGame.KFDialogManager'
HUDType=class'KFGame.KFGFXHudWrapper'
KFGFxManagerClass=class'KFGame.KFGFxMoviePlayer_Manager'
GameConductorClass=class'KFGame.KFGameConductor'
DifficultyInfoClass=class'KFGameDifficultyInfo'
bRestartLevel=false
bWaitingToStartMatch=true
bDelayedStart=true
ActionMusicDelay=5.0
ForcedMusicTracks(0)=KFMusicTrackInfo'WW_MMNU_Login.TrackInfo' // menu
ForcedMusicTracks(1)=KFMusicTrackInfo'WW_MMNU_Login.TrackInfo' // credits
ForcedMusicTracks(2)=KFMusicTrackInfo'WW_MACT_Default.TI_SH_Boss_DieVolter' // hans
ForcedMusicTracks(3)=KFMusicTrackInfo'WW_MACT_Default.TI_Boss_Patriarch' // patriarch
ForcedMusicTracks(4)=KFMusicTrackInfo'WW_MACT_Default.TI_Boss_Matriarch' // matriarch
ForcedMusicTracks(5)=KFMusicTrackInfo'WW_MACT_Default.TI_RG_KingFP' // king fp
ForcedMusicTracks(6)=KFMusicTrackInfo'WW_MACT_Default.TI_RG_Abomination' // abomination
ReservationTimeout=32
bLogReservations=true
MaxPlayersAllowed=`KF_MAX_PLAYERS
NumAlwaysRelevantZeds=3
ReconnectRespawnTime=30
bLogAnalytics=false
bLogGroupTeamBalance=true
// NVCHANGE_BEGIN - RLS - Debugging Effects
bNVAlwaysDramatic=false
bNVBlockDramatic=false
bNVAlwaysHeadshot=false
bNVDebugDamage=false
// NVCHANGE_END - RLS - Debugging Effects
bSplitBossDoshReward=true
DebugForcedOutbreakIdx=INDEX_NONE
DebugForceSpecialWaveZedType=INDEX_NONE
}