2020-12-13 15:01:13 +00:00
|
|
|
//=============================================================================
|
|
|
|
// KFWeap_MedicBase
|
|
|
|
//=============================================================================
|
|
|
|
// Base class for medic weapons that shoot healing darts and can lock on
|
|
|
|
//=============================================================================
|
|
|
|
// Killing Floor 2
|
|
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
class KFWeap_MedicBase extends KFWeapon
|
|
|
|
abstract;
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* @name Healing Darts
|
|
|
|
********************************************************************************************* */
|
|
|
|
/** DamageTypes for Instant Hit Weapons */
|
|
|
|
var class<DamageType> HealingDartDamageType;
|
|
|
|
|
|
|
|
/** How much to heal for when using this weapon */
|
|
|
|
var(Healing) int HealAmount;
|
|
|
|
|
|
|
|
/** How many points of heal ammo to recharge per second */
|
|
|
|
var(Healing) float HealFullRechargeSeconds;
|
|
|
|
|
|
|
|
/** Keeps track of incremental healing since we have to heal in whole integers */
|
|
|
|
var float HealingIncrement;
|
|
|
|
|
|
|
|
/** How many points of heal ammo to recharge per second. Calculated from the HealFullRechargeSeconds */
|
|
|
|
var float HealRechargePerSecond;
|
|
|
|
|
|
|
|
/** The sound of the healing dart hitting someone they will heal */
|
|
|
|
var AKEvent HealImpactSoundPlayEvent;
|
|
|
|
|
|
|
|
/** The sound of the healing dart hitting someone they will hurt */
|
|
|
|
var AKEvent HurtImpactSoundPlayEvent;
|
|
|
|
|
|
|
|
/** Sound to play when the weapon is fired */
|
|
|
|
var(Sounds) WeaponFireSndInfo DartFireSnd;
|
|
|
|
|
|
|
|
const ShootDartAnim = 'Shoot_Dart';
|
|
|
|
const ShootDartIronAnim = 'Shoot_Iron_Dart';
|
|
|
|
|
|
|
|
/** How long after we shoot a healing dart before a zed can grab us.
|
|
|
|
* Prevents us from missing healing shots from being grabbed */
|
|
|
|
var(Weapon) float HealDartShotWeakZedGrabCooldown;
|
|
|
|
|
|
|
|
/** Recoil override for healing dart alt-fire */
|
|
|
|
var(Recoil) int DartMaxRecoilPitch;
|
|
|
|
var(Recoil) int DartMinRecoilPitch;
|
|
|
|
var(Recoil) int DartMaxRecoilYaw;
|
|
|
|
var(Recoil) int DartMinRecoilYaw;
|
|
|
|
|
|
|
|
/** Controller rumble override for healing dart. */
|
|
|
|
var ForceFeedbackWaveform HealingDartWaveForm;
|
|
|
|
|
|
|
|
var repnotify byte HealingDartAmmo;
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* @name Weapon lock on support
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** The frequency with which we will check for a lock */
|
|
|
|
var(Locking) float LockCheckTime;
|
|
|
|
|
|
|
|
/** How far out should we be considering actors for a lock */
|
|
|
|
var(Locking) float LockRange;
|
|
|
|
|
|
|
|
/** How long does the player need to target an actor to lock on to it*/
|
|
|
|
var(Locking) float LockAcquireTime;
|
|
|
|
|
|
|
|
/** Once locked, how long can the player go without painting the object before they lose the lock */
|
|
|
|
var(Locking) float LockTolerance;
|
|
|
|
|
|
|
|
/** When true, this weapon is locked on target */
|
|
|
|
var bool bLockedOnTarget;
|
|
|
|
|
|
|
|
/** What "target" is this weapon locked on to */
|
|
|
|
var repnotify Actor LockedTarget;
|
|
|
|
|
|
|
|
/** What "target" is current pending to be locked on to */
|
|
|
|
var repnotify Actor PendingLockedTarget;
|
|
|
|
|
|
|
|
/** angle for locking for lock targets */
|
|
|
|
var(Locking) float LockAim;
|
|
|
|
|
|
|
|
/** Sound Effects to play when Locking */
|
|
|
|
var AkBaseSoundObject LockAcquiredSoundFirstPerson;
|
|
|
|
var AkBaseSoundObject LockTargetingStopEvent;
|
|
|
|
var AkBaseSoundObject LockTargetingStopEventFirstPerson;
|
|
|
|
var AkBaseSoundObject LockLostSoundFirstPerson;
|
|
|
|
var AkBaseSoundObject LockTargetingSoundFirstPerson;
|
|
|
|
|
|
|
|
/** If true, weapon will try to lock onto targets */
|
|
|
|
var bool bTargetLockingActive;
|
|
|
|
|
|
|
|
/** How much time is left before pending lock-on can be acquired */
|
|
|
|
var float PendingLockAcquireTimeLeft;
|
|
|
|
/** How much time is left before pending lock-on is lost */
|
|
|
|
var float PendingLockTimeout;
|
|
|
|
/** How much time is left before lock-on is lost */
|
|
|
|
var float LockedOnTimeout;
|
|
|
|
|
|
|
|
var bool bRechargeHealAmmo;
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Optics UI
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
var class<KFGFxWorld_MedicOptics> OpticsUIClass;
|
|
|
|
var KFGFxWorld_MedicOptics OpticsUI;
|
|
|
|
|
|
|
|
/** The last updated value for our ammo - Used to know when to update our optics ammo */
|
|
|
|
var byte StoredPrimaryAmmo;
|
|
|
|
var byte StoredSecondaryAmmo;
|
|
|
|
|
|
|
|
replication
|
|
|
|
{
|
|
|
|
// Server->Client properties
|
|
|
|
if (bNetDirty && Role == ROLE_Authority)
|
|
|
|
bLockedOnTarget, LockedTarget, PendingLockedTarget;
|
|
|
|
|
|
|
|
if (bNetDirty && Role == ROLE_Authority && bAllowClientAmmoTracking && bRechargeHealAmmo)
|
|
|
|
HealingDartAmmo;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* epic ===============================================
|
|
|
|
* ::ReplicatedEvent
|
|
|
|
*
|
|
|
|
* Called when a variable with the property flag "RepNotify" is replicated
|
|
|
|
*
|
|
|
|
* =====================================================
|
|
|
|
*/
|
|
|
|
simulated event ReplicatedEvent(name VarName)
|
|
|
|
{
|
|
|
|
if (VarName == nameof(LockedTarget))
|
|
|
|
{
|
|
|
|
// Clear the lock if we lost our LockedTarget and don't have a new PendingLockedTarget
|
|
|
|
if( OpticsUI != none )
|
|
|
|
{
|
|
|
|
if (LockedTarget == none && PendingLockedTarget == none)
|
|
|
|
{
|
|
|
|
OpticsUI.ClearLockOn();
|
|
|
|
}
|
|
|
|
else if (LockedTarget != none)
|
|
|
|
{
|
|
|
|
OpticsUI.LockedOn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (VarName == nameof(PendingLockedTarget))
|
|
|
|
{
|
|
|
|
// Clear the lock if we lost our LockedTarget and don't have a new PendingLockedTarget
|
|
|
|
if( OpticsUI != none )
|
|
|
|
{
|
|
|
|
if (PendingLockedTarget == none && LockedTarget == none)
|
|
|
|
{
|
|
|
|
OpticsUI.ClearLockOn();
|
|
|
|
}
|
|
|
|
else if (PendingLockedTarget != none)
|
|
|
|
{
|
|
|
|
OpticsUI.StartLockOn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (VarName == nameof(HealingDartAmmo))
|
|
|
|
{
|
|
|
|
AmmoCount[ALTFIRE_FIREMODE] = HealingDartAmmo;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Super.ReplicatedEvent(VarName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Actor
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check target locking - server-side only
|
|
|
|
* HealAmmo Regen client and server
|
|
|
|
*/
|
|
|
|
simulated event Tick( FLOAT DeltaTime )
|
|
|
|
{
|
|
|
|
// If we're not fully charged tick the HealAmmoRegen system
|
|
|
|
if( AmmoCount[ALTFIRE_FIREMODE] < MagazineCapacity[ALTFIRE_FIREMODE] )
|
|
|
|
{
|
|
|
|
HealAmmoRegeneration(DeltaTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Instigator != none && Instigator.weapon == self)
|
|
|
|
{
|
|
|
|
UpdateOpticsUI();
|
|
|
|
}
|
|
|
|
|
|
|
|
Super.Tick(DeltaTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Firing / Projectile
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** Instead of switch fire mode use as immediate alt fire */
|
|
|
|
simulated function AltFireMode()
|
|
|
|
{
|
|
|
|
if ( !Instigator.IsLocallyControlled() )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartFire - StopFire called from KFPlayerInput
|
|
|
|
StartFire(ALTFIRE_FIREMODE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @see KFWeapon::ConsumeAmmo */
|
|
|
|
simulated function ConsumeAmmo( byte FireModeNum )
|
|
|
|
{
|
2023-10-17 23:03:17 +00:00
|
|
|
local KFPerk Perk;
|
|
|
|
local float DartAmmoCostModifier;
|
2020-12-13 15:01:13 +00:00
|
|
|
// If its not the healing fire mode, return
|
|
|
|
if( FireModeNum != ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
Super.ConsumeAmmo(FireModeNum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
`if(`notdefined(ShippingPC))
|
|
|
|
if( bInfiniteAmmo )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
`endif
|
|
|
|
|
|
|
|
// If AmmoCount is being replicated, don't allow the client to modify it here
|
|
|
|
if (Role == ROLE_Authority || bAllowClientAmmoTracking)
|
|
|
|
{
|
2023-10-17 23:03:17 +00:00
|
|
|
Perk = GetPerk();
|
|
|
|
DartAmmoCostModifier = Perk != none ? Perk.GetDartAmmoCostModifier() : 1.0f;
|
|
|
|
|
2020-12-13 15:01:13 +00:00
|
|
|
// Don't consume ammo if magazine size is 0 (infinite ammo with no reload)
|
|
|
|
if (MagazineCapacity[1] > 0 && AmmoCount[1] > 0)
|
|
|
|
{
|
|
|
|
// Reduce ammo amount by heal ammo cost
|
2023-10-17 23:03:17 +00:00
|
|
|
AmmoCount[1] = Max(AmmoCount[1] - AmmoCost[1] * DartAmmoCostModifier, 0);
|
2020-12-13 15:01:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See Pawn.ProcessInstantHit
|
|
|
|
* @param DamageReduction: Custom KF parameter to handle penetration damage reduction
|
|
|
|
*/
|
|
|
|
simulated function ProcessInstantHitEx( byte FiringMode, ImpactInfo Impact, optional int NumHits, optional out float out_PenetrationVal, optional int ImpactNum )
|
|
|
|
{
|
|
|
|
local KFPawn HealTarget;
|
|
|
|
local KFPlayerController Healer;
|
|
|
|
local KFPerk InstigatorPerk;
|
|
|
|
local float AdjustedHealAmount;
|
|
|
|
|
|
|
|
HealTarget = KFPawn(Impact.HitActor);
|
|
|
|
Healer = KFPlayerController(Instigator.Controller);
|
|
|
|
|
|
|
|
InstigatorPerk = GetPerk();
|
|
|
|
if( InstigatorPerk != none )
|
|
|
|
{
|
|
|
|
InstigatorPerk.UpdatePerkHeadShots( Impact, InstantHitDamageTypes[FiringMode], ImpactNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (FiringMode == ALTFIRE_FIREMODE && HealTarget != none && WorldInfo.GRI.OnSameTeam(Instigator,HealTarget) )
|
|
|
|
{
|
|
|
|
// Let the accuracy system know that we hit someone
|
|
|
|
if( Healer != none )
|
|
|
|
{
|
|
|
|
Healer.AddShotsHit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
AdjustedHealAmount = HealAmount * static.GetUpgradeHealMod(CurrentWeaponUpgradeIndex);
|
|
|
|
HealTarget.HealDamage(AdjustedHealAmount, Instigator.Controller, HealingDartDamageType);
|
|
|
|
|
|
|
|
// Play a healed impact sound for the healee
|
|
|
|
if( HealImpactSoundPlayEvent != None && HealTarget != None && !bSuppressSounds )
|
|
|
|
{
|
|
|
|
HealTarget.PlaySoundBase(HealImpactSoundPlayEvent, false, false,,Impact.HitLocation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Play a hurt impact sound for the hurt
|
|
|
|
if( HurtImpactSoundPlayEvent != None && HealTarget != None && !bSuppressSounds )
|
|
|
|
{
|
|
|
|
HealTarget.PlaySoundBase(HurtImpactSoundPlayEvent, false, false,,Impact.HitLocation);
|
|
|
|
}
|
|
|
|
Super.ProcessInstantHitEx(FiringMode, Impact, NumHits, out_PenetrationVal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Spawn projectile is called once for each shot pellet fired */
|
|
|
|
simulated function KFProjectile SpawnProjectile( class<KFProjectile> KFProjClass, vector RealStartLoc, vector AimDir )
|
|
|
|
{
|
|
|
|
local KFProjectile SpawnedProjectile;
|
|
|
|
|
|
|
|
SpawnedProjectile = Super.SpawnProjectile(KFProjClass, RealStartLoc, AimDir);
|
|
|
|
|
|
|
|
if (bLockedOnTarget && KFProj_HealingDart(SpawnedProjectile) != None)
|
|
|
|
{
|
|
|
|
KFProj_HealingDart(SpawnedProjectile).SeekTarget = LockedTarget;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SpawnedProjectile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called on the client when the weapon is fired calculate the recoil parameters
|
|
|
|
* Network: LocalPlayer
|
|
|
|
*/
|
|
|
|
simulated event HandleRecoil()
|
|
|
|
{
|
|
|
|
// Separate recoil settings for healing darts. Doesn't update RecoilRate
|
|
|
|
// or BlendOutRate, but that could be problematic if currently recoiling.
|
|
|
|
if ( CurrentFireMode == ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
minRecoilPitch = DartMinRecoilPitch;
|
|
|
|
maxRecoilPitch = DartMaxRecoilPitch;
|
|
|
|
minRecoilYaw = DartMinRecoilYaw;
|
|
|
|
maxRecoilYaw = DartMaxRecoilYaw;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
minRecoilPitch = default.minRecoilPitch;
|
|
|
|
maxRecoilPitch = default.maxRecoilPitch;
|
|
|
|
minRecoilYaw = default.minRecoilYaw;
|
|
|
|
maxRecoilYaw = default.maxRecoilYaw;
|
|
|
|
}
|
|
|
|
|
|
|
|
Super.HandleRecoil();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** plays view shake on the owning client only */
|
|
|
|
simulated function ShakeView()
|
|
|
|
{
|
|
|
|
// All healing darts use the same force feedback wave form
|
|
|
|
if ( CurrentFireMode == ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
WeaponFireWaveForm = HealingDartWaveForm;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
WeaponFireWaveForm = default.WeaponFireWaveForm;
|
|
|
|
}
|
|
|
|
|
|
|
|
Super.ShakeView();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* State WeaponSingleFiring
|
|
|
|
* Fire must be released between every shot.
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
simulated state WeaponSingleFiring
|
|
|
|
{
|
|
|
|
/** Handle ClearPendingFire */
|
|
|
|
simulated function FireAmmunition()
|
|
|
|
{
|
|
|
|
if(CurrentFireMode == ALTFIRE_FIREMODE)
|
|
|
|
{
|
|
|
|
// Don't let a weak zed grab us when we just shot a healing dart
|
|
|
|
SetWeakZedGrabCooldownOnPawn(HealDartShotWeakZedGrabCooldown);
|
|
|
|
StartHealRecharge();
|
|
|
|
}
|
|
|
|
|
|
|
|
Super.FireAmmunition();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This makes it impossible for the server to fire before the fire animation has the chance to play on the client side.
|
|
|
|
simulated function StartFire(byte FireModeNum)
|
|
|
|
{
|
|
|
|
if(FireModeNum == ALTFIRE_FIREMODE && !HasAmmo(FireModeNum, AmmoCost[FireModeNum]))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Super.StartFire(FireModeNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Reload / recharge
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** Overridden to call StartHealRecharge on server */
|
|
|
|
function GivenTo( Pawn thisPawn, optional bool bDoNotActivate )
|
|
|
|
{
|
|
|
|
super.GivenTo( thisPawn, bDoNotActivate );
|
|
|
|
|
|
|
|
// StartHealRecharge gets called on the client from ClientWeaponSet (called from ClientGivenTo, called from GivenTo),
|
|
|
|
// but we also need this called on the server for remote clients, since the server needs to track the ammo too (to know if/when to spawn projectiles)
|
|
|
|
|
|
|
|
if( Role == ROLE_Authority && !thisPawn.IsLocallyControlled() )
|
|
|
|
{
|
|
|
|
StartHealRecharge();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Start the heal recharge cycle */
|
|
|
|
function StartHealRecharge()
|
|
|
|
{
|
|
|
|
local KFPerk InstigatorPerk;
|
|
|
|
local float UsedHealRechargeTime;
|
|
|
|
if (!bRechargeHealAmmo)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// begin ammo recharge on server
|
|
|
|
if( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
InstigatorPerk = GetPerk();
|
2023-09-21 19:31:11 +00:00
|
|
|
|
|
|
|
UsedHealRechargeTime = HealFullRechargeSeconds;
|
|
|
|
UsedHealRechargeTime *= static.GetUpgradeHealRechargeMod(CurrentWeaponUpgradeIndex);
|
2020-12-13 15:01:13 +00:00
|
|
|
|
|
|
|
InstigatorPerk.ModifyHealerRechargeTime( UsedHealRechargeTime );
|
|
|
|
// Set the healing recharge rate whenever we start charging
|
|
|
|
HealRechargePerSecond = MagazineCapacity[ALTFIRE_FIREMODE] / UsedHealRechargeTime;
|
|
|
|
HealingIncrement = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Heal Ammo Regen */
|
|
|
|
function HealAmmoRegeneration(float DeltaTime)
|
|
|
|
{
|
|
|
|
if (!bRechargeHealAmmo)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
HealingIncrement += HealRechargePerSecond * DeltaTime;
|
|
|
|
|
|
|
|
if( HealingIncrement >= 1.0 && AmmoCount[ALTFIRE_FIREMODE] < MagazineCapacity[ALTFIRE_FIREMODE] )
|
|
|
|
{
|
|
|
|
AmmoCount[ALTFIRE_FIREMODE]++;
|
|
|
|
HealingIncrement -= 1.0;
|
|
|
|
|
|
|
|
// Heal ammo regen is only tracked on the server, so even though we told the client he could
|
|
|
|
// keep track of ammo himself like a big boy, we still have to spoon-feed it to him.
|
|
|
|
if (bAllowClientAmmoTracking)
|
|
|
|
{
|
|
|
|
HealingDartAmmo = AmmoCount[ALTFIRE_FIREMODE];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Healing charge doesn't count as ammo for purposes of inventory management (e.g. switching) */
|
|
|
|
simulated function bool HasAnyAmmo()
|
|
|
|
{
|
|
|
|
if ( HasSpareAmmo() || HasAmmo(DEFAULT_FIREMODE) )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Target Locking
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function is used to adjust the LockTarget.
|
|
|
|
*/
|
|
|
|
function AdjustLockTarget(actor NewLockTarget)
|
|
|
|
{
|
|
|
|
if ( LockedTarget == NewLockTarget )
|
|
|
|
{
|
|
|
|
// no need to update
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NewLockTarget == None)
|
|
|
|
{
|
|
|
|
// Clear the lock
|
|
|
|
if (bLockedOnTarget)
|
|
|
|
{
|
|
|
|
bLockedOnTarget = false;
|
|
|
|
LockedTarget = None;
|
|
|
|
|
|
|
|
if (OpticsUI != none && PendingLockedTarget == none)
|
|
|
|
{
|
|
|
|
// Optics UI only exists for local players
|
|
|
|
OpticsUI.ClearLockOn();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bUsingSights )
|
|
|
|
{
|
|
|
|
ClientPlayTargetingSound(LockLostSoundFirstPerson);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Set the lock
|
|
|
|
bLockedOnTarget = true;
|
|
|
|
LockedTarget = NewLockTarget;
|
|
|
|
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
// Optics UI only exists for local players
|
|
|
|
OpticsUI.LockedOn();
|
|
|
|
}
|
|
|
|
|
|
|
|
ClientPlayTargetingSound(LockAcquiredSoundFirstPerson);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an potential target TA determine if we can lock on to it. By default only allow locking on
|
|
|
|
* to pawns.
|
|
|
|
*/
|
|
|
|
simulated function bool CanLockOnTo(Actor TA)
|
|
|
|
{
|
|
|
|
Local Pawn PawnTarget;
|
|
|
|
local KFPawn KFPawnTarget;
|
|
|
|
|
|
|
|
PawnTarget = Pawn(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) || (PawnTarget.Health >= PawnTarget.HealthMax) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
KFPawnTarget = KFPawn(PawnTarget);
|
|
|
|
if (KFPawnTarget != none && !KFPawnTarget.CanBeHealed())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure and only lock onto players on the same team
|
|
|
|
return WorldInfo.GRI.OnSameTeam(Instigator,TA);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** returns true if lock-on is possible */
|
|
|
|
function bool AllowTargetLockOn()
|
|
|
|
{
|
|
|
|
return !Instigator.bNoWeaponFiring;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function checks to see if we are locked on a target
|
|
|
|
*/
|
|
|
|
function CheckTargetLock()
|
|
|
|
{
|
|
|
|
local Actor BestTarget, HitActor, TA;
|
|
|
|
local vector StartTrace, EndTrace, Aim, HitLocation, HitNormal;
|
|
|
|
local rotator AimRot;
|
|
|
|
local float BestAim, BestDist;
|
|
|
|
|
|
|
|
if ( (Instigator == None) || (Instigator.Controller == None) || (self != Instigator.Weapon) )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !AllowTargetLockOn() )
|
|
|
|
{
|
|
|
|
AdjustLockTarget(None);
|
|
|
|
PendingLockedTarget = None;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear lock if target is destroyed
|
|
|
|
if ( LockedTarget != None )
|
|
|
|
{
|
|
|
|
if ( LockedTarget.bDeleteMe )
|
|
|
|
{
|
|
|
|
AdjustLockTarget(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BestTarget = None;
|
|
|
|
|
|
|
|
//@todo: if we ever want AI to use medic weapons, then bring back the commented-out code that used to be here
|
|
|
|
|
|
|
|
// Begin by tracing the shot to see if it hits anyone
|
|
|
|
Instigator.Controller.GetPlayerViewPoint( StartTrace, AimRot );
|
|
|
|
Aim = vector(AimRot);
|
|
|
|
EndTrace = StartTrace + Aim * LockRange;
|
|
|
|
HitActor = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true,,, TRACEFLAG_Bullet);
|
|
|
|
|
|
|
|
// Check for a hit
|
|
|
|
if ( (HitActor == None) || !CanLockOnTo(HitActor) )
|
|
|
|
{
|
|
|
|
// We didn't hit a valid target, have the controller attempt to pick a good target
|
|
|
|
BestAim = LockAim;
|
|
|
|
BestDist = 0.0;
|
|
|
|
TA = Instigator.Controller.PickTarget(class'Pawn', BestAim, BestDist, Aim, StartTrace, LockRange, True);
|
|
|
|
if ( TA != None && CanLockOnTo(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have a "possible" target, note its time mark
|
|
|
|
if ( BestTarget != None )
|
|
|
|
{
|
|
|
|
if ( BestTarget == LockedTarget )
|
|
|
|
{
|
|
|
|
LockedOnTimeout = LockTolerance;
|
|
|
|
}
|
|
|
|
// We have our best target, see if they should become our current target.
|
|
|
|
// Check for a new Pending Lock
|
|
|
|
else if (PendingLockedTarget != BestTarget)
|
|
|
|
{
|
|
|
|
PendingLockedTarget = BestTarget;
|
|
|
|
PendingLockTimeout = LockTolerance;
|
|
|
|
PendingLockAcquireTimeLeft = LockAcquireTime;
|
|
|
|
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
// Optics UI only exists for local players
|
|
|
|
OpticsUI.StartLockOn();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bUsingSights )
|
|
|
|
{
|
|
|
|
// Play the "targeting" beep when we begin attempting to lock onto a target
|
|
|
|
// that we haven't locked onto yet
|
|
|
|
ClientPlayTargetingSound(LockTargetingSoundFirstPerson);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Acquire new target if LockAcquireTime has passed
|
|
|
|
if ( PendingLockedTarget != None )
|
|
|
|
{
|
|
|
|
PendingLockAcquireTimeLeft -= LockCheckTime;
|
|
|
|
if ( PendingLockedTarget == BestTarget && PendingLockAcquireTimeLeft <= 0 )
|
|
|
|
{
|
|
|
|
AdjustLockTarget(PendingLockedTarget);
|
|
|
|
PendingLockedTarget = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If we lost a target, attempt to invalidate the pending target
|
|
|
|
else if ( PendingLockedTarget != None )
|
|
|
|
{
|
|
|
|
PendingLockTimeout -= LockCheckTime;
|
|
|
|
if ( PendingLockTimeout <= 0 || !CanLockOnTo(PendingLockedTarget) )
|
|
|
|
{
|
|
|
|
PendingLockedTarget = None;
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
// Optics UI only exists for local players
|
|
|
|
OpticsUI.ClearLockOn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the new target is not the same, attempt to invalidate current locked on target
|
|
|
|
if ( LockedTarget != None && BestTarget != LockedTarget )
|
|
|
|
{
|
|
|
|
LockedOnTimeout -= LockCheckTime;
|
|
|
|
if ( LockedOnTimeout <= 0.f || !CanLockOnTo(LockedTarget) )
|
|
|
|
{
|
|
|
|
AdjustLockTarget(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Plays a first person targeting beep sound (Local Player Only) */
|
|
|
|
unreliable client function ClientPlayTargetingSound(AkBaseSoundObject Sound)
|
|
|
|
{
|
|
|
|
if( Sound != None && !bSuppressSounds )
|
|
|
|
{
|
|
|
|
if ( Instigator != None && Instigator.IsHumanControlled() )
|
|
|
|
{
|
|
|
|
PlaySoundBase(Sound, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tells the weapon to play a firing sound (uses CurrentFireMode)
|
|
|
|
* Overridden to support the dart shooting sounds
|
|
|
|
*/
|
|
|
|
simulated function PlayFiringSound( byte FireModeNum )
|
|
|
|
{
|
|
|
|
if ( !bPlayingLoopingFireSnd )
|
|
|
|
{ //uses darts
|
|
|
|
if( FireModeNum == ALTFIRE_FIREMODE && bRechargeHealAmmo)
|
|
|
|
{
|
|
|
|
WeaponPlayFireSound(DartFireSnd.DefaultCue, DartFireSnd.FirstPersonCue);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Super.PlayFiringSound(FireModeNum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Need to make noise if super isn't called
|
|
|
|
MakeNoise(1.0,'PlayerFiring'); // AI
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Override for 1st person healing dart anims */
|
|
|
|
simulated function name GetWeaponFireAnim(byte FireModeNum)
|
|
|
|
{
|
|
|
|
if ( FireModeNum == ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
return (bUsingSights) ? ShootDartIronAnim : ShootDartAnim;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Super.GetWeaponFireAnim(FireModeNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
@name Optics UI
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** Get our optics movie from the inventory once our InvManager is created */
|
|
|
|
reliable client function ClientWeaponSet(bool bOptionalSet, optional bool bDoNotActivate)
|
|
|
|
{
|
|
|
|
local KFInventoryManager KFIM;
|
|
|
|
|
|
|
|
super.ClientWeaponSet(bOptionalSet, bDoNotActivate);
|
|
|
|
|
|
|
|
if (OpticsUI == none)
|
|
|
|
{
|
|
|
|
KFIM = KFInventoryManager(InvManager);
|
|
|
|
if (KFIM != none)
|
|
|
|
{
|
|
|
|
//Create the screen's UI piece
|
|
|
|
OpticsUI = KFGFxWorld_MedicOptics(KFIM.GetOpticsUIMovie(OpticsUIClass));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize our displayed ammo count and healer charge
|
|
|
|
StartHealRecharge();
|
|
|
|
}
|
|
|
|
|
|
|
|
function ItemRemovedFromInvManager()
|
|
|
|
{
|
|
|
|
local KFInventoryManager KFIM;
|
|
|
|
local KFWeap_MedicBase KFW;
|
|
|
|
|
|
|
|
Super.ItemRemovedFromInvManager();
|
|
|
|
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
KFIM = KFInventoryManager(InvManager);
|
|
|
|
if (KFIM != none)
|
|
|
|
{
|
|
|
|
// @todo future implementation will have optics in base weapon class
|
|
|
|
foreach KFIM.InventoryActors(class'KFWeap_MedicBase', KFW)
|
|
|
|
{
|
|
|
|
if( KFW != self && KFW.OpticsUI.Class == OpticsUI.class)
|
|
|
|
{
|
|
|
|
// A different weapon is still using this optics class
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Create the screen's UI piece
|
|
|
|
KFIM.RemoveOpticsUIMovie(OpticsUI.class);
|
|
|
|
|
|
|
|
OpticsUI.Close();
|
|
|
|
OpticsUI = none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Unpause our optics movie and reinitialize our ammo when we equip the weapon */
|
|
|
|
simulated function AttachWeaponTo(SkeletalMeshComponent MeshCpnt, optional Name SocketName)
|
|
|
|
{
|
|
|
|
super.AttachWeaponTo(MeshCpnt, SocketName);
|
|
|
|
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
OpticsUI.SetPause(false);
|
|
|
|
OpticsUI.ClearLockOn();
|
|
|
|
UpdateOpticsUI(true);
|
|
|
|
OpticsUI.SetShotPercentCost( AmmoCost[ALTFIRE_FIREMODE]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Pause the optics movie once we unequip the weapon so it's not playing in the background */
|
|
|
|
simulated function DetachWeapon()
|
|
|
|
{
|
|
|
|
local Pawn OwnerPawn;
|
|
|
|
super.DetachWeapon();
|
|
|
|
|
|
|
|
OwnerPawn = Pawn(Owner);
|
|
|
|
if( OwnerPawn != none && OwnerPawn.Weapon == self )
|
|
|
|
{
|
|
|
|
if (OpticsUI != none)
|
|
|
|
{
|
|
|
|
OpticsUI.SetPause();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update our displayed ammo count if it's changed
|
|
|
|
*/
|
|
|
|
simulated function UpdateOpticsUI(optional bool bForceUpdate)
|
|
|
|
{
|
|
|
|
if (OpticsUI != none && OpticsUI.OpticsContainer != none)
|
|
|
|
{
|
|
|
|
if (AmmoCount[DEFAULT_FIREMODE] != StoredPrimaryAmmo || bForceUpdate)
|
|
|
|
{
|
|
|
|
StoredPrimaryAmmo = AmmoCount[DEFAULT_FIREMODE];
|
|
|
|
OpticsUI.SetPrimaryAmmo(StoredPrimaryAmmo);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AmmoCount[ALTFIRE_FIREMODE] != StoredSecondaryAmmo || bForceUpdate)
|
|
|
|
{
|
|
|
|
StoredSecondaryAmmo = AmmoCount[ALTFIRE_FIREMODE];
|
|
|
|
OpticsUI.SetHealerCharge(StoredSecondaryAmmo);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(OpticsUI.MinPercentPerShot != AmmoCost[ALTFIRE_FIREMODE])
|
|
|
|
{
|
|
|
|
OpticsUI.SetShotPercentCost( AmmoCost[ALTFIRE_FIREMODE] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* state Inactive
|
|
|
|
* This state is the default state. It needs to make sure Zooming is reset when entering/leaving
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
auto simulated state Inactive
|
|
|
|
{
|
|
|
|
simulated function BeginState(name PreviousStateName)
|
|
|
|
{
|
|
|
|
Super.BeginState(PreviousStateName);
|
|
|
|
|
|
|
|
if ( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
bTargetLockingActive = false;
|
|
|
|
AdjustLockTarget(None);
|
|
|
|
ClearTimer(nameof(CheckTargetLock));
|
|
|
|
}
|
|
|
|
|
|
|
|
// force stop beep/lock
|
|
|
|
PendingLockedTarget = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function EndState(Name NextStateName)
|
|
|
|
{
|
|
|
|
Super.EndState(NextStateName);
|
|
|
|
|
|
|
|
if ( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
bTargetLockingActive = true;
|
|
|
|
SetTimer(LockCheckTime, true, nameof(CheckTargetLock));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* State WeaponSprinting
|
|
|
|
* This is the default Sprinting State. It's performed on both the client and the server.
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
/** Override AllowTargetLockOn */
|
|
|
|
simulated state WeaponSprinting
|
|
|
|
{
|
|
|
|
ignores AllowTargetLockOn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* Trader
|
|
|
|
********************************************************************************************/
|
|
|
|
|
|
|
|
/** Allows weapon to set its own trader stats (can set number of stats, names and values of stats) */
|
|
|
|
static simulated event SetTraderWeaponStats( out array<STraderItemWeaponStats> WeaponStats )
|
|
|
|
{
|
|
|
|
super.SetTraderWeaponStats( WeaponStats );
|
|
|
|
|
|
|
|
WeaponStats.Length = WeaponStats.Length + 1;
|
|
|
|
WeaponStats[WeaponStats.Length-1].StatType = TWS_HealAmount;
|
|
|
|
WeaponStats[WeaponStats.Length-1].StatValue = default.HealAmount;
|
|
|
|
|
|
|
|
WeaponStats.Length = WeaponStats.Length + 1;
|
|
|
|
WeaponStats[WeaponStats.Length-1].StatType = TWS_RechargeTime;
|
|
|
|
WeaponStats[WeaponStats.Length-1].StatValue = default.HealFullRechargeSeconds;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* HUD
|
|
|
|
********************************************************************************************/
|
|
|
|
|
|
|
|
/** Determines the secondary ammo left for HUD display */
|
|
|
|
simulated function int GetSecondaryAmmoForHUD()
|
|
|
|
{
|
|
|
|
return AmmoCount[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultproperties
|
|
|
|
{
|
|
|
|
// Healing charge
|
|
|
|
HealAmount=20
|
|
|
|
HealFullRechargeSeconds=15
|
|
|
|
HealDartShotWeakZedGrabCooldown=0.5
|
|
|
|
|
|
|
|
DartMaxRecoilPitch=250
|
|
|
|
DartMinRecoilPitch=200
|
|
|
|
DartMaxRecoilYaw=100
|
|
|
|
DartMinRecoilYaw=-100
|
|
|
|
|
|
|
|
// Lock On Functionality
|
|
|
|
LockRange=50000
|
|
|
|
LockAim=0.98
|
|
|
|
LockChecktime=0.1
|
|
|
|
LockAcquireTime=0.2
|
|
|
|
LockTolerance=0.2
|
|
|
|
|
|
|
|
// ALT_FIREMODE
|
|
|
|
FiringStatesArray(ALTFIRE_FIREMODE)=WeaponSingleFiring
|
|
|
|
WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_Projectile
|
|
|
|
FireInterval(ALTFIRE_FIREMODE)=+0.175
|
|
|
|
InstantHitDamage(ALTFIRE_FIREMODE)=0 //Acidic compound skill can adjust that
|
|
|
|
Spread(ALTFIRE_FIREMODE)=0.015
|
|
|
|
AmmoCost(ALTFIRE_FIREMODE)=50
|
|
|
|
|
|
|
|
MagazineCapacity[1]=100
|
|
|
|
HealingDartAmmo=100
|
|
|
|
bCanRefillSecondaryAmmo=false
|
|
|
|
|
|
|
|
// Aim Assist
|
|
|
|
AimCorrectionSize=40.f
|
|
|
|
|
|
|
|
bRechargeHealAmmo=true
|
|
|
|
}
|
|
|
|
|