//============================================================================= // 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 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 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 arrayGameModes; // 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 DifficultyInfoClass; var class DifficultyInfoConsoleClass; /** Trader */ var array TraderList; var transient KFTraderTrigger ScriptedTrader; /** Pickups */ var array ItemPickups; var array AmmoPickups; /** Keep track off all pickup factories so we don't have to use all actors */ var array 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 DeathPenaltyModifiers; /** Dosh Penalty for dying mid wave by difficulty*/ var array 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 GameLengthDoshScale; /** Available spawn managers (game length) */ var array< class > 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 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 > AIClassList; var protected const array< class > NonSpawnAIClassList; var protected const array< class > AIBossClassList; var protected const array< class > 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 var int AllowSeasonalSkinsIndex; var int WeeklySelectorIndex; /** 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 > 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 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 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 Maps; // var array RoundLimits; }; var globalconfig array 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 InValidMaps; /************************************************************************************ * @name Dialog ***********************************************************************************/ var KFDialogManager DialogManager; var class DialogManagerClass; var class 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, EFM_WWL }; /** 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 PlayerReservations; var int ReservationTimeout; struct native PlayerGroupStruct { var byte Team; var array PlayerGroup; structdefaultproperties { Team=128 } }; var transient array 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 OutbreakEventClass; /************************************************************************************ * @name Kismet Monster Properties ***********************************************************************************/ enum EMonsterProperties { EMonsterProperties_Enraged, EMonsterProperties_Sprinting }; var int SpawnedMonsterProperties[EMonsterProperties]; /** Boss Rush */ var transient array< byte > BossRushEnemies; // Maps to skip const MapBiolapse = 'KF-Biolapse'; const MapNightmare = 'KF-Nightmare'; const MapPowerCore = 'KF-PowerCore_Holdout'; const MapDescent = 'KF-TheDescent'; const MapKrampus = 'KF-KrampusLair'; const MapSantas = 'KF-SantasWorkshop'; const MapSteam = 'KF-SteamFortress'; const MapElysium = 'KF-Elysium'; /************************************************************************************ * @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 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 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 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 ) { local string OptionRead; OptionRead = ParseOption(Options, "WeeklySelectorIndex"); if (OptionRead != "") { WeeklySelectorIndex = int(OptionRead); } else // If doesn't exist on the Options we default it.. { WeeklySelectorIndex = -1; } if (WeeklySelectorIndex > 0) { // 0 is default, we move one index to the left so it matches first Weekly Mode KFGameEngine(class'Engine'.static.GetEngine()).SetWeeklyEventIndex(WeeklySelectorIndex - 1); } else if (KFGameEngine(class'Engine'.static.GetEngine()).GetIsForceWeeklyEvent() == false) { // If we didn't force via cmd line and we ask for default, force the intended to make sure we are updated KFGameEngine(class'Engine'.static.GetEngine()).SetWeeklyEventIndex(KFGameEngine(class'Engine'.static.GetEngine()).GetIntendedWeeklyEventIndex()); } `Log("TEST - InitGame : " $WeeklySelectorIndex); 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); } OptionRead = ParseOption(Options, "AllowSeasonalSkins"); if (OptionRead != "") { if (int(OptionRead) == 0) { AllowSeasonalSkinsIndex = 1; // disable.. } else { AllowSeasonalSkinsIndex = 0; // enable.. } } else // If doesn't exist on the Options we default it.. { AllowSeasonalSkinsIndex = 0; } 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); } function UpdateGameSettings() { local name SessionName; local KFOnlineGameSettings KFGameSettings; super.UpdateGameSettings(); if (WorldInfo.NetMode == NM_DedicatedServer || WorldInfo.NetMode == NM_ListenServer) { if (GameInterface != None) { SessionName = PlayerReplicationInfoClass.default.SessionName; if (PlayfabInter != none && PlayfabInter.GetGameSettings() != none) { KFGameSettings = KFOnlineGameSettings(PlayfabInter.GetGameSettings()); } else { KFGameSettings = KFOnlineGameSettings(GameInterface.GetGameSettings(SessionName)); } if (KFGameSettings != none) { KFGameSettings.bNoSeasonalSkins = AllowSeasonalSkinsIndex == 1; KFGameSettings.WeeklySelectorIndex = WeeklySelectorIndex; } } } } //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, DesiredWeeklySelectorIndex; // 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 = ""; 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 = ""; 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 = ""; 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; } DesiredWeeklySelectorIndex = ParseOption(Options, "WeeklySelectorIndex"); if( DesiredWeeklySelectorIndex != "" && int(DesiredWeeklySelectorIndex) != WeeklySelectorIndex) { `log("Got bad weekly selector index"@DesiredWeeklySelectorIndex@"expected"@WeeklySelectorIndex); ErrorMessage = "Server No longer available. Mismatch DesiredWeeklySelectorIndex."; 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 ) { KFPC.UpdatePerkOnInit(); KFPC.InitGameplayPostProcessFX(); 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; MyKFGRI.NotifyAllowSeasonalSkins(AllowSeasonalSkinsIndex); MyKFGRI.NotifyWeeklySelector(WeeklySelectorIndex); `Log("TEST - InitGRIVariables- NotifyWeeklySelector : " $WeeklySelectorIndex); 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] ); } ResetPickups( ItemPickups, NumWeaponPickups ); ResetPickups( AmmoPickups, NumAmmoPickups ); } /** Pick random pickup items to enable and put all others to sleep */ /** Pick random pickup items to enable and put all others to sleep */ function ResetPickups( array PickupList, int NumPickups ) { local byte i, ChosenIndex; local array PossiblePickups; local int NumIterations; if (PickupList.Length == 0) return; PossiblePickups = PickupList; NumIterations = Min(NumPickups, PickupList.Length - 1); for ( i = 0; i < NumIterations; 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 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 PickupList, KFPickupFactory LastPickup ) { local byte i; local array 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 ) { if (NextFactory.CanUsePickup()) { NextFactory.bToBeActivated = true; NextFactory.SetTimer( RespawnDelay, , nameof(NextFactory.Reset) ); } else { NextFactory.SetPickupHidden(); } } } /************************************************************************************ * @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 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 ); } } } function ResetGunGame(KFPlayerController_WeeklySurvival KFPC_WS) { } function RestartGunGamePlayerWeapon(KFPlayerController_WeeklySurvival KFPC_WS, byte WaveToUse) { } /** 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) { if (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 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 GetBossAISpawnType() { return GetSpecificBossClass(BossIndex, KFMapInfo(WorldInfo.GetMapInfo())); } static function class GetSpecificBossClass(int Index, KFMapInfo MapInfo = none) { local array< class > 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) { if (OutbreakEvent.ActiveEvent.SpawnRateMultiplier > 0.0f) { SpawnRateMod *= 1.f / OutbreakEvent.ActiveEvent.SpawnRateMultiplier; } else { SpawnRateMod = 0.0f; } } 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 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, Actor DamageCauser, TraceHitInfo HitInfo) { local class KFDT; KFDT = class(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, vector Momentum, Actor DamageCauser); function Killed(Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class DT) { local KFPlayerReplicationInfo KilledPRI; local KFPlayerController KFPC; local int PlayerScoreDelta, TeamPenalty; local KFPerk KFPCP; local KFPawn_Monster MonsterPawn; local string KillerLabel; local class 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 ); if (LastHitByDamageType != none) { //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(DT) != none && class(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 GetLastHitByDamageType(class DT, KFPawn_Monster P, Controller Killer) { local class RealDT; // Null check if( DT == none ) { return none; } RealDT = class(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) { 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 ) { 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) { 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 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 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 MonsterClass, const out array 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 (MyKFGRI.IsBountyHunt() == false) { if (bIsBossKill && !bSplitBossDoshReward) { DamagerKFPRI.AddDosh(GetAdjustedAIDoshValue(MonsterClass), true); } else { DamagerKFPRI.AddDosh(EarnedDosh, true); } } if( DamagerKFPRI.Team != none ) { //Dosh if (MyKFGRI.IsBountyHunt() == false) { 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 MonsterClass, const out array > DamageCausers, const out array > DamageTypes); /** Grant xp rewards */ native private function AddPlayerXP(KFPlayerController PC, INT XP, class 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) { local bool bIsHuman; local KFPlayerController KFPC; local KFPerk KillersPerk; local class KFDT; // Skip if the damagetype is (or can apply) damage over time KFDT = class(DamageType); if ( KFDT != None && KFDT.default.DoT_Type != DOT_None ) { return; } // Skip for certain damage types we want to discard if (KFDT != none && KFDT.default.bCanZedTime == false) { 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] ); } simulated function ForceWWLMusicTrack() { MyKFGRI.ForceNewMusicTrack( default.ForcedMusicTracks[EFM_WWL] ); } function bool IsWeekly() { return MyKFGRI.IsA('KFGameReplicationInfo_WeeklySurvival'); } /********************************************************************************************* * @name Map rotation *********************************************************************************************/ /** * Returns the next map to play. */ function string GetNextMap() { local array MapList; local int i; local name MapName; 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; /** This could be changed to read replicated values instead of engine ones. */ if (IsWeekly()) { MapName = name(GameMapCycles[ActiveMapCycle].Maps[MapCycleIndex]); if ((class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 0 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[0]) || // Boom (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 1 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[1]) || // Cranium Cracker (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 2 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[2]) || // Tiny Terror (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 3 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[3]) || // Bobble Zed (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 4 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[4]) || // Poundemonium (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 5 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[5]) || // Up Up and Decay (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 6 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[6]) || // Zed Time (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 7 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[7]) || // Beefcake (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 8 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[8]) || // Bloodthirst (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 9 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[9]) || // Coliseum (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 10 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[10]) || // Arachnophobia (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 11 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[11]) || // Scavenger (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 12 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[12]) || // WW (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 13 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[13]) || // Abandon (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 15 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[15]) || // Shrunken Heads (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 18 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[18])) // Perk Roulette { if (MapName == MapSantas) { continue; } } if ((class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 11 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[11]) || // Scavenger (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 14 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[14]) || // Boss Rush (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 16 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[16])) // Gun Game { if (MapName == MapBiolapse || MapName == MapNightmare || MapName == MapPowerCore || MapName == MapDescent || MapName == MapKrampus) { continue; } } if (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 19 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[19]) // Contamination { if (MapName == MapBiolapse || MapName == MapNightmare || MapName == MapPowerCore || MapName == MapDescent || MapName == MapKrampus || MapName == MapElysium || MapName == MapSantas) { continue; } } if (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 20 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[20]) // Bounty Hunt { if (MapName == MapBiolapse || MapName == MapNightmare || MapName == MapPowerCore || MapName == MapDescent || MapName == MapKrampus || MapName == MapElysium || MapName == MapSteam) { continue; } } if (class'KFGameEngine'.static.GetWeeklyEventIndexMod() == 14 || OutbreakEvent.ActiveEvent == OutbreakEvent.SetEvents[14]) { if (MapName == MapSteam) { continue; } } } 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 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 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 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 Reservations; `log("KFGameInfo.MakeReservations", bLogReservations); ReadReservations(URLOptions, PlayerId, Reservations); if (Reservations.length > 0) { if (!AddPlayerReservations(Reservations)) { `log("KFGameInfo.MakeReservations failed", bLogReservations); OutError = ""; } } } 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 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 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 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); `log("KFGameInfo.TimeReservations Timeout reservations: " $ReservationTimeout); } } ++i; } } if (PlayerReservations.length == 0) { ClearTimer( nameOf(TimeReservations) ); } } function ReadReservations(const string URLOptions, const UniqueNetId PlayerId, out array 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 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 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 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; local float TraderTimeModifier; TraderTimeModifier = 1.f; if (OutbreakEvent != none && OutbreakEvent.ActiveEvent.TraderTimeModifier != 1.f) { TraderTimeModifier = OutbreakEvent.ActiveEvent.TraderTimeModifier; } MapOverride = DifficultyInfo.GetTraderTimeByMap(WorldInfo.GetMapName(true)); if (MapOverride > 0.f) { return MapOverride * TraderTimeModifier; } return DifficultyInfo.GetTraderTimeByDifficulty() * TraderTimeModifier; } function SkipTrader(int TimeAfterSkipTrader); function ResumeEndlessGame() { local KFPawn_Human KFPH; foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH ) { if( KFPH.GetTeamNum() != 0 ) { KFPH.bCanPickupInventory = true; } } WorldInfo.bPlayersOnly = false; WorldInfo.bPlayersOnlyPending = false; } function PauseEndlessGame() { local KFPawn_Human KFPH; foreach WorldInfo.AllPawns( class'KFPawn_Human', KFPH ) { if( KFPH.GetTeamNum() != 0 ) { KFPH.bCanPickupInventory = false; } } // Pause game but allow players to move around. WorldInfo.bPlayersOnlyPending = true; } function bool ShouldOverrideDoshOnKill(class 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; } /*********************************************** * @name Initial loadout modifier **********************************************/ simulated function AddWeaponsFromSpawnList(KFPawn P); simulated function OverrideHumanDefaults(KFPawn_Human P); /*********************************************** * @name Helper for halloween 2022 barmwich bonfire seasonal objective **********************************************/ function ClearActorFromBonfire(Actor Other); /*********************************************** * @name Damage Modifier for Event **********************************************/ simulated function ModifyDamageGiven(out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class DamageType, optional int HitZoneIdx ); /*********************************************** * @name Notify a player is initialized **********************************************/ simulated function NotifyPlayerStatsInitialized(KFPlayerController_WeeklySurvival KFPC){} simulated function int WeeklyExtraNumberOfZeds() { return 0; } simulated function int WeeklyCurrentExtraNumberOfZeds() { return 0; } 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 AllowSeasonalSkinsIndex=0 WeeklySelectorIndex=-1 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 ForcedMusicTracks(7)=KFMusicTrackInfo'WW_MACT_Default.TI_WWL' // WWL Weekly 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 }