//============================================================================= // KFMapObjective_CollectActors //============================================================================= // Objective class for keeping track of collectibles found by players //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class KFMapObjective_CollectActors extends KFMapObjective_VolumeBase; /** A delay from the the start of an objective, so the player can't automatically activate the first actor */ var() float ActivationDelay; /** How long until the next actor can be activated. */ var() float TimeUntilNextActivation; /** A sound to play when the objective is fully complete */ var() AkEvent SuccessSoundEvent; /** How many actors needs to be activated for the objective to be completed. */ var() array NumRequired; /** How many actors have been activated. */ var repnotify int NumAccomplished; /** An actor that visually represents the delivery volume*/ var() SkeletalMeshActor DeliveryMeshActor; /** An animation to play on the DeliveryMeshActor when this objective activates*/ var() name DeliveryOpenAnimName; /** An animation to play on the DeliveryMeshActor when this objective deactivates*/ var() name DeliveryCloseAnimName; /** How long to delay the playing of the close anim after the objective is complete (to give Kismet time to finish things) */ var() float DeliveryCloseAnimDelay; /** Amount of timer since last collectible was reset to consider "recent" in terms of UI messaging to the player*/ var float CollectibleResetTimerLength; /** Whether a collectible has been reset recently*/ var repnotify bool bCollectibleReset; struct CollectibleActorInfo { // Collectible actor var() KFObjectiveCollectActor Collectible; // Trigger for collectible actor var() KFUsableTrigger CollectibleTrigger; }; // All collectible actors associated with this objective var() array CollectibleInfo; // Whether the sequence of collectibles should be randomized var() bool bRandomSequence; // The number of collectible actors that should be active at the same time var() array NumDesiredActiveCollectibles; var transient array AvailableCollectibleIndices; var transient class DeliveryClass; var float CollectActorIconSize; var const color CollectibleIconAvailableColor; var const color CollectibleIconCarriedColor; var float UpdateTrailActorInterval; enum ESoundSelectionType { ESoundSelectionType_Random, ESoundSelectionType_Location, ESoundSelectionType_Ordered, ESoundSelectionType_Progress }; struct KFSoundEventGroup { var() ESoundSelectionType SoundType; var() array SoundEvents; var int CurrentEvent; }; // AUDIO EVENTS /** Sounds to play when one of the collectibles is dropped on the ground */ var() KFSoundEventGroup CollectibleDroppedSoundEvents; /** Sounds to play when one of the collectibles is successfully delivered */ var() KFSoundEventGroup CollectibleDeliveredSoundEvents; /** Sounds to play when one of the collect actors is activated */ var() KFSoundEventGroup CollectibleActivateSoundEvents; /** Sounds to play when one of the players picks up an object from its original spawn */ var() KFSoundEventGroup CollectibleCollectSoundEvents; /** A sound to play when the players have not collected in object in some time */ var() AkEvent CollectActorReminderSoundEvent; /** How frequently to play audio when a collectible is dropped on the ground */ var() float CollectibleDroppedInterval; /** How frequently to play audio when the player picks up an object from the collect actor */ var() float CollectibleCollectInterval; /** How frequently to remind players that they need to pick up a collectible */ var() float CollectActorReminderInterval; replication { if (bNetDirty) NumAccomplished, bCollectibleReset; } simulated function ReplicatedEvent(name VarName) { if (VarName == nameof(NumAccomplished)) { if (NumAccomplished != 0) { TriggerObjectiveProgressEvent(, float(NumAccomplished)/float(GetNumRequired())); } } else { super.ReplicatedEvent(VarName); } } ////////////////////////////////////////////////////////////////////////////////////////////////////// // KFInterface_MapObjective simulated function ActivateObjective() { local int i; super.ActivateObjective(); // every few seconds check whether a new trail actor has been set SetTimer(UpdateTrailActorInterval, true, nameof(UpdateTrailActor)); if (DeliveryMeshActor != none) { DeliveryMeshActor.SkeletalMeshComponent.PlayAnim(DeliveryOpenAnimName); } if (Role == ROLE_Authority) { // initialize the array of collectibles to choose from for (i = 0; i < CollectibleInfo.Length; ++i) { CollectibleInfo[i].Collectible.ObjectiveOwner = self; AvailableCollectibleIndices.AddItem(i); } NumAccomplished = 0; bActive = true; bForceNetUpdate = true; if (ActivationDelay > 0.f) { SetTimer(ActivationDelay, false, nameof(ChooseNextCollectible)); } else { ChooseNextCollectible(); } } } simulated function PlayDeliveryMeshCloseAnim() { if (DeliveryMeshActor != none) { DeliveryMeshActor.SkeletalMeshComponent.PlayAnim(DeliveryCloseAnimName); } } simulated function DeactivateObjective() { local CollectibleActorInfo Info; local KFPawn_Human KFPH; local KFDroppedPickup_Carryable DroppedCarryable; local KFCarryableObject_Collectible CarryableObject; local Inventory InventoryItem; super.DeactivateObjective(); if (DeliveryCloseAnimDelay > 0.f) { SetTimer(DeliveryCloseAnimDelay, false, nameof(PlayDeliveryMeshCloseAnim)); } else { PlayDeliveryMeshCloseAnim(); } if (Role == ROLE_Authority) { bActive = false; bForceNetUpdate = true; if (GetProgress() >= 1.f) { PlaySoundBase(SuccessSoundEvent, false, WorldInfo.NetMode == NM_DedicatedServer); } else { PlaySoundBase(FailureSoundEvent, false, WorldInfo.NetMode == NM_DedicatedServer); } ClearTimer(nameof(ChooseNextCollectible)); // also disable all of the actor collectors foreach CollectibleInfo(Info) { Info.CollectibleTrigger.bActive = false; Info.Collectible.SetEnabled(false); Info.Collectible.SetActive(false); Info.Collectible.bForceNetUpdate = true; } // grant the players' rewards // remove the remaining carryables in the world from players' hands foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { GrantReward(KFPlayerReplicationInfo(KFPH.PlayerReplicationInfo), KFPlayerController(KFPH.Controller)); if (KFPH != none && KFPH.InvManager != none) { InventoryItem = KFPH.InvManager.FindInventoryType(class(DeliveryClass), true); if (InventoryItem != none) if (InventoryItem != none) { KFPH.InvManager.RemoveFromInventory(InventoryItem); CarryableObject = KFCarryableObject_Collectible(InventoryItem); if (CarryableObject != none) { CarryableObject.UpdateReplicationInfo(false); } } } } // destroy all pickups sitting on the ground foreach AllActors(class'KFDroppedPickup_Carryable', DroppedCarryable) { if (DroppedCarryable != none) { DroppedCarryable.Destroy(); } } } ClearTimer(nameof(Timer_CollectibleCollectCooldown)); ClearTimer(nameof(Timer_CollectibleDroppedCooldown)); ClearTimer(nameof(CollectActorReminder)); ClearTimer(nameof(UpdateTrailActor)); if (WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor != none) { TrailActor.Destroy(); TrailActor = none; } } } simulated function bool IsActive() { return bActive; } 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 float(NumAccomplished) / float(GetNumRequired()); } simulated function bool IsComplete() { return GetProgress() >= 1.f; } simulated function float GetActivationPctChance() { return 1.f; } simulated function string GetLocalizedRequirements() { return Localize("Objectives", default.RequirementsLocKey, "KFGame") @ GetNumRequired(); } simulated function GetLocalizedStatus(out string statusMessage, out int bWarning, out int bNotification) { local int i; local bool bDropped, bCarried, bReadyForPickup; statusMessage = ""; if (GetProgress() >= 1.f) { statusMessage = Localize("Objectives", "KillRemainingZeds", LocalizationPackageName); bWarning = 0; bNotification = 0; return; } for (i = 0; i < CollectibleInfo.Length; ++i) { if (CollectibleInfo[i].Collectible != none) { switch (CollectibleInfo[i].Collectible.CollectibleState) { case ECollectibleState_None: if (CollectibleInfo[i].Collectible.bActive) { bReadyForPickup = true; } break; case ECollectibleState_Carried: bCarried = true; break; case ECollectibleState_Dropped: bDropped = true; break; } } } if (bDropped) { statusMessage = Localize("Objectives", "Dropped", LocalizationPackageName); bWarning = 1; return; } if (bCarried) { statusMessage = Localize("Objectives", "PickedUp", LocalizationPackageName); bNotification = 1; return; } if (bCollectibleReset) { statusMessage = Localize("Objectives", "Reset", LocalizationPackageName); bWarning = 1; return; } if (bReadyForPickup) { statusMessage = Localize("Objectives", "PickupTransportItem", LocalizationPackageName); bNotification = 1; return; } } simulated function bool ShouldDrawIcon() { return true; } simulated function Vector GetIconLocation() { return Location; } simulated function DrawHUDActiveCollectibles(KFHUDBase hud, Canvas drawCanvas, bool bDropShadow) { local KFDroppedPickup_Carryable droppedCarryable; local int i; local float DropShadowModifier; DropShadowModifier = bDropShadow ? 1.0f : 0.0f; for (i = 0; i < CollectibleInfo.Length; i++) { if (CollectibleInfo[i].CollectibleTrigger.bActive && CollectibleInfo[i].Collectible != none) { DrawIconAtLocation(hud, drawCanvas, GetCollectibleIcon(), CollectibleInfo[i].Collectible.Location, -16.f + DropShadowModifier, -50.f + DropShadowModifier); } } // at the location of all of the dropped collectible objects foreach AllActors(class'KFDroppedPickup_Carryable', droppedCarryable) { if (droppedCarryable != none) { DrawIconAtLocation(hud, drawCanvas, GetCollectibleIcon(), droppedCarryable.Location, -16.f + DropShadowModifier, -50.f + DropShadowModifier); } } } simulated function DrawHUDCarriedCollectibles(KFHUDBase hud, Canvas drawCanvas, bool bDropShadow) { local int i; local KFPawn_Human KFPH; local float BarLength, FontScale, ResModifier, DropShadowModifier; local PlayerController LocalPC; local KFPlayerReplicationInfo KFPRI; local PlayerReplicationInfo PRI; local array PawnPlayerControllers; LocalPC = GetALocalPlayerController(); ResModifier = WorldInfo.static.GetResolutionBasedHUDScale() * hud.FriendlyHudScale; BarLength = FMin(hud.PlayerStatusBarLengthMax * (drawCanvas.ClipX / 1024.f), hud.PlayerStatusBarLengthMax) * ResModifier; FontScale = class'KFGameEngine'.Static.GetKFFontScale() * hud.FriendlyHudScale; DropShadowModifier = bDropShadow ? 1.0f : 0.0f; foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { // don't show the icon above the local player's head if (LocalPC == none || LocalPC != KFPH.Controller) { if (KFPH.Mesh != none && KFPH.CylinderComponent != none && KFPH.Mesh.SkeletalMesh != none && KFPH.Mesh.bAnimTreeInitialised && `TimeSince(KFPH.Mesh.LastRenderTime) < 0.2f) { PawnPlayerControllers.AddItem(KFPH.Controller); if (ClassIsChildOf(KFPH.WeaponClassForAttachmentTemplate, DeliveryClass)) { DrawIconAtLocation(hud, drawCanvas, GetCollectibleIcon(), KFPH.Mesh.GetPosition() + (KFPH.CylinderComponent.CollisionHeight * vect(0, 0, 2.5f)), (BarLength * -0.5f) - (hud.PlayerStatusIconSize * ResModifier) - (CollectActorIconSize * ResModifier) + DropShadowModifier, // to the left of the perk icon CollectActorIconSize * FontScale * ResModifier * 0.5f + DropShadowModifier); // centered vertically } } } } for (i = 0; i < WorldInfo.GRI.PRIArray.Length; i++) { PRI = WorldInfo.GRI.PRIArray[i]; KFPRI = KFPlayerReplicationInfo(PRI); if (KFPRI != none && KFPRI.bCarryingCollectible) { if (PawnPlayerControllers.Find(KFPRI.Owner) == INDEX_NONE && PRI != LocalPC.PlayerReplicationInfo) { DrawIconAtLocation(hud, drawCanvas, GetCollectibleIcon(), KFPRI.GetSmoothedPawnIconLocation(hud.HumanPlayerIconInterpMult) + class'KFPawn_Human'.default.CylinderComponent.CollisionHeight * vect(0, 0, 2), -1 * (hud.PlayerStatusIconSize * ResModifier) - (CollectActorIconSize * ResModifier) + DropShadowModifier, // to the left of the perk icon -1 * CollectActorIconSize * ResModifier + DropShadowModifier); // centered vertically } } } } simulated function DrawHUD(KFHUDBase hud, Canvas drawCanvas) { // at the location of all the active locations out in the world if (!IsLocalPlayerCarryingACollectible()) { if (drawCanvas != none) { drawCanvas.SetDrawColorStruct(hud.PlayerBarShadowColor); DrawHUDActiveCollectibles(hud, drawCanvas, true); drawCanvas.SetDrawColorStruct(CollectibleIconAvailableColor); DrawHUDActiveCollectibles(hud, drawCanvas, false); } } // at the location of all the collectible objects being carried by players if (drawCanvas != none) { drawCanvas.SetDrawColorStruct(hud.PlayerBarShadowColor); DrawHUDCarriedCollectibles(hud, drawCanvas, true); drawCanvas.SetDrawColorStruct(CollectibleIconCarriedColor); DrawHUDCarriedCollectibles(hud, drawCanvas, false); } } // don't show the objective HUD unless the local player is carrying a collectible simulated function bool ShouldShowObjectiveHUD() { return IsLocalPlayerCarryingACollectible(); } // always show the objective progress regardless of draw distance simulated function bool HasObjectiveDrawDistance() { return false; } simulated function Texture2D GetCollectibleIcon() { return ObjectiveIcon; } simulated function string GetProgressText() { if (!bActive) { return ""; } return NumAccomplished $ "/" $ GetNumRequired(); } simulated function bool GetIsMissionCritical() { return bIsMissionCriticalObjective; } // end KFInterface_MapObjective ////////////////////////////////////////////////////////////////////////////////////////////////////// simulated function PlaySoundEvent(out KFSoundEventGroup SoundGroup, int nSpecificIndex) { local int nIndex; switch (SoundGroup.SoundType) { case ESoundSelectionType_Random: nIndex = Rand(SoundGroup.SoundEvents.Length); break; case ESoundSelectionType_Location: nIndex = nSpecificIndex; break; case ESoundSelectionType_Ordered: nIndex = SoundGroup.CurrentEvent; SoundGroup.CurrentEvent++; //loop back to the beginning if (SoundGroup.CurrentEvent >= SoundGroup.SoundEvents.length) { SoundGroup.CurrentEvent = 0; } break; case ESoundSelectionType_Progress: nIndex = NumAccomplished; break; } if (nIndex >= 0 && nIndex < SoundGroup.SoundEvents.Length && SoundGroup.SoundEvents[nIndex] != none) { PlaySoundBase(SoundGroup.SoundEvents[nIndex], false, WorldInfo.NetMode == NM_DedicatedServer); } } simulated function ResetOrderedSoundEventsForRespawn() { if (CollectibleActivateSoundEvents.SoundType == ESoundSelectionType_Ordered) { CollectibleActivateSoundEvents.CurrentEvent--; } if (CollectibleCollectSoundEvents.SoundType == ESoundSelectionType_Ordered) { CollectibleCollectSoundEvents.CurrentEvent--; } if (CollectibleDroppedSoundEvents.SoundType == ESoundSelectionType_Ordered) { CollectibleDroppedSoundEvents.CurrentEvent--; } } simulated function OnCollectActor(KFObjectiveCollectActor Collectible) { if (!bActive) { return; } if (!IsTimerActive(nameof(Timer_CollectibleCollectCooldown))) { //play the matching "collected" sound for that collect actor PlaySoundEvent(CollectibleCollectSoundEvents, GetCollectibleIndexFromCollectActor(Collectible)); SetTimer(CollectibleCollectInterval, false, nameof(Timer_CollectibleCollectCooldown)); } } simulated function ProgressObjective() { local KFGameReplicationInfo KFGRI; if (Role == ROLE_Authority) { NumAccomplished++; bForceNetUpdate = true; if (GetProgress() >= 1.f) { KFGRI = KFGameReplicationInfo(WorldInfo.GRI); if (KFGRI != none) { KFGRI.DeactivateObjective(); } } else { if (TimeUntilNextActivation > 0.f) { SetTimer(TimeUntilNextActivation, false, nameof(ChooseNextCollectible)); } else { ChooseNextCollectible(); } } TriggerObjectiveProgressEvent(, float(NumAccomplished) / float(GetNumRequired())); } } simulated function ChooseNextCollectible() { local int i, j, RandAvailableIdx, RandCollectibleIdx, NumActiveCollectibles; local bool NeedsAvailableInit; if (Role == ROLE_Authority) { if (CollectibleInfo.Length == 0) { return; } // if there are more required deliveries than there are spawn locations, // then enter a mode where whenever a collectible is delivered it gets respawned back where it came from NeedsAvailableInit = AvailableCollectibleIndices.Length == 0 && CollectibleInfo.Length < GetNumRequired(); for (i = 0; i < CollectibleInfo.Length; ++i) { // check number of active (non-completed) collectibles if (CollectibleInfo[i].CollectibleTrigger.bActive) { NumActiveCollectibles++; } // initialize the array of collectibles to choose from else if (NeedsAvailableInit) { AvailableCollectibleIndices.AddItem(i); } } // if less than the desired amount for(j = 0; j < GetNumDesiredActive() - NumActiveCollectibles; j++) { if (AvailableCollectibleIndices.Length <= 0) break; // choose another (random or ordered) and spawn it RandAvailableIdx = bRandomSequence ? Rand(AvailableCollectibleIndices.Length) : 0; RandCollectibleIdx = AvailableCollectibleIndices[RandAvailableIdx]; AvailableCollectibleIndices.Remove(RandAvailableIdx, 1); // unhide the chosen collectible and set the trigger and collect actor as active CollectibleInfo[RandCollectibleIdx].Collectible.SetEnabled(true); CollectibleInfo[RandCollectibleIdx].Collectible.SetActive(true); CollectibleInfo[RandCollectibleIdx].Collectible.bForceNetUpdate = true; CollectibleInfo[RandCollectibleIdx].CollectibleTrigger.bActive = true; // play audio event for activating object PlaySoundEvent(CollectibleActivateSoundEvents, RandCollectibleIdx); } } } simulated function Actor FindClosestActiveCollector() { local CollectibleActorInfo Info; local float shortestDistance, currentDistance; local Actor closestActor; local PlayerController LocalPC; LocalPC = GetALocalPlayerController(); closestActor = none; shortestDistance = -1.f; if (LocalPC != none && LocalPC.Pawn != none) { foreach CollectibleInfo(Info) { if (Info.Collectible != none && Info.Collectible.bActive) { // measure the distance from the local player to the object and choose the closest one currentDistance = VSize(Info.Collectible.Location - LocalPC.Pawn.Location); if (shortestDistance < 0 || currentDistance < shortestDistance) { shortestDistance = currentDistance; closestActor = Info.CollectibleTrigger; } } } } return closestActor; } simulated function bool GetClosestPlayerCarryingACollectible(out vector CarrierLocation) { local KFPawn_Human KFPH; local PlayerController LocalPC; local array PawnPlayerControllers; local int i; local PlayerReplicationInfo PRI; local KFPlayerReplicationInfo KFPRI; local bool bFoundCarrier; local float CurrentDistance; LocalPC = GetALocalPlayerController(); bFoundCarrier = false; // carried by players within replication range foreach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) { if (LocalPC == none || LocalPC != KFPH.Controller) { if (KFPH.Mesh != none && KFPH.CylinderComponent != none && KFPH.Mesh.SkeletalMesh != none && KFPH.Mesh.bAnimTreeInitialised && `TimeSince(KFPH.Mesh.LastRenderTime) < 0.2f) { PawnPlayerControllers.AddItem(KFPH.Controller); // carrying the current objective type if (ClassIsChildOf(KFPH.WeaponClassForAttachmentTemplate, DeliveryClass)) { if (LocalPC.Pawn != none) { if (!bFoundCarrier || VSize(KFPH.Mesh.GetPosition() - LocalPC.Pawn.Location) < CurrentDistance) { CarrierLocation = KFPH.Mesh.GetPosition(); CurrentDistance = VSize(CarrierLocation - LocalPC.Pawn.Location); bFoundCarrier = true; } } } } } } // using the replication info to get the smoothed position of players outside of range for (i = 0; i < WorldInfo.GRI.PRIArray.Length; i++) { PRI = WorldInfo.GRI.PRIArray[i]; KFPRI = KFPlayerReplicationInfo(PRI); if (KFPRI != none && KFPRI.bCarryingCollectible) { if (PawnPlayerControllers.Find(KFPRI.Owner) == INDEX_NONE && PRI != LocalPC.PlayerReplicationInfo) { if (!bFoundCarrier || VSize(KFPRI.GetSmoothedPawnIconLocation(0.0f) - LocalPC.Pawn.Location) < CurrentDistance) { CarrierLocation = KFPRI.GetSmoothedPawnIconLocation(0.0f); CurrentDistance = VSize(CarrierLocation - LocalPC.Pawn.Location); bFoundCarrier = true; } } } } return bFoundCarrier; } simulated function bool GetClosestDroppedCollectible(out Actor DroppedCarryable) { local KFDroppedPickup_Carryable CurrentCarryable; local float CurrentDistance, ShortestDistance; local PlayerController LocalPC; LocalPC = GetALocalPlayerController(); if (LocalPC != none && LocalPC.Pawn != none) { foreach AllActors(class'KFDroppedPickup_Carryable', CurrentCarryable) { if (CurrentCarryable != none) { CurrentDistance = VSize(CurrentCarryable.Location - LocalPC.Pawn.Location); if (DroppedCarryable == none || CurrentDistance < ShortestDistance) { ShortestDistance = CurrentDistance; DroppedCarryable = CurrentCarryable; } } } } return DroppedCarryable != none; } simulated function UpdateTrailActor() { local Actor pathTarget; local vector CarrierLocation; local PlayerController LocalPC; local float AnchorDist; // only set a trail actor if this map objective is active if (bActive && WorldInfo.NetMode != NM_DedicatedServer) { if (TrailActor == none) { TrailActor = class'WorldInfo'.static.GetWorldInfo().Spawn(class'KFReplicatedShowPathActor', none); } if (IsLocalPlayerCarryingACollectible()) { //TrailActor.SetEmitterTemplate(ParticleSystem'FX_Objective_Temp.FX_Objective_Temp_Transport_Trail'); TrailActor.SetEmitterTemplate(ParticleSystem'FX_Gameplay_EMIT.FX_Objective_White_Trail'); } else { //TrailActor.SetEmitterTemplate(ParticleSystem'FX_Objective_Temp.FX_Objective_Temp_Collect_Trail'); TrailActor.SetEmitterTemplate(ParticleSystem'FX_Gameplay_EMIT.FX_Objective_White_Trail'); } if (IsLocalPlayerCarryingACollectible()) // local player carrying an object { pathTarget = self; } else { if (GetClosestPlayerCarryingACollectible(CarrierLocation)) // any player carrying an object { // getting the anchor from the carrier location so the same type of pathfinding can be used LocalPC = GetALocalPlayerController(); if (LocalPC.Pawn != none) { pathTarget = LocalPC.Pawn.GetBestAnchor(none, CarrierLocation, false, false, AnchorDist); } } else if (!GetClosestDroppedCollectible(pathTarget)) // any dropped collectible { // any active collectible pathTarget = FindClosestActiveCollector(); } } if (pathTarget != TrailActor.Target) { TrailActor.SetPathTarget(pathTarget); } } } simulated function ResetCollectReminder() { SetTimer(CollectActorReminderInterval, true, nameof(CollectActorReminder)); } simulated function ClearCollectReminder() { ClearTimer(nameof(CollectActorReminder)); } simulated function CollectActorReminder() { if (CollectActorReminderSoundEvent != none) { PlaySoundBase(CollectActorReminderSoundEvent, false, WorldInfo.NetMode == NM_DedicatedServer); } } simulated function int GetCollectibleIndexFromCollectActor(KFObjectiveCollectActor collectActor) { local int i; for (i = 0; i < CollectibleInfo.Length; ++i) { if (CollectibleInfo[i].Collectible == collectActor) { return i; } } return INDEX_NONE; } // This event is received from a volume that has this actor as its AssociatedActor event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal) { local KFDroppedPickup Pickup; local KFPawn_Human KFPH; local bool bShouldProgress; local KFCarryableObject_Collectible carryableObject; local Inventory InventoryItem; local KFInventoryManager KFInvManager; super.Touch(Other, OtherComp, HitLocation, HitNormal); if (bActive) { bShouldProgress = false; Pickup = KFDroppedPickup(Other); if (Pickup != none && Pickup.Inventory != none) { if (ClassIsChildOf(Pickup.Inventory.Class, DeliveryClass)) { // find which collectible this belongs to; needs to happen before the object gets destroyed below carryableObject = KFCarryableObject_Collectible(Pickup.Inventory); if (carryableObject != none && carryableObject.ParentCollectActor != none) { //play the matching "dropped off" sound for that collect actor PlaySoundEvent(CollectibleDeliveredSoundEvents, GetCollectibleIndexFromCollectActor(carryableObject.ParentCollectActor)); } Pickup.Destroy(); bShouldProgress = true; } } KFPH = KFPawn_Human(Other); if (KFPH != none) { InventoryItem = KFPH.InvManager.FindInventoryType(class(DeliveryClass), true); if (InventoryItem != none) { // find which collectible this belongs to; needs to happen before the object gets destroyed below carryableObject = KFCarryableObject_Collectible(InventoryItem); if (carryableObject != none && carryableObject.ParentCollectActor != none) { //play the matching "dropped off" sound for that collect actor PlaySoundEvent(CollectibleDeliveredSoundEvents, GetCollectibleIndexFromCollectActor(carryableObject.ParentCollectActor)); carryableObject.UpdateReplicationInfo(false); } KFInvManager = KFInventoryManager(KFPH.InvManager); if (KFInvManager != none && KFInvManager.ItemIsInInventory(InventoryItem)) { KFPH.InvManager.RemoveFromInventory(InventoryItem); InventoryItem.Destroy(); bShouldProgress = true; } } } if (bShouldProgress) { if (carryableObject != none && carryableObject.ParentCollectActor != none) { carryableObject.ParentCollectActor.SetCollectibleState(ECollectibleState_Delivered); } ProgressObjective(); } } } simulated function Timer_CollectibleReset() { bCollectibleReset = false; } simulated function RespawnCollectible(KFObjectiveCollectActor collectActor) { local int i; bCollectibleReset = true; SetTimer(CollectibleResetTimerLength, false, nameof(Timer_CollectibleReset)); ResetOrderedSoundEventsForRespawn(); for (i = 0; i < CollectibleInfo.Length; i++) { if (CollectibleInfo[i].Collectible == collectActor) { AvailableCollectibleIndices.InsertItem(0, i); CollectibleInfo[i].Collectible.SetEnabled(false); CollectibleInfo[i].Collectible.SetActive(false); CollectibleInfo[i].CollectibleTrigger.bActive = false; ChooseNextCollectible(); break; } } } simulated function OnCarryableDropped(KFObjectiveCollectActor collectActor) { if (!IsTimerActive(nameof(Timer_CollectibleDroppedCooldown))) { PlaySoundEvent(CollectibleDroppedSoundEvents, GetCollectibleIndexFromCollectActor(collectActor)); SetTimer(CollectibleDroppedInterval, false, nameof(Timer_CollectibleDroppedCooldown)); } } simulated function DrawIconAtLocation(KFHUDBase hud, Canvas drawCanvas, Texture2D icon, vector iconLocation, float xOffset, float yOffset) { local vector ScreenPos; local float ResModifier; local float ViewDot; local vector ViewLocation, ViewVector; local rotator ViewRotation; local PlayerController LocalPC; // determine whether that object is within the player's view LocalPC = GetALocalPlayerController(); if (LocalPC != none) { LocalPC.GetPlayerViewPoint(ViewLocation, ViewRotation); ViewVector = vector(ViewRotation); } ViewDot = Normal((iconLocation + (class'KFPawn_Human'.default.CylinderComponent.CollisionHeight * vect(0, 0, 1))) - ViewLocation) dot ViewVector; if (icon != none && ViewDot > 0) { ResModifier = WorldInfo.static.GetResolutionBasedHUDScale() * hud.FriendlyHudScale; ScreenPos = drawCanvas.Project(iconLocation); ScreenPos.X += xOffset; ScreenPos.Y += yOffset; if (ScreenPos.X < 0 || ScreenPos.X > drawCanvas.ClipX || ScreenPos.Y < 0 || ScreenPos.Y > drawCanvas.ClipY) { //if it is off screen, do not render return; } //draw icon drawCanvas.SetPos(ScreenPos.X, ScreenPos.Y + 12); //offset to better align with the other icons in the player's name tag drawCanvas.DrawTile(icon, CollectActorIconSize * ResModifier, CollectActorIconSize * ResModifier, 0, 0, 256, 256); } } simulated function bool IsLocalPlayerCarryingACollectible() { local PlayerController LocalPC; local KFPawn_Human KFPH; local Inventory InventoryItem; LocalPC = GetALocalPlayerController(); if (LocalPC != none && LocalPC.Pawn != none) { KFPH = KFPawn_Human(LocalPC.Pawn); InventoryItem = KFPH.InvManager.FindInventoryType(class(DeliveryClass), true); return (KFPH != none && InventoryItem != none); } return false; } simulated function int GetNumRequired() { local int PlayerCount; local KFGameReplicationInfo KFGRI; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); PlayerCount = Clamp(KFGRI.GetNumPlayers(), 1, 6) - 1; if (PlayerCount < NumRequired.Length) { return NumRequired[PlayerCount]; } else if (NumRequired.Length > 0) { // if there weren't enough entries, use the last one return NumRequired[NumRequired.Length - 1]; } return 1; } simulated function int GetNumDesiredActive() { local int PlayerCount; local KFGameReplicationInfo KFGRI; KFGRI = KFGameReplicationInfo(WorldInfo.GRI); PlayerCount = Clamp(KFGRI.GetNumPlayers(), 1, 6) - 1; if (PlayerCount < NumDesiredActiveCollectibles.Length) { return NumDesiredActiveCollectibles[PlayerCount]; } else if (NumDesiredActiveCollectibles.Length > 0) { // if there weren't enough entries, use the last one return NumDesiredActiveCollectibles[NumDesiredActiveCollectibles.Length - 1]; } return 1; } function Timer_CollectibleCollectCooldown() {} function Timer_CollectibleDroppedCooldown() {} defaultproperties { LocalizationPackageName = "KFGame" DescriptionLocKey="TransportWaveDescription" DescriptionShortLocKey = "TransportWaveDescriptionShort" RequirementsLocKey="RequiredCollectActor" LocalizationKey="TransportWaveObjective" NameShortLocKey="TransportWaveObjective" Physics=PHYS_None bStatic=false bNoDelete=true bTickIsDisabled=false RemoteRole=ROLE_SimulatedProxy bAlwaysRelevant=true bOnlyDirtyReplication=true bSkipActorPropertyReplication=true bIgnoreNetRelevancyCollision=true NetUpdateFrequency=0.1 bReplicateRigidBodyLocation=false bBlockActors=false bWorldGeometry=false bCollideWorld=false bNoEncroachCheck=false bProjTarget=false bCanStepUpOn=false bMovable=false bRandomSequence=true NumDesiredActiveCollectibles=(1) CollectActorIconSize=32.f; CollectibleIconAvailableColor=(R=0, G=255, B=0, A=255); CollectibleIconCarriedColor=(R=185, G=70, B=255, A=255); DeliveryClass = class'KFCarryableObject_Collectible' UpdateTrailActorInterval = 1.f; CollectActorReminderInterval = 40.f; CollectibleDroppedInterval = 20.f; CollectibleCollectInterval = 20.f; SupportedEvents.Add(class'KFSeqEvent_ObjectiveProgress') NumRequired=(1) PerPlayerSpawnRateMod=(1.f, 1.f, 1.f, 1.f, 1.f, 1.f) ObjectiveIcon=Texture2D'Objectives_UI.UI_Objectives_ObjectiveMode' CollectibleResetTimerLength=60.0f; DeliveryCloseAnimDelay=7.f }