1147 lines
33 KiB
Ucode
1147 lines
33 KiB
Ucode
//=============================================================================
|
|
// 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<int> 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<CollectibleActorInfo> 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<int> NumDesiredActiveCollectibles;
|
|
|
|
var transient array<int> 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<AKEvent> 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<Inventory>(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<KFGameInfo> 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<Controller> 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<Controller> 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<Inventory>(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<Inventory>(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
|
|
} |