//============================================================================= // KFMapObjective_HoldVolume //============================================================================= // Objective type for the dosh hold objective. While this is active, players // have to keep zeds out of an area in order to earn bonus dosh. If no // players or any zed are in the volume, the wave end bonus ticks down. //============================================================================= // Killing Floor 2 // Copyright (C) 2017 Tripwire Interactive LLC // - Dan Weiss //============================================================================= class KFMapObjective_DoshHold extends KFMapObjective_AreaDefense; `include(KFGame/KFGameDialog.uci) /** Timer before penalty check starts */ var() const float PenaltyStartupTimer; /** Timer length for checking the dosh penalty rule set */ var() const float DoshPenaltyCheckTimer; /** Penalties for various states of the volume*/ var() const int NoHumansPenalty; var() const int ZedsPenalty; /** Whether the trail shows up during the previous trader time */ var() bool bUseEarlyTrail; /* Percentage of the wave's zeds that needs to be killed to obtain the max reward */ var() const float PctOfWaveZedsKilledForMaxReward; var transient float RewardPerZed; struct WaveLengthPctChances { var() float PctChances[11]; structdefaultproperties { PctChances[0]=1.f PctChances[1]=1.f PctChances[2]=1.f PctChances[3]=1.f PctChances[4]=1.f PctChances[5]=1.f PctChances[6]=1.f PctChances[7]=1.f PctChances[8]=1.f PctChances[9]=1.f PctChances[10]=1.f } }; var() WaveLengthPctChances ActivatePctChances[3]; /** Sound event to play when objective is activated (overrides default trader dialog for this event) */ var() AkEvent ActivationSoundEventOverride; /** A sound to play when the objective is fully complete (overrides default trader dialog for this event) */ var() AkEvent SuccessSoundEvent100pctOverride; /** A sound to play when the objective is mostly complete (overrides default trader dialog for this event) */ var() AkEvent SuccessSoundEvent85pctOverride; /** A sound to play when the objective is adequately complete (overrides default trader dialog for this event) */ var() AkEvent SuccessSoundEvent50pctOverride; /** A sound to play when the objective is barely complete (overrides default trader dialog for this event) */ var() AkEvent SuccessSoundEvent25pctOverride; /** A sound to play when the objective is failed (overrides default trader dialog for this event) */ var() AkEvent FailureSoundEventOverride; /** Sound event to play when wave is 25% complete */ var() AkEvent WaveProgressSoundEvent25pct; /** Sound event to play when wave is 50% complete */ var() AkEvent WaveProgressSoundEvent50pct; /** Sound event to play when wave is 75% complete */ var() AkEvent WaveProgressSoundEvent75pct; /** Sound event to play when wave is 90% complete */ var() AkEvent WaveProgressSoundEvent90pct; /** Sound event to play to remind players about the objective */ var() AkEvent RemindPlayersSoundEvent; /** How often to remind players about the objective if they aren't engaged in completing it */ var() float RemindPlayersTime; var transient float PrevWaveProgress; var transient bool bRemindPlayers; simulated event ReplicatedEvent(name VarName) { if (VarName == nameof(bActive)) { if (!bActive) { DeactivateObjective(); } } } //----------------------------------------------------------------------------- // Volume //----------------------------------------------------------------------------- event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal) { local int PlayerCount; super.Touch(Other, OtherComp, HitLocation, HitNormal); if (KFPawn_Human(Other) != none && TouchingHumans.Find(Other) == INDEX_NONE) { PlayerCount = Clamp(KFGameInfo(WorldInfo.Game).GetLivingPlayerCount(), 1, 6) - 1; if (TouchingHumans.Length >= PlayerThresholds[PlayerCount] && IsTimerActive('StartPenaltyCheck')) { StartPenaltyCheck(); } } } function NotifyZedKilled(Controller Killer, Pawn KilledPawn, bool bIsBoss) { local int i; local KFGameInfo KFGI; local KFGameReplicationInfo KFGRI; if (ROLE == Role_Authority) { if (bActive) { KFGI = KFGameInfo(WorldInfo.Game); if (KFGI != none) { for (i = 0; i < TouchingHumans.Length; i++) { if (Killer == TouchingHumans[i].Controller) { if (RewardPerZed == 0) { KFGRI = KFGameReplicationInfo(WorldInfo.Game.GameReplicationInfo); RewardPerZed = GetMaxDoshReward() / (PctOfWaveZedsKilledForMaxReward * KFGRI.WaveTotalAICount); } CurrentRewardAmount = FMin(CurrentRewardAmount + RewardPerZed, float(GetMaxDoshReward())); break; } } } if (GetProgress() >= 1.0f) { DeactivateObjective(); } } } } //// Designers wanted this, but now they don't want it, but they might want it again, //// so the code is staying here. //simulated function NotifyObjectiveSelected() //{ // if (WorldInfo.NetMode != NM_DedicatedServer) // { // ActivateBoundarySplines(); // UpdateMeshArrayState(); // // if (bUseTrailToVolume && bUseEarlyTrail) // { // if (TrailActor == none) // { // TrailActor = class'WorldInfo'.static.GetWorldInfo().Spawn(class'KFReplicatedShowPathActor', none); // } // if (TrailActor != none) // { // TrailActor.SetEmitterTemplate(ParticleSystem'FX_Gameplay_EMIT.FX_Objective_White_Trail'); // TrailActor.SetPathTarget(self, self, VCT_NotInVolume); // } // } // } //} function CheckBonusState() { local int i; local int PlayerCount; bDangerState = false; PlayerCount = Clamp(KFGameInfo(WorldInfo.Game).GetLivingPlayerCount(), 1, 6) - 1; if (TouchingHumans.Length < PlayerThresholds[PlayerCount]) { bDangerState = true; if(bRemindPlayers) { if (RemindPlayersSoundEvent != none) { PlaySoundBase(RemindPlayersSoundEvent,, WorldInfo.NetMode == NM_DedicatedServer); SetTimer(RemindPlayersTime, false, 'Timer_AllowRemindPlayers'); } bRemindPlayers = false; } } else if (TouchingZeds.Length > 0) { //Remove dead zeds for (i = TouchingZeds.Length - 1; i >= 0; --i) { if (!IsValidZed(TouchingZeds[i])) { TouchingZeds.Remove(i, 1); } } } UpdateMeshArrayState(); } simulated function Timer_AllowRemindPlayers() { bRemindPlayers = true; } function StartPenaltyCheck() { ClearTimer('StartPenaltyCheck'); if(bActive) { SetTimer(DoshPenaltyCheckTimer, true, 'CheckBonusState'); } } function ActivationVO() { if (ActivationSoundEventOverride != none) { PlaySoundBase(ActivationSoundEventOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendA, WorldInfo, true); } } simulated function ActivateObjective() { local int PlayerCount; super.ActivateObjective(); if (Role == ROLE_Authority) { // dosh hold reward starts at zero and counts up when a zed is killed CurrentRewardAmount = 0; // RewardPerZed will be set upon killing the first zed because this function (ActivateObjective) // is called before KFGRI.WaveTotalAICount is set for the current wave RewardPerZed = 0; PlayerCount = Clamp(KFGameInfo(WorldInfo.Game).GetLivingPlayerCount(), 1, 6) - 1; if (TouchingHumans.Length >= PlayerThresholds[PlayerCount]) { StartPenaltyCheck(); } else { SetTimer(PenaltyStartupTimer, false, 'StartPenaltyCheck'); } //Because we're tired of dealing with bugs in the VO system in smart ways, delay by a frame // to avoid first tick replication not having a controller. SetTimer(0.01f, false, 'ActivationVO'); SetTimer(1.f, true, 'Timer_CheckPawnCount'); SetTimer(1.f, true, 'Timer_CheckWaveProgress'); PrevWaveProgress = 0; bRemindPlayers = true; } } simulated function Timer_CheckWaveProgress() { local KFGameReplicationInfo KFGRI; local float WaveProgress; KFGRI = KFGameReplicationInfo(WorldInfo.Game.GameReplicationInfo); if (KFGRI != none) { WaveProgress = 1.f - (float(KFGRI.AIRemaining) / float(KFGRI.WaveTotalAICount)); if (PrevWaveProgress < 0.25 && WaveProgress >= 0.25) { PlaySoundBase(WaveProgressSoundEvent25pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (PrevWaveProgress < 0.5 && WaveProgress >= 0.5) { PlaySoundBase(WaveProgressSoundEvent50pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (PrevWaveProgress < 0.75 && WaveProgress >= 0.75) { PlaySoundBase(WaveProgressSoundEvent75pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (PrevWaveProgress < 0.9 && WaveProgress >= 0.9) { PlaySoundBase(WaveProgressSoundEvent90pct, false, WorldInfo.NetMode == NM_DedicatedServer); } PrevWaveProgress = WaveProgress; } } simulated function Timer_CheckPawnCount() { local int PlayerCount; PlayerCount = Clamp(KFGameInfo(WorldInfo.Game).GetLivingPlayerCount(), 1, 6) - 1; bTooFewPlayers = TouchingHumans.Length < PlayerThresholds[PlayerCount]; bTooManyZeds = TouchingZeds.Length > ZedThresholds[PlayerCount]; } simulated function DeactivateObjective() { local KFPawn_Human KFPH; local KFPlayerController KFPC; local bool bOneHumanAlive; super.DeactivateObjective(); if (Role == ROLE_Authority) { ClearTimer('CheckBonusState'); ClearTimer('StartPenaltyCheck'); ClearTimer('Timer_AllowRemindPlayers'); ClearTimer('Timer_CheckWaveProgress'); ClearTimer('Timer_CheckPawnCount'); bOneHumanAlive = false; foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { if (KFPH.IsAliveAndWell()) { bOneHumanAlive = true; } } // give the dosh hold reward to a player even if they died if (CurrentRewardAmount > 0 && bOneHumanAlive) { foreach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { GrantReward(KFPlayerReplicationInfo(KFPC.PlayerReplicationInfo), KFPC); } } if (bOneHumanAlive) { PlayDeactivationDialog(); } } KFPC = KFPlayerController(GetALocalPlayerController()); if (KFPC != none && KFPC.MyGFxHUD != none) { KFPC.MyGFxHUD.WaveInfoWidget.ObjectiveContainer.SetFailState(CurrentRewardAmount <= 0); } } function PlayDeactivationDialog() { if (CurrentRewardAmount <= 0) { if (FailureSoundEventOverride != none) { PlaySoundBase(FailureSoundEventOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendAFailed, WorldInfo, true); } BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GMT_ObjectiveLost); } else { if (GetProgress() <= JustWinThreshold) { if (SuccessSoundEvent25pctOverride != none) { PlaySoundBase(SuccessSoundEvent25pctOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendAWonJust, WorldInfo, true); } } else if (GetProgress() <= StandardWinThreshold) { if (SuccessSoundEvent50pctOverride != none) { PlaySoundBase(SuccessSoundEvent50pctOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendAWon, WorldInfo, true); } } else if (GetProgress() <= GoodWinThreshold) { if (SuccessSoundEvent85pctOverride != none) { PlaySoundBase(SuccessSoundEvent85pctOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendAWonGood, WorldInfo, true); } } else { if (SuccessSoundEvent100pctOverride != none) { PlaySoundBase(SuccessSoundEvent100pctOverride, false, WorldInfo.NetMode == NM_DedicatedServer); } else { class'KFTraderDialogManager'.static.PlayGlobalDialog(`TRAD_ObjDefendAWonPerf, WorldInfo, true); } } } } simulated function int GetDoshReward() { return CurrentRewardAmount; } simulated function float GetProgress() { local int MaxDoshReward; MaxDoshReward = GetMaxDoshReward(); if (MaxDoshReward == 0) { return 0; } return CurrentRewardAmount / float(MaxDoshReward); } simulated function bool IsComplete() { return GetProgress() > 0.f && !bActive; } simulated function float GetActivationPctChance() { local KFGameReplicationInfo KFGRI; local int ArrayEnd; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGRI != none) { // Since we're using a static array for rewards, we need to know the true end of the array. ArrayEnd = Clamp(KFGRI.WaveMax - 2, 0, ArrayCount(ActivatePctChances[KFGRI.GameLength].PctChances) - 1); return ActivatePctChances[KFGRI.GameLength].PctChances[Clamp(KFGRI.WaveNum - 1, 0, ArrayEnd)]; } return 1.f; } simulated function string GetLocalizedRequirements() { local int PlayerCount; PlayerCount = Clamp(KFGameReplicationInfo(WorldInfo.GRI).GetNumPlayers(), 1, 6) - 1; return Localize("Objectives", default.RequirementsLocKey, "KFGame") @ PlayerThresholds[PlayerCount]; } simulated function GetLocalizedStatus(out string statusMessage, out int bWarning, out int bNotification); simulated function string GetProgressText() { return string(GetMaxDoshReward()); } simulated function bool GetProgressTextIsDosh() { return true; } simulated function bool ShouldShowObjectiveHUD() { return !IsComplete(); } defaultproperties { DescriptionLocKey="DescriptionDoshHold" DescriptionShortLocKey="DescriptionDoshHoldShort" RequirementsLocKey = "RequiredDoshHold" LocalizationKey="DoshHold" NameShortLocKey="DoshHold" PenaltyStartupTimer=20.f DoshPenaltyCheckTimer=0.25f NoHumansPenalty=5 ZedsPenalty=1 // Short Match ActivatePctChances[0]={( PctChances[0]=.0, PctChances[1]=.35, PctChances[2]=.35, PctChances[3]=.35 )} // Normal Match ActivatePctChances[1]={( PctChances[0]=.0, PctChances[1]=.35, PctChances[2]=.35, PctChances[3]=.35, PctChances[4]=.35, PctChances[5]=.35, PctChances[6]=.35 )} // Long Match ActivatePctChances[2]={( PctChances[0]=.0, PctChances[1]=.35, PctChances[2]=.35, PctChances[3]=.35, PctChances[4]=.35, PctChances[5]=.35, PctChances[6]=.35, //6 PctChances[7]=.35, //7 PctChances[8]=.35, //8 PctChances[9]=.35, //9 PctChances[10]=.35 //1.0 )} PlayerThresholds[0]=1 PlayerThresholds[1]=1 PlayerThresholds[2]=1 PlayerThresholds[3]=1 PlayerThresholds[4]=1 PlayerThresholds[5]=1 ZedThresholds[0]=0 ZedThresholds[1]=0 ZedThresholds[2]=0 ZedThresholds[3]=0 ZedThresholds[4]=0 ZedThresholds[5]=0 ObjectiveIcon=Texture2D'Objectives_UI.UI_Objectives_ObjectiveMode' RemindPlayersTime=30.f bUseEarlyTrail=false DoshRewards = (150,150,200,250,300,350,400,400,400,400,400) XPRewards = (50,50,100,150,200,250,300,350,350,350,350) XPDifficultyScalars=(1.0f,1.1f,1.2f,1.3f) DoshDifficultyScalars=(1.0f,1.1f,1.2f,1.3f) PctOfWaveZedsKilledForMaxReward=0.7f }