//============================================================================= // KFMapObjective_RepairActors //============================================================================= // Objective type for the repair panels. Player must weld n number of actors in // order to repair them. After one actor is repaired, another will break. //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class KFMapObjective_RepairActors extends KFMapObjective_ActorBase; /** How many actors needs to be repaired for the objective to be completed, per number of players */ var() const int ActivationsRequiredForPlayerCount[6]; /** A delay from the the start of an objective, so the player can't automatically repair the first actor */ var() float ActivationDelay; /** A sound to play when each repair actor is activated */ var() array ActorActivationSoundEvents; /** A sound to play when each repair actor is repaired */ var() array ActorRepairedSoundEvents; /** How many actors needs to be repaired for the objective to be completed. */ var int ActivationsRequired; /** How many actors have been repaired. */ var repnotify int ActorsRepaired; /** List of all the actors that must be repaired by the user. */ var() array RepairableActors; /** List of Repairable Actors we haven't repaired yet. This will ensure we cycle through all actors before repeating. */ var array UnusedRepairableActors; /** Reference to last actor repaired so we don't fix the same actor one after the other. */ var KFRepairableActor LastRepairedActor; /** Current Actor that needs to be repaired. */ var repnotify KFRepairableActor CurrentActorToRepair; /** How long until the next repairable actor can be activated. */ var() float TimeUntilNextActivation; /** A sound to play when the objective is fully complete */ var() AkEvent SuccessSoundEvent100pct; /** A sound to play when the objective is mostly complete */ var() AkEvent SuccessSoundEvent85pct; /** A sound to play when the objective is adequately complete */ var() AkEvent SuccessSoundEvent50pct; /** A sound to play when the objective is barely complete */ var() AkEvent SuccessSoundEvent25pct; /** Win thresholds - Named to match the VO tracks*/ var float JustWinThreshold; var float StandardWinThreshold; var float GoodWinThreshold; /** Whether the sequence of repairables should be randomized */ var() bool bRandomSequence; replication { if(bNetDirty) CurrentActorToRepair, ActorsRepaired, ActivationsRequired; } simulated event ReplicatedEvent(name VarName) { if (VarName == nameof(CurrentActorToRepair)) { UpdateTrailActor(); } else if (VarName == nameof(ActorsRepaired)) { if (ActorsRepaired != 0) { TriggerObjectiveProgressEvent(, float(ActorsRepaired)/float(ActivationsRequired)); } } else { super.ReplicatedEvent(VarName); } } simulated function ActivateObjective() { local int PlayerCount; local KFRepairableActor CurrActor; super.ActivateObjective(); foreach RepairableActors(CurrActor) { CurrActor.OnRepairCompelete = OnActorRepaired; } if (WorldInfo.NetMode != NM_DedicatedServer) { if (bUseTrailToObjective) { TrailActor = class'WorldInfo'.static.GetWorldInfo().Spawn(class'KFReplicatedShowPathActor', none); TrailActor.SetEmitterTemplate(ParticleSystem'FX_Gameplay_EMIT.FX_Objective_White_Trail'); } } if (Role == ROLE_Authority) { PlayerCount = Clamp(KFGameInfo(WorldInfo.Game).GetLivingPlayerCount(), 1, 6) - 1; ActivationsRequired = ActivationsRequiredForPlayerCount[PlayerCount]; ActorsRepaired = 0; bIsActive = true; if (ActivationDelay > 0.f) { SetTimer(ActivationDelay, false, 'ActivateNextRepairableActor'); } else { ActivateNextRepairableActor(); } } } simulated function DeactivateObjective() { local KFPlayerController KFPC; local KFPawn_Human KFPH; local KFRepairableActor CurrActor; super.DeactivateObjective(); if(Role == ROLE_Authority) { bIsActive = false; if (!HasFailedObjective()) { foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { GrantReward(KFPlayerReplicationInfo(KFPH.PlayerReplicationInfo), KFPlayerController(KFPH.Controller)); if (KFPlayerController(KFPH.Controller) != none) { if (GetTotalProgress() >= 1.0f) { // @todo: hook up seasonal event here if/when desired } } } } else { `log("objective failed"); } PlayDeactivationDialog(); ClearTimer(nameof(ActivateNextRepairableActor)); } foreach RepairableActors(CurrActor) { CurrActor.Reset(); } if(WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor != none) { TrailActor.Destroy(); TrailActor = none; } } KFPC = KFPlayerController(GetALocalPlayerController()); if (KFPC != none && KFPC.MyGFxHUD != none) { KFPC.MyGFxHUD.WaveInfoWidget.ObjectiveContainer.SetFailState(HasFailedObjective()); } } function PlayDeactivationDialog() { if (GetTotalProgress() <= 0.0f) { PlaySoundBase(FailureSoundEvent, false, WorldInfo.NetMode == NM_DedicatedServer); BroadcastLocalizedMessage(class'KFLocalMessage_Priority', GMT_ObjectiveLost); } else { if (GetTotalProgress() <= JustWinThreshold) { PlaySoundBase(SuccessSoundEvent25pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (GetTotalProgress() <= StandardWinThreshold) { PlaySoundBase(SuccessSoundEvent50pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else if (GetTotalProgress() <= GoodWinThreshold) { PlaySoundBase(SuccessSoundEvent85pct, false, WorldInfo.NetMode == NM_DedicatedServer); } else { PlaySoundBase(SuccessSoundEvent100pct, false, WorldInfo.NetMode == NM_DedicatedServer); } } } function ActivateNextRepairableActor() { if(Role == ROLE_Authority) { LastRepairedActor = CurrentActorToRepair; CurrentActorToRepair = ChooseNextActorToRepair(); if (CurrentActorToRepair != none) { CurrentActorToRepair.PlayDestroyed(); } if (ActorsRepaired < ActorActivationSoundEvents.Length) { PlaySoundBase(ActorActivationSoundEvents[ActorsRepaired], false, WorldInfo.NetMode == NM_DedicatedServer); } } UpdateTrailActor(); } simulated function UpdateTrailActor() { if (WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor != none) { TrailActor.SetPathTarget(CurrentActorToRepair.RepairTrigger); } } } function KFRepairableActor ChooseNextActorToRepair() { local int ChosenActorIndex; local KFRepairableActor ChosenActor; local array ValidActors; if (RepairableActors.length == 0) { return none; } if (UnusedRepairableActors.length == 0) { UnusedRepairableActors = RepairableActors; } ValidActors = UnusedRepairableActors; if(ValidActors.length > 1) { ValidActors.RemoveItem(LastRepairedActor); } if (bRandomSequence) { ChosenActorIndex = RandRange(0, ValidActors.length - 1); } else { // if not random choose the next actor in the sequence ChosenActorIndex = 0; } ChosenActor = ValidActors[ChosenActorIndex]; UnusedRepairableActors.Remove(ChosenActorIndex, 1); return ChosenActor; } function OnActorRepaired(KFRepairableActor RepairedActor) { local KFGameReplicationInfo KFGRI; if (!bIsActive) { return; } if (Role == ROLE_Authority) { if (ActorsRepaired < ActorRepairedSoundEvents.Length) { PlaySoundBase(ActorRepairedSoundEvents[ActorsRepaired], false, WorldInfo.NetMode == NM_DedicatedServer); } } ActorsRepaired++; TriggerObjectiveProgressEvent(, float(ActorsRepaired) / float(ActivationsRequired)); if (GetTotalProgress() >= 1.f) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGRI != none) { KFGRI.DeactivateObjective(); } } else { if (TimeUntilNextActivation > 0.f) { SetTimer(TimeUntilNextActivation, false, nameof(ActivateNextRepairableActor)); CurrentActorToRepair = none; UpdateTrailActor(); } else { ActivateNextRepairableActor(); } } } simulated function bool IsActive() { return bIsActive; } simulated function bool IsBonus() { return true; } 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 bool UsesProgress() { return false; } simulated function bool ShouldShowObjectiveHUD() { return false; } simulated function float GetProgress() { if (!HasFailedObjective()) { return float(ActorsRepaired) / float(ActivationsRequired); } return 0.f; } simulated function bool IsComplete() { return GetProgress() >= 1.f; } simulated function float GetTotalProgress() { if (ActivationsRequired == 0) { return 0.f; } return float(ActorsRepaired) / float(ActivationsRequired); } simulated function float GetActivationPctChance() { return 1.f; } simulated function string GetLocalizedRequirements() { return Localize("Objectives", default.RequirementsLocKey, "KFGame") @ ActivationsRequired; } simulated function bool ShouldDrawIcon() { return CurrentActorToRepair != none; } simulated function Vector GetIconLocation() { if (CurrentActorToRepair != none) { return CurrentActorToRepair.Location + CurrentActorToRepair.IconLocationOffset; } return Location; } simulated function int GetVoshReward() { local int MaxDosh; MaxDosh = GetMaxVoshReward(); if (MaxDosh == 0) { return MaxDosh; } return int(MaxDosh * GetTotalProgress()); } simulated function int GetXPReward() { local int MaxXP; MaxXP = GetMaxXPReward(); if (MaxXP == 0) { return MaxXP; } return int(MaxXP * GetTotalProgress()); } simulated function string GetProgressText() { if (!bIsActive) { return ""; } return ActorsRepaired $ "/" $ ActivationsRequired; } simulated function bool GetIsMissionCritical() { return bIsMissionCriticalObjective; } // this is currently a copy of DrawObjectiveHUD, // except it uses the individual repairable weld completion instead of the overall objective completion simulated function DrawHUD(KFHUDBase hud, Canvas drawCanvas) { local float Percentage; local float BarHeight, BarLength; local vector ScreenPos, TargetLocation; local float ResModifier; local float ThisDot; local KFGameReplicationInfo KFGRI; local vector ViewLocation, ViewVector; local rotator ViewRotation; local KFPlayerController KFPC; ResModifier = WorldInfo.static.GetResolutionBasedHUDScale() * hud.FriendlyHudScale; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); KFPC = KFPlayerController(GetALocalPlayerController()); if (!ShouldDrawIcon() || KFGRI.bHidePawnIcons) { return; } if (KFPC != none) { KFPC.GetPlayerViewPoint(ViewLocation, ViewRotation); } ViewVector = vector(ViewRotation); ThisDot = Normal(GetIconLocation() - ViewLocation) dot ViewVector; if (ThisDot <= 0) { return; } BarLength = FMin(hud.PlayerStatusBarLengthMax * (drawCanvas.ClipX / 1024.f), hud.PlayerStatusBarLengthMax) * ResModifier; BarHeight = FMin(8.f * (drawCanvas.ClipX / 1024.f), 8.f) * ResModifier; TargetLocation = GetIconLocation(); ScreenPos = drawCanvas.Project(TargetLocation); // Make sure that the entire health bar is on screen ScreenPos.X = FClamp(ScreenPos.X, BarLength * 0.5f + hud.PlayerStatusIconSize, drawCanvas.ClipX - BarLength * 0.5f); ScreenPos.Y = FClamp(ScreenPos.Y, hud.PlayerStatusIconSize * 0.5f, drawCanvas.ClipY - hud.PlayerStatusIconSize * 0.5f); //Draw progress bar Percentage = FMin(FClamp(float(CurrentActorToRepair.WeldIntegrity) / float(CurrentActorToRepair.MaxWeldIntegrity), 0.f, 1.f), 1); hud.DrawKFBar(Percentage, BarLength, BarHeight, ScreenPos.X - (BarLength * 0.5f), ScreenPos.Y, hud.NonPlayerHealth); //draw objective icon if (GetIcon() != none) { drawCanvas.SetDrawColorStruct(hud.PlayerBarShadowColor); drawCanvas.SetPos((ScreenPos.X - (BarLength * 0.75)) + 1, (ScreenPos.Y - BarHeight * 2.0) + 1); drawCanvas.DrawTile(GetIcon(), hud.PlayerStatusIconSize * ResModifier, hud.PlayerStatusIconSize * ResModifier, 0, 0, 256, 256); drawCanvas.SetDrawColorStruct(GetIconColor()); drawCanvas.SetPos(ScreenPos.X - (BarLength * 0.75), ScreenPos.Y - BarHeight * 2.0); drawCanvas.DrawTile(GetIcon(), hud.PlayerStatusIconSize * ResModifier, hud.PlayerStatusIconSize * ResModifier, 0, 0, 256, 256); } } defaultproperties { LocalizationKey="RepairObjective" DescriptionLocKey="UseWelderToRepair" NameShortLocKey="RepairObjective" DescriptionShortLocKey="UseWelderToRepairShort" RequirementsLocKey="RepairObjectiveRequired" bAlwaysRelevant=true RemoteRole=ROLE_SimulatedProxy TimeUntilNextActivation=5.f SupportedEvents.Add(class'KFSeqEvent_ObjectiveProgress') GameModeBlacklist.Add(class'KFGameInfo_Endless') GameModeBlacklist.Add(class'KFGameInfo_WeeklySurvival') Begin Object Class=SpriteComponent Name=Sprite Sprite=Texture2D'EditorResources.S_Actor' HiddenGame=True AlwaysLoadOnClient=False AlwaysLoadOnServer=False End Object Components.Add(Sprite) ActivationsRequired=6 bUseTrailToObjective=true PerPlayerSpawnRateMod=(1.f, 1.f, 1.f, 1.f, 1.f, 1.f) ObjectiveIcon=Texture2D'Objectives_UI.UI_Objectives_ObjectiveMode' //These are basically range caps. For example: // Just win would be 0% - 25% // Standard win would be 25% - 50% // Good win would be 50% - 85% // Perfect win then assumes everything above good win JustWinThreshold=0.25 StandardWinThreshold=0.5 GoodWinThreshold=0.85 LocalizationPackageName="KFGame" bRandomSequence=true }