606 lines
21 KiB
Ucode
606 lines
21 KiB
Ucode
//=============================================================================
|
|
// KFMeleeHelperAI
|
|
//=============================================================================
|
|
// Manages melee attack related functionality for AI controlled pawns
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// - Andrew "Strago" Ladenberger
|
|
//=============================================================================
|
|
class KFMeleeHelperAI extends KFMeleeHelperBase within Actor
|
|
native;
|
|
|
|
/** The default damage applied to each melee hit **/
|
|
var() float BaseDamage;
|
|
/** Default DamageType class */
|
|
var() const class<KFDamageType> MyDamageType;
|
|
/** How much base physics push force an attack will cause */
|
|
var() float MomentumTransfer;
|
|
|
|
/** Struct to keep track of the actors we hit with the move and when it happened */
|
|
struct native SwipeHitActorData
|
|
{
|
|
/** The actor who was hit */
|
|
var transient KFPawn HitActor;
|
|
/** Last time the actor was hit */
|
|
var transient float LastHitTime;
|
|
};
|
|
|
|
/** List of actors hit by a swipe attack (see UpdateStabbedActors) */
|
|
var transient array<SwipeHitActorData> SwipedActors;
|
|
/** If set, track all swiped actors to prevent giving damage too frequently */
|
|
var bool bTrackSwipeHits;
|
|
|
|
/** Door damage multiplier for player zed (versus) attacks */
|
|
var float PlayerDoorDamageMultiplier;
|
|
/** Swipe FOV used by player controlled zeds */
|
|
var float PlayerControlledFOV;
|
|
/** Scale factor for player controlled melee impact camera shake */
|
|
var float MeleeImpactCamScale;
|
|
|
|
/*********************************************************************************************
|
|
* Player ping compensation
|
|
*********************************************************************************************/
|
|
|
|
struct native DelayedMeleeInfo
|
|
{
|
|
var Pawn Victim;
|
|
var int Damage;
|
|
var float Momentum;
|
|
var float TimeOfDamage;
|
|
var class<KFDamageType> DamageType;
|
|
};
|
|
|
|
/** Cached damage values */
|
|
var array<DelayedMeleeInfo> PendingDamage;
|
|
|
|
/** Upper limit (in ms) for Ping compensation */
|
|
var int MaxPingCompensation;
|
|
/** Artificial fudge factor to find the sweet spot for online play */
|
|
var float PingCompensationScale;
|
|
|
|
cpptext
|
|
{
|
|
void TickPingCompensation(AWorldInfo* WorldInfo);
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Common (Public)
|
|
*********************************************************************************************/
|
|
|
|
/**
|
|
* Deals melee damage to a target actor
|
|
* @param Victim Target actor to recieve damage
|
|
* @param Damage Damage to apply if this attack is successful
|
|
* @param HitLocation Location to apply damage to on the target actor
|
|
* @param MomentumScalar Momentum transfer to apply if this attack is successful
|
|
* @param DamageType DamageType to apply if this attack is successful
|
|
*/
|
|
function ApplyMeleeDamage(Actor Victim, int Damage, optional float InMomentum=1.f, optional class<KFDamageType> InDamageType, optional vector HitLocation)
|
|
{
|
|
local vector HitDirection;
|
|
local KFPawn_Monster InstigatorPawn;
|
|
|
|
// Make sure we're doing _some_ damage
|
|
Damage = Max(Damage, 1);
|
|
|
|
InstigatorPawn = KFPawn_Monster(Instigator);
|
|
if ( InstigatorPawn != none )
|
|
{
|
|
// Notify pawn
|
|
InstigatorPawn.NotifyMeleeDamageDealt();
|
|
|
|
// Notify AIC. After TakeDamage so we can review damage results
|
|
if( InstigatorPawn.MyKFAIC != none )
|
|
{
|
|
InstigatorPawn.MyKFAIC.NotifyMeleeDamageDealt();
|
|
}
|
|
|
|
// Apply rally boost damage
|
|
Damage = InstigatorPawn.GetRallyBoostDamage( Damage );
|
|
}
|
|
|
|
// Bump the hit location so that TakeHitInfo will always replicate
|
|
if ( IsZero(HitLocation) )
|
|
{
|
|
HitLocation = Victim.Location;
|
|
HitLocation.Z += FRand();
|
|
}
|
|
HitDirection = Normal(HitLocation - Instigator.Location);
|
|
|
|
// After modifiers are applied, ensure damage is never zero
|
|
Victim.TakeDamage( Damage, Instigator.Controller, HitLocation, HitDirection * InMomentum, InDamageType,, Outer );
|
|
|
|
// play camera shake, etc...
|
|
PlayMeleeHitEffects(Victim, HitLocation, HitDirection);
|
|
|
|
`log(Victim$"**** Melee attack! BaseDamage="@BaseDamage@", ModifiedDamage="@Damage, bLogMelee);
|
|
}
|
|
|
|
/** Simplified version of RateMeleeVictim designed for any actor type */
|
|
function bool ShouldDealDamageToEnemy(Actor Target, optional float Range=MaxHitRange)
|
|
{
|
|
local Vector VectToEnemy, HitLoc, HitNormal;
|
|
local Actor HitActor;
|
|
local TraceHitInfo HitInfo;
|
|
local vector TraceOffset;
|
|
|
|
// Enemy must not be too far
|
|
VectToEnemy = Target.Location - Location;
|
|
if( VSizeSq(VectToEnemy) > Square(Range) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// must be somewhat facing enemy
|
|
if( Normal(VectToEnemy) dot Vector(Instigator.Rotation) < 0.0f )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Trace world and block iff block actors is set (ignore CanBecomeDynamic actors)
|
|
HitActor = Trace(HitLoc, HitNormal, Target.Location, Location, false,, HitInfo, TRACEFLAG_Blocking);
|
|
if( HitActor != None && HitInfo.HitComponent != None && HitInfo.HitComponent.BlockActors && (HitActor.bWorldGeometry || HitActor.bGameRelevant) )
|
|
{
|
|
// Try again at the EyeHeight - Both must fail to obstruct damage
|
|
TraceOffset = Instigator.BaseEyeHeight * vect(0,0,1);
|
|
HitActor = Trace(HitLoc, HitNormal, Target.Location + TraceOffset, Location + TraceOffset, false,, HitInfo, TRACEFLAG_Blocking);
|
|
if( HitActor != None && HitInfo.HitComponent != None && HitInfo.HitComponent.BlockActors && (HitActor.bWorldGeometry || HitActor.bGameRelevant) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Area Hit Detection
|
|
*********************************************************************************************/
|
|
|
|
/**
|
|
* Do damage to multiple targets around the character
|
|
* @param Damage Damage to apply if this attack is successful
|
|
* @param MomentumScalar Momentum transfer to apply if this attack is successful
|
|
* @param DamageType DamageType to apply if this attack is successful
|
|
* @param Range Maximum range for hit detection
|
|
* @param InFOVCosine Maximum FOV for hit detection. If 0, allows any angle (360 degrees)
|
|
* @param bPlayersOnly If true, skip non-player controlled pawns
|
|
*/
|
|
function bool DoAreaImpact(
|
|
int Damage,
|
|
optional float MomentumScalar=1.f,
|
|
optional class<KFDamageType> DamageType=MyDamageType,
|
|
optional float Range=MaxHitRange,
|
|
optional float InFOVCosine=0.f,
|
|
optional bool bPlayersOnly)
|
|
{
|
|
local KFPawn KFP;
|
|
local bool bFoundHit;
|
|
|
|
// @note - At low ranges CollidingActors (no VisibleCollidingActors) is okay,
|
|
// but AllPawns is constant and much faster 99% of the time.
|
|
foreach WorldInfo.AllPawns( class'KFPawn', KFP, Location, Range )
|
|
{
|
|
if ( bPlayersOnly && !KFP.IsHumanControlled() )
|
|
continue;
|
|
|
|
if ( RateMeleeVictim(KFP, Location, Location + Normal(Vector(Instigator.Rotation)) * Range, Range, InFOVCosine) > 0.f )
|
|
{
|
|
ApplyMeleeDamage(KFP, Damage, MomentumScalar, DamageType);
|
|
bFoundHit = true;
|
|
}
|
|
}
|
|
|
|
return bFoundHit;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Player (non-AI) zed for versus mode
|
|
*********************************************************************************************/
|
|
|
|
/**
|
|
* Default hit detection for player controlled zeds in Versus mode
|
|
* @return TRUE if Pawn (not world) was hit for use by zed-time
|
|
*/
|
|
function bool DoPlayerControlledImpact(int Damage,
|
|
optional float MomentumScalar=1.f,
|
|
optional class<KFDamageType> DamageType=MyDamageType)
|
|
{
|
|
local bool bHitPawn;
|
|
|
|
bHitPawn = DoAreaImpact(Damage, MomentumScalar, DamageType,, PlayerControlledFOV);
|
|
if ( !bHitPawn )
|
|
{
|
|
DoPlayerWorldTrace(Damage, MomentumScalar, MyDamageType);
|
|
}
|
|
|
|
return bHitPawn;
|
|
}
|
|
|
|
/**
|
|
* A simplified forward trace to hit world geometry and other destructibles.
|
|
* @see also KFMeleeHelperWeapon::DoWeaponInstantTrace())
|
|
*/
|
|
function bool DoPlayerWorldTrace(int Damage,
|
|
optional float MomentumScalar=1.f,
|
|
optional class<KFDamageType> DamageType=MyDamageType)
|
|
{
|
|
local vector StartTrace, EndTrace;
|
|
local vector HitLocation, HitNormal;
|
|
local Actor HitActor;
|
|
|
|
StartTrace = GetMeleeStartTraceLocation();
|
|
EndTrace = StartTrace + vector(GetMeleeAimRotation()) * MaxHitRange;
|
|
|
|
// There is no weapon to trace from, so we added a custom non-recursive trace that hits
|
|
// actors (e.g. destructibles) and world geoemtry (e.g. doors), but not pawns.
|
|
HitActor = TraceNoPawns(HitLocation, HitNormal, EndTrace, StartTrace);
|
|
|
|
if( HitActor != None )
|
|
{
|
|
if ( HitActor.bCanBeDamaged && HitActor.IsA('KFDoorActor') )
|
|
{
|
|
Damage *= PlayerDoorDamageMultiplier;
|
|
}
|
|
ApplyMeleeDamage(HitActor, Damage, MomentumScalar, DamageType, HitLocation);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Replicate (serverside hit detection) camera shake for human instigator
|
|
*/
|
|
simulated function PlayMeleeHitEffects(Actor Target, vector HitLocation, vector HitDirection, optional bool bShakeInstigatorCamera=true)
|
|
{
|
|
Super.PlayMeleeHitEffects(Target, HitLocation, HitDirection);
|
|
|
|
// local player controlled effects
|
|
if( bShakeInstigatorCamera && Instigator.IsHumanControlled() )
|
|
{
|
|
PlayerController(Instigator.Controller).ClientPlayCameraShake(MeleeImpactCamShake, MeleeImpactCamScale, true, CAPS_UserDefined, rotator(-HitDirection));
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Anim Notify Handling
|
|
*********************************************************************************************/
|
|
|
|
/** Notification called from KFAnimNotify_MeleeImpact. */
|
|
function MeleeImpactNotify(KFAnimNotify_MeleeImpact Notify)
|
|
{
|
|
local KFAIController KFAIC;
|
|
local float DoorDamageScale;
|
|
local float MomentumScalar;
|
|
local bool bDealtDmg;
|
|
local class<KFDamageType> CurrentDamageType;
|
|
|
|
if ( Instigator == None || (Instigator.Role < ROLE_Authority && !Instigator.IsLocallyControlled()) )
|
|
{
|
|
return; // AutomonmousProxy in Versus, but doesn't work with CSHD
|
|
}
|
|
|
|
MomentumScalar = Notify.bCanDoKnockback ? Notify.MomentumTransferScale * MomentumTransfer : 1.f;
|
|
|
|
KFAIC = KFAIController(Instigator.Controller);
|
|
// TODO: Get more accurate target from MeleeAttack command
|
|
|
|
CurrentDamageType = Notify.CustomDamageType != none ? Notify.CustomDamageType : MyDamageType;
|
|
|
|
if( KFAIC != none && (KFAIC.DoorEnemy != none || KFAIC.ActorEnemy != none) )
|
|
{
|
|
// Scale door damage based on number of attackers
|
|
if( KFAIC.DoorEnemy != none )
|
|
{
|
|
DoorDamageScale = KFAIC.DoorEnemy.GetAIDoorDamageScale();
|
|
}
|
|
bDealtDmg = CheckEnemyImpact((Notify.DamageScale * BaseDamage) * DoorDamageScale, MomentumScalar, CurrentDamageType);
|
|
}
|
|
else if ( Notify.bDoAreaDamage )
|
|
{
|
|
bDealtDmg = DoAreaImpact(Notify.DamageScale * BaseDamage, MomentumScalar, CurrentDamageType);
|
|
}
|
|
else if ( Notify.bDoSwipeDamage )
|
|
{
|
|
bDealtDmg = DoSwipeImpact(Notify.DamageScale * BaseDamage, Notify.SwipeDirection, MomentumScalar,,, CurrentDamageType);
|
|
}
|
|
else if( Instigator.IsHumanControlled() )
|
|
{
|
|
// Before you ask, yes you can be a human in the "AI" helper. This is a simplification for third person player
|
|
// zeds in versus mode. For Area/Swipe the same hit detection will work (ish) for both.
|
|
bDealtDmg = DoPlayerControlledImpact(Notify.DamageScale * BaseDamage, MomentumScalar, CurrentDamageType);
|
|
}
|
|
else
|
|
{
|
|
bDealtDmg = CheckEnemyImpact(Notify.DamageScale * BaseDamage, MomentumScalar, CurrentDamageType, Notify.AttackReachOverride > 0.f ? Notify.AttackReachOverride : MaxHitRange );
|
|
}
|
|
|
|
if ( bDealtDmg && Notify.bCanCauseZedTime )
|
|
{
|
|
KFGameInfo(WorldInfo.Game).DramaticEvent(0.03);
|
|
}
|
|
}
|
|
|
|
/** Do damage directly to AI Controller Enemy
|
|
* @param Damage Damage to apply if this attack is successful
|
|
* @param MomentumScalar Momentum transfer to apply if this attack is successful
|
|
* @param DamageType DamageType to apply if this attack is successful
|
|
* @param Range Maximum range for hit detection
|
|
*/
|
|
protected function bool CheckEnemyImpact(int Damage,
|
|
float MomentumScalar,
|
|
class<KFDamageType> InDamageType=MyDamageType,
|
|
optional float AttackReachOverride=MaxHitRange)
|
|
{
|
|
local KFAIController AIC;
|
|
|
|
AIC = KFAIController(Instigator.Controller);
|
|
|
|
if ( AIC != None )
|
|
{
|
|
if ( AIC.Enemy != None && ShouldDealDamageToEnemy(AIC.Enemy, AttackReachOverride) )
|
|
{
|
|
ResolvePawnMeleeDamage(AIC.Enemy, Damage, MomentumScalar, InDamageType);
|
|
return true;
|
|
}
|
|
else if ( AIC.DoorEnemy != None && (VSizeSq(Location - AIC.DoorEnemy.Location) < Square(500)) )
|
|
{
|
|
ApplyMeleeDamage(AIC.DoorEnemy, Damage, MomentumScalar, InDamageType);
|
|
return true;
|
|
}
|
|
else if ( AIC.ActorEnemy != None && (VSizeSq(Location - AIC.ActorEnemy.Location) < Square(500)) )
|
|
{
|
|
// Apply damage to KFDestructibleActors, see KFDestructibleActor.TakeDamage() where there's special
|
|
// handling to insta-kill the destructible if the attacker is a NPC with the destructible as the
|
|
// NPC's ActorEnemy.
|
|
if( KFDestructibleActor(AIC.ActorEnemy) != none )
|
|
{
|
|
ApplyMeleeDamage(AIC.ActorEnemy, Damage, MomentumScalar, InDamageType);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Swipe Hit Detection
|
|
*********************************************************************************************/
|
|
|
|
/** A modified Cone hit detection that keeps track of all actors that have been hit
|
|
* @param Damage Damage to apply if this attack is successful
|
|
* @param SwipeDir Direction to use for hit detection
|
|
* @param MomentumScalar Momentum transfer to apply if this attack is successful
|
|
* @param DamageType DamageType to apply if this attack is successful
|
|
* @param Range Maximum range for hit detection
|
|
* @param bPlayersOnly If true, skip non-player controlled pawns
|
|
*/
|
|
protected function bool DoSwipeImpact(
|
|
int Damage,
|
|
optional EPawnOctant SwipeDir=DIR_Forward,
|
|
optional float MomentumScalar=1.f,
|
|
optional float Range=MaxHitRange,
|
|
optional bool bPlayersOnly,
|
|
optional class<KFDamageType> InDamageType=MyDamageType)
|
|
{
|
|
local Pawn P;
|
|
local vector HitLoc, HitNorm;
|
|
local Actor HitActor;
|
|
local vector ConeDir, ConeStart;
|
|
local float ConeRange;
|
|
local bool bFoundHit;
|
|
|
|
// initialize cone collision
|
|
ConeDir = GetSwipeVector(SwipeDir);
|
|
ConeStart = Location + vect(0,0,32);
|
|
ConeRange = Range; // VSize(BladeEnd - BladeStart)
|
|
|
|
if ( bLogMelee )
|
|
{
|
|
// draw swipe trace
|
|
DrawDebugCone(ConeStart, ConeDir, ConeRange, Acos(0.7071f), Acos(0.7071f), 16, MakeColor(64,64,64,0), TRUE);
|
|
}
|
|
|
|
// @perf - AllPawns is ~ 10X faster than VisibleCollidingActors, 10X TraceActors, 2X CollidingActors
|
|
// - Use AllPawns when we only need pawn collision (fixed cost at any range)
|
|
// - Use CollidingActors when we need to hit pawns and/or world objects
|
|
// - Use TraceActors sparingly! (may give better results than CollidingActors)
|
|
// - Avoid VisibleCollidingActors (RateMeleeVictim handles this)
|
|
foreach WorldInfo.AllPawns( class'Pawn', P, Location, ConeRange )
|
|
{
|
|
if ( P == Instigator || P.bTearOff )
|
|
continue;
|
|
if ( bPlayersOnly && !P.IsHumanControlled() )
|
|
continue;
|
|
|
|
// Make sure victim is in FOV
|
|
if( Normal2D(P.Location - ConeStart) dot ConeDir < 0.7071f )
|
|
{
|
|
`log(GetFuncName()@"rejected:"@P$", dot:"@Normal(P.Location - ConeStart) dot ConeDir, bLogMelee);
|
|
continue;
|
|
}
|
|
|
|
// Don't let them melee the target through a wall
|
|
HitActor = Trace( HitLoc, HitNorm, P.Location + (P.BaseEyeHeight * vect(0,0,1)), Location, false,,, TRACEFLAG_Blocking );
|
|
// KAssets block non-actor traces even if they're not world geometry. Double check that what we've actually hit
|
|
// is really world geometry, or is at least GameRelevant. -MattF
|
|
if( HitActor != none && HitActor != P && (HitActor.bWorldGeometry || HitActor.bGameRelevant) )
|
|
{
|
|
`log(GetFuncName()@"rejected:"@P$", melee obstruction:"@HitActor, bLogMelee);
|
|
continue;
|
|
}
|
|
|
|
ProcessSwipeHit(P, Damage, MomentumScalar, InDamageType);
|
|
bFoundHit = true;
|
|
}
|
|
|
|
// For versus players: Do swipe detection normally, then for each forward direction
|
|
// apply a simple line trace to hit world geometry and destructibles.
|
|
if ( !bFoundHit && SwipeDir == DIR_Forward && Instigator.IsHumanControlled() )
|
|
{
|
|
DoPlayerWorldTrace(Damage, MomentumScalar, MyDamageType);
|
|
}
|
|
|
|
if ( bLogMelee && bFoundHit )
|
|
{
|
|
// draw swipe hit
|
|
DrawDebugCone(ConeStart, ConeDir, ConeRange, Acos(0.7071f), Acos(0.7071f), 16, MakeColor(255,0,0,255), TRUE);
|
|
}
|
|
|
|
return bFoundHit;
|
|
}
|
|
|
|
/** Returns hard-coded normal vectors for each 8-way swipe direction */
|
|
protected function vector GetSwipeVector(EPawnOctant SwipeDir)
|
|
{
|
|
local rotator R;
|
|
|
|
switch (SwipeDir)
|
|
{
|
|
case DIR_ForwardLeft: R = rot(0,-8192,0); break;
|
|
case DIR_Left: R = rot(0,-16384,0); break;
|
|
case DIR_BackwardLeft: R = rot(0,-24576,0); break;
|
|
case DIR_ForwardRight: R = rot(0,8192,0); break;
|
|
case DIR_Backward: R = rot(0,32768,0); break;
|
|
case DIR_BackwardRight: R = rot(0,24576,0); break;
|
|
case DIR_Right: R = rot(0,16384,0); break;
|
|
}
|
|
|
|
return vector(Rotation + R);
|
|
}
|
|
|
|
/**
|
|
* DoSwipeFire found a hit, now cause damage to it.
|
|
* @param A - the actor that was hit in the swipe
|
|
* @param Damage - amount of damage to apply if successful
|
|
* @param InHitLoc - Location of hit detection result
|
|
* @param MomentumScalar - Amount of momentum to apply if successful
|
|
* @param DamageType - DamageType to apply if successful
|
|
*/
|
|
protected function ProcessSwipeHit(Actor A, int Damage, float MomentumScalar, class<KFDamageType> InDamageType)
|
|
{
|
|
local KFPawn Victim;
|
|
local int ListIdx;
|
|
|
|
Victim = KFPawn(A);
|
|
|
|
if (Victim == None )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Track player hits to ignore overlaps in hit detection
|
|
if ( bTrackSwipeHits && A.IsA('KFPawn_Human') )
|
|
{
|
|
// See if this actor is already in the list
|
|
ListIdx = SwipedActors.Find('HitActor', Victim);
|
|
|
|
// Not in the list
|
|
if (ListIdx == INDEX_NONE)
|
|
{
|
|
// Add this actor to the list and mark them for damage
|
|
ListIdx = SwipedActors.Add(1);
|
|
SwipedActors[ListIdx].HitActor = Victim;
|
|
SwipedActors[ListIdx].LastHitTime = WorldInfo.TimeSeconds;
|
|
}
|
|
// In the list so see if we need to reset the time and do damage
|
|
else if ( `TimeSince(SwipedActors[ListIdx].LastHitTime) > 0.25f )
|
|
{
|
|
SwipedActors[ListIdx].LastHitTime = WorldInfo.TimeSeconds;
|
|
}
|
|
else
|
|
{
|
|
//`log("Swipe damage ignored (interval too short)"@A);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ResolvePawnMeleeDamage(Victim, Damage, MomentumScalar, InDamageType);
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Ping Compensation for Parry/Block
|
|
*********************************************************************************************/
|
|
|
|
/** handle ping compensation before dealing damage */
|
|
protected function ResolvePawnMeleeDamage(Pawn Victim, int Damage, float Momentum, class<KFDamageType> InDamageType)
|
|
{
|
|
local DelayedMeleeInfo NewDmgInfo;
|
|
local float RealDeltaSeconds;
|
|
local float PingCompensation;
|
|
|
|
if ( Instigator.Role < ROLE_Authority )
|
|
{
|
|
return; // how did we get here?
|
|
}
|
|
|
|
// Do not apply ping compensation to player instigated damage!
|
|
if ( Instigator.IsHumanControlled() )
|
|
{
|
|
ApplyMeleeDamage(Victim, Damage, Momentum, InDamageType);
|
|
return;
|
|
}
|
|
|
|
// If necessary, apply ping compensation
|
|
if ( Victim.PlayerReplicationInfo != None && Victim.Weapon != None &&
|
|
ClassIsChildOf(Victim.Weapon.Class, class'KFWeap_MeleeBase') )
|
|
{
|
|
// Calculate delay. Half-ping for anim to reach client and half again for parry key press to reach server
|
|
PingCompensation = Min(Victim.PlayerReplicationInfo.Ping * `PING_SCALE, MaxPingCompensation) / 1000.f;
|
|
|
|
// artifically reduce the delay to balance parry timing vs. AI reaction time
|
|
PingCompensation *= PingCompensationScale;
|
|
|
|
// "round" (div by 2) to the closest frame on the server
|
|
RealDeltaSeconds = WorldInfo.DeltaSeconds / WorldInfo.TimeDilation;
|
|
PingCompensation -= (RealDeltaSeconds / 2.f);
|
|
|
|
if ( PingCompensation > 0 )
|
|
{
|
|
NewDmgInfo.Victim = Victim;
|
|
NewDmgInfo.Damage = Damage;
|
|
NewDmgInfo.Momentum = Momentum;
|
|
NewDmgInfo.TimeOfDamage = WorldInfo.RealTimeSeconds + PingCompensation;
|
|
NewDmgInfo.DamageType = InDamageType;
|
|
PendingDamage.AddItem(NewDmgInfo);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ApplyMeleeDamage(Victim, Damage, Momentum, InDamageType);
|
|
}
|
|
|
|
/** Called from C++ when ping compensation expires */
|
|
event ApplyDelayedPawnDamage(int i)
|
|
{
|
|
local KFGameReplicationInfo KFGRI;
|
|
|
|
// If the damage dealer is now dead, don't allow damage after trader/respawn.
|
|
if ( !Instigator.IsAliveAndWell() )
|
|
{
|
|
KFGRI = KFGameReplicationInfo(WorldInfo.GRI);
|
|
if ( KFGRI != None && KFGRI.bTraderIsOpen )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
ApplyMeleeDamage(PendingDamage[i].Victim, PendingDamage[i].Damage, PendingDamage[i].Momentum, PendingDamage[i].DamageType);
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
DefaultFOVCosine=0.f
|
|
MyDamageType=class'KFDT_Slashing'
|
|
|
|
bTrackSwipeHits=true
|
|
MaxPingCompensation=200
|
|
PingCompensationScale=0.5f
|
|
|
|
PlayerControlledFOV=0.05f
|
|
PlayerDoorDamageMultiplier=10.0f
|
|
MeleeImpactCamScale=1.f
|
|
}
|