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

453 lines
16 KiB
Ucode

//=============================================================================
// KFMeleeHelperBase
//=============================================================================
// Contains common solutions for melee hit detection and Effects
// @note - Uses "within actor" and instigator so that this object can be
// associated with either a Pawn or a Weapon.
//=============================================================================
// Killing Floor 2
// Copyright (C) 2015 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================
class KFMeleeHelperBase extends Object within Actor
dependson(KFAnimNotify_MeleeImpact)
native
config(Game);
/// ABOUT HIT DETECTION
/** #1 FOV (aka Area, Cone) Hit Detection
* @brief: Optimized method that iterates through pawns and rates them based on
* distance to the center of the cone. Highly flexible, works well for single
* impact weapons (e.g. pistol whip). Single or multiple targets.
* @see: FindVictimByFOV, RateMeleeVictim, TraceMeleeAttackHitZones
*/
/** #2 Weapon Instant Trace
* @brief: Similar to InstantFire used by guns, this is a zero-extent line trace usually to the
* center of the screen. Works well for non critical impacts such as world geometry
* and ragdolls. Single target only.
* @see: DoWeaponInstantTrace
*/
/** #3 Hitbox Collision Detection
* @brief: Advanced type that follows the attack animation with moving hit boxes. Maintains a
* list of actors that have been hit which resets with each new attack. Multiple targets.
* @see: TickHitboxCollisionDetection, ProcessHitboxCollision
*/
/** #4 Enemy (aka Direct) Impact
* @brief: AI can directly damage their target Enemy after some basic validation.
* @see: CheckEnemyImpact
*/
/** #5 Swipe Hit Detection
* @brief: A hybrid of FOV collision and Hitbox collision that can be used to hit multiple
* targets over a time interval with. Multiple targets.
* @see: DoSwipeImpact
*/
/*********************************************************************************************
* FOV (aka Area, Cone) Hit Detection
*********************************************************************************************/
/** The maximum range at which this attack can be used **/
var() const float MaxHitRange;
/** Optional dot product check for melee hit detection */
var() const float DefaultFOVCosine;
/*********************************************************************************************
* Hitbox Collision Detection
*********************************************************************************************/
struct native MeleeHitboxInfo
{
var vector BoneOffset;
var vector LastLoc;
};
var const array<MeleeHitBoxInfo> HitboxChain;
/** Trace extent used for all hitboxes */
var vector HitboxExtent;
/** Distance between each new hitbox */
var float HitboxSpacing;
/** Name of the bone to start the hitbox chain from */
var name HitboxBoneName;
/** Direction along the bone to create the hitbox chain */
var EAxis HitboxBoneAxis;
/** If true, use TRACE_ComplexCollision */
var bool bHitboxCollideComplex;
/** If true, only the last hitbox uses extent trace collision */
var bool bOnlyUseHitboxExtentAtHead;
/** If true, perform an extra "fasttrace" before damaging a pawn */
var bool bDoHitboxObstructionTrace;
/** If true, hitboxes only collide with pawns */
var bool bHitboxPawnsOnly;
/** Cached list of actors that have collided with this attack */
var const array<Actor> ActorsCollidedWith;
/*********************************************************************************************
* Impact effects
*********************************************************************************************/
/** This is the camera shake that we play when we hit a melee attack **/
var() const CameraShake MeleeVictimCamShake;
/** This is the camera shake that we play when we hit a melee attack **/
var() const CameraShake MeleeImpactCamShake;
/** Content references for this weapon's impact effects */
var KFImpactEffectInfo WorldImpactEffects;
/** If TRUE, meleeing a FSM with this weapons will cause fracture. */
var bool bAllowMeleeToFracture;
/** If TRUE, this attack animation already hit something */
var bool bHasAlreadyHit;
/*********************************************************************************************
* Debug
********************************************************************************************* */
// `log conditions. More efficient than using log tags, because the msg is not evaluated.
var bool bLogMelee;
var bool bDebugShowCollision;
/*********************************************************************************************
* C++
*********************************************************************************************/
cpptext
{
}
/**
* Returns the location that should be the start of traces for MeleeAttackImpact()
*/
simulated function vector GetMeleeStartTraceLocation()
{
// use eye location instead of GetWeaponStartTraceLocation so it works in third person
return Instigator.Location + vect(0,0,1) * Instigator.BaseEyeHeight;
}
/**
* GetAdjustedAim begins a chain of function class that allows the weapon, the pawn and the controller to make
* on the fly adjustments to where this weapon is pointing.
*/
simulated function Rotator GetMeleeAimRotation()
{
local rotator R;
// Start the chain, see Pawn.GetAdjustedAimFor()
if( Instigator != None )
{
R = Instigator.GetBaseAimRotation();
}
return R;
}
native function SetMeleeRange( float NewRange );
/**
* Returns the default melee attack hit detection range for this helper
*/
simulated function float GetMeleeRange()
{
return MaxHitRange;
}
/**
* Separate check for destructible objects, fixes issues with notifies/delays/etc. Only
* attack an object once per swing, etc.
*/
function bool MeleeAttackDestructibles()
{
}
/** Use the currently equipped weapon to perform a InstantFire() like line trace */
function DoWeaponInstantTrace(vector StartTrace, vector EndTrace, optional out ImpactInfo out_Impact)
{
if ( Instigator != None && Instigator.Weapon != None )
{
// @note: the return has not been processed for hit zones, but we're
// only using this for simplified world traces so it works.
out_Impact = Instigator.Weapon.CalcWeaponFire(StartTrace, EndTrace);
}
}
/*********************************************************************************************
* FOV (aka Area, Cone) Hit Detection
*********************************************************************************************/
/** Custom trace that excludes pawns, but not other actors. Useful for destructibles. */
native function Actor TraceNoPawns(out vector HitLocation, out vector HitNormal, vector TraceEnd, vector TraceStart);
/** Returns a victim used to apply melee damage to */
simulated function Pawn FindVictimByFOV(vector StartTrace, vector EndTrace, optional float Range=MaxHitRange, optional float FOVCosine=DefaultFOVCosine)
{
local Pawn P, BestVictim;
local float NewRating, BestRating;
BestRating = 0.f;
// @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, StartTrace, Range )
{
NewRating = RateMeleeVictim(P, StartTrace, EndTrace, Range, FOVCosine, BestRating);
if ( NewRating > BestRating )
{
BestVictim = P;
BestRating = NewRating;
}
}
if( bLogMelee )
{
DrawDebugCone(StartTrace, EndTrace - StartTrace, Range, Acos(FOVCosine), Acos(FOVCosine), 16, MakeColor(64,64,64,255), TRUE);
}
return BestVictim;
}
/** Returns a victim used to apply melee damage to */
simulated function float RateMeleeVictim(Pawn Victim, vector StartTrace, vector EndTrace, float Range, float FOVCosine, optional float RatingToBeat)
{
local float VictimRating;
local vector VictimLocation, DirToVictim, AimDir;
// First make sure Pawn is relevant (ie not dead)
if ( Victim == Instigator || Victim.bDeleteMe || Victim.bTearOff )
return -1.f;
// If we're player-controlled, filter out teammates
if( Instigator.IsHumanControlled() && Victim.GetTeamNum() == Instigator.GetTeamNum() )
{
return -1.f;
}
`log("Melee considering:"@Victim, bLogMelee);
// Discard if the z-distance is too great
VictimLocation = GetMeleeHitTestLocation(Victim);
if ( Abs(VictimLocation.Z - Instigator.Location.Z) > Instigator.CylinderComponent.CollisionHeight * 1.5 )
{
`log("rejected within vertical melee range", bLogMelee);
return -1.f;
}
// Add the victim's CollisionRadius - Gives better results (closer to CalcWeaponFire).
Range += Victim.CylinderComponent.CollisionRadius;
// Discard if the best victim is closer
VictimRating = (Range * Range) - VSizeSq2D(VictimLocation - StartTrace);
if ( VictimRating < RatingToBeat )
{
`log("rejected victim with lower rating", bLogMelee);
return -1.f;
}
// If our check FOV is a cone, then perform a simple dot product
if( FOVCosine > 0.f )
{
AimDir = Normal(EndTrace - StartTrace);
DirToVictim = Normal(VictimLocation - StartTrace);
// Make sure victim is in FOV
if( DirToVictim dot AimDir < FOVCosine )
{
`log("rejected:"@Victim@"dot:"@DirToVictim dot AimDir, bLogMelee);
return -1.f;
}
else
{
`log("accepted:"@Victim@"dot:"@DirToVictim dot AimDir, bLogMelee);
}
}
// Finally trace to make sure there are no obstructions. ie acquiring someone through a wall
if ( !FastTrace(VictimLocation, StartTrace) )
{
`log("rejected:"@Victim@"melee obstruction: ", bLogMelee);
return -1.f;
}
return VictimRating;
}
/**
* Returns location to use when testing if this pawn was hit by a melee attack. Useful to override, e.g., for
* small creatures close to the ground.
*/
simulated function vector GetMeleeHitTestLocation(Pawn P)
{
return P.Location;
}
/**
* Brute force method to find a valid ImpactList for this victim.
*/
simulated function bool TraceMeleeAttackHitZones(Pawn P, vector StartTrace, vector EndTrace, optional out ImpactInfo out_Impact, optional name BoneName)
{
local array<ImpactInfo> ImpactList;
// if we have a bone name (e.g zero-extent hitboxes) we can improve the results by using the bone loc
if ( BoneName != '' )
{
EndTrace = P.Mesh.GetBoneLocation(BoneName);
}
// First, trace once along the supplied vector to try and find a hit zone
if ( !TraceAllPhysicsAssetInteractions(P.Mesh, EndTrace, StartTrace, ImpactList, vect(0,0,0), true) )
{
// As a fallback trace to a generic location on the mesh
EndTrace = P.Mesh.GetBoneLocation(class'KFPawn'.default.TorsoBoneName);
if ( IsZero(EndTrace) )
{
EndTrace = P.Location;
}
TraceAllPhysicsAssetInteractions(P.Mesh, EndTrace, StartTrace, ImpactList, vect(0,0,0), true);
}
if ( ImpactList.Length > 0 )
{
out_Impact = ImpactList[0];
return true;
}
return false;
}
/*********************************************************************************************
* Hitbox Collision Detection
*********************************************************************************************/
/** Called from NotifyTick to trace along each hitbox */
native function TickHitboxCollisionDetection(SkeletalMeshComponent SkelComp, float DeltaTime);
/** Called from Notify to initialize hitbox settings */
native function BeginHitboxCollisionDetection(SkeletalMeshComponent SkelComp);
/** Called once to place hitboxes */
native function CreateHitboxChain(SkeletalMeshComponent SkelComp);
/** Swaps the hitboxes with a new hitboxchain array */
native function SetHitboxChain( const array<MeleeHitboxInfo> NewChain );
/** returns true if a new actor was successfully added */
//native function bool AddHitboxCollidingActor(Actor A);
native function bool HasCollidedWithHitbox(Actor A);
/** 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)
{
if( HitActor == None )
{
return;
}
// Convert Trace Information to ImpactInfo type.
Impact.HitActor = HitActor;
Impact.HitLocation = HitLocation;
Impact.HitNormal = HitNormal;
Impact.RayDir = Normal(EndTrace-StartTrace);
Impact.StartTrace = StartTrace;
Impact.HitInfo = HitInfo;
`log(GetFuncName()@HitActor@HitInfo.Item@HitInfo.BoneName, bLogMelee);
}
/** Called when a hitbox notify starts to allow script to perform a custom world trace */
event InitWorldTraceForHitboxCollision();
/*********************************************************************************************
* Impact FX
*********************************************************************************************/
/**
* Called by ProcessMeleeHit to spawn effects
* Network: Local Player and Server
*/
simulated function PlayMeleeHitEffects(Actor Target, vector HitLocation, vector HitDirection, optional bool bShakeInstigatorCamera=true)
{
local Pawn Victim;
local PlayerController PC;
local FracturedStaticMeshActor FracActor;
// Victim camera shake (Server-only - via RPC)
if( WorldInfo.NetMode != NM_Client )
{
// If we hit a pawn
Victim = Pawn( Target );
if( Victim != none )
{
// Shake victim (Server)
if( Victim != None && Victim.Controller != None )
{
PC = PlayerController(Victim.Controller);
if( PC != None )
{
PC.ClientPlayCameraShake(MeleeVictimCamShake, 1.f, true);
}
}
}
}
// Local client impacts
if( Instigator.IsHumanControlled() && Instigator.IsLocallyControlled() )
{
// Fracture meshes if we hit them
if ( bAllowMeleeToFracture )
{
FracActor = FracturedStaticMeshActor(Target);
if ( FracActor != None )
{
class'KFMeleeHelperBase'.static.MeleeFractureMeshImpact(FracActor, HitLocation, HitDirection);
return;
}
}
// Play world geometry impacts for local players
if ( !(Target.bCanBeDamaged && Target.IsA('Pawn')) )
{
`ImpactEffectManager.PlayImpactEffects(HitLocation, Instigator, HitDirection, WorldImpactEffects);
}
}
}
/** Takes some chunks out of a fracture mesh */
static function MeleeFractureMeshImpact( FracturedStaticMeshActor FracActor, vector HitLocation, vector HitNormal )
{
if( FracActor != none )
{
FracActor.BreakOffPartsInRadius( HitLocation - (HitNormal * 15.0), 35.0, 100.0, TRUE);
FracActor.SetLoseChunkReplacementMaterial();
}
}
defaultproperties
{
bAllowMeleeToFracture=true
Begin Object Class=CameraShake Name=MeleeImpactCamShake0
bSingleInstance=true
OscillationDuration=0.35f
RotOscillation={(Pitch=(Amplitude=250.f,Frequency=60.f),
Yaw=(Amplitude=150.f,Frequency=70.f),
Roll=(Amplitude=150.f,Frequency=100.f))}
End Object
MeleeVictimCamShake=MeleeImpactCamShake0
DefaultFOVCosine=0.6f
MaxHitRange=150
//HitboxExtent=(X=8,Y=8,Z=8)
HitboxSpacing=30
HitboxBoneName=RW_Damage
HitboxBoneAxis=AXIS_Z
bDoHitboxObstructionTrace=TRUE
//bDebugShowCollision=TRUE
MeleeImpactCamShake=CameraShake'FX_CameraShake_Arch.Melee.Default_Melee'
WorldImpactEffects=KFImpactEffectInfo'FX_Impacts_ARCH.Blunted_melee_impact'
}