//============================================================================= // KFGameInfo_WeeklySurvival //============================================================================= // Weekly variant of survival with runtime adjusted rule sets. //============================================================================= // Killing Floor 2 // Copyright (C) 2017 Tripwire Interactive LLC // - Dan Weiss //============================================================================= class KFGameInfo_WeeklySurvival extends KFGameInfo_Survival; /** Current frame booms */ var int CurrentFrameBooms; /** Index of event to use as the default block */ var int ActiveEventIdx; /** List with perks in random order */ var array PerkRouletteRandomList; var int PerkRouletteRandomInitialIndex; var int PerkRouletteRandomWaveNum; struct PerkRoulette_PlayerMessageDelegate { var() KFPlayerController_WeeklySurvival KFPC_WS; var() class InMessageClass; var() int SwitchValue; }; var array PerkRoulette_PlayersDelegateData; var array PerkRoulette_PlayersDelegateInventory; //----------------------------------------------------------------------------- // Statics static event class SetGameType(string MapName, string Options, string Portal) { local KFGameEngine KGE; KGE = KFGameEngine(class'Engine'.static.GetEngine()); if (KGE != none) { //Valid index if (KGE.GetWeeklyEventIndex() >= 0) { return super.SetGameType(MapName, Options, Portal); } } //Invalid state, set to normal survival return class'KFGameInfo_Survival'; } static function bool GametypeChecksDifficulty() { return false; } static function bool GametypeChecksWaveLength() { return false; } //----------------------------------------------------------------------------- // Initialization event InitGame( string Options, out string ErrorMessage ) { Super.InitGame(Options, ErrorMessage); //SetModifiedGameDifficulty(); SetPickupItemList(); SetZedTimeOverrides(); SetSpawnPointOverrides(); OutbreakEvent.SetWorldInfoOverrides(); } event PreBeginPlay() { super.PreBeginPlay(); OutbreakEvent.UpdateGRI(); if (Role == Role_Authority && MyKFGRI != none) { MyKFGRI.NotifyWeeklyEventIndex(ActiveEventIdx); if ( OutbreakEvent.ActiveEvent.bUnlimitedWeaponPickups) { MyKFGRI.NotifyBrokenTrader(); } } } event PostBeginPlay() { super.PostBeginPlay(); if (OutbreakEvent.ActiveEvent.TimeBetweenWaves >= 0.f) { TimeBetweenWaves = OutbreakEvent.ActiveEvent.TimeBetweenWaves; } } function CreateOutbreakEvent() { //The KFGameEngine at startup will store the week index of our current time // Pull from there and figure out which event it corresponds to. // The beginning of time to reset the loop can be changed in UKFGameEngine::UpdateTimedGameEvents local KFGameEngine KGE; super.CreateOutbreakEvent(); KGE = KFGameEngine(class'Engine'.static.GetEngine()); if (KGE != none) { ActiveEventIdx = KGE.GetWeeklyEventIndex() % OutbreakEvent.SetEvents.Length; } ActiveEventIdx = OutbreakEvent.SetActiveEvent(ActiveEventIdx); } function bool UsesModifiedDifficulty() { return true; } function SetModifiedGameDifficulty() { super.SetModifiedGameDifficulty(); if (OutbreakEvent == none) { CreateOutbreakEvent(); } //Set game difficulty. super will create the intended DifficultyInfo object. MinGameDifficulty = OutbreakEvent.ActiveEvent.EventDifficulty; MaxGameDifficulty = OutbreakEvent.ActiveEvent.EventDifficulty; GameDifficulty = Clamp(GameDifficulty, MinGameDifficulty, MaxGameDifficulty); } //for difficulty override function bool UsesModifiedLength() { return true; } function SetModifiedGameLength() { GameLength = OutbreakEvent.ActiveEvent.GameLength; } /** Allow for updates to various game systems if we have an override allowable item list */ function SetPickupItemList() { local STraderItem TraderItem; local KFPickupFactory_Item ItemFactory; local int Idx; if (MyKFGRI.IsGunGameMode()) { foreach AllActors(class'KFPickupFactory_Item', ItemFactory) { for (Idx = ItemFactory.ItemPickups.Length - 1; Idx >= 0; --Idx) { if (ItemFactory.ItemPickups[Idx].ItemClass.Name != 'KFInventory_Armor') { ItemFactory.ItemPickups.Remove(Idx, 1); } } } return; } //If we have an override weapon list, it's not enough to block trader and default inventory. // Iterate through the item pickups in the map to trim their lists as well. if (OutbreakEvent.ActiveEvent.TraderWeaponList != none) { //So many loops foreach AllActors(class'KFPickupFactory_Item', ItemFactory) { //we dont want item pickups, so kiss them goodbye if(OutbreakEvent.ActiveEvent.OverrideItemPickupModifier == 0) { ItemFactory.ShutDown(); ItemFactory.ItemPickups.Remove(0, ItemFactory.ItemPickups.Length); continue; } foreach OutbreakEvent.ActiveEvent.TraderWeaponList.SaleItems(TraderItem) { for (Idx = ItemFactory.ItemPickups.Length - 1; Idx >= 0; --Idx) { if (ItemFactory.ItemPickups[Idx].ItemClass.Name != TraderItem.ClassName) { ItemFactory.ItemPickups.Remove(Idx, 1); } } } } } } function SetZedTimeOverrides() { if (ZedTimeSlomoScale != OutbreakEvent.ActiveEvent.OverrideZedTimeSlomoScale) { ZedTimeSlomoScale = OutbreakEvent.ActiveEvent.OverrideZedTimeSlomoScale; } } function SetSpawnPointOverrides() { local KFSpawnVolume KFSV; foreach WorldInfo.AllActors(class'KFSpawnVolume', KFSV) { if (OutbreakEvent.ActiveEvent.OverrideSpawnDerateTime >= 0.f) { KFSV.SpawnDerateTime = OutbreakEvent.ActiveEvent.OverrideSpawnDerateTime; } if (OutbreakEvent.ActiveEvent.OverrideTeleportDerateTime >= 0.f) { KFSV.TeleportDerateTime = OutbreakEvent.ActiveEvent.OverrideTeleportDerateTime; } } } /** Enable some hax to permanently be in zed time */ function SetPermanentZedTime() { local KFPlayerController KFPC; if (OutbreakEvent.ActiveEvent.bPermanentZedTime) { ZedTimeRemaining = 999999.f; bZedTimeBlendingOut = false; LastZedTimeEvent = WorldInfo.TimeSeconds; SetZedTimeDilation(ZedTimeSlomoScale); foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { if (KFPC != none) { KFPC.EnterZedTime(); } } } } //Do a reset on the permanent zed time. Leaves us in zed time, but puts valid players into the partial mode. function ResetPermanentZed() { local KFPlayerController KFPC; local KFPawn KFP; foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { KFP = KFPawn(KFPC.Pawn); if (KFPC != none && KFP != none) { KFP.bUnaffectedByZedTime = !KFPC.IsAffectedByZedTime(); if (KFP.bUnaffectedByZedTime) { KFPC.StartPartialZedTimeSightCounter(); } KFPC.ClientEnterZedTime(KFP.bUnaffectedByZedTime); } } } function float GetAdjustedAIDoshValue( class MonsterClass ) { if (!OutbreakEvent.ActiveEvent.bBossRushMode) { return super.GetAdjustedAIDoshValue(MonsterClass) * OutbreakEvent.ActiveEvent.DoshOnKillGlobalModifier; } else { if ((WaveNum-1) < OutbreakEvent.ActiveEvent.BossRushOverrideParams.PerWaves.length) { return super.GetAdjustedAIDoshValue(MonsterClass) * OutbreakEvent.ActiveEvent.BossRushOverrideParams.PerWaves[WaveNum-1].DoshOnKillGlobalModifier; } } return super.GetAdjustedAIDoshValue(MonsterClass); } protected function ScoreMonsterKill( Controller Killer, Controller Monster, KFPawn_Monster MonsterPawn ) { super.ScoreMonsterKill(Killer, Monster, MonsterPawn); if(OutbreakEvent.ActiveEvent.bHealAfterKill) { if( MonsterPawn != none && MonsterPawn.DamageHistory.Length > 0 ) { if(OutbreakEvent.ActiveEvent.bHealWithHeadshot) { if (MonsterPawn.LastHitZoneIndex == HZI_HEAD) { HealAfterKilling( MonsterPawn, Killer, false ); } } else { HealAfterKilling( MonsterPawn, Killer ); } } } if (OutbreakEvent.ActiveEvent.bGunGameMode) { GunGameScoreAssistanceAfterKilling(MonsterPawn, Killer); } } /** Heal players after a Zed was killed, based in more heal to the player that was the killer and less heal to the players that damaged the Zed */ function HealAfterKilling(KFPawn_Monster MonsterPawn , Controller Killer, optional bool bGivePowerUp = true) { local int i; local int j; local KFPlayerController KFPC; local KFPlayerReplicationInfo DamagerKFPRI; local array DamageHistory; local array Attackers; local KFPawn_Human PawnHuman; local KFGameInfo KFGI; DamageHistory = MonsterPawn.DamageHistory; KFGI = KFGameInfo(WorldInfo.Game); 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 ) { DamagerKFPRI = KFPlayerReplicationInfo(DamageHistory[i].DamagerPRI); if( DamagerKFPRI != none ) { KFPC = KFPlayerController(DamagerKFPRI.Owner); if( KFPC != none ) { if(Attackers.Find(KFPC) < 0) { PawnHuman = KFPawn_Human(KFPC.Pawn); Attackers.AddItem(KFPC); /* Weekly event Aracnophobia (10): 2 kind of heales: one for killing and another for killing by jumping on enemies. HealByAssistance is used for the latest, no need to add extra variables. */ if( KFPC == Killer && KFGI != none && KFGI.OutbreakEvent.ActiveEvent.bGoompaJumpEnabled ) { for (j = 0; j < DamageHistory[i].DamageTypes.Length; j++) { if (DamageHistory[i].DamageTypes[j] == class 'KFDT_GoompaStomp') { PawnHuman.HealDamageForce(MonsterPawn.HealByAssistance, KFPC, class'KFDT_Healing', false, false ); return; } } PawnHuman.HealDamageForce(MonsterPawn.HealByKill, KFPC, class'KFDT_Healing', false, false ); return; } // if( KFPC == Killer ) { PawnHuman.HealDamageForce(MonsterPawn.HealByKill, KFPC, class'KFDT_Healing', false, false ); if( bGivePowerUp && ( KFPawn_ZedFleshpound(MonsterPawn) != none || KFPawn_ZedScrake(MonsterPawn) != none )) { KFPC.ReceivePowerUp(class'KFPowerUp_HellishRage_NoCostHeal'); } } else { PawnHuman.HealDamageForce(MonsterPawn.HealByAssistance, KFPC, class'KFDT_Healing', false, false ); } } } } } } } function GunGameScoreAssistanceAfterKilling(KFPawn_Monster MonsterPawn , Controller Killer) { local int i; local KFPlayerController_WeeklySurvival KFPC_WS; local array DamageHistory; local KFPlayerReplicationInfo DamagerKFPRI; local array Attackers; DamageHistory = MonsterPawn.DamageHistory; 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) { DamagerKFPRI = KFPlayerReplicationInfo(DamageHistory[i].DamagerPRI); if (DamagerKFPRI != none) { KFPC_WS = KFPlayerController_WeeklySurvival(DamagerKFPRI.Owner); if (KFPC_WS != none && KFPC_WS != Killer) { if (Attackers.Find(KFPC_WS) < 0) { Attackers.AddItem(KFPC_WS); if (KFPC_WS.Pawn.Health > 0) { KFPC_WS.GunGameData.Score += MonsterPawn.GunGameAssistanceScore; UpdateGunGameLevel(KFPC_WS); } } } } } } } function StartMatch() { super.StartMatch(); if (OutbreakEvent.ActiveEvent.bForceWWLMusic) { ForceWWLMusicTrack(); } } function CreateDifficultyInfo(string Options) { super.CreateDifficultyInfo(Options); //If we want to use custom weapon respawn times, set them here if (OutbreakEvent.ActiveEvent.bUseOverrideItemRespawnTime) { DifficultyInfo.NumPlayers_WeaponPickupRespawnTime = OutbreakEvent.ActiveEvent.OverrideItemRespawnTime; } //If we want to use custom ammo respawn times, set them here if (OutbreakEvent.ActiveEvent.bUseOverrideAmmoRespawnTime) { DifficultyInfo.NumPlayers_AmmoPickupRespawnTime = OutbreakEvent.ActiveEvent.OverrideAmmoRespawnTime; } } event PostLogin( PlayerController NewPlayer ) { local KFPlayerController_WeeklySurvival KFPC_WS; local KFPawn_Customization KFCustomizePawn; super.PostLogin(NewPlayer); KFPC_WS = KFPlayerController_WeeklySurvival(NewPlayer); if (KFPC_WS != none) { KFPC_WS.bUsingPermanentZedTime = OutbreakEvent.ActiveEvent.bPermanentZedTime; KFPC_WS.ZedTimeRadius = OutbreakEvent.ActiveEvent.ZedTimeRadius * OutbreakEvent.ActiveEvent.ZedTimeRadius; KFPC_WS.ZedTimeBossRadius = OutbreakEvent.ActiveEvent.ZedTimeBossRadius * OutbreakEvent.ActiveEvent.ZedTimeBossRadius; KFPC_WS.ZedTimeHeight = OutbreakEvent.ActiveEvent.ZedTimeHeight; KFPC_WS.ZedRecheckTime = OutbreakEvent.ActiveEvent.PermanentZedResetTime; //Handle any visual-related things for customization pawn so the pregame lobby has the fun things KFCustomizePawn = KFPawn_Customization(KFPC_WS.Pawn); if (KFCustomizePawn != none) { KFCustomizePawn.IntendedHeadScale = OutbreakEvent.ActiveEvent.PlayerSpawnHeadScale; KFCustomizePawn.SetHeadScale(KFCustomizePawn.IntendedHeadScale, KFCustomizePawn.CurrentHeadScale); } } LoadGunGameWeapons(NewPlayer); } function SetBossIndex() { local BossSpawnReplacement Replacement; local int ReplaceIdx; local int i; // Ignore normal events. if (OutbreakEvent.ActiveEvent.bBossRushMode) { if (BossRushEnemies.length == 0) { for(i=0; i < default.AIBossClassList.length; ++i) { BossRushEnemies.AddItem(i); } } } BossIndex = Rand(default.AIBossClassList.Length); //Search in the replacement list for the one that the game type wanted to use // If we find it, grab the appropriate index into the original AI class list // so we can properly cache it. foreach OutbreakEvent.ActiveEvent.BossSpawnReplacementList(Replacement) { if (Replacement.SpawnEntry == BossIndex) { ReplaceIdx = AIBossClassList.Find(Replacement.NewClass); if (ReplaceIdx != INDEX_NONE) { BossIndex = ReplaceIdx; break; } } } MyKFGRI.CacheSelectedBoss(BossIndex); } //----------------------------------------------------------------------------- // Ticking function Tick(float DeltaTime) { CurrentFrameBooms = 0; super.Tick(DeltaTime); if (MyKFGRI.IsRandomPerkMode()) { // This deals with players joining at any time (lobby, or in wave) ChooseRandomPerks(false); } } function TickZedTime( float DeltaTime ) { super.TickZedTime(DeltaTime); //If we're in permanent mode with a valid wave, set remaining time to a stupid value to stay in zed time if (OutbreakEvent.ActiveEvent.bPermanentZedTime && IsWaveActive()) { //Keep up the timer if we have enough zeds left or it's a boss phase if (MyKFGRI.AIRemaining > OutbreakEvent.ActiveEvent.PermanentZedTimeCutoff || WaveNum == WaveMax) { ZedTimeRemaining = 999999.f; } //Else start the fade back to normal else if (ZedTimeRemaining > ZedTimeBlendOutTime) { ZedTimeRemaining = ZedTimeBlendOutTime; ClearZedTimePCTimers(); } } } function WaveEnded(EWaveEndCondition WinCondition) { local KFPawn_Human Pawn; local bool bWasFirstTime; // This function is called multiple times in a row. Only apply it once. bWasFirstTime = bWaveStarted; // Choose new perk before the end of wave message triggers in supper. if (MyKFGRI.IsRandomPerkMode() && WinCondition == WEC_WaveWon) { PerkRouletteRandomWaveNum++; ChooseRandomPerks(true); } super.WaveEnded(WinCondition); if (OutbreakEvent.ActiveEvent.bPermanentZedTime && ZedTimeRemaining > ZedTimeBlendOutTime) { ClearZedTimePCTimers(); ZedTimeRemaining = ZedTimeBlendOutTime; } if (OutbreakEvent.ActiveEvent.bHealPlayerAfterWave) { foreach WorldInfo.AllPawns(class'KFPawn_Human', Pawn) { Pawn.Health = Pawn.HealthMax; } } if (WinCondition == WEC_WaveWon && bWasFirstTime) { GrantExtraDoshOnWaveWon(); } DisableGlobalDamage(); } /** Grant dosh to every player even no matter it's state when a wave is won. */ function GrantExtraDoshOnWaveWon() { local KFPlayerController KFPC; local int ExtraDosh; // if (OutbreakEvent.ActiveEvent.bBossRushMode && (WaveNum-1) < OutbreakEvent.ActiveEvent.BossRushOverrideParams.PerWaves.length) { ExtraDosh = OutbreakEvent.ActiveEvent.BossRushOverrideParams.PerWaves[WaveNum-1].ExtraDoshGrantedonWaveWon; foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { KFPlayerReplicationInfo(KFPC.PlayerReplicationInfo).AddDosh(ExtraDosh, true); } } } function ClearZedTimePCTimers() { local KFPlayerController_WeeklySurvival KFPC; foreach AllActors(class'KFPlayerController_WeeklySurvival', KFPC) { KFPC.ClearTimer('RecheckZedTime'); } } //----------------------------------------------------------------------------- // Completion function EndOfMatch(bool bVictory) { local KFPlayerController KFPC; if (bVictory) { foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { KFPC.CompletedWeeklySurvival(); } } super.EndOfMatch(bVictory); } function StartWave() { super.StartWave(); // Stop Global Damage for boss wave if (!OutbreakEvent.ActiveEvent.bApplyGlobalDamageBossWave && WaveNum == WaveMax) { DisableGlobalDamage(); } // In case there was a previous boss wave. Not sure if possible else if (OutbreakEvent.ActiveEvent.GlobalDamageTickRate > 0.f && OutbreakEvent.ActiveEvent.GlobalDamageTickAmount > 0.f) { if(!IsTimerActive('EnableGlobalDamage', self)) { SetTimer(OutbreakEvent.ActiveEvent.DamageDelayAfterWaveStarted, false, 'EnableGlobalDamage', self); } // Check if we are in the zed frustration time to stop applying damage SetTimer(1.0f, true, 'CheckForZedFrustrationMode', self); } if (OutbreakEvent.ActiveEvent.bPermanentZedTime) { //If we're a boss wave, wait until the camera animation is going if (WaveNum == WaveMax) { SetTimer(0.25f, true, nameof(BossCameraZedTimeRecheck)); } else { //Enable permanent zed time SetPermanentZedTime(); } } if (OutbreakEvent.ActiveEvent.AdditionalBossWaveInfo != none && WaveNum == WaveMax) { SetTimer(OutbreakEvent.ActiveEvent.AdditionalBossWaveStartDelay, true, nameof(SpawnBossWave)); } if (OutbreakEvent.ActiveEvent.bUnlimitedWeaponPickups) { OverridePickupList(); } } function bool OverridePickupList() { local KFPickupFactory PickupFactory; local KFPickupFactory_Item ItemFactory; local KFGameReplicationInfo_WeeklySurvival KFGRI_WS; KFGRI_WS=KFGameReplicationInfo_WeeklySurvival(MyKFGRI); if (KFGRI_WS == none) return false; foreach ItemPickups(PickupFactory) { ItemFactory = KFPickupFactory_Item(PickupFactory); if (ItemFactory == none) continue; KFGRI_WS.OverrideWeaponPickups(ItemFactory); ItemFactory.OverridePickup(); } return true; } function EnableGlobalDamage() { MyKFGRI.SetGlobalDamage(true); SetTimer(OutbreakEvent.ActiveEvent.GlobalDamageTickRate, true, 'ApplyGlobalDamage', OutbreakEvent); } function DisableGlobalDamage() { MyKFGRI.SetGlobalDamage(false); if (IsTimerActive('ApplyGlobalDamage', OutbreakEvent)) { ClearTimer('ApplyGlobalDamage', OutbreakEvent); } if (IsTimerActive('EnableGlobalDamage', self)) { ClearTimer('EnableGlobalDamage', self); } } function CheckForZedFrustrationMode() { if(IsTimerActive('ApplyGlobalDamage', OutbreakEvent)) { if(class'KFAIController'.default.FrustrationThreshold > 0 && MyKFGRI.AIRemaining <= class'KFAIController'.default.FrustrationThreshold) { DisableGlobalDamage(); ClearTimer('CheckForZedFrustrationMode', self); } } } function BossCameraZedTimeRecheck() { local KFPawn_Monster KFM; local KFInterface_MonsterBoss BossRef; foreach WorldInfo.AllPawns(class'KFPawn_Monster', KFM) { if (KFM.static.IsABoss()) { BossRef = KFInterface_MonsterBoss(KFM); if (BossRef != none) { if (BossRef.UseAnimatedBossCamera()) { return; } //We have a boss that isn't animating. Go ahead and start zed time else { ClearTimer(nameof(BossCameraZedTimeRecheck)); SetPermanentZedTime(); } } } } } //Hijack existing boss summon functionality to spawn extra boss wave function SpawnBossWave() { SetTimer(OutbreakEvent.ActiveEvent.AdditionalBossWaveFrequency, false, nameof(SpawnBossWave)); SpawnManager.SummonBossMinions(OutbreakEvent.ActiveEvent.AdditionalBossWaveInfo.Squads, GetAdditionalBossSpawns()); if (!OutbreakEvent.ActiveEvent.bContinuousAdditionalBossWave) { //Arbitrary time, but delay a bit for spawns to go through, then cut off additional boss wave spawning til next timer hit SetTimer(2.f, false, nameof(PauseAdditionalBossWaves)); } } function PauseAdditionalBossWaves() { SpawnManager.StopSummoningBossMinions(); } function byte GetAdditionalBossSpawns() { return byte(Lerp(OutbreakEvent.ActiveEvent.AdditionalBossSpawnCount.X, OutbreakEvent.ActiveEvent.AdditionalBossSpawnCount.Y,fMax(NumPlayers, 1) / float(MaxPlayers))); } function OpenTrader() { //If we're in permanent zed time, disable it for trader time if (OutbreakEvent.ActiveEvent.bPermanentZedTime && ZedTimeRemaining > ZedTimeBlendOutTime) { ClearZedTimePCTimers(); ZedTimeRemaining = ZedTimeBlendOutTime; } if (!OutbreakEvent.ActiveEvent.bDisableTraders) { super.OpenTrader(); } else if (KFGameReplicationInfo(GameReplicationInfo) != none) { KFGameReplicationInfo(GameReplicationInfo).StartScavengeTime(TimeBetweenWaves); } } function SetupNextTrader() { if (!OutbreakEvent.ActiveEvent.bDisableTraders) { super.SetupNextTrader(); } } State TraderOpen { function BeginState( Name PreviousStateName ) { super.BeginState(PreviousStateName); //Adding the call here. Whether or not super gets called is based on ActiveEvent flag. ResetAllPickups(); } } function InitAllPickups() { super.InitAllPickups(); //If this event is trying to do things to the pickup count, redo the init functionality if (OutbreakEvent.ActiveEvent.OverrideItemPickupModifier >= 0.f || OutbreakEvent.ActiveEvent.OverrideAmmoPickupModifier >= 0.f) { NumWeaponPickups = ItemPickups.Length * (OutbreakEvent.ActiveEvent.OverrideItemPickupModifier >= 0.f ? OutbreakEvent.ActiveEvent.OverrideItemPickupModifier : DifficultyInfo.GetItemPickupModifier()); NumAmmoPickups = AmmoPickups.Length * (OutbreakEvent.ActiveEvent.OverrideAmmoPickupModifier >= 0.f ? OutbreakEvent.ActiveEvent.OverrideAmmoPickupModifier : DifficultyInfo.GetAmmoPickupModifier()); `if(`__TW_SDK_) if( BaseMutator != none ) { BaseMutator.ModifyPickupFactories(); } `endif ResetAllPickups(); } } function ResetAllPickups() { local bool bCallReset; bCallReset = false; switch(OutbreakEvent.ActiveEvent.PickupResetTime) { case PRS_Wave: bCallReset = IsWaveActive(); break; case PRS_Trader: bCallReset = !IsWaveActive(); break; case PRS_WaveAndTrader: bCallReset = true; break; case PRS_Never: bCallReset = false; break; } if (bCallReset) { super.ResetAllPickups(); } } function ResetPickups( array PickupList, int NumPickups ) { //If we're resetting ammo and in an ammo list, use that modifier if (OutbreakEvent.ActiveEvent.WaveAmmoPickupModifiers.Length >= WaveMax && KFPickupFactory_Ammo(PickupList[0]) != none) { NumPickups *= OutbreakEvent.ActiveEvent.WaveAmmoPickupModifiers[WaveNum]; super(KFGameInfo).ResetPickups(PickupList, NumPickups); } //If we're resetting items and in an item list, use that modifier else if (OutbreakEvent.ActiveEvent.WaveItemPickupModifiers.Length >= WaveMax && KFPickupFactory_Item(PickupList[0]) != none) { NumPickups *= OutbreakEvent.ActiveEvent.WaveItemPickupModifiers[WaveNum]; if(OutbreakEvent.ActiveEvent.OverrideItemPickupModifier == 0) NumPickups = 0; super(KFGameInfo).ResetPickups(PickupList, NumPickups); } //Otherwise, use normal path else { super.ResetPickups(PickupList, NumPickups); } } //----------------------------------------------------------------------------- // Spawning /** Allow specific game type to override the spawn class. Default implementation returns from the AI class list. */ function class GetAISpawnType(EAIType AIType) { if (WaveNum < WaveMax || OutbreakEvent.ActiveEvent.bAllowSpawnReplacementDuringBossWave) { return OutbreakEvent.GetAISpawnOverrirde(AIType); } //No override, return default return AIClassList[AIType]; } /** Whether or not a specific primary weapon is allowed. Called at player spawn time while setting inventory. */ function bool AllowPrimaryWeapon(string ClassPath) { local STraderItem Item; if (OutbreakEvent.ActiveEvent.SpawnWeaponList != none) { foreach OutbreakEvent.ActiveEvent.SpawnWeaponList.SaleItems(Item) { if ( name(Item.WeaponDef.default.WeaponClassPath) == name(ClassPath) ) { return true; } } return false; } return true; } /** Whether or not a specific secondary weapon is allowed. Called at player spawn time while setting inventory. */ function bool AllowSecondaryWeapon(string ClassPath) { local STraderItem Item; if (OutbreakEvent.ActiveEvent.SpawnWeaponList != none && OutbreakEvent.ActiveEvent.bSpawnWeaponListAffectsSecondaryWeapons) { foreach OutbreakEvent.ActiveEvent.SpawnWeaponList.SaleItems(Item) { if ( name(Item.WeaponDef.default.WeaponClassPath) == name(ClassPath) ) { return true; } } return false; } return true; } /** Allows gametype to adjust starting grenade count. Called at player spawn time from GiveInitialGrenadeCount in the inventory. */ function int AdjustStartingGrenadeCount(int CurrentCount) { if (OutbreakEvent.ActiveEvent.bDisableGrenades) { return 0; } return CurrentCount; } /** Allows gametype to validate a perk for the current match */ function bool IsPerkAllowed(class PerkClass) { Local int index; if(OutbreakEvent.ActiveEvent.PerksAvailableList.length == 0) { return true; } for( index=0 ; index InventoryClass; local Inventory Inv; local KFWeapon Weapon; // Deactivated preload in console version if (OutbreakEvent.ActiveEvent.bGunGameMode && WorldInfo.IsConsoleBuild() == false) { KFPC_WS = KFPlayerController_WeeklySurvival(NewPlayer); if (KFPC_WS == none) { return; } for (i=0; i < OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels.Length; i++) { RandomNumber = Rand(OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels[i].GrantedWeapons.Length); KFPC_WS.GunGameData.GunGamePreselectedWeapons.AddItem(RandomNumber); InventoryClass = class (DynamicLoadObject(OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels[i].GrantedWeapons[RandomNumber].default.WeaponClassPath, class'Class')); Inv = KFPC_WS.Pawn.InvManager.CreateInventory(InventoryClass, true); if (Inv != none) { Weapon = KFWeapon(Inv); if (Weapon != none) { Weapon.RemoveGun(); } } } } } function RestartPlayer(Controller NewPlayer) { local KFPawn_Human KFPH; super.RestartPlayer(NewPlayer); KFPH = KFPawn_Human(NewPlayer.Pawn); OutbreakEvent.AdjustRestartedPlayer(KFPH); } function RestartGunGamePlayerWeapon(KFPlayerController_WeeklySurvival KFPC_WS, byte WaveToUse) { local byte i; local int CurrentGunGameWaveLevel; super.RestartGunGamePlayerWeapon(KFPC_WS, WaveToUse); ResetGunGame(KFPC_WS); CurrentGunGameWaveLevel = -1; // Find wave level, the data needs to be ordered for (i = 0; i < OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameRespawnLevels.Length; i++) { if (WaveToUse >= OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameRespawnLevels[i].Wave) { CurrentGunGameWaveLevel = OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameRespawnLevels[i].Level - 1; } } // If any level we force gun game update if (CurrentGunGameWaveLevel >= 0) { KFPC_WS.GunGameData.Score = OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels[CurrentGunGameWaveLevel].RequiredScore; UpdateGunGameLevel(KFPC_WS); } } function DoDeathExplosion(Pawn DeadPawn, KFGameExplosion ExplosionTemplate, class ExplosionIgnoreClass) { local KFExplosionActorReplicated ExploActor; if (CurrentFrameBooms < OutbreakEvent.ActiveEvent.MaxBoomsPerFrame) { ExploActor = Spawn(class'KFExplosionActorReplicated', DeadPawn, , DeadPawn.Location); if (ExploActor != none) { ExploActor.InstigatorController = DeadPawn.Controller; ExploActor.Instigator = DeadPawn; ExploActor.Attachee = DeadPawn; ExplosionTemplate.ActorClassToIgnoreForDamage = ExplosionIgnoreClass; ExploActor.Explode(ExplosionTemplate, vect(0, 0, 1)); ++CurrentFrameBooms; } } } simulated function AddWeaponsFromSpawnList(KFPawn P) { local STraderItem Item; if (OutbreakEvent.ActiveEvent.SpawnWeaponList != none || OutbreakEvent.ActiveEvent.bAddSpawnListToLoadout) { foreach OutbreakEvent.ActiveEvent.SpawnWeaponList.SaleItems(Item) { P.DefaultInventory.AddItem(class(DynamicLoadObject(Item.WeaponDef.default.WeaponClassPath, class'Class'))); } } } simulated function OverrideHumanDefaults(KFPawn_Human P) { if (OutbreakEvent.ActiveEvent.JumpZ >= 0.0f) { P.JumpZ = OutbreakEvent.ActiveEvent.JumpZ; } } simulated function ModifyDamageGiven(out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class DamageType, optional int HitZoneIdx ) { local KFPlayerController_WeeklySurvival KFPC; local int Streak; if (OutbreakEvent.ActiveEvent.bGoompaJumpEnabled) { KFPC = KFPlayerController_WeeklySurvival(DamageInstigator); if (KFPC != none) { Streak = KFPC.GoompaStreakBonus < KFPC.MaxGoompaStreak ? KFPC.GoompaStreakBonus : KFPC.MaxGoompaStreak; InDamage *= (1 + OutbreakEvent.ActiveEvent.GoompaStreakDamage * Streak); } } } /* * Gun Game */ function ResetGunGame(KFPlayerController_WeeklySurvival KFPC_WS) { KFPC_WS.GunGameData.Score = 0; KFPC_WS.GunGameData.Level = 0; KFPC_WS.UpdateGunGameWidget(0, OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels[0].RequiredScore, 0, OutbreakEvent.ActiveEvent.GunGamePerksData.GunGameLevels.Length); } function NotifyKilled(Controller Killer, Controller Killed, Pawn KilledPawn, class damageType ) { local KFPawn_Monster KFPM; local KFPlayerController_WeeklySurvival KFPC_WS_Killer, KFPC_WS_Killed; super.NotifyKilled(Killer, Killed, KilledPawn, damageType); KFPM = KFPawn_Monster(KilledPawn); KFPC_WS_Killer = KFPlayerController_WeeklySurvival(Killer); KFPC_WS_Killed = KFPlayerController_WeeklySurvival(Killed); if (OutbreakEvent.ActiveEvent.bGunGameMode) { // If pawn is monster increase gun game score for that monster if (KFPM != none && KFPC_WS_Killer != none) { if (KFPC_WS_Killer.Pawn.Health > 0) { KFPC_WS_Killer.GunGameData.Score += KFPM.GunGameKilledScore; UpdateGunGameLevel(KFPC_WS_Killer); } } else { // If pawn is human reset game score (we can just check Killed exists as Controller if (KFPC_WS_Killed != none) { ResetGunGame(KFPC_WS_Killed); } } } if (OutbreakEvent.ActiveEvent.bVIPGameMode) { if (KFPC_WS_Killed != none && KFPC_WS_Killed.VIPGameData.isVIP) { // UnregisterPlayer is done on the same frame but this function comes first.. // we queue a petition to end the game if no vip is found SetTimer(1.5f, false, 'OnVIPDiesEndMatch'); } } if (MyKFGRI.IsRandomPerkMode()) { if (KFPC_WS_Killed != none) { PerkRoulette_PlayersDelegateInventory.AddItem(KFPC_WS_Killed); } } } function GunGameLevelGrantWeapon(KFPlayerController_WeeklySurvival KFPC_WS, class ToGrantWeaponDefinition) { local class InventoryClass; local Inventory Inv; local KFWeapon KFW; InventoryClass = class (DynamicLoadObject(ToGrantWeaponDefinition.default.WeaponClassPath, class'Class')); Inv = KFPC_WS.Pawn.InvManager.CreateInventory(InventoryClass, true); if (Inv != none) { KFW = KFWeapon(Inv); if (KFW != none) { KFW.bDropOnDeath = false; KFW.bGivenAtStart = true; KFW = KFInventoryManager(KFPC_WS.Pawn.InvManager).CombineWeaponsOnPickup( KFW ); KFW.NotifyPickedUp(); // Refill ammo KFW.AmmoCount[0] = KFW.MagazineCapacity[0]; KFW.AddAmmo(KFW.GetMaxAmmoAmount(0)); KFW.AmmoCount[1] = KFW.MagazineCapacity[1]; KFW.AddSecondaryAmmo(KFW.GetMaxAmmoAmount(1)); KFPC_WS.Pawn.InvManager.SetCurrentWeapon(KFW); } } } function UpdateGunGameLevel(KFPlayerController_WeeklySurvival KFPC_WS) { local byte CurrentLevel, InitialLevel, RandomNumber; local class ToGrantWeaponDefinition; local GunGamePerkData PerkData; local KFWeapon CurrentWeapon; local bool found_base_weapon; if (!OutbreakEvent.ActiveEvent.bGunGameMode) return; PerkData = OutbreakEvent.ActiveEvent.GunGamePerksData; InitialLevel = KFPC_WS.GunGameData.Level; CurrentLevel = KFPC_WS.GunGameData.Level; // Update to the current level while (CurrentLevel < PerkData.GunGameLevels.Length && KFPC_WS.GunGameData.Score >= PerkData.GunGameLevels[CurrentLevel].RequiredScore) { ++CurrentLevel; } // Update HUD if (CurrentLevel > PerkData.GunGameLevels.Length - 1) { KFPC_WS.UpdateGunGameWidget(KFPC_WS.GunGameData.Score, -1, PerkData.GunGameLevels.Length, PerkData.GunGameLevels.Length); } else { KFPC_WS.UpdateGunGameWidget(KFPC_WS.GunGameData.Score, PerkData.GunGameLevels[CurrentLevel].RequiredScore, CurrentLevel, PerkData.GunGameLevels.Length); } if (InitialLevel != CurrentLevel) { // If this player reached last level.. if (CurrentLevel > PerkData.GunGameLevels.Length - 1) { if (bGunGamePlayerOnLastGun == false) { KFPC_WS.GunGameData.GiveWeaponMaster = true; } bGunGamePlayerOnLastGun = true; KFPC_WS.PlayGunGameMessage(true); } else { KFPC_WS.PlayGunGameMessage(false); } KFPC_WS.GunGameData.Level = CurrentLevel; found_base_weapon = false; // Remove Previous Granted Items foreach KFPC_WS.Pawn.InvManager.InventoryActors ( class'KFWeapon', CurrentWeapon ) { // (not if it's knife/9mm/syringe) if (!class'KFPerk'.static.IsKnife(CurrentWeapon) && !class'KFPerk_SWAT'.static.Is9mm(CurrentWeapon) && !class'KFPerk'.static.IsSyringe(CurrentWeapon) && !class'KFPerk'.static.IsWelder(CurrentWeapon)) { // To prevent audio/vfx lock, while firing when removing the equipped weapon we do a proper gun remove // This new function manages it's state internally CurrentWeapon.RemoveGun(); } if (class'KFPerk_SWAT'.static.Is9mm(CurrentWeapon)) { found_base_weapon = true; } } // We need to grant 9MM is we don't have it and we jumped levels if (CurrentLevel > 1 && found_base_weapon == false) { ToGrantWeaponDefinition = PerkData.GunGameLevels[0].GrantedWeapons[0]; GunGameLevelGrantWeapon(KFPC_WS, ToGrantWeaponDefinition); } // Grant Weapon // Generate random weapon to grant from the list // Deactivated preload in console version if (WorldInfo.IsConsoleBuild()) { RandomNumber = Rand(PerkData.GunGameLevels[CurrentLevel-1].GrantedWeapons.Length); } else { RandomNumber = KFPC_WS.GunGameData.GunGamePreselectedWeapons[CurrentLevel-1]; } ToGrantWeaponDefinition = PerkData.GunGameLevels[CurrentLevel-1].GrantedWeapons[RandomNumber]; GunGameLevelGrantWeapon(KFPC_WS, ToGrantWeaponDefinition); } } /////////////////////////////////////////////////////////////////////////////////// function UnregisterPlayer(PlayerController PC) { local KFPlayerController_WeeklySurvival KFPC_WS; super.UnregisterPlayer(PC); KFPC_WS = KFPlayerController_WeeklySurvival(PC); if (OutbreakEvent.ActiveEvent.bVIPGameMode) { if (KFPC_WS != none && KFPC_WS.VIPGameData.IsVIP) { ChooseVIP(false, KFPC_WS); } } } function WaveStarted() { Super.WaveStarted(); if (OutbreakEvent.ActiveEvent.bVIPGameMode) { if (WaveNum <= 1) { ChooseVIP(true); } } if (MyKFGRI.IsRandomPerkMode()) { RandomPerkWaveStarted(); } } /** * Weekly 17: VIP MODE */ function OnVIPDiesEndMatch() { local KFPlayerController KFPC; foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { KFPC.SetCameraMode('ThirdPerson'); } WaveEnded(WEC_TeamWipedOut); } function ChooseVIP_SetupVIP() { local KFPlayerController_WeeklySurvival KFPC_WS, NewVIP; local KFGameReplicationInfo KFGRI; NewVIP = none; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS != none) { if (KFPC_WS.VIPGameData.IsVIP) { NewVIP = KFPC_WS; break; } } } if (NewVIP != none) { //`Log("Setup new VIP: " $NewVIP); if (NewVIP.Pawn != none) { //`Log("Finished setup new VIP: " $NewVIP); NewVIP.GetPerk().PerkSetOwnerHealthAndArmor(false); if (NewVIP.VIPGameData.PendingHealthReset) { NewVIP.VIPGameData.PendingHealthReset = false; // Change current health directly, Pawn.HealDamage does a lot of other stuff that can block the healing NewVIP.Pawn.Health = NewVIP.Pawn.HealthMax; } // Replicate new data to clients KFGRI.UpdateVIPPlayer(KFPlayerReplicationInfo(NewVIP.PlayerReplicationInfo)); KFGRI.UpdateVIPMaxHealth(NewVIP.Pawn.HealthMax); KFGRI.UpdateVIPCurrentHealth(NewVIP.Pawn.Health); NewVIP.PlayVIPGameChosenSound(3.5f); ClearTimer('ChooseVIP_SetupVIP'); } } } function ChooseVIP(bool ForceAddHealth, optional KFPlayerController_WeeklySurvival PlayerJustLeft = none) { local int RandomNumber; local KFPlayerController_WeeklySurvival KFPC_WS, CurrentVIP, NewVIP; local array PotentialVIP; local KFGameReplicationInfo KFGRI; //`Log("ChooseVIP!!!!!"); ClearTimer('ChooseVIP_SetupVIP'); KFGRI = KFGameReplicationInfo(WorldInfo.GRI); foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS != none) { if (KFPC_WS.VIPGameData.IsVIP == false && KFPC_WS.VIPGameData.WasVIP == false) { PotentialVIP.AddItem(KFPC_WS); } if (KFPC_WS.VIPGameData.IsVIP) { CurrentVIP = KFPC_WS; } } } if (CurrentVIP != none) { //`Log("Remove old VIP: " $CurrentVIP); CurrentVIP.VIPGameData.IsVIP = false; CurrentVIP.GetPerk().PerkSetOwnerHealthAndArmor(false); } // If there's no potential VIP we restart if (PotentialVIP.Length == 0) { foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS != none) { KFPC_WS.VIPGameData.WasVIP = false; if (PlayerJustLeft == none || PlayerJustLeft != KFPC_WS) { PotentialVIP.AddItem(KFPC_WS); } } } } if (PotentialVIP.Length > 0) { RandomNumber = Rand(PotentialVIP.Length); NewVIP = PotentialVIP[RandomNumber]; NewVIP.VIPGameData.IsVIP = true; NewVIP.VIPGameData.WasVIP = true; } if (NewVIP != none) { if (ForceAddHealth || (KFGRI != none && KFGRI.bWaveIsActive == false)) { NewVIP.VIPGameData.PendingHealthReset = true; } // If there's no Pawn we have to wait on a Timer function if (NewVIP.Pawn != none) { ChooseVIP_SetupVIP(); } else { SetTimer(0.25f, true, 'ChooseVIP_SetupVIP'); } ClearTimer('OnVIPDiesEndMatch'); } } function OnOutbreakWaveWon() { Super.OnOutbreakWaveWon(); // GunGame Mode if (OutbreakEvent.ActiveEvent.bGunGameMode) { MyKFGRI.GunGameWavesCurrent += 1; // If we unlocked last weapon we only finish if we completed the boss wave // If we didn't unlock to last weapon and we just finished last wave (before BOSS), repeat if (bGunGamePlayerOnLastGun) { MyKFGRI.bWaveGunGameIsFinal = true; if (WaveNum < WaveMax) { WaveNum = WaveMax - 1; } } else if (WaveNum >= WaveMax - 1) { // Repeat wave before BOSS till forever WaveNum = WaveMax - 2; } MyKFGRI.bNetDirty = true; } // VIP Mode if (OutbreakEvent.ActiveEvent.bVIPGameMode) { ChooseVIP(true); } } /* * Weekly 18: Random Perks */ simulated function NotifyPlayerStatsInitialized(KFPlayerController_WeeklySurvival KFPC) { if (KFPC != none && MyKFGRI.IsRandomPerkMode()) { ChooseInitialRandomPerk(KFPC); } } simulated function RandomPerkWaveStarted() { local KFPlayerController_WeeklySurvival KFPC_WS; foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS.InitialRandomPerk == 255) { `Log("PLAYER - RandomPerkWaveStart : " $KFPC_WS); ChooseInitialRandomPerk(KFPC_WS); } } } function InitializeRandomPerkList(array PerkList) { local array AvailablePerks; local int i; local byte NewRandomIndex; for (i = 0; i < PerkList.Length; ++i) { AvailablePerks.Additem(i); } while(AvailablePerks.Length > 0) { NewRandomIndex = Rand(AvailablePerks.Length); PerkRouletteRandomList.AddItem(AvailablePerks[NewRandomIndex]); AvailablePerks.Remove(NewRandomIndex, 1); } PerkRouletteRandomInitialIndex = 0; PerkRouletteRandomWaveNum = 0; } function ChooseInitialRandomPerk(KFPlayerController_WeeklySurvival KFPC_WS) { if (PerkRouletteRandomList.Length == 0) { // First case, fill random array InitializeRandomPerkList(KFPC_WS.PerkList); } if (KFPC_WS.InitialRandomPerk == 255) { // Choose initial random perk KFPC_WS.InitialRandomPerk = PerkRouletteRandomInitialIndex; PerkRouletteRandomInitialIndex = (PerkRouletteRandomInitialIndex + 1) % PerkRouletteRandomList.Length; //`Log("PLAYER : " $KFPC_WS); //`Log("InitialRandomPerk : " $KFPC_WS.InitialRandomPerk); } } function bool CanAssignDefaultWeaponsToPlayer(Controller NewPlayer) { local KFPlayerController_WeeklySurvival KFPC_WS; local int i; // We can't assign default weapons if we have to wait to assign the New Perk KFPC_WS = KFPlayerController_WeeklySurvival(NewPlayer); if (KFPC_WS != none) { for (i = 0; i < PerkRoulette_PlayersDelegateInventory.Length ; ++i) { if (KFPC_WS == PerkRoulette_PlayersDelegateInventory[i]) { return false; } } } return true; } function PerkRoulette_InventoryCustomDelegate() { local KFPlayerController_WeeklySurvival KFPC_WS; local KFPerk Perk; local int i; local byte NewPerk; for (i = PerkRoulette_PlayersDelegateInventory.Length - 1 ; i >= 0 ; --i) { KFPC_WS = PerkRoulette_PlayersDelegateInventory[i]; if (KFPC_WS == none) { PerkRoulette_PlayersDelegateInventory.Remove(i, 1); continue; } Perk = KFPC_WS.GetPerk(); // While the new Perk is not valid, or is not the one we expect continue if (Perk == none) { continue; } else { NewPerk = (KFPC_WS.InitialRandomPerk + PerkRouletteRandomWaveNum) % PerkRouletteRandomList.Length; if (KFPC_WS.Perklist[PerkRouletteRandomList[NewPerk]].PerkClass != Perk.Class) { continue; } } if (KFPC_WS.Pawn != none) { AddDefaultInventory(KFPC_WS.Pawn); PerkRoulette_PlayersDelegateInventory.Remove(i, 1); } } if (PerkRoulette_PlayersDelegateInventory.Length == 0) { ClearTimer(nameof(PerkRoulette_InventoryCustomDelegate)); } } function ChooseRandomPerks(bool isEndWave) { local KFPlayerController_WeeklySurvival KFPC_WS; local byte NewPerk; if (isEndWave) { ClearTimer(nameof(PerkRoulette_InventoryCustomDelegate)); if (PerkRoulette_PlayersDelegateInventory.Length > 0) { SetTimer(0.5f, true, nameof(PerkRoulette_InventoryCustomDelegate)); } } foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS.InitialRandomPerk == 255 || KFPC_WS.PlayerReplicationInfo.bOnlySpectator || KFPC_WS.IsInState('Spectating')) { continue; } // If the perk assigned to this player is different than the one it should have.. reassign NewPerk = (KFPC_WS.InitialRandomPerk + PerkRouletteRandomWaveNum) % PerkRouletteRandomList.Length; if (PerkRouletteRandomList[NewPerk] != KFPC_WS.SavedPerkIndex || isEndWave) { KFPC_WS.ForceNewPerk(PerkRouletteRandomList[NewPerk]); } if (isEndWave) { KFPC_WS.PlayRandomPerkChosenSound(); } } } // // Overide BroadcastLocalizedMessage for the RandomPerk weekly (18) // Perk is replicated while the message is sent through a RPC, so the message arrives before // the perk is updated on clients. // Override the BroadcastLocalizedMessage function to RPC each player the message with their own // new perk class as the optional object so the client knows the information before it gets the perk updated. // event BroadcastLocalizedMessage( class InMessageClass, optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2, optional Object OptionalObject ) { if (!MyKFGRI.IsRandomPerkMode() || Switch != GMT_WaveEnd) { Super.BroadcastLocalizedMessage(InMessageClass, Switch, RelatedPRI_1, RelatedPRI_2, OptionalObject); } else { BroadcastCustomWaveEndMessage(InMessageClass, Switch); } } function BroadcastCustomWaveEndMessage(class InMessageClass, optional int Switch) { local KFPlayerController_WeeklySurvival KFPC_WS; local PerkRoulette_PlayerMessageDelegate PlayerMessageData; PerkRoulette_PlayersDelegateData.Remove(0, PerkRoulette_PlayersDelegateData.Length); ClearTimer(nameof(BroadcastCustomDelegate)); foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { if (KFPC_WS.InitialRandomPerk == 255) { continue; } if (KFPC_WS.Pawn.IsAliveAndWell() == false || KFPC_WS.PlayerReplicationInfo.bOnlySpectator || KFPC_WS.IsInState('Spectating')) { PlayerMessageData.KFPC_WS = KFPC_WS; PlayerMessageData.InMessageClass = InMessageClass; PlayerMessageData.SwitchValue = Switch; PerkRoulette_PlayersDelegateData.AddItem(PlayerMessageData); continue; } KFPC_WS.ReceiveLocalizedMessage( InMessageClass, Switch, None, None, KFPC_WS.GetPerk().Class); } if (PerkRoulette_PlayersDelegateData.Length > 0) { SetTimer(1.f, true, nameof(BroadcastCustomDelegate)); } } function BroadcastCustomDelegate() { local int i; local PerkRoulette_PlayerMessageDelegate PlayerMessageData; for (i = PerkRoulette_PlayersDelegateData.Length - 1 ; i >= 0 ; --i) { PlayerMessageData = PerkRoulette_PlayersDelegateData[i]; if (PlayerMessageData.KFPC_WS == none) { PerkRoulette_PlayersDelegateData.Remove(i, 1); continue; } if (PlayerMessageData.KFPC_WS.Pawn.IsAliveAndWell() == false || PlayerMessageData.KFPC_WS.PlayerReplicationInfo.bOnlySpectator || PlayerMessageData.KFPC_WS.IsInState('Spectating')) { continue; } PlayerMessageData.KFPC_WS.ReceiveLocalizedMessage(PlayerMessageData.InMessageClass , PlayerMessageData.SwitchValue , None , None , PlayerMessageData.KFPC_WS.GetPerk().Class); PerkRoulette_PlayersDelegateData.Remove(i, 1); } if (PerkRoulette_PlayersDelegateData.Length == 0) { ClearTimer(nameof(BroadcastCustomDelegate)); } } // defaultproperties { //Overrides GameReplicationInfoClass=class'KFGameContent.KFGameReplicationInfo_WeeklySurvival' PlayerControllerClass=class'KFGame.KFPlayerController_WeeklySurvival' OutbreakEventClass=class'KFOutbreakEvent_Weekly' }