//============================================================================= // KFGameInfo_WeeklySurvival //============================================================================= // Weekly variant of KFPC for handling per-player functionality related to // different weekly survival modes. //============================================================================= // Killing Floor 2 // Copyright (C) 2017 Tripwire Interactive LLC // - Dan Weiss //============================================================================= class KFPlayerController_WeeklySurvival extends KFPlayerController native(Controller) dependson(EphemeralMatchStats); /** If the game mode is using permanent zed time, our handling of some functionality is a bit different */ var bool bUsingPermanentZedTime; /** One of our changes is a radius/height check for relevant actors that would normally kick us out of zed time */ var float ZedTimeRadius; var float ZedTimeBossRadius; var float ZedTimeHeight; /** How often to check for coming out of partial zed time if bUsingPermanentZedTime is on */ var float ZedRecheckTime; /** Number of consecutive goompa stomps */ var int GoompaStreak; /** Bonus to apply */ var int GoompaStreakBonus; /** Max number of goompa stomps for damage bonus */ var transient int MaxGoompaStreak; var protected const name RhytmMethodRTPCName; var protected const AkEvent RhythmMethodSoundReset; var protected const AkEvent RhythmMethodSoundHit; var protected const AkEvent RhythmMethodSoundTop; var protected const AkEvent AracnoStompSoundEvent; var protected const AKEvent GunGameLevelUpSoundEvent; var protected const AKEvent GunGameLevelUpFinalWeaponSoundEvent; var protected const AKEvent VIPChosenSoundEvent; var protected const AKEvent VIPLowHealthSoundEvent; var protected float VIPLowHealthLastTimePlayed; struct native GunGameInfo { var transient byte Level; var transient int Score; var array GunGamePreselectedWeapons; var byte WaveToUseForRestart; var bool GiveWeaponMaster; structdefaultproperties { Level=0; Score=0; WaveToUseForRestart=0; GiveWeaponMaster=false; } }; var transient GunGameInfo GunGameData; struct native VIPGameInfo { var bool IsVIP; var bool WasVIP; var bool PendingHealthReset; var int ExtraHealth; var int DamageHealthLimit; var int DamageHealthTop; var int DamageHealthBottom; var float DamageLimitModifier; var float OutputDamageTopModifier; var float InputDamageTopModifier; var float OutputDamageBottomModifier; var float InputDamageBottomModifier; structdefaultproperties { IsVIP = false WasVIP = false PendingHealthReset = false ExtraHealth = 100 DamageHealthLimit = 100 DamageHealthTop = 50 DamageHealthBottom = 25 DamageLimitModifier = 1.0 OutputDamageTopModifier = 1.5 InputDamageTopModifier = 0.75 OutputDamageBottomModifier = 1.75 InputDamageBottomModifier = 0.5 } }; var transient VIPGameInfo VIPGameData; cpptext { virtual UBOOL TestZedTimeVisibility(APawn* P, UNetConnection* Connection, UBOOL bLocalPlayerTest) override; } replication { if (bNetDirty) bUsingPermanentZedTime, ZedTimeRadius, ZedTimeBossRadius, ZedTimeHeight, GoompaStreak, GunGameData; } simulated event PostBeginPlay() { local KFGameInfo KFGI; super.PostBeginPlay(); KFGI = KFGameInfo(WorldInfo.Game); if (KFGI != none && KFGI.OutbreakEvent != none && KFGI.OutbreakEvent.ActiveEvent.bGoompaJumpEnabled) { MaxGoompaStreak = KFGI.OutbreakEvent.ActiveEvent.GoompaStreakMax; } } function EnterZedTime() { local bool bNewResult; local KFPawn KFP; local KFPerk MyPerk; //Some hax around rechecking zed time if we're in permanent mode. Do the recheck on IsAffectedByZedTime // and only call client function if the result is different than before. if (bUsingPermanentZedTime) { SetTimer(ZedRecheckTime, false, 'RecheckZedTime'); KFP = KFPawn(Pawn); if (KFP != none) { bNewResult = IsAffectedByZedTime(); //== because Pawn is !PC result if (KFP.bUnaffectedByZedTime == bNewResult) { MyPerk = GetPerk(); if ( MyPerk != none ) { MyPerk.NotifyZedTimeStarted(); } KFP.bUnaffectedByZedTime = !bNewResult; if ( KFP.bUnaffectedByZedTime ) { StartPartialZedTimeSightCounter(); } // Call the client ClientEnterZedTime(KFP.bUnaffectedByZedTime); } } } else { super.EnterZedTime(); } } function RecheckZedTime() { EnterZedTime(); } reliable client function UpdateWaveCount() { if (MyGFxHUD != none) { MyGFxHUD.UpdateWaveCount(); } } reliable client function UpdateGunGameWidget(int score, int max_score, int level, int max_level) { if (MyGFxHUD != none) { MyGFxHUD.UpdateGunGameWidget(score, max_score, level, max_level); } } simulated function UpdateVIPWidget(ReplicatedVIPGameInfo VIPInfo) { if (MyGFxHUD != none) { MyGFxHUD.UpdateVIP(VIPInfo, VIPInfo.VIPPlayer == PlayerReplicationInfo); } } function bool CanUseHealObject() { local KFGameReplicationInfo KFGRI; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); // VIP cannot heal if (KFGRI != none && KFGRI.VIPRepPlayer != none && KFGRI.VIPRepPlayer == KFPlayerReplicationInfo(PlayerReplicationInfo)) { return false; } return super.CanUseHealObject(); } /** Arachnophobia Goompa Stomp Streak functions */ function UpdateGoompaStreak() { ++GoompaStreak; GoompaStreakBonus = GoompaStreak; UpdateGoompaCounterWidget(GoompaStreak, MaxGoompaStreak); GoompaStompMessage(GoompaStreak); if (IsTimerActive(nameof(ResetStreakInfo))) { ClearTimer(nameof(ResetStreakInfo)); } } function ResetGoompaStreak() { local KFGameInfo KFGI; if (GoompaStreak > 0) { KFGI = KFGameInfo(WorldInfo.Game); GoompaStreak = 0; if(KFGI != none) { SetTimer(KFGI.OutbreakEvent.ActiveEvent.GoompaBonusDuration, false, nameof(ResetStreakInfo)); } } } function ResetStreakInfo() { UpdateGoompaCounterWidget(GoompaStreak, MaxGoompaStreak); GoompaStompMessage(GoompaStreak); GoompaStreakBonus = 0; } function bool IsGoompaBonusActive() { return GoompaStreakBonus > 0; } reliable client function GoompaStompMessage( byte StompNum) { local int i; local AkEvent TempAkEvent; if( MyGFxHUD == none ) { return; } i = StompNum; UpdateGoompaCounterWidget( StompNum, MaxGoompaStreak ); if (StompNum == 0) { TempAkEvent = RhythmMethodSoundReset; } else if (StompNum == MaxGoompaStreak - 1) { TempAkEvent = RhythmMethodSoundHit; } else if (StompNum == MaxGoompaStreak) { TempAkEvent = RhythmMethodSoundTop; ++i; } if( TempAkEvent != none ) { PlayRMEffect( TempAkEvent, RhytmMethodRTPCName, i ); } if (StompNum > 0 && AracnoStompSoundEvent != none) { PlaySoundBase(AracnoStompSoundEvent); } } reliable client function PlayGunGameMessage(bool isLastLevel) { if (isLastLevel) { if (GunGameLevelUpFinalWeaponSoundEvent != none) { PlaySoundBase(GunGameLevelUpFinalWeaponSoundEvent); } } else { if (GunGameLevelUpSoundEvent != none) { PlaySoundBase(GunGameLevelUpSoundEvent); } } } reliable client function PlayVIPSound_ChosenInternal() { if (VIPChosenSoundEvent != none) { PlaySoundBase(VIPChosenSoundEvent); } } reliable client function PlayVIPGameChosenSound(float delay) { // Put a timer because the sound happens at the same time as end wave and it's difficult to distinguish SetTimer(delay, false, nameof(PlayVIPSound_ChosenInternal)); } reliable client function PlayVIPGameLowHealthSound() { if (VIPLowHealthSoundEvent != none) { if (WorldInfo.TimeSeconds - VIPLowHealthLastTimePlayed > 8.f) { VIPLowHealthLastTimePlayed = WorldInfo.TimeSeconds; PlaySoundBase(VIPLowHealthSoundEvent); } } } /** Resets all gameplay FX to initial state. Append to this list if additional effects are added. */ function ResetGameplayPostProcessFX() { local KFGameReplicationInfo KFGRI; super.ResetGameplayPostProcessFX(); KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if((KFGRI != none && KFGRI.bIsWeeklyMode && KFGRI.CurrentWeeklyIndex == 12) && GameplayPostProcessEffectMIC != none) { GameplayPostProcessEffectMIC.SetScalarParameterValue(EffectZedTimeSepiaParamName, 1.f); } } simulated function SetBossCamera( KFInterface_MonsterBoss Boss ) { local KFGameReplicationInfo KFGRI; local bool bIsBossRush; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); bIsBossRush = KFGRI.bIsWeeklyMode && KFGRI.CurrentWeeklyIndex == 14; if (bIsBossRush && Boss.GetHealthPercent() <= 0.0f && KFGRI.WaveNum != KFGRI.WaveMax) { SetTimer(5.0f, false, nameof(ResetBossCamera)); } super.SetBossCamera(Boss); } simulated function ResetBossCamera() { super(PlayerController).ResetCameraMode(); } function RestartGunGame() { local KFGameInfo KFGI; local KFGameReplicationInfo KFGRI; KFGI = KFGameInfo(WorldInfo.Game); KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGI != none && KFGRI != none) { KFGI.RestartGunGamePlayerWeapon(self, GunGameData.WaveToUseForRestart); } } function UpdateInitialHeldWeapon() { //local KFWeapon KFW; local KFPawn_Human KFPH; local KFGameInfo KFGI; local KFGameReplicationInfo KFGRI; KFPH = KFPawn_Human(Pawn); if (KFPH == none || KFPH.InvManager == none) { return; } /*foreach KFPH.InvManager.InventoryActors( class'KFWeapon', KFW ) { /** Seems its in order, so knife goes first. Equip it */ KFPH.InvManager.SetCurrentWeapon(KFW); break; }*/ KFGI = KFGameInfo(WorldInfo.Game); KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGI != none && KFGRI != none) { KFGI.ResetGunGame(self); GunGameData.WaveToUseForRestart = KFGRI.WaveNum; SetTimer(1.0, false, 'RestartGunGame'); } } function AdjustDamage(out int InDamage, Controller InstigatedBy, class DamageType, Actor DamageCauser, Actor DamageReceiver) { local KFGameInfo KFGI; local float Multiplier, ModifierRange, HealthTop, HealthRange; local KFGameReplicationInfo KFGRI; super.AdjustDamage(InDamage, InstigatedBy, DamageType, DamageCauser, DamageReceiver); KFGI = KFGameInfo(WorldInfo.Game); if (Pawn != None && KFGI != none && KFGI.OutbreakEvent != none && KFGI.OutbreakEvent.ActiveEvent.bVIPGameMode) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); // If I am the VIP doing the damage, and I am NOT doing damage to myself if (KFGRI != none && KFGRI.VIPRepPlayer != none && KFGRI.VIPRepPlayer == KFPlayerReplicationInfo(PlayerReplicationInfo) && InstigatedBy == self && DamageReceiver != self.Pawn) { Multiplier = 1.0; //`Log("Current health for VIP OUTPUT DAMAGE: " $Pawn.Health); if (Pawn.Health < VIPGameData.DamageHealthLimit) { if (Pawn.Health <= VIPGameData.DamageHealthBottom) { Multiplier = VIPGameData.OutputDamageBottomModifier; } else { if (Pawn.Health > VIPGameData.DamageHealthTop) { Multiplier = VIPGameData.DamageLimitModifier; // From 1.0 to 1.5 on the range of 100 - 50 ModifierRange = VIPGameData.OutputDamageTopModifier - VIPGameData.DamageLimitModifier; HealthTop = VIPGameData.DamageHealthLimit; HealthRange = Abs(HealthTop - VIPGameData.DamageHealthTop); } else { // From 1.5 to 1.75 on the range of 50 - 25 Multiplier = VIPGameData.OutputDamageTopModifier; ModifierRange = VIPGameData.OutputDamageBottomModifier - VIPGameData.OutputDamageTopModifier; HealthTop = VIPGameData.DamageHealthTop; HealthRange = Abs(HealthTop - VIPGameData.DamageHealthBottom); } Multiplier += ModifierRange * ((HealthTop - Pawn.Health) / HealthRange); } } else { Multiplier = VIPGameData.DamageLimitModifier; } //`Log("Multiplier for VIP OUTPUT DAMAGE: Output: " $Multiplier); InDamage = int(float(InDamage) * Multiplier); } } } function AdjustVIPDamage(out int InDamage, Controller InstigatedBy) { local KFGameInfo KFGI; local float Multiplier, ModifierRange, HealthTop, HealthRange; local KFGameReplicationInfo KFGRI; KFGI = KFGameInfo(WorldInfo.Game); if (Pawn != None && KFGI != none && KFGI.OutbreakEvent != none && KFGI.OutbreakEvent.ActiveEvent.bVIPGameMode) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); // If I am the VIP // We do it on a different step as don't want to scale InDamage to VIP Armour when receiving damage if (KFGRI != none && KFGRI.VIPRepPlayer != none && KFGRI.VIPRepPlayer == KFPlayerReplicationInfo(PlayerReplicationInfo)) { Multiplier = 1.0; //`Log("Current health for VIP INPUT DAMAGE: " $Pawn.Health); if (Pawn.Health < VIPGameData.DamageHealthLimit) { if (Pawn.Health <= VIPGameData.DamageHealthBottom) { Multiplier = VIPGameData.InputDamageBottomModifier; } else { if (Pawn.Health > VIPGameData.DamageHealthTop) { Multiplier = VIPGameData.DamageLimitModifier; // From 1.0 to 0.5 on the range of 100 - 50 ModifierRange = VIPGameData.InputDamageTopModifier - VIPGameData.DamageLimitModifier; HealthTop = VIPGameData.DamageHealthLimit; HealthRange = Abs(HealthTop - VIPGameData.DamageHealthTop); } else { // From 0.5 to 0.25 on the range of 50 - 25 Multiplier = VIPGameData.InputDamageTopModifier; ModifierRange = VIPGameData.InputDamageBottomModifier - VIPGameData.InputDamageTopModifier; HealthTop = VIPGameData.DamageHealthTop; HealthRange = Abs(HealthTop - VIPGameData.DamageHealthBottom); } Multiplier += ModifierRange * ((HealthTop - Pawn.Health) / HealthRange); } } else { Multiplier = VIPGameData.DamageLimitModifier; } //`Log("Multiplier for VIP INPUT DAMAGE: Output: " $Multiplier); InDamage = int(float(InDamage) * Multiplier); } } } function NotifyTakeHit(Controller InstigatedBy, vector HitLocation, int Damage, class damageType, vector Momentum) { local KFPlayerController_WeeklySurvival KFPC_WS; Super.NotifyTakeHit(InstigatedBy,HitLocation,Damage,damageType,Momentum); if (VIPGameData.IsVIP) { // Only sound once we pass down 50, sound again if recovered health and go down again if (Pawn.Health < 50 && Pawn.Health + Damage >= 50) { foreach WorldInfo.AllControllers(class'KFPlayerController_WeeklySurvival', KFPC_WS) { KFPC_WS.PlayVIPGameLowHealthSound(); } } } } function UpdateVIPDamage() { local KFGameReplicationInfo KFGRI; if (VIPGameData.IsVIP) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGRI != none) { KFGRI.UpdateVIPCurrentHealth(Pawn.Health); } } } // defaultProperties { GoompaStreak = 0 MaxGoompaStreak = -1 GoompaStreakBonus = 0 RhytmMethodRTPCName ="R_Method" RhythmMethodSoundReset =AkEvent'WW_UI_PlayerCharacter.Play_R_Method_Reset' RhythmMethodSoundHit =AkEvent'WW_UI_PlayerCharacter.Play_R_Method_Hit' RhythmMethodSoundTop =AkEvent'WW_UI_PlayerCharacter.Play_R_Method_Top' AracnoStompSoundEvent =AkEvent'WW_GLO_Runtime.WeeklyArcno' GunGameLevelUpSoundEvent=AkEvent'WW_GLO_Runtime.WeeklyAALevelUp' GunGameLevelUpFinalWeaponSoundEvent=AkEvent'WW_GLO_Runtime.WeeklyAALevelFinal' VIPChosenSoundEvent=AkEvent'WW_UI_Menu.Play_AAR_TOPWEAPON_SLIDEIN_B' VIPLowHealthSoundEvent=AkEvent'WW_GLO_Runtime.WeeklyVIPAlarm' VIPLowHealthLastTimePlayed = 0.f }