2020-12-13 15:01:13 +00:00
|
|
|
//=============================================================================
|
|
|
|
// KFWeap_HealerBase
|
|
|
|
//=============================================================================
|
|
|
|
// Base weapon class used for the healer
|
|
|
|
//=============================================================================
|
|
|
|
// Killing Floor 2
|
|
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
|
|
// - Andrew "Strago" Ladenberger
|
|
|
|
//=============================================================================
|
|
|
|
class KFWeap_HealerBase extends KFWeapon
|
|
|
|
abstract;
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
Ammo / Reload
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** How many points of heal ammo to recharge per second */
|
|
|
|
var() float HealSelfRechargeSeconds;
|
|
|
|
|
|
|
|
/** How many points of heal ammo to recharge per second when healing ourselves */
|
|
|
|
var() float HealOtherRechargeSeconds;
|
|
|
|
|
|
|
|
/** How many points of heal ammo to recharge per second */
|
|
|
|
var float HealRechargeTime;
|
|
|
|
|
|
|
|
/** 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;
|
|
|
|
|
|
|
|
/** Set while healer is mixing / regenerating */
|
|
|
|
var repnotify bool bIsAmmoRecharging;
|
|
|
|
|
|
|
|
/** Automatically use self-heal and then re-equip previous weapon */
|
|
|
|
var bool bQuickHealMode;
|
|
|
|
|
|
|
|
/** If set, play the reload version of the firing anim */
|
|
|
|
var bool bPlayHealAndReload;
|
|
|
|
|
|
|
|
/** The sound effect to play once we've finished recharging */
|
|
|
|
var AKEvent RechargeCompleteSound;
|
|
|
|
|
|
|
|
/** How long after we shart trying to heal before a zed can grab us.
|
|
|
|
* Prevents us from missing healing shots from being grabbed */
|
|
|
|
var(Weapon) float HealAttemptWeakZedGrabCooldown;
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
Healing (aka Firing Mode)
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** Maximum range to find teammate */
|
|
|
|
var() float HealingRangeSQ;
|
|
|
|
|
|
|
|
/** Reference to a healable friendly set in TickSpecial */
|
|
|
|
var Pawn HealTarget;
|
|
|
|
|
|
|
|
/** Reference to last valid heal target, used for "Heal Miss" dialog */
|
|
|
|
var Pawn LastValidHealTarget;
|
|
|
|
|
|
|
|
/** Store last update to we can prevent idle->ready->idle to quickly */
|
|
|
|
var float LastReadyHealTime;
|
|
|
|
|
|
|
|
/** How much to heal when playing a standalone game */
|
|
|
|
var const float StandAloneHealAmount;
|
|
|
|
|
|
|
|
var bool bIsQuickHealMessageShowing;
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
Animations
|
|
|
|
********************************************************************************************* */
|
|
|
|
|
|
|
|
/** animation names */
|
|
|
|
const IdleReadyAnim = 'Idle_Ready';
|
|
|
|
const HealSelfAnim = 'Heal_Self';
|
|
|
|
const HealSelfReloadAnim = 'Heal_Self_Reload';
|
|
|
|
const HealTeamAnim = 'Heal_Team';
|
|
|
|
const HealTeamReloadAnim = 'Heal_Team_Reload';
|
|
|
|
const QuickHealAnim = 'Heal_Quick';
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
Screen
|
|
|
|
********************************************************************************************* */
|
|
|
|
var class<KFGFxWorld_HealerScreen> ScreenUIClass;
|
|
|
|
var KFGFxWorld_HealerScreen ScreenUI;
|
|
|
|
|
|
|
|
var float LastUIUpdateTime;
|
|
|
|
var float UIUpdateInterval;
|
|
|
|
|
|
|
|
replication
|
|
|
|
{
|
|
|
|
if (bNetDirty)
|
|
|
|
bIsAmmoRecharging;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated event ReplicatedEvent(name VarName)
|
|
|
|
{
|
|
|
|
super.ReplicatedEvent(VarName);
|
|
|
|
if (VarName == nameof(bIsAmmoRecharging))
|
|
|
|
{
|
|
|
|
if(!bIsAmmoRecharging && Owner != none)
|
|
|
|
{
|
|
|
|
PlaySoundBase( RechargeCompleteSound, true, false, false, Owner.Location );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
function GivenTo(Pawn NewOwner, optional bool bDoNotActivate)
|
|
|
|
{
|
|
|
|
local KFInventoryManager KFInvManger;
|
|
|
|
|
|
|
|
Super.GivenTo( NewOwner, bDoNotActivate );
|
|
|
|
|
|
|
|
//Register with inventory manager for easy access later. (Eg: HUD indicator)
|
|
|
|
KFInvManger = KFInventoryManager(InvManager);
|
|
|
|
if( InvManager != none && KFInvManger != none )
|
|
|
|
{
|
|
|
|
KFInvManger.HealerWeapon = self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Turn on the UI screen when we equip the healer */
|
|
|
|
simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
|
|
|
|
{
|
|
|
|
super.AttachWeaponTo( MeshCpnt, SocketName );
|
|
|
|
|
|
|
|
if (Instigator != none && Instigator.IsLocallyControlled() )
|
|
|
|
{
|
|
|
|
//Create the screen's UI piece
|
|
|
|
if (ScreenUI == none)
|
|
|
|
{
|
|
|
|
ScreenUI = new( self ) ScreenUIClass;
|
|
|
|
ScreenUI.Init();
|
|
|
|
ScreenUI.Start(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ScreenUI != none)
|
|
|
|
{
|
|
|
|
ScreenUI.SetPause(false);
|
|
|
|
ScreenUI.SetCharge( MagazineCapacity[0] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Turn off the UI screen when we unequip the healer */
|
|
|
|
simulated function DetachWeapon()
|
|
|
|
{
|
|
|
|
super.DetachWeapon();
|
|
|
|
if (Instigator != none && Instigator.IsLocallyControlled() && ScreenUI != none)
|
|
|
|
{
|
|
|
|
ScreenUI.SetPause();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated event Destroyed()
|
|
|
|
{
|
|
|
|
if (Instigator != none && Instigator.IsLocallyControlled() && ScreenUI != none)
|
|
|
|
{
|
|
|
|
ScreenUI.Close();
|
|
|
|
}
|
|
|
|
super.Destroyed();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* Ammunition
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
/** SpareAmmoCount is used for recharging, but it doesn't show on HUD */
|
|
|
|
simulated function int GetSpareAmmoForHUD()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function bool HasAnyAmmo()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Performs actual ammo reloading */
|
|
|
|
simulated function PerformReload(optional byte FireModeNum)
|
|
|
|
{
|
|
|
|
local KFPerk InstigatorPerk;
|
|
|
|
|
|
|
|
InstigatorPerk = GetPerk();
|
|
|
|
// begin ammo recharge on server
|
|
|
|
if ( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
bIsAmmoRecharging = true;
|
|
|
|
|
|
|
|
InstigatorPerk.ModifyHealerRechargeTime( HealRechargeTime );
|
|
|
|
// Set the healing recharge rate whenever we start charging
|
|
|
|
HealRechargePerSecond = MagazineCapacity[0]/HealRechargeTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Heal Ammo Regen */
|
|
|
|
function HealAmmoRegeneration(float DeltaTime)
|
|
|
|
{
|
|
|
|
HealingIncrement += HealRechargePerSecond * DeltaTime;
|
|
|
|
|
|
|
|
if( HealingIncrement >= 1.0 && AmmoCount[0] < MagazineCapacity[0] )
|
|
|
|
{
|
|
|
|
AmmoCount[0]++;
|
|
|
|
HealingIncrement -= 1.0;
|
|
|
|
|
|
|
|
if( AmmoCount[0] == MagazineCapacity[0] )
|
|
|
|
{
|
|
|
|
bIsAmmoRecharging = false;
|
|
|
|
|
|
|
|
if(Instigator.IsLocallyControlled() && Owner != none)
|
|
|
|
{
|
|
|
|
PlaySoundBase( RechargeCompleteSound, true, false, false, Owner.Location );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns true if weapon can potentially be reloaded */
|
|
|
|
// reload is handled automatically, see bPlayHealAndReload
|
|
|
|
simulated function bool CanReload(optional byte FireModeNum);
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the weapon isn't an instant hit, or a simple projectile, it should use the tyoe EWFT_Custom. In those cases
|
|
|
|
* this function will be called. It should be subclassed by the custom weapon.
|
|
|
|
*/
|
|
|
|
simulated function CustomFire()
|
|
|
|
{
|
|
|
|
local float HealAmount;
|
|
|
|
|
|
|
|
if( Role == ROLE_Authority )
|
|
|
|
{
|
|
|
|
// Healing a teammate
|
|
|
|
if( CurrentFireMode == DEFAULT_FIREMODE )
|
|
|
|
{
|
|
|
|
HealAmount = InstantHitDamage[CurrentFireMode];
|
|
|
|
HealTarget.HealDamage( HealAmount, Instigator.Controller, InstantHitDamageTypes[CurrentFireMode]);
|
|
|
|
HealRechargeTime = HealOtherRechargeSeconds;
|
|
|
|
}
|
|
|
|
// Healing Self
|
|
|
|
else if( CurrentFireMode == ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
if ( GetActivePlayerCount() < 2 )
|
|
|
|
{
|
|
|
|
HealAmount = StandAloneHealAmount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
HealAmount = InstantHitDamage[CurrentFireMode];
|
|
|
|
}
|
|
|
|
Instigator.HealDamage(HealAmount, Instigator.Controller, InstantHitDamageTypes[CurrentFireMode]);
|
|
|
|
HealRechargeTime = HealSelfRechargeSeconds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @see Weapon::StartFire
|
|
|
|
*/
|
|
|
|
simulated function StartFire(byte FireModeNum)
|
|
|
|
{
|
|
|
|
local Pawn Healee;
|
|
|
|
|
|
|
|
// Reload, grenade, melee all work the same as base weapon
|
|
|
|
if ( FireModeNum > ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
Super.StartFire(FireModeNum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Healee = (FireModeNum == DEFAULT_FIREMODE) ? HealTarget : Instigator;
|
|
|
|
|
|
|
|
// Exit if we have no valid target for a team heal or the target has full health
|
|
|
|
if ( Healee == None || !IsValidHealingTarget( Healee ) )
|
|
|
|
{
|
|
|
|
// Play miss dialog. Define "miss" as tried to fire, but recently lost target.
|
|
|
|
// @todo (JDR): replace magic number with less-magical number
|
|
|
|
if ( FireModeNum == DEFAULT_FIREMODE && HealTarget == None
|
|
|
|
&& `TimeSince(LastReadyHealTime) < 2.f )// && LastValidHealTarget.bIsMoving )
|
|
|
|
{
|
|
|
|
MissHeal( LastValidHealTarget );
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( Instigator == None || !Instigator.bNoWeaponFiring )
|
|
|
|
{
|
|
|
|
// always do this (medics may be able to heal without reloading)
|
|
|
|
// @todo bPlayHealAndReload when reload animations are back in
|
|
|
|
//bPlayHealAndReload = true;
|
|
|
|
|
|
|
|
if( Role < Role_Authority )
|
|
|
|
{
|
|
|
|
// tell the server who we want to heal
|
|
|
|
ServerStartHeal(FireModeNum, HealTarget, bPlayHealAndReload);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start fire locally
|
|
|
|
BeginFire(FireModeNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function bool IsValidHealingTarget( Pawn Healee )
|
|
|
|
{
|
|
|
|
if( Healee != none )
|
|
|
|
{
|
|
|
|
return Healee.Health < Healee.HealthMax && Healee.IsAliveAndWell();
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Set the heal target and start firing mode */
|
|
|
|
reliable server private function ServerStartHeal(byte FireModeNum, Pawn P, bool bClientIsReloading)
|
|
|
|
{
|
|
|
|
if ( FireModeNum == DEFAULT_FIREMODE && !P.IsAliveAndWell() )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sync up reload status (AmmoCount is probably good enough )
|
|
|
|
bPlayHealAndReload = bClientIsReloading;
|
|
|
|
|
|
|
|
HealTarget = P;
|
|
|
|
ServerStartFire(FireModeNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function MissHeal( Pawn P )
|
|
|
|
{
|
|
|
|
if( Role < ROLE_Authority )
|
|
|
|
{
|
|
|
|
ServerMissHeal( P );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
`DialogManager.PlayHealMissDialog( KFPawn(Instigator), KFPawn(P) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unreliable server private function ServerMissHeal( Pawn P )
|
|
|
|
{
|
|
|
|
MissHeal( P );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* Effects / Mesh / Animations / Sounds
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when damage is healed (instead of when heal anim starts)
|
|
|
|
*/
|
|
|
|
simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation )
|
|
|
|
{
|
|
|
|
PlayFiringSound(CurrentFireMode);
|
|
|
|
|
|
|
|
if( Instigator != none && Instigator.IsFirstPerson() )
|
|
|
|
{
|
|
|
|
ShakeView();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function name GetHealAnimName(byte FireModeNum)
|
|
|
|
{
|
|
|
|
if ( bPlayHealAndReload )
|
|
|
|
{
|
|
|
|
return (FireModeNum == DEFAULT_FIREMODE) ? HealTeamReloadAnim : HealSelfReloadAnim;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (FireModeNum == DEFAULT_FIREMODE) ? HealTeamAnim : HealSelfAnim;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* Timing
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
/** @see Weapon::GetFireInterval */
|
|
|
|
simulated function float GetFireInterval( byte FireModeNum )
|
|
|
|
{
|
|
|
|
local name AnimName;
|
|
|
|
|
|
|
|
if ( FireModeNum <= ALTFIRE_FIREMODE )
|
|
|
|
{
|
|
|
|
// Base the fire internval on the animation. This is a little unusual, but since we're
|
|
|
|
// in a WeaponSingleFiring state and the server/client anim are sync'ed up it's okay.
|
|
|
|
AnimName = GetHealAnimName(FireModeNum);
|
|
|
|
return FMax(MySkelMesh.GetAnimInterruptTime(AnimName), 0.01f);
|
|
|
|
}
|
|
|
|
return super.GetFireInterval( FireModeNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
// The healer should only be able to refire if it's animation or interrupt has completed
|
|
|
|
simulated function bool ShouldRefire()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @see Weapon::Tick */
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
|
|
{
|
|
|
|
Super.Tick(DeltaTime);
|
|
|
|
|
|
|
|
// @todo: this could should not be called if ammo is charged
|
|
|
|
if (Role == ROLE_Authority && AmmoCount[0] < MagazineCapacity[0])
|
|
|
|
{
|
|
|
|
HealAmmoRegeneration(DeltaTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Instigator != none && Instigator.WorldInfo.TimeSeconds - LastUIUpdateTime > UIUpdateInterval)
|
|
|
|
{
|
|
|
|
LastUIUpdateTime = Instigator.WorldInfo.TimeSeconds; // throttle the updates so we're not spamming Actionscript with data.
|
|
|
|
UpdateInteractionMessage();
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateScreenUI();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Updates weither or not the "Press GBA_QuickHeal to heal" should be shown based on health, ammo and other factors
|
|
|
|
simulated function UpdateInteractionMessage()
|
|
|
|
{
|
|
|
|
local KFPlayerController InstigatorKFPC;
|
|
|
|
local bool bCannotBeHealed;
|
|
|
|
|
|
|
|
//Update Interaction message
|
|
|
|
if (Instigator != none && Instigator.IsLocallyControlled() && Instigator.Health > 0)
|
|
|
|
{
|
|
|
|
InstigatorKFPC = KFPlayerController(Instigator.Controller);
|
|
|
|
|
|
|
|
if (InstigatorKFPC == none)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-02 11:56:51 +00:00
|
|
|
//Check if the player can heal
|
|
|
|
bCannotBeHealed = !InstigatorKFPC.CanUseHealObject();
|
2020-12-13 15:01:13 +00:00
|
|
|
|
|
|
|
if (bIsQuickHealMessageShowing)
|
|
|
|
{
|
|
|
|
//We use AmmoCount[0] since the healer weapon only uses this ammo. AmmoCost[ALTFIRE_FIREMODE] is the cost to heal yourself
|
|
|
|
if (Instigator.Health > InstigatorKFPC.LowHealthThreshold || AmmoCount[0] < AmmoCost[ALTFIRE_FIREMODE] || bCannotBeHealed)
|
|
|
|
{
|
|
|
|
bIsQuickHealMessageShowing = false;
|
|
|
|
InstigatorKFPC.ReceiveLocalizedMessage(class'KFLocalMessage_Interaction', IMT_None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//We use AmmoCount[0] since the healer weapon only uses this ammo. AmmoCost[ALTFIRE_FIREMODE] is the cost to heal yourself
|
|
|
|
if (Instigator.Health <= InstigatorKFPC.LowHealthThreshold && AmmoCount[0] >= AmmoCost[ALTFIRE_FIREMODE] && !bCannotBeHealed)
|
|
|
|
{
|
|
|
|
bIsQuickHealMessageShowing = true;
|
|
|
|
InstigatorKFPC.ReceiveLocalizedMessage(class'KFLocalMessage_Interaction', IMT_HealSelfWarning);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Only update the screen screen if we have the healer equipped and it's value is different than our AmmoCount */
|
|
|
|
simulated function UpdateScreenUI()
|
|
|
|
{
|
|
|
|
if ( Instigator != none && Instigator.IsLocallyControlled() && Instigator.Weapon == self )
|
|
|
|
{
|
|
|
|
if ( ScreenUI != none && ScreenUI.CurrentCharge != AmmoCount[0] )
|
|
|
|
{
|
|
|
|
ScreenUI.SetCharge( AmmoCount[0] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* State Active
|
|
|
|
* A Weapon this is being held by a pawn should be in the active state. In this state,
|
|
|
|
* a weapon should loop any number of idle animations, as well as check the PendingFire flags
|
|
|
|
* to see if a shot has been fired.
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
simulated state Active
|
|
|
|
{
|
|
|
|
simulated event Tick(float DeltaTime)
|
|
|
|
{
|
|
|
|
// Caution - Super will skip our global, but global will skip super's state function!
|
|
|
|
Global.Tick(DeltaTime);
|
|
|
|
|
|
|
|
// will trace each call, but it's decently fast (zero-extent)
|
|
|
|
UpdateHealTarget();
|
|
|
|
UpdateScreenUI();
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function PlayIdleAnim()
|
|
|
|
{
|
|
|
|
local int IdleIndex;
|
|
|
|
|
|
|
|
if ( Instigator != none && Instigator.IsFirstPerson() )
|
|
|
|
{
|
|
|
|
// Update target immediately and enable tick (first time)
|
|
|
|
UpdateHealTarget(true);
|
|
|
|
|
|
|
|
if( HealTarget != None )
|
|
|
|
{
|
|
|
|
PlayAnimation(IdleReadyAnim, 0.0, true, 0.2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IdleIndex = Rand(IdleAnims.Length);
|
|
|
|
PlayAnimation(IdleAnims[IdleIndex], 0.0, true, 0.2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find friendly pawns within healing range
|
|
|
|
* Network: Local Player
|
|
|
|
*/
|
|
|
|
simulated function UpdateHealTarget(optional bool bSkipOnAnimEnd)
|
|
|
|
{
|
|
|
|
local bool bHasHealTarget;
|
|
|
|
local vector AimDir, StartTrace, Projection;
|
|
|
|
local Pawn P;
|
|
|
|
local KFPawn KFP;
|
|
|
|
local float DistSQ, FOV, HealTargetRating, BestHealTargetRating;
|
|
|
|
|
|
|
|
// prevent state from changing too often (also a optimization)
|
|
|
|
if ( `TimeSince(LastReadyHealTime) < 0.2f )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bHasHealTarget = (HealTarget != None);
|
|
|
|
HealTarget = None;
|
|
|
|
|
|
|
|
// define range to use for CalcWeaponFire()
|
|
|
|
StartTrace = Instigator.GetWeaponStartTraceLocation();
|
|
|
|
AimDir = vector( GetAdjustedAim(StartTrace) );
|
|
|
|
|
|
|
|
// consider adding this to KFMeleeHelperWeapon so it can be used by other weapons
|
|
|
|
foreach WorldInfo.AllPawns( class'Pawn', P )
|
|
|
|
{
|
|
|
|
if( P != Instigator && P.GetTeamNum() == Instigator.GetTeamNum() && P.IsAliveAndWell() )
|
|
|
|
{
|
|
|
|
KFP = KFPawn(P);
|
|
|
|
if (KFP != none && !KFP.CanBeHealed())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Projection = P.Location - StartTrace;
|
|
|
|
|
|
|
|
DistSQ = VSizeSQ( Projection );
|
|
|
|
if( DistSQ > HealingRangeSQ )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
FOV = AimDir dot Normal( Projection );
|
|
|
|
if( FOV > 0.4f )
|
|
|
|
{
|
|
|
|
HealTargetRating = FOV + ( 0.4f * (1.f - (DistSQ / HealingRangeSQ)) );
|
|
|
|
if( HealTargetRating > BestHealTargetRating && FastTrace(P.Location, StartTrace) )
|
|
|
|
{
|
|
|
|
BestHealTargetRating = HealTargetRating;
|
|
|
|
HealTarget = P;
|
|
|
|
LastValidHealTarget = P;
|
|
|
|
LastReadyHealTime = WorldInfo.TimeSeconds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// refresh idle anim if ready state has changed
|
|
|
|
if ( !bSkipOnAnimEnd && bHasHealTarget != (HealTarget != None) )
|
|
|
|
{
|
|
|
|
OnAnimEnd(none,0.f,0.f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* State WeaponHealing
|
|
|
|
* Firing mode state for healer items.
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
simulated state WeaponHealing extends WeaponSingleFiring
|
|
|
|
{
|
|
|
|
simulated function byte GetWeaponStateId()
|
|
|
|
{
|
|
|
|
return WEP_Heal;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** "fires" in EndState instead of begin */
|
|
|
|
simulated event BeginState( Name PreviousStateName )
|
|
|
|
{
|
|
|
|
`LogInv("PreviousStateName:" @ PreviousStateName);
|
|
|
|
|
|
|
|
// Don't let a weak zed grab us while in the middle of a heal
|
|
|
|
SetWeakZedGrabCooldownOnPawn(HealAttemptWeakZedGrabCooldown);
|
|
|
|
|
|
|
|
PlayHeal();
|
|
|
|
TimeWeaponFiring( CurrentFireMode );
|
|
|
|
|
|
|
|
NotifyBeginState();
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function PlayHeal()
|
|
|
|
{
|
|
|
|
local name AnimName;
|
|
|
|
AnimName = GetHealAnimName(CurrentFireMode);
|
|
|
|
|
|
|
|
if( Instigator != none && Instigator.IsFirstPerson() )
|
|
|
|
{
|
|
|
|
if ( AnimName != '' )
|
|
|
|
{
|
|
|
|
PlayAnimation(AnimName,,,FireTweenTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function RefireCheckTimer()
|
|
|
|
{
|
|
|
|
// Do actual healing and start recharging used ammo
|
|
|
|
FireAmmunition();
|
|
|
|
PerformReload();
|
|
|
|
|
|
|
|
HandleFinishedFiring();
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function EndState(Name NextStateName)
|
|
|
|
{
|
|
|
|
Super.EndState(NextStateName);
|
|
|
|
NotifyEndState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************
|
|
|
|
* State WeaponQuickHeal
|
|
|
|
* Special firing state that combines Equip, Heal, and PutAway into one action
|
|
|
|
*********************************************************************************************/
|
|
|
|
|
|
|
|
// Global defs for state code
|
|
|
|
simulated function QuickHealEndTimer();
|
|
|
|
simulated function QuickHealFireTimer();
|
|
|
|
|
|
|
|
simulated state WeaponQuickHeal extends WeaponHealing
|
|
|
|
{
|
|
|
|
simulated function byte GetWeaponStateId()
|
|
|
|
{
|
|
|
|
return WEP_HealQuick;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated event BeginState( Name PreviousStateName )
|
|
|
|
{
|
|
|
|
Super.BeginState(PreviousStateName);
|
|
|
|
bQuickHealMode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function EndState(Name NextStateName)
|
|
|
|
{
|
|
|
|
Super.EndState(NextStateName);
|
|
|
|
ClearTimer(nameof(QuickHealEndTimer));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Override normal firing state timers */
|
|
|
|
simulated function TimeWeaponFiring( byte FireModeNum )
|
|
|
|
{
|
|
|
|
local float Duration, QuickHealFireTime;
|
|
|
|
local name AnimName;
|
|
|
|
|
|
|
|
AnimName = GetHealAnimName(FireModeNum);
|
|
|
|
|
|
|
|
// Set a end state timer seperate from RefireCheckTimer
|
|
|
|
Duration = MySkelMesh.GetAnimLength(AnimName);
|
|
|
|
if ( Duration > 0 )
|
|
|
|
{
|
|
|
|
SetTimer(Duration, false, nameof(QuickHealEndTimer));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use interrupts in the animation to determine when to fire
|
|
|
|
QuickHealFireTime = FMax(MySkelMesh.GetAnimInterruptTime(AnimName), 0.01f);
|
|
|
|
SetTimer(QuickHealFireTime, false, nameof(QuickHealFireTimer));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle state exit */
|
|
|
|
simulated function QuickHealEndTimer()
|
|
|
|
{
|
|
|
|
KFInventoryManager(InvManager).SwitchToLastWeapon();
|
|
|
|
|
|
|
|
// Start weapon put away
|
|
|
|
PutDownWeapon();
|
|
|
|
// Skip anim and go straight to inactive
|
|
|
|
WeaponIsDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Do actual healing and start recharging used ammo */
|
|
|
|
simulated function QuickHealFireTimer()
|
|
|
|
{
|
|
|
|
FireAmmunition();
|
|
|
|
PerformReload();
|
|
|
|
}
|
|
|
|
|
|
|
|
simulated function name GetHealAnimName(byte FireModeNum)
|
|
|
|
{
|
|
|
|
return QuickHealAnim;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handle bQuickHealMode */
|
|
|
|
simulated function Activate()
|
|
|
|
{
|
|
|
|
Super.Activate();
|
|
|
|
|
|
|
|
HealTarget = none; //clear heal target so that distance exploit cannot happen
|
|
|
|
|
|
|
|
if ( bQuickHealMode && Instigator.IsLocallyControlled() )
|
|
|
|
{
|
|
|
|
SetCurrentFireMode(ALTFIRE_FIREMODE);
|
|
|
|
GotoState('WeaponQuickHeal');
|
|
|
|
|
|
|
|
if ( Role < ROLE_Authority )
|
|
|
|
{
|
|
|
|
ServerStartQuickHeal();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Notify server of quick heal */
|
|
|
|
reliable server private function ServerStartQuickHeal()
|
|
|
|
{
|
|
|
|
SetCurrentFireMode(ALTFIRE_FIREMODE);
|
|
|
|
GotoState('WeaponQuickHeal');
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns the number of players active in the game */
|
|
|
|
function int GetActivePlayerCount()
|
|
|
|
{
|
|
|
|
local KFPlayerController KFPC;
|
|
|
|
local int totalPlayers;
|
|
|
|
|
|
|
|
totalPlayers = 0;
|
|
|
|
foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC )
|
|
|
|
{
|
|
|
|
if( KFPC.bIsAchievementPlayer )
|
|
|
|
{
|
|
|
|
totalPlayers++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return totalPlayers;
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultproperties
|
|
|
|
{
|
|
|
|
UIUpdateInterval=1.f
|
|
|
|
FireTweenTime=0.3f
|
|
|
|
HealingRangeSQ=23000.f
|
|
|
|
StandAloneHealAmount=50.0f
|
|
|
|
ScreenUIClass=class'KFGFxWorld_HealerScreen'
|
|
|
|
|
|
|
|
// Aim Assist
|
|
|
|
AimCorrectionSize=0.f
|
|
|
|
bTargetAdhesionEnabled=false
|
|
|
|
|
|
|
|
// Heal Friendly
|
|
|
|
FiringStatesArray(DEFAULT_FIREMODE)=WeaponHealing
|
|
|
|
WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Custom
|
|
|
|
FireInterval(DEFAULT_FIREMODE)=+0.2
|
|
|
|
InstantHitDamage(DEFAULT_FIREMODE)=20.0
|
|
|
|
InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Healing'
|
|
|
|
AmmoCost(DEFAULT_FIREMODE)=100
|
|
|
|
HealAttemptWeakZedGrabCooldown=1.0
|
|
|
|
|
|
|
|
// Heal Self
|
|
|
|
FiringStatesArray(ALTFIRE_FIREMODE)=WeaponHealing
|
|
|
|
WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_Custom
|
|
|
|
FireInterval(ALTFIRE_FIREMODE)=+2.0
|
|
|
|
InstantHitDamage(ALTFIRE_FIREMODE)=20.0
|
|
|
|
InstantHitDamageTypes(ALTFIRE_FIREMODE)=class'KFDT_Healing'
|
|
|
|
AmmoCost(ALTFIRE_FIREMODE)=100
|
|
|
|
|
|
|
|
// Ammo
|
|
|
|
MagazineCapacity[0]=100
|
|
|
|
SpareAmmoCapacity[0]=0
|
|
|
|
bCanBeReloaded=true
|
|
|
|
bReloadFromMagazine=true
|
|
|
|
bInfiniteSpareAmmo=true
|
|
|
|
HealSelfRechargeSeconds=15
|
|
|
|
HealOtherRechargeSeconds=7.5
|
|
|
|
bAllowClientAmmoTracking=false
|
|
|
|
|
|
|
|
// Inventory
|
|
|
|
GroupPriority=6
|
|
|
|
InventoryGroup=IG_Equipment
|
|
|
|
|
|
|
|
// Fire Effects
|
|
|
|
WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_Syringe.Play_WEP_SA_Syringe_3P_Fire_Single', FirstPersonCue=AkEvent'WW_WEP_SA_Syringe.Play_WEP_SA_Syringe_1P_Fire_Single')
|
|
|
|
WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_Syringe.Play_WEP_SA_Syringe_3P_Fire_Single', FirstPersonCue=AkEvent'WW_WEP_SA_Syringe.Play_WEP_SA_Syringe_1P_Fire_Single')
|
|
|
|
|
|
|
|
RechargeCompleteSound=AkEvent'WW_WEP_SA_Syringe.Play_WEP_SA_Syringe_Charged'
|
|
|
|
|
|
|
|
AssociatedPerkClasses(0)=none
|
|
|
|
}
|