1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFMapObjective_EscortPawns.uc
2020-12-13 18:01:13 +03:00

658 lines
19 KiB
Ucode

//=============================================================================
// 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<KFScriptedPawnSpawner> 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<int> 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<int> 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<KFGameInfo> 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')
}