1
0
KF2-Dev-Scripts/KFGame/Classes/KFMeleeHelperWeapon.uc

840 lines
26 KiB
Ucode
Raw Normal View History

2020-12-13 15:01:13 +00:00
//=============================================================================
// KFMeleeHelperWeapon
//=============================================================================
// Manages melee attack related functionality for 1st person weapons
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
//=============================================================================
class KFMeleeHelperWeapon extends KFMeleeHelperBase within KFWeapon
config(Game);
/*********************************************************************************************
* Directional Attacks
*********************************************************************************************/
/** If set, plays 8-way directional animations */
var() bool bUseDirectionalMelee;
/** Count each time ChooseBestAttack is called. Used to alternate direction */
var transient byte ChooseAtkCount;
enum EMeleeAttackType
{
ATK_Normal,
ATK_Combo,
ATK_DrawStrike,
};
/** Attack settings that should be initialized before calling BeginMeleeAttack */
2021-06-02 20:06:18 +00:00
var protected EPawnOctant NextAttackDir;
var protected EMeleeAttackType NextAttackType;
2020-12-13 15:01:13 +00:00
/** Direction of the last played attack */
var EPawnOctant CurrentAttackDir;
/*********************************************************************************************
* Combo (aka Chain) Attacks
*********************************************************************************************/
/** attack anim variant settings */
var() bool bHasChainAttacks;
/** 4-way directional combo chain sequence */
var() array<EPawnOctant> ChainSequence_F;
var() array<EPawnOctant> ChainSequence_B;
var() array<EPawnOctant> ChainSequence_L;
var() array<EPawnOctant> ChainSequence_R;
/** Cached chain attack sequence currently in use */
var array<EPawnOctant> AttackChainSequence;
/** Index into the currently playing attack chain sequence */
var byte CurrentAtkChainIdx;
/** Number of attacks played in this sequence */
var int NumChainedAttacks;
/** Last time MeleeAnimTimer was called */
var transient float LastMeleeAnimEnd_ActorTime;
/** Set when InitAttackSequence is called during an attack to change direction of the next attack */
var transient bool bResetChainSequence;
/*********************************************************************************************
* Notify & Timing
*********************************************************************************************/
/** Should use timer instead of anim notifies for melee hits? */
var() bool bUseMeleeHitTimer;
/** Time (after melee starts) before traces for impact start occuring */
var() float InitialImpactDelay;
/** Time (after impact checks start) to retry impacts */
var() float ImpactRetryDuration;
/** World time to stop retrying traces for melee impact */
var() float ImpactComplete_ActorTime;
/** Stores whether or not an impact has happened this attack */
var bool bHitEnemyThisAttack;
/*********************************************************************************************
* Animation / FX
*********************************************************************************************/
/** Scale animation playrate by fatigue level */
var InterpCurveFloat FatigueCurve;
/** Scale parameter for ClientPlayCameraShake */
var float MeleeImpactCamShakeScale;
/*********************************************************************************************
* Anim Notify / Impact Timing
*********************************************************************************************/
2020-12-13 15:09:05 +00:00
simulated function EPawnOctant GetNextAttackDir()
{
return NextAttackDir;
}
simulated function SetNextAttackDir(EPawnOctant _nextAttackDir)
{
NextAttackDir = _nextAttackDir;
}
2020-12-13 15:01:13 +00:00
/** Notification called from KFAnimNotify_MeleeImpact. */
simulated function MeleeImpactNotify(KFAnimNotify_MeleeImpact_1P Notify)
{
local bool bResult;
if ( !bHasAlreadyHit && !bUseMeleeHitTimer )
{
// Check impacts for players first
bResult = MeleeAttackImpact();
// If no impact with player occurs
if( !bResult && Instigator.Role == ROLE_Authority )
{
// Check for impact with world/destructibles
bResult = MeleeAttackDestructibles();
}
bHasAlreadyHit = bResult;
}
}
/**
* Function handles checking for all types of melee impacts
*/
simulated function bool MeleeImpactTimer()
{
local bool bResult;
if ( bUseMeleeHitTimer )
{
// If this is the start first check of a melee attack
if( ImpactComplete_ActorTime < 0.f )
{
// Update the window of time to repeat checks for impact
ImpactComplete_ActorTime = GetActorTimeSeconds() + ImpactRetryDuration;
}
// If window has expired - FAIL
if( GetActorTimeSeconds() > ImpactComplete_ActorTime )
{
return FALSE;
}
}
// Check impacts for players first
bResult = MeleeAttackImpact();
// If no impact with player occurs
if( !bResult && Instigator.Role == ROLE_Authority )
{
// Check for impact with world/destructibles
bResult = MeleeAttackDestructibles();
}
// If no impact has occured
if( !bResult && bUseMeleeHitTimer )
{
// Set timer to retry impact
SetTimer(0.1, FALSE, nameof(MeleeImpactTimer), self);
}
return bResult;
}
/**
* Main hit detection and resolution for melee attacks
* Network: Local Player
*/
simulated function bool MeleeAttackImpact()
{
local Array<ImpactInfo> ImpactList;
local int Idx;
//local ImpactInfo RealImpact;
local vector StartTrace, EndTrace;
local bool bHasAnyHit;
// local player only for clientside hit detection
if ( Instigator == None || !Instigator.IsLocallyControlled() )
{
return false;
}
StartTrace = GetMeleeStartTraceLocation();
EndTrace = StartTrace + vector(GetMeleeAimRotation()) * MaxHitRange;
CalcWeaponMeleeAttack(StartTrace, EndTrace, ImpactList);
bHasAnyHit = ImpactList.length > 0;
for (Idx = 0; Idx < ImpactList.Length; Idx++)
{
ProcessMeleeHit(CurrentFireMode, ImpactList[Idx]);
}
if ( Instigator.Role < ROLE_Authority )
{
SendClientImpactList(CurrentFireMode, ImpactList);
}
return bHasAnyHit;
}
/*********************************************************************************************
* FOV (aka Area, Cone) Hit Detection
*********************************************************************************************/
/**
* CalcWeaponMeleeAttack: Simulate an instant hit melee attack.
* This doesn't deal any damage nor trigger any effect. It just simulates a shot and returns
* the hit information, to be post-processed later.
*/
simulated function ImpactInfo CalcWeaponMeleeAttack(vector StartTrace, vector EndTrace, optional out array<ImpactInfo> ImpactList, optional vector Extent)
{
local KFPawn KFPOwner;
local KFSM_InteractionPawnFollower FollowerSM;
local Pawn BestVictim;
local ImpactInfo CurrentImpact;
local array<Pawn> VictimList;
local vector RayDir;
// nudge impact direction (momentum) based on attack type
RayDir = GetAdjustedRayDir(EndTrace - StartTrace);
// If an enemy pawn has engaged us in an interaction special move, try to target them first
KFPOwner = KFPawn( Instigator );
if( KFPOwner.IsDoingSpecialMove() )
{
FollowerSM = KFSM_InteractionPawnFollower( KFPOwner.SpecialMoves[KFPOwner.SpecialMove] );
if( FollowerSM != none
&& FollowerSM.Leader != none
&& RateMeleeVictim(FollowerSM.Leader, StartTrace, EndTrace, MaxHitRange, DefaultFOVCosine) > -1.f )
{
BestVictim = FollowerSM.Leader;
}
}
// Find nearby pawn targets - in the future we may want to calculate multiple victim impacts
if( BestVictim == none )
{
BestVictim = FindVictimByFOV(StartTrace, EndTrace);
}
if ( BestVictim != None )
{
VictimList[0] = BestVictim;
CalcVictimImpactList(VictimList, StartTrace, EndTrace, RayDir, ImpactList);
}
else if ( Instigator.Weapon != None )
{
DoWeaponInstantTrace(StartTrace, EndTrace, CurrentImpact);
if( CurrentImpact.HitActor != None )
{
// Check for world geometry or CanBeDamaged (e.g. ragdolls, destructibles)
if ( CurrentImpact.HitActor == WorldInfo
|| CurrentImpact.HitActor.bBlockActors
|| CurrentImpact.HitActor.bCanBeDamaged )
{
CurrentImpact.RayDir = RayDir;
ImpactList[ImpactList.Length] = CurrentImpact;
}
}
`log(WorldInfo.TimeSeconds @ GetFuncName() @ "HitWallCheck, HitActor:" @ CurrentImpact.HitActor, bLogMelee && CurrentImpact.HitActor != None);
}
if( bLogMelee )
{
DrawDebugLine(StartTrace, EndTrace, 128, 0, 0, TRUE);
DrawDebugLine(StartTrace, StartTrace + RayDir * 100, 0, 128, 0, TRUE);
// while debugging melee flush the lines after some inactivity
SetTimer(10.f, false, nameof(FlushPersistentDebugLines));
}
return CurrentImpact;
}
/** Converts a KFPawn list into a ImpactInfo list */
simulated function CalcVictimImpactList(array<Pawn> VictimList, vector StartTrace, vector EndTrace, vector RayDir, optional out array<ImpactInfo> ImpactList)
{
local ImpactInfo HitZoneImpact;
local int i;
for ( i = 0; i < VictimList.Length; i++ )
{
if ( TraceMeleeAttackHitZones(VictimList[i], StartTrace, EndTrace, HitZoneImpact) )
{
HitZoneImpact.RayDir = RayDir; // not set by TraceAllPhysicsAssetInteractions
ImpactList[ImpactList.Length] = HitZoneImpact;
`log(GetFuncName()@"HitZone:"$ImpactList[0].HitInfo.BoneName@"DmgScale:"$VSize(RayDir)@"HitActor:"$VictimList[i], bLogMelee);
}
else
{
`log(GetFuncName()@"HitVictimCheck missed all hit zones"); // should never happen
}
}
}
/** Calculate a new Impact RayDir for a given AttackType. Used by FOV-style
* collision to simulate directional attack momentum.
*/
simulated function vector GetAdjustedRayDir(vector ImpactRayDir)
{
local rotator R;
if ( bUseDirectionalMelee )
{
switch ( CurrentAttackDir )
{
case DIR_Forward: R = rot(-8192, 0, 0); break;
case DIR_ForwardLeft: R = rot(-8192,-8192, 0); break;
case DIR_ForwardRight: R = rot(-8192, 8192, 0); break;
case DIR_Backward: R = rot( 8192, 0, 0); break;
case DIR_BackwardLeft: R = rot( 8192,-8192, 0); break;
case DIR_BackwardRight: R = rot( 8192, 8192, 0); break;
case DIR_Left: R = rot( 0, -8192, 0); break;
case DIR_Right: R = rot( 0, 8192, 0); break;
}
return vector(rotator(ImpactRayDir) + R);
}
return Normal(ImpactRayDir);
}
/*********************************************************************************************
* Hitbox Collision Detection
*********************************************************************************************/
/** Recieve collision event and register impact with CSHD */
event ProcessHitboxCollision(Actor HitActor, vector StartTrace, vector EndTrace, vector HitLocation, vector HitNormal, const out TraceHitInfo HitInfo, optional out ImpactInfo Impact)
{
local Array<ImpactInfo> ImpactList;
local ImpactInfo HitZoneImpact;
local int Idx;
if ( Instigator != None && Instigator.IsLocallyControlled() )
{
Super.ProcessHitboxCollision(HitActor, StartTrace, EndTrace, HitLocation, HitNormal, HitInfo, Impact);
ImpactList[0] = Impact;
// Trace physics asset hit zones
if ( HitActor.IsA('KFPawn') )
{
if ( TraceMeleeAttackHitZones(KFPawn(HitActor), StartTrace, EndTrace, HitZoneImpact, HitInfo.BoneName) )
{
HitZoneImpact.RayDir = Impact.RayDir; // TraceAllPhysicsAssetInteractions doesn't set RayDir
ImpactList[0] = HitZoneImpact;
}
else
{
`log(GetFuncName()@"missed hit zone... continuing with hitbox TraceInfo");
}
// encode damage scale based on fan collision into the raydir
ImpactList[0].RayDir *= GetDamageScaleByAngle(HitLocation);
}
for (Idx = 0; Idx < ImpactList.Length; Idx++)
{
ProcessMeleeHit(CurrentFireMode, ImpactList[Idx]);
}
if ( Instigator.Role < ROLE_Authority )
{
SendClientImpactList(CurrentFireMode, ImpactList);
}
}
}
/** Called when a hitbox notify starts to allow script to perform a custom world trace */
event InitWorldTraceForHitboxCollision()
{
SetTimer(0.1, false, nameof(HitboxSimpleWorldTrace), self);
}
/** Performs a simple forward trace if bHitboxPawnsOnly=TRUE */
function bool HitboxSimpleWorldTrace()
{
local vector StartTrace, EndTrace;
local ImpactInfo CurrentImpact;
local array<ImpactInfo> ImpactList;
local int Idx;
// local player only for clientside hit detection
if ( Instigator == None || !Instigator.IsLocallyControlled() )
{
return false;
}
StartTrace = GetMeleeStartTraceLocation();
EndTrace = StartTrace + vector(GetMeleeAimRotation()) * MaxHitRange;
DoWeaponInstantTrace(StartTrace, EndTrace, CurrentImpact);
if( CurrentImpact.HitActor != None )
{
// Check for world geometry and other destructibles
if ( CurrentImpact.HitActor.bWorldGeometry || (CurrentImpact.HitActor.bCanBeDamaged && !CurrentImpact.HitActor.IsA('Pawn')) )
{
CurrentImpact.RayDir = GetAdjustedRayDir(EndTrace - StartTrace);
ImpactList[ImpactList.Length] = CurrentImpact;
for (Idx = 0; Idx < ImpactList.Length; Idx++)
{
ProcessMeleeHit(CurrentFireMode, ImpactList[Idx]);
}
if ( Instigator.Role < ROLE_Authority )
{
SendClientImpactList(CurrentFireMode, ImpactList);
}
`log(GetFuncName()@CurrentImpact.HitActor, bLogMelee);
return true;
}
}
return false;
}
/*********************************************************************************************
* Weapon melee state (e.g. MeleeAttackBasic) animation and timing
*********************************************************************************************/
/** Called from state MeleeAtacking */
simulated function BeginMeleeAttack(optional bool bIsChainAttack)
{
local Pawn P;
local KFPawn_Monster KFPM;
local vector Projection;
local float MeleeDuration;
// Don't let a weak zed grab us when we just melee attacked
// Ramm - commented out, too exploitable
// SetWeakZedGrabCooldownOnPawn(0.5);
// initialize attack settings
bHasAlreadyHit = false;
bHitEnemyThisAttack = false;
// Let the playercontroller know we did an attack
if ( Instigator != None && Instigator.Controller != none &&
KFPlayerController(Instigator.Controller) != none )
{
KFPlayerController(Instigator.Controller).AddShotsFired(1);
}
// Clear reset flag (see InitAttackSequence, ContinueMeleeAttack)
bResetChainSequence = false;
// save the direction of this attack
CurrentAttackDir = (CurrentFireMode == BASH_FIREMODE || CurrentFireMode == CUSTOM_FIREMODE ) ? DIR_None : NextAttackDir;
// Notify enemy pawns of melee strike
if( WorldInfo.NetMode != NM_Client )
{
foreach WorldInfo.AllPawns( class'Pawn', P )
{
if( P.GetTeamNum() != Instigator.GetTeamNum() && P.IsAliveAndWell() && !P.IsHumanControlled() )
{
Projection = Instigator.Location - P.Location;
if( VSizeSQ(Projection) <= Square(MaxHitRange + P.CylinderComponent.CollisionRadius) )
{
KFPM = KFPawn_Monster( P );
if( KFPM != none && KFPM.MyKFAIC != none )
{
KFPM.MyKFAIC.ReceiveMeleeWarning( CurrentAttackDir, Projection, Instigator );
}
}
}
}
}
// Select and play attack animation
MeleeDuration = PlayMeleeAttackAnimation();
if ( MeleeDuration > 0.f )
{
// @hack: Add current DeltaSeconds to timer. This fixes an off-by-one frame issue that can
// that can cause the timer to interrupt animation at low framerate. This is particularly
// bad for melee when using impact anim notifies.
SetTimer(MeleeDuration + WorldInfo.DeltaSeconds, FALSE, nameof(MeleeCheckTimer), self);
// set the timer to check for hits
if( bUseMeleeHitTimer && InitialImpactDelay > 0.f )
{
// Clear complete time so it is intialized properly
ImpactComplete_ActorTime = -1.f;
SetTimer(InitialImpactDelay, FALSE, nameof(MeleeImpactTimer), self);
}
}
else
{
`warn("MeleeDuration is zero!!!");
SetTimer(0.001, false, nameof(HandleFinishedFiring));
}
}
/** Plays the melee attack animations and returns the interrupt time */
simulated function float PlayMeleeAttackAnimation()
{
local name MeleeAnimName;
local float InterruptTime, Rate;
// Calc Reload Duration
MeleeAnimName = GetMeleeAnimName(NextAttackDir, NextAttackType);
InterruptTime = MySkelMesh.GetAnimInterruptTime(MeleeAnimName);
Rate = EvalInterpCurveFloat(FatigueCurve, NumChainedAttacks);
if ( InterruptTime > 0.f )
{
// Call back into weapon (rather than direct to 'PlayAnimation') so it can choose to override
PlayMeleeAnimation(MeleeAnimName, Rate, GetMeleeBlendInTime());
}
return InterruptTime * Rate;
}
/** Returns the desired blend in time for this melee attack animation */
simulated function float GetMeleeBlendInTime()
{
// Chain attack anims go offscreen, so we need to disable blending for the next action
if ( bHasChainAttacks )
{
// This can sometimes happen when AtkType != MAT_Combo. LastRenderTime will catch it most of the time.
if ( NextAttackType == ATK_Combo || `TimeSince(LastRenderTime) > 0.f )
{
return 0.f;
}
}
return 0.1f;
}
/**
* Returns the type of melee attack we want to perform
* Network: Local Player
*/
simulated function EPawnOctant ChooseAttackDir()
{
local vector MoveDir;
local EPawnOctant AttackDir;
ChooseAtkCount++;
// Prefer to use acceleration, but if the player only has velocity use that.
// This allows the player to let go off the movement keys just before an attack.
MoveDir = (IsZero(Instigator.Acceleration)) ? Instigator.Velocity : Instigator.Acceleration;
if ( Instigator == None || !bUseDirectionalMelee || IsZero(MoveDir) )
{
if ( bUseDirectionalMelee )
{
// alternate left and right
return ((ChooseAtkCount & 1) > 0) ? DIR_Left : DIR_Right;
}
return DIR_None;
}
// Get desired attack direction. This is reasonbly close on server/client, but it could still use
// a little work to sync perfectly. To see why run with pktlag and spin around like a crazy person.
AttackDir = class'KFPawn'.static.CalcQuadRegion(Instigator.GetViewRotation(), MoveDir);
// reverse left/right so that we're moving in the direction of the attack
switch ( AttackDir )
{
case DIR_Left: return DIR_Right;
case DIR_Right: return DIR_Left;
}
return AttackDir;
}
/** Sets the type of attack when StartFire is called */
simulated function InitAttackSequence(EPawnOctant NewAtkDir, EMeleeAttackType NewAtkType)
{
`log(Instigator@"SetNextAttackType:"@NewAtkDir, bLogMelee);
CurrentAtkChainIdx = 0;
NumChainedAttacks = 0;
NextAttackDir = NewAtkDir;
NextAttackType = NewAtkType;
// initialize chain attack sequence
if ( bUseDirectionalMelee )
{
AttackChainSequence.Length = 0;
if ( NewAtkType == ATK_DrawStrike )
{
AttackChainSequence = ChainSequence_R;
}
else
{
switch (NextAttackDir)
{
case DIR_Forward: AttackChainSequence = ChainSequence_F; break;
case DIR_ForwardLeft: AttackChainSequence = ChainSequence_F; break;
case DIR_ForwardRight: AttackChainSequence = ChainSequence_F; break;
case DIR_Backward: AttackChainSequence = ChainSequence_B; break;
case DIR_Left: AttackChainSequence = ChainSequence_L; break;
case DIR_Right: AttackChainSequence = ChainSequence_R; break;
}
}
bResetChainSequence = IsMeleeing();
}
// If the player uses a default melee attack within a short time of another attack, use combo anims
if ( NewAtkType == ATK_Normal && !IsMeleeing() )
{
if ( LastMeleeAnimEnd_ActorTime > 0.f && ActorTimeSince(LastMeleeAnimEnd_ActorTime) < 0.1f )
{
NextAttackType = ATK_Combo;
}
}
}
/** see Weapon::RefireCheckTimer() */
simulated function MeleeCheckTimer()
{
ClearTimer(nameof(MeleeCheckTimer), self);
LastMeleeAnimEnd_ActorTime = GetActorTimeSeconds();
if ( IsMeleeing() )
{
// if switching to another weapon, abort firing and put down right away
if( bWeaponPutDown )
{
`LogInv("Weapon put down requested during fire, put it down now");
PutDownWeapon();
return;
}
// If weapon should keep on firing, then do not leave state and fire again.
if( bHasChainAttacks && ShouldContinueMelee(NumChainedAttacks) )
{
ContinueMeleeAttack();
return;
}
// Otherwise we're done firing
HandleFinishedFiring();
}
}
/** Called from state ShouldRefire */
simulated function ContinueMeleeAttack()
{
// increment attack sequence
if ( bUseDirectionalMelee && AttackChainSequence.Length > 0 )
{
NumChainedAttacks++;
// increment sequence unless it's been reset/interrupted
if ( !bResetChainSequence )
{
NextAttackDir = AttackChainSequence[CurrentAtkChainIdx];
CurrentAtkChainIdx = (CurrentAtkChainIdx + 1) % AttackChainSequence.Length;
}
`log("IncrementAttackSequence ChainIdx="$CurrentAtkChainIdx@"Direction="$NextAttackDir, bLogMelee);
}
// these attacks always play combo attack anims
NextAttackType = ATK_Combo;
BeginMeleeAttack(true);
// If this attack is our last attack in the sequence, clear pending fire.
if( PendingFire(CurrentFireMode) && !ShouldContinueMelee(NumChainedAttacks) )
{
ClearPendingFire(CurrentFireMode);
}
}
/*********************************************************************************************
* Damage & Hit FX
*********************************************************************************************/
/**
* @see ProcessInstantHit
* Network: LocalPlayer and Server
*/
simulated function ProcessMeleeHit(byte FiringMode, ImpactInfo Impact)
{
local KActorFromStatic NewKActor;
local StaticMeshComponent HitStaticMesh;
local FracturedStaticMeshActor FracActor;
local KFPawn HitPawn;
local vector Momentum;
if ( Impact.HitActor != None )
{
if ( Impact.HitActor.bWorldGeometry )
{
HitStaticMesh = StaticMeshComponent(Impact.HitInfo.HitComponent);
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() )
{
NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh);
if ( NewKActor != None )
{
Impact.HitActor = NewKActor;
}
}
// Fracture meshes if we hit them
if( bAllowMeleeToFracture )
{
FracActor = FracturedStaticMeshActor(Impact.HitActor);
if(FracActor != None)
{
class'KFMeleeHelperBase'.static.MeleeFractureMeshImpact( FracActor, Impact.HitLocation, Impact.HitNormal );
}
}
}
// Notify pawn of melee damage. Similar to NotifyTakeHit, but before AdjustDamage is called.
HitPawn = KFPawn(Impact.HitActor);
if ( HitPawn != None )
{
if( !bHitEnemyThisAttack && HitPawn.GetTeamNum() != Instigator.GetTeamNum() )
{
// Let the playercontroller know we did hit with this attack
if ( Instigator != None && Instigator.Controller != none &&
KFPlayerController(Instigator.Controller) != none )
{
KFPlayerController(Instigator.Controller).AddShotsHit(1);
}
bHitEnemyThisAttack = true;
}
HitPawn.NotifyMeleeTakeHit(Instigator.Controller, Impact.HitLocation);
}
// Get momentum transfer from the owning weapon
Momentum = Normal(Impact.RayDir) * InstantHitMomentum[FiringMode];
// play effects before doing damage, because doing damage can change the actor (e.g. destructibles) and result in incorrect sounds
PlayMeleeHitEffects(Impact.HitActor, Impact.HitLocation, Impact.RayDir);
2020-12-13 15:09:05 +00:00
PawnTakeDamage(Impact, FiringMode, Momentum);
2020-12-13 15:01:13 +00:00
// notify weapon for custom code
NotifyMeleeCollision(Impact.HitActor, Impact.HitLocation);
}
}
2020-12-13 15:09:05 +00:00
simulated function PawnTakeDamage(ImpactInfo Impact,byte FiringMode, vector Momentum)
{
Impact.HitActor.TakeDamage( GetMeleeDamage(FiringMode, Impact.RayDir), Instigator.Controller,
Impact.HitLocation, Momentum,
GetDamageType(FiringMode), Impact.HitInfo, Outer );
}
2020-12-13 15:01:13 +00:00
/** returns the damage type to use for this attack */
simulated function class<DamageType> GetDamageType(byte FiringMode)
{
// Use the FiringMode to determine type of attack
return InstantHitDamageTypes[FiringMode];
}
/** Get damage scale adjusted to impact angle */
simulated function float GetDamageScaleByAngle(vector HitLoc)
{
local float DotResult;
local vector Origin;
local vector Aim;
if ( Instigator == None || !Instigator.IsLocallyControlled() )
{
return 1.f;
}
Origin = GetMeleeStartTraceLocation();
Aim = vector(Instigator.GetBaseAimRotation());
switch ( CurrentAttackDir )
{
case DIR_Left:
case DIR_Right:
// for horizontal attacks do 50-75% damage
DotResult = 0.75f * Normal2D(Aim) dot Normal2D(HitLoc - Origin);
return FMax(DotResult, 0.50f);
}
return 1.f;
}
/**
* Called by ProcessMeleeHit to spawn effects
* Network: Local Player and Server
*/
simulated function PlayMeleeHitEffects(Actor Target, vector HitLocation, vector HitDirection, optional bool bShakeInstigatorCamera=true)
{
// @note: Skipping super(). No victim camera shake is intentional
if( WorldInfo.NetMode != NM_DedicatedServer )
{
// first person (local) fire effects
if ( Instigator.IsFirstPerson() )
{
PlayerController(Instigator.Controller).ClientPlayCameraShake(MeleeImpactCamShake, MeleeImpactCamShakeScale, true, CAPS_UserDefined, rotator(-HitDirection));
if ( Target.IsA('Pawn') )
{
AddBlood(0.01f, 0.1f);
}
}
// If we hit a pawn we can skip the Tracing code in PlayImpactEffects. Pawn FX are handled in
// PlayHit and this prevents incorrect (world) FX from playing if the trace is bad.
if ( !(Target.bCanBeDamaged && Target.IsA('Pawn')) )
{
// Use ImpactEffectManager to material based world impacts
`ImpactEffectManager.PlayImpactEffects(HitLocation, Instigator, HitDirection, WorldImpactEffects);
}
}
}
defaultproperties
{
bAllowMeleeToFracture=true
bHitboxPawnsOnly=true
// damage settings
InitialImpactDelay=0.2
ImpactRetryDuration=0.2
// default chain attack sequences
ChainSequence_F=(DIR_Left, DIR_ForwardRight, DIR_ForwardLeft, DIR_ForwardRight, DIR_ForwardLeft)
ChainSequence_B=(DIR_BackwardRight, DIR_ForwardLeft, DIR_BackwardLeft, DIR_ForwardRight, DIR_Left, DIR_Right, DIR_Left)
ChainSequence_L=(DIR_Right, DIR_Left, DIR_ForwardRight, DIR_ForwardLeft, DIR_Right, DIR_Left)
ChainSequence_R=(DIR_Left, DIR_Right, DIR_ForwardLeft, DIR_ForwardRight, DIR_Left, DIR_Right)
FatigueCurve=(Points=((InVal=2.f,OutVal=1.f),(InVal=15.f, OutVal=1.5f)))
WorldImpactEffects=KFImpactEffectInfo'FX_Impacts_ARCH.Blunted_melee_impact'
MeleeImpactCamShakeScale=1.f
}