//============================================================================= // KFMapObjective_EscortPawns //============================================================================= // Objective type for escorting pawns. Pawns can be spawned in sequence or at // the same time and need to be defended. //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class KFMapObjective_EscortPawns extends KFMapObjective_ActorBase; // Scripted pawn spawners that will spawn the escorts related to this objective var() array EscortPawnSpawners; /** How many escorts need to be escorted successfully for the objective to be completed. */ var() int NumEscortsRequired; var int NumEscortsAccomplished; var int NumEscortsFinished; var float Progress; var repnotify transient Actor ActiveEscortActor; var float CompletionPct; // Sound to play when escort is 100% done and in perfect health var() AkEvent ProgressSoundEvent100pctPerfect; // Sound to play when escort is 100% done var() AkEvent ProgressSoundEvent100pct; // Sound to play when escort is 100% done and in poor health var() AkEvent ProgressSoundEvent100pctPoor; // Sound to play when escort is 85% done and in perfect health var() AkEvent ProgressSoundEvent85pctPerfect; // Sound to play when escort is 85% done var() AkEvent ProgressSoundEvent85pct; // Sound to play when escort is 85% done and in poor health var() AkEvent ProgressSoundEvent85pctPoor; // Sound to play when escort is 50% done and in perfect health var() AkEvent ProgressSoundEvent50pctPerfect; // Sound to play when escort is 50% done var() AkEvent ProgressSoundEvent50pct; // Sound to play when escort is 50% done and in poor health var() AkEvent ProgressSoundEvent50pctPoor; // Sound to play when escort is 25% done and in perfect health var() AkEvent ProgressSoundEvent25pctPerfect; // Sound to play when escort is 25% done var() AkEvent ProgressSoundEvent25pct; // Sound to play when escort is 25% done and in poor health var() AkEvent ProgressSoundEvent25pctPoor; // Percentage of escort health considered to be "poor," used for the "poor" progress sound events var() float PoorHealthPctThreshold; // Sound to play when escort takes damage var() AkEvent EscortPawnTakeDamageEvent; // Cooldown period for the "take damage" sound event var() float EscortPawnTakeDamageEventCooldown; // Sound to play to remind players that escort is in critical condition var() AkEvent EscortPawnCriticalReminderEvent; // How frequently to remind players that escort is in critical condition var() float EscortPawnCriticalReminderEventInterval; var repnotify int CompletionProgressIdx, HealthProgressIdx; replication { if (bNetDirty) Progress, ActiveEscortActor, CompletionPct, CompletionProgressIdx, HealthProgressIdx; } simulated function ReplicatedEvent(name VarName) { switch (VarName) { case nameof(ActiveEscortActor): UpdateTrailActor(); break; case nameof(CompletionProgressIdx): TriggerCompletionPctProgress(); break; case nameof(HealthProgressIdx): TriggerHealthPctProgress(); break; default: super.ReplicatedEvent(VarName); }; } function CheckCompletionPctProgress(float PrevCompletionPct, float CurrCompletionPct) { local int PrevCompletionProgressIdx, GenEvtIdx, i; local KFSeqEvent_EscortPawnsCompletionPct GenEvt; PrevCompletionProgressIdx = CompletionProgressIdx; // check the first applicable generated event (assumes all applicable events are identical) for (GenEvtIdx = 0; GenEvtIdx < GeneratedEvents.Length; GenEvtIdx++) { GenEvt = KFSeqEvent_EscortPawnsCompletionPct(GeneratedEvents[GenEvtIdx]); if (GenEvt != None) { for (i = 0; i < GenEvt.ProgressThresholds.Length; i++) { if (PrevCompletionPct < GenEvt.ProgressThresholds[i] && CurrCompletionPct >= GenEvt.ProgressThresholds[i]) { CompletionProgressIdx = GenEvt.ProgressOutputStartIndex + i; break; } } break; } } if (CompletionProgressIdx != PrevCompletionProgressIdx) { TriggerCompletionPctProgress(); } } simulated function TriggerCompletionPctProgress() { local int GenEvtIdx; local KFSeqEvent_EscortPawnsCompletionPct GenEvt; local array ActivateIndices; ActivateIndices.AddItem(CompletionProgressIdx); // notify all applicable generated events (assumes all applicable events are identical) for (GenEvtIdx = 0; GenEvtIdx < GeneratedEvents.Length; GenEvtIdx++) { GenEvt = KFSeqEvent_EscortPawnsCompletionPct(GeneratedEvents[GenEvtIdx]); if (GenEvt != None) { GenEvt.Reset(); GenEvt.CheckActivate(self, self,, ActivateIndices); } } } function CheckHealthPctProgress(float PrevHealthPct, float CurrHealthPct) { local int PrevHealthProgressIdx, GenEvtIdx, i; local KFSeqEvent_EscortPawnsHealthPct GenEvt; PrevHealthProgressIdx = HealthProgressIdx; // check the first applicable generated event (assumes all applicable events are identical) for (GenEvtIdx = 0; GenEvtIdx < GeneratedEvents.Length; GenEvtIdx++) { GenEvt = KFSeqEvent_EscortPawnsHealthPct(GeneratedEvents[GenEvtIdx]); if (GenEvt != None) { if (GenEvt.bUseReverseProgress) { for (i = 0; i < GenEvt.ProgressThresholds.Length; i++) { if (PrevHealthPct >= GenEvt.ProgressThresholds[i] && CurrHealthPct < GenEvt.ProgressThresholds[i]) { // set idx negative for reverse progress // add 1 to keep idx from being 0, since 0 is ambiguous HealthProgressIdx = -(GenEvt.ProgressOutputStartIndex + i + 1); break; } } } else { for (i = 0; i < GenEvt.ProgressThresholds.Length; i++) { if (PrevHealthPct < GenEvt.ProgressThresholds[i] && CurrHealthPct >= GenEvt.ProgressThresholds[i]) { // add 1 to keep idx from being 0, since 0 is ambiguous HealthProgressIdx = GenEvt.ProgressOutputStartIndex + i + 1; break; } } } if (HealthProgressIdx != PrevHealthProgressIdx) { TriggerHealthPctProgress(); break; } } } } simulated function TriggerHealthPctProgress() { local int GenEvtIdx; local KFSeqEvent_EscortPawnsHealthPct GenEvt; local array ActivateIndices; // subtract the 1 that we added in CheckHealthPctProgress ActivateIndices.AddItem(Max(HealthProgressIdx, -HealthProgressIdx) - 1); // notify all applicable generated events (assumes all applicable events are identical) for (GenEvtIdx = 0; GenEvtIdx < GeneratedEvents.Length; GenEvtIdx++) { GenEvt = KFSeqEvent_EscortPawnsHealthPct(GeneratedEvents[GenEvtIdx]); if (GenEvt != None) { if ((GenEvt.bUseReverseProgress && HealthProgressIdx < 0) || (!GenEvt.bUseReverseProgress && HealthProgressIdx > 0)) { GenEvt.Reset(); GenEvt.CheckActivate(self, self,, ActivateIndices); } } } } // Interface functions simulated function ActivateObjective() { local KFScriptedPawnSpawner EscortPawnStart; super.ActivateObjective(); if (Role == ROLE_Authority) { bIsActive = true; NumEscortsAccomplished = 0; NumEscortsFinished = 0; // set delegates to help keep track of objective progress foreach EscortPawnSpawners(EscortPawnStart) { EscortPawnStart.Delegate_OnPawnStartedRoute = OnEscortStarted; EscortPawnStart.Delegate_OnPawnReachedRouteMarker = OnEscortReachedRouteMarker; EscortPawnStart.Delegate_OnPawnReachedGoal = OnEscortReachedGoal; EscortPawnStart.Delegate_OnPawnEndedRoute = OnEscortEndedRoute; // reset progress goals if this has already been completed once if (EscortPawnStart.bReachedGoal) { EscortPawnStart.bReachedStart = false; EscortPawnStart.bReachedGoal = false; EscortPawnStart.RouteDistTraversed = 0; } // make sure to set up any scripted pawns that were spawned before the objective started if (EscortPawnStart.Pawn != none && EscortPawnStart.bReachedStart) { OnEscortStarted(EscortPawnStart.Pawn); } } } } simulated function DeactivateObjective() { local KFPawn_Human KFPH; super.DeactivateObjective(); if (Role == ROLE_Authority) { bIsActive = false; if (NumEscortsAccomplished >= NumEscortsRequired) { foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { GrantReward(KFPlayerReplicationInfo(KFPH.PlayerReplicationInfo), KFPlayerController(KFPH.Controller)); } } else { if (FailureSoundEvent != none) { PlaySoundBase(FailureSoundEvent, false, WorldInfo.NetMode == NM_DedicatedServer); } } } if (WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor != none) { TrailActor.Destroy(); TrailActor = none; } if (ActiveEscortActor != none) { KFPawn_Scripted(ActiveEscortActor).RemoveEscortPawnOnHud(); } } } simulated function bool IsActive() { return bIsActive; } simulated function bool UsesProgress() { return true; } simulated function bool IsBonus(); function bool CanActivateObjective() { return !IsCurrentGameModeBlacklisted(); } function bool IsCurrentGameModeBlacklisted() { local class CurrGameClass; foreach GameModeBlacklist(CurrGameClass) { if (CurrGameClass == WorldInfo.GRI.GameClass) { return true; } } return false; } simulated function float GetProgress() { return FClamp(Progress, 0.f, 1.f); } simulated function bool IsComplete() { return GetProgress() >= 1.f; } simulated function float GetActivationPctChance() { return 1.f; } simulated function string GetProgressText() { return int(GetProgress() * 100) $ "%"; } simulated function string GetLocalizedRequirements() { return ""; } simulated function GetLocalizedStatus(out string statusMessage, out int bWarning, out int bNotification) { local KFPawn_Scripted ActiveScriptedPawn; statusMessage = ""; if (GetProgress() >= 1.f) { statusMessage = Localize("Objectives", "KillRemainingZeds", LocalizationPackageName); bWarning = 0; bNotification = 0; return; } ActiveScriptedPawn = KFPawn_Scripted(ActiveEscortActor); if (ActiveScriptedPawn != none) { if (ActiveScriptedPawn.GetHealthPercent() <= 0.01f) { statusMessage = Localize("Objectives", "NeedsRepair", LocalizationPackageName); bWarning = 1; return; } else if (ActiveScriptedPawn.GetHealthPercent() <= 0.25f) { statusMessage = Localize("Objectives", "LowHealth", LocalizationPackageName); bWarning = 1; return; } if (ActiveScriptedPawn.SpeedScalarForObstacles <= 0.0f) { statusMessage = Localize("Objectives", "Stopped", LocalizationPackageName); bWarning = 1; return; } if (ActiveScriptedPawn.IsBlockedByZed()) { statusMessage = Localize("Objectives", "Blocked", LocalizationPackageName); bWarning = 1; return; } if (ActiveScriptedPawn.WasAttackedRecently()) { statusMessage = Localize("Objectives", "BeingAttacked", LocalizationPackageName); bWarning = 1; return; } } } simulated function bool ShouldDrawIcon(){} simulated function Vector GetIconLocation(){} simulated function Texture2D GetIcon() { if (ActiveEscortActor != none) { return KFPawn_Scripted(ActiveEscortActor).GetStateIconTexture(); } return DefaultIcon; } // Non-interface functions function OnEscortStarted(KFPawn_Scripted StartedPawn) { ActiveEscortActor = StartedPawn; StartedPawn.SetActive(true); UpdateTrailActor(); StartedPawn.Delegate_OnTakeDamage = OnEscortTakeDamage; StartedPawn.Delegate_OnHealDamage = OnEscortHealDamage; StartedPawn.Delegate_OnChangeState = OnEscortChangeState; OnEscortChangeState(0, 0); } function OnEscortReachedRouteMarker(int MarkerIdx, SplineActor Marker, int SubIdx) { local float PawnsProgress; local KFScriptedPawnSpawner EscortPawnSpawner; local float ActivePawnHealthPct; foreach EscortPawnSpawners(EscortPawnSpawner) { PawnsProgress += EscortPawnSpawner.GetProgress() / float(EscortPawnSpawners.Length); } ActivePawnHealthPct = float(KFPawn_Scripted(ActiveEscortActor).Health) / float(KFPawn_Scripted(ActiveEscortActor).HealthMax); CheckCompletionPctProgress(Progress, PawnsProgress); CompletionPct = ActivePawnHealthPct; if (Progress < 0.25 && PawnsProgress >= 0.25) { if (ActivePawnHealthPct > 0.99 && ProgressSoundEvent25pctPerfect != none) { PlaySoundBase(ProgressSoundEvent25pctPerfect, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ActivePawnHealthPct > PoorHealthPctThreshold && ProgressSoundEvent25pct != none) { PlaySoundBase(ProgressSoundEvent25pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ProgressSoundEvent25pctPoor != none) { PlaySoundBase(ProgressSoundEvent25pctPoor, false, WorldInfo.NetMode == NM_DedicatedServer); } } else if (Progress < 0.5 && PawnsProgress >= 0.5) { if (ActivePawnHealthPct > 0.99 && ProgressSoundEvent50pctPerfect != none) { PlaySoundBase(ProgressSoundEvent50pctPerfect, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ActivePawnHealthPct > PoorHealthPctThreshold && ProgressSoundEvent50pct != none) { PlaySoundBase(ProgressSoundEvent50pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ProgressSoundEvent50pctPoor != none) { PlaySoundBase(ProgressSoundEvent50pctPoor, false, WorldInfo.NetMode == NM_DedicatedServer); } } else if (Progress < 0.85 && PawnsProgress >= 0.85) { if (ActivePawnHealthPct > 0.99 && ProgressSoundEvent85pctPerfect != none) { PlaySoundBase(ProgressSoundEvent85pctPerfect, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ActivePawnHealthPct > PoorHealthPctThreshold && ProgressSoundEvent85pct != none) { PlaySoundBase(ProgressSoundEvent85pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ProgressSoundEvent85pctPoor != none) { PlaySoundBase(ProgressSoundEvent85pctPoor, false, WorldInfo.NetMode == NM_DedicatedServer); } } else if (Progress < 1.0 && PawnsProgress >= 1.0) { if (ActivePawnHealthPct > 0.99 && ProgressSoundEvent100pctPerfect != none) { PlaySoundBase(ProgressSoundEvent100pctPerfect, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ActivePawnHealthPct > PoorHealthPctThreshold && ProgressSoundEvent100pct != none) { PlaySoundBase(ProgressSoundEvent100pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (ProgressSoundEvent100pctPoor != none) { PlaySoundBase(ProgressSoundEvent100pctPoor, false, WorldInfo.NetMode == NM_DedicatedServer); } } Progress = PawnsProgress; } function OnEscortReachedGoal() { OnEscortEndedRoute(true); } function OnEscortEndedRoute(bool bSuccess) { local KFGameReplicationInfo KFGRI; ++NumEscortsFinished; if (bSuccess) { ++NumEscortsAccomplished; TriggerObjectiveProgressEvent(); } if ((NumEscortsAccomplished >= NumEscortsRequired) || (NumEscortsFinished == EscortPawnSpawners.Length) || (((EscortPawnSpawners.Length - NumEscortsFinished) + NumEscortsAccomplished) < NumEscortsRequired)) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGRI != none) { Progress = 1.0f; // ensure this is set to completed KFGRI.DeactivateObjective(); } } ActiveEscortActor = none; UpdateTrailActor(); ClearTimer(nameof(Timer_EscortPawnTakeDamageEventCooldown)); ClearTimer(nameof(Timer_EscortPawnCriticalReminderEventInterval)); } function OnEscortTakeDamage(int Damage) { local KFPawn_Scripted KFPS; local float PrevHealthPct, CurrHealthPct; if (EscortPawnTakeDamageEvent != none && !IsTimerActive(nameof(Timer_EscortPawnTakeDamageEventCooldown))) { PlaySoundBase(EscortPawnTakeDamageEvent,, WorldInfo.NetMode == NM_DedicatedServer); SetTimer(EscortPawnTakeDamageEventCooldown, false, nameof(Timer_EscortPawnTakeDamageEventCooldown)); } KFPS = KFPawn_Scripted(ActiveEscortActor); PrevHealthPct = float(KFPS.Health + Damage) / float(KFPS.HealthMax); CurrHealthPct = float(KFPS.Health) / float(KFPS.HealthMax); CheckHealthPctProgress(PrevHealthPct, CurrHealthPct); } function Timer_EscortPawnTakeDamageEventCooldown() { // this function is just here to allow us to set a timer, which we check the activity of } function OnEscortHealDamage(int HealAmount) { local KFPawn_Scripted KFPS; local float PrevHealthPct, CurrHealthPct; KFPS = KFPawn_Scripted(ActiveEscortActor); PrevHealthPct = float(KFPS.Health - HealAmount) / float(KFPS.HealthMax); CurrHealthPct = float(KFPS.Health) / float(KFPS.HealthMax); CheckHealthPctProgress(PrevHealthPct, CurrHealthPct); } function OnEscortChangeState(int CurrState, int PrevState) { if (KFPawn_Scripted(ActiveEscortActor).IsInCriticalCondition()) { if (EscortPawnCriticalReminderEvent != none && !IsTimerActive(nameof(Timer_EscortPawnCriticalReminderEventInterval))) { SetTimer(EscortPawnCriticalReminderEventInterval, true, nameof(Timer_EscortPawnCriticalReminderEventInterval)); } } else { ClearTimer(nameof(Timer_EscortPawnCriticalReminderEventInterval)); } } function Timer_EscortPawnCriticalReminderEventInterval() { PlaySoundBase(EscortPawnCriticalReminderEvent,, WorldInfo.NetMode == NM_DedicatedServer); } simulated function UpdateTrailActor() { if (bUseTrailToObjective && WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor == none) { TrailActor = class'WorldInfo'.static.GetWorldInfo().Spawn(class'KFReplicatedShowPathActor', none); TrailActor.SetEmitterTemplate(ParticleSystem'FX_Gameplay_EMIT.FX_Objective_White_Trail'); } TrailActor.SetPathTarget(ActiveEscortActor); } } simulated function bool GetIsMissionCritical() { return bIsMissionCriticalObjective; } defaultproperties { bIsMissionCriticalObjective=true LocalizationKey="EscortPawnsObjective" DescriptionLocKey="EscortPawnsDescription" NameShortLocKey="EscortPawnsObjective" DescriptionShortLocKey="EscortPawnsDescriptionShort" bAlwaysRelevant=true RemoteRole=ROLE_SimulatedProxy Begin Object Class=SpriteComponent Name=Sprite Sprite=Texture2D'EditorResources.S_Actor' HiddenGame=True AlwaysLoadOnClient=False AlwaysLoadOnServer=False End Object Components.Add(Sprite) GameModeBlacklist.Add(class'KFGameInfo_Endless') GameModeBlacklist.Add(class'KFGameInfo_WeeklySurvival') PerPlayerSpawnRateMod=(1.f, 1.f, 1.f, 1.f, 1.f, 1.f) DefaultIcon=Texture2D'Objectives_UI.UI_Objectives_ObjectiveMode' PoorHealthPctThreshold=0.5 LocalizationPackageName="KFGame" CompletionProgressIdx=999 HealthProgressIdx=999 SupportedEvents.Add(class'KFSeqEvent_EscortPawnsCompletionPct') SupportedEvents.Add(class'KFSeqEvent_EscortPawnsHealthPct') }