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

682 lines
19 KiB
Ucode

//=============================================================================
// KFTargetingWeaponComponent
//=============================================================================
// Actor that encapsulates the management and replication of targeting weapon info.
// Enables any weapon to be targeting.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2019 Tripwire Interactive LLC
//=============================================================================
class KFTargetingWeaponComponent extends Actor
abstract;
/** Fire mode 0 is the default weapon firing. */
const DEFAULT_FIREMODE = 0;
/** Fire mode 1 is the alternate weapon firing. */
const ALTFIRE_FIREMODE = 1;
enum ETargetingMode
{
ETargetingMode_Zed,
ETargetingMode_Player,
};
const TARGETINGMODE_FLAGS_ZED = 1;
const TARGETINGMODE_FLAGS_PLAYER = 2;
const TARGETINGMODE_FLAGS_ALL = 3;
var byte TargetingModeFlags[2];
var KFWeapon KFW;
/*********************************************************************************************
* @name Weapon lock on support
**********************************************************************************************/
/** How far out should we be considering actors for a lock */
var float LockRange[2];
/** How long does the player need to target an actor to lock on to it*/
var float LockAcquireTime[2];
/** How long does the player need to target a large zed to lock on to it */
var float LockAcquireTime_Large[2];
/** How long does the player need to target a boss to lock on to it*/
var float LockAcquireTime_Boss[2];
/** How long does the player need to target a versus zed to lock on to it */
var float LockAcquireTime_Versus[2];
/** Once locked, how long can the player go without painting the object before they lose the lock */
var float LockTolerance[2];
/** What "target" is this weapon locked on to */
var KFPawn LockedTarget[2];
/** What hitzone is this weapon locked on to */
var int LockedHitZone[2];
/** What "target" is current pending to be locked on to */
var KFPawn PendingLockedTarget[2];
/** What hitzone is this weapon trying to lock on to */
var int PendingHitZone[2];
/** angle for locking for lock targets */
var float LockAim[2];
/** The frequency with which we play the Lock Targeting sound */
var float LockTargetingSoundInterval[2];
/** Sound Effects to play when Locking */
var AkBaseSoundObject LockAcquiredSoundFirstPerson;
var AkBaseSoundObject LockLostSoundFirstPerson;
var AkBaseSoundObject LockTargetingSoundFirstPerson;
/** How much time is left before pending lock-on can be acquired */
var float PendingLockAcquireTimeLeft[2];
/** How much time is left before pending lock-on is lost */
var float PendingLockTimeout[2];
/** How much time is left before lock-on is lost */
var float LockedOnTimeout;
var float LockedOnTimeoutTimer[2];
/** Location on an enemy we're currently locked onto */
var vector LockedAimLocation[2];
var array<name> HumanTargetableBoneNames;
/** Vulnerable zones on an enemy we're currently locked onto */
var array<vector> TargetVulnerableLocations_Zed;
var array<vector> TargetVulnerableLocations_Player;
/** The frequency with which we replicate the targeting location to the server. Only matters for showing other players where we shot */
var float TargetLocationReplicationInterval;
var float TargetLocationReplicationTimer[2];
var bool bTargetingUpdated;
simulated event PostBeginPlay()
{
super.PostBeginPlay();
SetTickIsDisabled(true);
}
simulated function Init(KFWeapon InKFW)
{
KFW = InKFW;
Instigator = InKFW.Instigator;
}
simulated event Tick(float DeltaTime)
{
super.Tick(DeltaTime);
if (Instigator != none && Instigator.Controller != none && Instigator.IsLocallyControlled())
{
CheckTargetLock(DeltaTime);
}
}
/**
* This function checks to see if we are locked on a target
*/
simulated function CheckTargetLock(float DeltaTime)
{
local bool bAllowLockOn;
local byte Flags;
bTargetingUpdated = false;
if ((Instigator == None) ||
(Instigator.Controller == None) ||
(KFW != Instigator.Weapon) ||
KFPlayerController(Instigator.Controller) == none)
{
return;
}
TargetVulnerableLocations_Zed.Length = 0;
TargetVulnerableLocations_Player.Length = 0;
Flags = TargetingModeFlags[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
if (!AllowTargetLockOn(ETargetingMode_Zed))
{
AdjustLockTarget(ETargetingMode_Zed, None);
PendingLockedTarget[ETargetingMode_Zed] = None;
LockedAimLocation[ETargetingMode_Zed]=vect(0,0,0);
}
else
{
bAllowLockOn = true;
if ((Flags & TARGETINGMODE_FLAGS_ZED) != 0)
{
UpdateTargetLock(ETargetingMode_Zed, DeltaTime);
}
}
if (!AllowTargetLockOn(ETargetingMode_Player))
{
AdjustLockTarget(ETargetingMode_Player, None);
PendingLockedTarget[ETargetingMode_Player] = None;
LockedAimLocation[ETargetingMode_Player]=vect(0,0,0);
}
else
{
bAllowLockOn = true;
if ((Flags & TARGETINGMODE_FLAGS_PLAYER) != 0)
{
UpdateTargetLock(ETargetingMode_Player, DeltaTime);
}
}
if (!bAllowLockOn)
{
ClearTimer(nameof(PlayTargetingBeepTimer));
return;
}
bTargetingUpdated = true;
}
/** returns true if lock-on is possible */
simulated function bool AllowTargetLockOn(byte TargetingMode)
{
return !Instigator.bNoWeaponFiring;
}
simulated function AdjustLockTarget(byte TargetingMode, KFPawn NewLockTarget)
{
if (LockedTarget[TargetingMode] == NewLockTarget)
{
// no need to update
return;
}
if (NewLockTarget == None)
{
// Clear the lock
if (LockedTarget[TargetingMode] != none)
{
LockedTarget[TargetingMode] = None;
if (TargetingMode == ETargetingMode_Zed)
{
TargetVulnerableLocations_Zed.Length = 0;
}
else
{
TargetVulnerableLocations_Player.Length = 0;
}
LockedAimLocation[TargetingMode]=vect(0,0,0);
ServerSetTargetingLocation(TargetingMode, LockedAimLocation[TargetingMode]);
if (Instigator != None && Instigator.IsHumanControlled())
{
KFW.PlaySoundBase(LockLostSoundFirstPerson, true);
}
}
}
else
{
// Set the lock
LockedTarget[TargetingMode] = NewLockTarget;
if (Instigator != None && Instigator.IsHumanControlled())
{
KFW.PlaySoundBase(LockAcquiredSoundFirstPerson, true);
}
}
}
reliable server private function ServerSetTargetingLocation(byte TargetingMode, vector NewTargetingLocation)
{
LockedAimLocation[TargetingMode] = NewTargetingLocation;
}
simulated function PlayTargetingBeepTimer()
{
if (Instigator != None && Instigator.IsHumanControlled() )
{
KFW.PlaySoundBase(LockTargetingSoundFirstPerson, true);
}
}
simulated function UpdateTargetLock(byte TargetingMode, float DeltaTime)
{
local Actor BestTarget;
BestTarget = DetermineBestTarget(TargetingMode);
UpdateLockTargets(TargetingMode, DeltaTime, BestTarget);
SetupAimLock(TargetingMode, DeltaTime);
}
simulated function Actor DetermineBestTarget(byte TargetingMode)
{
local vector StartTrace;
local rotator ViewRotation;
local vector X,Y,Z;
local Actor BestTarget, HitActor, TA;
local vector EndTrace, Aim, HitLocation, HitNormal;
local KFPlayerController KFPC;
KFPC = KFPlayerController(Instigator.Controller);
// Get the location and rotation that a shot would take
StartTrace = Instigator.GetWeaponStartTraceLocation();
ViewRotation = Instigator.GetViewRotation();
// Add in the free-aim rotation
ViewRotation += KFPC.WeaponBufferRotation;
GetAxes(ViewRotation, X, Y, Z);
BestTarget = None;
// Begin by tracing the shot to see if it hits anyone
Aim = vector(ViewRotation);
EndTrace = StartTrace + Aim * GetLockRange();
HitActor = KFW.Trace(HitLocation, HitNormal, EndTrace, StartTrace, true,,, TRACEFLAG_Bullet);
// Check for a hit
if ((HitActor == None) || !CanLockOnTo(TargetingMode, HitActor))
{
TA = PickTarget(Aim, StartTrace, TargetingMode == ETargetingMode_Player);
if (TA != None && CanLockOnTo(TargetingMode, TA))
{
// Trace to see if we hit a destructible, as the PickTarget code only traces against world geometry
// @todo: Set up pick target to ignore pawns (as it should), but not trace through destructibles
HitActor = Trace(HitLocation, HitNormal, TA.Location, StartTrace, true,,, TRACEFLAG_Bullet);
// Make sure we didn't hit a destructible
if (KFFracturedMeshActor(HitActor) != none || KFDestructibleActor(HitActor) != none)
{
BestTarget = none;
}
else
{
BestTarget = TA;
}
}
}
else // We hit a valid target
{
BestTarget = HitActor;
}
return BestTarget;
}
simulated function bool CanLockOnTo(byte TargetingMode, Actor TA)
{
Local KFPawn PawnTarget;
PawnTarget = KFPawn(TA);
// Make sure the pawn is legit, isn't dead, and isn't already at full health
if ((TA == None) ||
!TA.bProjTarget ||
TA.bDeleteMe ||
(PawnTarget == None) ||
(TA == Instigator) ||
(PawnTarget.Health <= 0) ||
(!KFW.bUseAltFireMode && !KFW.HasAmmo(DEFAULT_FIREMODE)) ||
(KFW.bUseAltFireMode && !KFW.HasAmmo(ALTFIRE_FIREMODE)))
{
return false;
}
return ((TargetingMode == ETargetingMode_Zed && !WorldInfo.GRI.OnSameTeam(Instigator,TA)) ||
(TargetingMode == ETargetingMode_Player && WorldInfo.GRI.OnSameTeam(Instigator,TA)));
}
simulated function Actor PickTarget(vector Aim, vector StartTrace, optional bool bTargetTeammates=false)
{
local float BestAim, BestDist;
BestAim = GetLockAim();
BestDist = 0.0;
return KFPlayerController(Instigator.Controller).GetPickedAimAtTarget(
BestAim, BestDist, Aim, StartTrace, GetLockRange(), bTargetTeammates);
}
simulated function UpdateLockTargets(byte TargetingMode, float DeltaTime, Actor BestTarget)
{
// clear lock if target is destroyed
if (LockedTarget[TargetingMode] != None)
{
if (LockedTarget[TargetingMode].bDeleteMe)
{
AdjustLockTarget(TargetingMode, None);
}
}
// If we have a "possible" target, note its time mark
if (BestTarget != None)
{
if (BestTarget == LockedTarget[TargetingMode])
{
UpdateTargetLocked(TargetingMode);
}
// We have our best target, see if they should become our current target.
// Check for a new Pending Lock
else if (PendingLockedTarget[TargetingMode] != BestTarget)
{
UpdatePendingLockTarget(TargetingMode, BestTarget);
}
AcquireLockTarget(TargetingMode, DeltaTime, BestTarget);
}
// If we lost a target, attempt to invalidate the pending target
else
{
TimeoutPendingLockTarget(TargetingMode, DeltaTime);
}
TimeoutLockTarget(TargetingMode, DeltaTime, BestTarget);
}
simulated function UpdateTargetLocked(byte TargetingMode)
{
LockedOnTimeoutTimer[TargetingMode] = GetLockTolerance();
if (PendingLockedTarget[TargetingMode] != none)
{
ClearTimer(nameof(PlayTargetingBeepTimer));
PendingLockedTarget[TargetingMode] = None;
}
}
simulated function UpdatePendingLockTarget(byte TargetingMode, Actor BestTarget)
{
local KFPawn_Monster KFPM;
KFPM = KFPawn_Monster(BestTarget);
PendingLockedTarget[TargetingMode] = KFPawn(BestTarget);
PendingLockTimeout[TargetingMode] = GetLockTolerance();
PendingLockAcquireTimeLeft[TargetingMode] = GetLockAcquireTime();
if (KFPM != none)
{
if (KFPM.IsABoss())
{
PendingLockAcquireTimeLeft[TargetingMode] = GetLockAcquireTime_Boss();
}
else if (KFPM.bVersusZed)
{
PendingLockAcquireTimeLeft[TargetingMode] = GetLockAcquireTime_Versus();
}
else if (KFPM.IsLargeZed())
{
PendingLockAcquireTimeLeft[TargetingMode] = GetLockAcquireTime_Large();
}
}
SetTimer(LockTargetingSoundInterval[TargetingMode], true, nameof(PlayTargetingBeepTimer));
// Play the "targeting" beep when we begin attempting to lock onto a target
// that we haven't locked onto yet
if (Instigator != None && Instigator.IsHumanControlled())
{
PlaySoundBase(LockTargetingSoundFirstPerson, true);
}
}
simulated function AcquireLockTarget(byte TargetingMode, float DeltaTime, Actor BestTarget)
{
// Acquire new target if LockAcquireTime has passed
if (PendingLockedTarget[TargetingMode] != None)
{
PendingLockAcquireTimeLeft[TargetingMode] -= DeltaTime;
if (PendingLockedTarget[TargetingMode] == BestTarget && PendingLockAcquireTimeLeft[TargetingMode] <= 0)
{
AdjustLockTarget(TargetingMode, PendingLockedTarget[TargetingMode]);
PendingLockedTarget[TargetingMode] = None;
ClearTimer(nameof(PlayTargetingBeepTimer));
}
}
}
simulated function TimeoutPendingLockTarget(byte TargetingMode, float DeltaTime)
{
if (PendingLockedTarget[TargetingMode] != None)
{
PendingLockTimeout[TargetingMode] -= DeltaTime;
if (PendingLockTimeout[TargetingMode] <= 0 || !CanLockOnTo(TargetingMode, PendingLockedTarget[TargetingMode]))
{
PendingLockedTarget[TargetingMode] = None;
ClearTimer(nameof(PlayTargetingBeepTimer));
}
}
}
simulated function TimeoutLockTarget(byte TargetingMode, float DeltaTime, Actor BestTarget)
{
// If the new target is not the same, attempt to invalidate current locked on target
if (LockedTarget[TargetingMode] != None && BestTarget != LockedTarget[TargetingMode])
{
LockedOnTimeoutTimer[TargetingMode] -= DeltaTime;
if (LockedOnTimeoutTimer[TargetingMode] <= 0.f || !CanLockOnTo(TargetingMode, LockedTarget[TargetingMode]))
{
AdjustLockTarget(TargetingMode, None);
}
}
}
simulated function SetupAimLock(byte TargetingMode, float DeltaTime)
{
local vector Aim, StartTrace, BestZoneLocation;
local int OldHitZone;
Aim = vector(Instigator.GetViewRotation());
//StartTrace = KFW.GetSafeStartTraceLocation();
StartTrace = Instigator.GetWeaponStartTraceLocation();
// Set the aiming location if we have a target lock
if (LockedTarget[TargetingMode] != none)
{
OldHitZone = LockedHitZone[TargetingMode];
LockedHitZone[TargetingMode] =
AddTargetingZones(TargetingMode, LockedTarget[TargetingMode], StartTrace, Aim, BestZoneLocation);
if (OldHitZone != LockedHitZone[TargetingMode])
{
if (Instigator != None && Instigator.IsHumanControlled())
{
KFW.PlaySoundBase(LockTargetingSoundFirstPerson, true);
}
}
TargetLocationReplicationTimer[TargetingMode] -= DeltaTime;
if (TargetLocationReplicationTimer[TargetingMode] <= 0 || IsZero(LockedAimLocation[TargetingMode]))
{
TargetLocationReplicationTimer[TargetingMode] = TargetLocationReplicationInterval;
ServerSetTargetingLocation(TargetingMode, LockedAimLocation[TargetingMode]);
}
// Set the location where the shot will go
LockedAimLocation[TargetingMode] = BestZoneLocation;
}
else
{
LockedHitZone[TargetingMode] = -1;
}
// Set the pending target location
if (PendingLockedTarget[TargetingMode] != none)
{
PendingHitZone[TargetingMode] =
AddTargetingZones(TargetingMode, PendingLockedTarget[TargetingMode], StartTrace, Aim, BestZoneLocation);
}
else
{
PendingHitZone[TargetingMode] = -1;
}
}
simulated function int AddTargetingZones(
byte TargetingMode, KFPawn KFPTarget, vector StartTrace, vector Aim, out vector BestZoneLocation)
{
local KFPawn_Monster KFPM;
local KFPawn_Human KFPH;
if (TargetingMode == ETargetingMode_Zed)
{
KFPM = KFPawn_Monster(KFPTarget);
if (KFPM != none)
{
return GetZedVulnerableLocations(KFPM, Aim, StartTrace, BestZoneLocation);
}
}
else
{
KFPH = KFPawn_Human(KFPTarget);
if (KFPH != none)
{
return GetHumanVulnerableLocations(KFPH, Aim, StartTrace, BestZoneLocation);
}
}
return -1;
}
simulated function int GetZedVulnerableLocations(
KFPawn_Monster Zed, vector Aim, vector StartTrace, out vector BestZoneLocation)
{
local vector ZoneLocation;
local int BestZoneIndex;
local float BestZoneDot;
local vector DirToZone;
local float DotToZone;
local int i;
// Get all the vulnerable hit zones
for (i = 0; i < Zed.WeakSpotSocketNames.Length; i++)
{
Zed.Mesh.GetSocketWorldLocationAndRotation(Zed.WeakSpotSocketNames[i], ZoneLocation);
if (!IsZero(ZoneLocation))
{
TargetVulnerableLocations_Zed.AddItem(ZoneLocation);
// Figure out which target zone we are closest to
DirToZone = ZoneLocation - StartTrace;
DotToZone = Normal(Aim) dot Normal(DirToZone);
if (DotToZone > BestZoneDot)
{
BestZoneIndex = TargetVulnerableLocations_Zed.Length - 1;
BestZoneDot = DotToZone;
// Set the location for the zone we are closest targeting
BestZoneLocation = ZoneLocation;
}
}
}
return BestZoneIndex;
}
simulated function int GetHumanVulnerableLocations(
KFPawn_Human Human, vector Aim, vector StartTrace, out vector BestZoneLocation)
{
local vector ZoneLocation;
local int BestZoneIndex;
local float BestZoneDot;
local vector DirToZone;
local float DotToZone;
local int i;
// Get all the vulnerable hit zones
for (i = 0; i < HumanTargetableBoneNames.Length; i++)
{
ZoneLocation = Human.Mesh.GetBoneLocation(HumanTargetableBoneNames[i]);
if(!IsZero(ZoneLocation))
{
TargetVulnerableLocations_Player.AddItem(ZoneLocation);
// Figure out which target zone we are closest to
DirToZone = ZoneLocation - StartTrace;
DotToZone = Normal(Aim) dot Normal(DirToZone);
if( DotToZone > BestZoneDot )
{
BestZoneIndex = TargetVulnerableLocations_Player.Length - 1;
BestZoneDot = DotToZone;
// Set the location for the zone we are closest targeting
BestZoneLocation = ZoneLocation;
}
}
}
return BestZoneIndex;
}
/*********************************************************************************************
* @name KFWeapon hooks
**********************************************************************************************/
simulated function OnWeaponAttachedTo()
{
SetTickIsDisabled(false);
}
simulated function OnWeaponDetached()
{
SetTickIsDisabled(true);
}
/*********************************************************************************************
* @name Getters
**********************************************************************************************/
simulated function float GetLockRange()
{
return LockRange[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockAim()
{
return LockAim[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockAcquireTime()
{
return LockAcquireTime[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockAcquireTime_Large()
{
return LockAcquireTime_Large[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockAcquireTime_Boss()
{
return LockAcquireTime_Boss[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockAcquireTime_Versus()
{
return LockAcquireTime_Versus[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
simulated function float GetLockTolerance()
{
return LockTolerance[KFW.bUseAltFireMode ? ALTFIRE_FIREMODE : DEFAULT_FIREMODE];
}
defaultproperties
{
RemoteRole=ROLE_SimulatedProxy
bOnlyRelevantToOwner=true
bHidden=true
bCollideActors=false
bProjTarget=false
bStatic=false
bNoDelete=false
}