278 lines
9.4 KiB
Ucode
278 lines
9.4 KiB
Ucode
//=============================================================================
|
|
// KFSM_DeathAnim
|
|
//=============================================================================
|
|
// Plays a partial ragdoll anim after TearOff & PlayDying (Client-side only)
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// - Andrew "Strago" Ladenberger
|
|
//=============================================================================
|
|
|
|
class KFSM_DeathAnim extends KFSpecialMove;
|
|
|
|
/** Chaining death anims */
|
|
var float NextDeathAnim_ActorTime;
|
|
var byte NumChainedDeathAnims;
|
|
var byte MaxChainedDeathAnims;
|
|
|
|
/** motors flag to make sure it always get disabled */
|
|
var bool bHasDeathMotorsActive;
|
|
|
|
static function byte PackSMFlags(KFPawn P, vector HitDir)
|
|
{
|
|
// @todo
|
|
return 0;
|
|
}
|
|
|
|
/** Restrictions for doing Death Animation. */
|
|
protected function bool InternalCanDoSpecialMove()
|
|
{
|
|
if( PawnOwner.Physics == PHYS_RigidBody && KFPOwner != None
|
|
&& KFPOwner.PawnAnimInfo.bCanPlayDeathAnimations )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Play a death animation followed by rag doll physics.
|
|
* Return TRUE if successful, FALSE if couldn't find an animation and wants to go into ragdoll.
|
|
*/
|
|
function bool PlayDeathAnimation(class<DamageType> DamageType, vector HitDirection, optional name HitBoneName)
|
|
{
|
|
local name DeathAnim;
|
|
local float Duration;
|
|
|
|
DeathAnim = KFPOwner.PawnAnimInfo.ChooseDeathAnimation(KFPOwner, DamageType, HitDirection, HitBoneName);
|
|
|
|
if( DeathAnim != '' )
|
|
{
|
|
// Play one shot death animation
|
|
Duration = PlaySpecialMoveAnim(DeathAnim, EAS_FullBody, 0.2, -1.f);
|
|
}
|
|
|
|
// If animation failed to play, end special move now
|
|
if ( Duration <= 0.f )
|
|
{
|
|
// Do rag doll death instead.
|
|
KFPOwner.EndSpecialMove();
|
|
return FALSE;
|
|
}
|
|
|
|
// TRUE = blend to physics/motors. FALSE = plain animation, no physics used. Used for debugging.
|
|
if( TRUE )
|
|
{
|
|
StartDeathAnimMotors();
|
|
}
|
|
else
|
|
{
|
|
KFPOwner.EndSpecialMove();
|
|
}
|
|
|
|
`log("PlayDeathAnimation success DamageType:"$DamageType@"BoneName:"$HitBoneName, KFPOwner.bLogCustomAnim);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/** Calculate direction and pick death animation from anim group list */
|
|
function bool GetDeathAnimation(class<DamageType> DamageType, vector HitDirection, optional name HitBoneName)
|
|
{
|
|
// CalcOctagonRegion()
|
|
// Check HitBoneName
|
|
// Check High impact
|
|
}
|
|
|
|
/**
|
|
* Notification called when body stance animation finished playing.
|
|
* @param SeqNode - Node that finished playing. You can get to the SkeletalMeshComponent by looking at SeqNode->SkelComponent
|
|
* @param PlayedTime - Time played on this animation. (play rate independant).
|
|
* @param ExcessTime - how much time overlapped beyond end of animation. (play rate independant).
|
|
*/
|
|
function AnimEndNotify(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
|
|
{
|
|
StopDeathAnimMotors();
|
|
KFPOwner.EndSpecialMove();
|
|
}
|
|
|
|
/** Event called when Special Move is finished. */
|
|
function SpecialMoveEnded( Name PrevMove, Name NextMove )
|
|
{
|
|
// If move was ended by something else (aka AbortSpecialMove), cleanup motors
|
|
if ( bHasDeathMotorsActive )
|
|
{
|
|
StopDeathAnimMotors();
|
|
}
|
|
|
|
KFPOwner.StopAllAnimations();
|
|
|
|
`log("DeathAnimation finished ChainCount:"$NumChainedDeathAnims, KFPOwner.bLogCustomAnim);
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Internal
|
|
*********************************************************************************************/
|
|
|
|
function StartDeathAnimMotors()
|
|
{
|
|
// Turn on bone springs
|
|
//SetSpringForBone(PawnOwner.PelvisBoneName, TRUE);
|
|
//SetSpringForBone(PawnOwner.RightHandBoneName, TRUE);
|
|
//SetSpringForBone(PawnOwner.LeftHandBoneName, TRUE);
|
|
//SetSpringForBone(PawnOwner.LeftFootBoneName, TRUE);
|
|
//SetSpringForBone(PawnOwner.RightFootBoneName, TRUE);
|
|
|
|
// Disable animation tree SkelControls
|
|
PawnOwner.Mesh.bIgnoreControllers = 1;
|
|
|
|
// Set physics to be updated from Kinematic data
|
|
PawnOwner.Mesh.bUpdateJointsFromAnimation = TRUE;
|
|
PawnOwner.Mesh.bUpdateKinematicBonesFromAnimation = TRUE;
|
|
|
|
// Turn on motors
|
|
if ( PawnOwner.Mesh.PhysicsAssetInstance != None )
|
|
{
|
|
//PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularDriveParams(5000.f, 250.f, 0.f, PawnOwner.Mesh, TRUE);
|
|
PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularDriveParams(KFPOwner.PawnAnimInfo.DeathPhysMotorStrength.X, KFPOwner.PawnAnimInfo.DeathPhysMotorStrength.Y, 0.f, PawnOwner.Mesh, TRUE);
|
|
PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularPositionDrive(TRUE, TRUE, PawnOwner.Mesh, TRUE);
|
|
}
|
|
|
|
// If Pawn had broken constraints, then we need to make sure child bodies and joints are not turned back into kinematic state
|
|
if ( KFPOwner.bHasBrokenConstraints )
|
|
{
|
|
PawnOwner.Mesh.UpdateMeshForBrokenConstraints();
|
|
}
|
|
|
|
bHasDeathMotorsActive = true;
|
|
}
|
|
|
|
function StopDeathAnimMotors()
|
|
{
|
|
// Turn off bone springs
|
|
//SetSpringForBone(PawnOwner.PelvisBoneName, FALSE);
|
|
//SetSpringForBone(PawnOwner.RightHandBoneName, FALSE);
|
|
//SetSpringForBone(PawnOwner.LeftHandBoneName, FALSE);
|
|
//SetSpringForBone(PawnOwner.LeftFootBoneName, FALSE);
|
|
//SetSpringForBone(PawnOwner.RightFootBoneName, FALSE);
|
|
|
|
// Kinematic data can now be ignored (we're not using velocity motors)
|
|
PawnOwner.Mesh.bUpdateJointsFromAnimation = FALSE;
|
|
PawnOwner.Mesh.bUpdateKinematicBonesFromAnimation = FALSE;
|
|
|
|
if (PawnOwner.Mesh.PhysicsAssetInstance != None)
|
|
{
|
|
// Turn off position motors
|
|
PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularPositionDrive(FALSE, FALSE, PawnOwner.Mesh, TRUE);
|
|
|
|
// Turn on velocity motors, to add some joint friction
|
|
//PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularDriveParams(0.f, 0.f, 0.02f, PawnOwner.Mesh, TRUE);
|
|
//PawnOwner.Mesh.PhysicsAssetInstance.SetAllMotorsAngularVelocityDrive(TRUE, TRUE, PawnOwner.Mesh, TRUE);
|
|
}
|
|
|
|
bHasDeathMotorsActive = false;
|
|
}
|
|
|
|
/**
|
|
* Creates a spring between physics representation of bone and animated position.
|
|
* To have physics match animation.
|
|
*/
|
|
final function SetSpringForBone(Name InBoneName, bool bEnable)
|
|
{
|
|
local int BoneIndex;
|
|
local matrix BoneMatrix;
|
|
local RB_BodyInstance BoneBody;
|
|
|
|
// Filter through sockets and bones. Make sure we have a valid bone name to use.
|
|
InBoneName = PawnOwner.Mesh.GetSocketBoneName(InBoneName);
|
|
|
|
if( InBoneName == '' )
|
|
{
|
|
`Warn(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Has non existing Bone or Socket named:" @ InBoneName @ "Unable to create HipBodyInstance!");
|
|
return;
|
|
}
|
|
|
|
BoneIndex = PawnOwner.Mesh.MatchRefBone(InBoneName);
|
|
if( BoneIndex != INDEX_NONE )
|
|
{
|
|
BoneMatrix = PawnOwner.Mesh.GetBoneMatrix(BoneIndex);
|
|
BoneBody = PawnOwner.Mesh.FindBodyInstanceNamed(InBoneName);
|
|
|
|
if( BoneBody != None )
|
|
{
|
|
BoneBody.EnableBoneSpring(bEnable, bEnable, BoneMatrix);
|
|
if( bEnable )
|
|
{
|
|
BoneBody.OverextensionThreshold = 50.f;
|
|
BoneBody.SetBoneSpringParams(10000.f, 500.f, 10000.f, 500.f);
|
|
BoneBody.bDisableOnOverExtension = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`Warn(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "BodyInstance not found for BoneName:" @ InBoneName);
|
|
ScriptTrace();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
`Warn(PawnOwner.WorldInfo.TimeSeconds @ PawnOwner @ class @ GetFuncName() @ "Has non existing Index for BoneName:" @ InBoneName @ "Unable to create BodyInstance!");
|
|
}
|
|
}
|
|
|
|
/** Notification forwarded from RB_BodyInstance, when a spring is over extended and disabled. */
|
|
function OnRigidBodySpringOverextension(RB_BodyInstance BodyInstance)
|
|
{
|
|
local Name PelvisBoneName;
|
|
local RB_BodyInstance PelvisBodyInstance;
|
|
|
|
// Make sure we have correct bone name if this is a socket.
|
|
PelvisBoneName = PawnOwner.Mesh.GetSocketBoneName(KFPOwner.PelvisBoneName);
|
|
PelvisBodyInstance = PawnOwner.Mesh.FindBodyInstanceNamed(PelvisBoneName);
|
|
|
|
// If Pelvis spring was broken, we can't keep the other limbs (hands & legs) under spring influence, it would look too bad.
|
|
// So we just turn them off and force the Pawn into rag doll.
|
|
if( PelvisBodyInstance == BodyInstance )
|
|
{
|
|
StopDeathAnimMotors();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Death Anim Chanining
|
|
*********************************************************************************************/
|
|
|
|
/** Handle death animation chaining */
|
|
function NotifyOwnerTakeHit(class<KFDamageType> DamageType, vector HitLoc, vector HitDir, Controller InstigatedBy)
|
|
{
|
|
local TraceHitInfo HitInfo;
|
|
local name DeathAnim;
|
|
|
|
// Since the pawn is torn off, the chaining effect will only play for locally controlled players
|
|
if ( NumChainedDeathAnims < MaxChainedDeathAnims && PawnOwner.ActorTimeSince(NextDeathAnim_ActorTime) > 0 )
|
|
{
|
|
// don't double process the initial impact (possible on the server)
|
|
if ( NumChainedDeathAnims == 0 && PawnOwner.WorldInfo.TimeSeconds == KFPOwner.TimeOfDeath )
|
|
{
|
|
return;
|
|
}
|
|
|
|
KFPOwner.CheckHitInfo(HitInfo, KFPOwner.Mesh, HitDir, HitLoc);
|
|
|
|
DeathAnim = KFPOwner.PawnAnimInfo.ChooseDeathAnimation(KFPOwner, DamageType, HitDir, HitInfo.BoneName);
|
|
if( DeathAnim != '' )
|
|
{
|
|
PlaySpecialMoveAnim(DeathAnim, EAS_FullBody, 0.2, -1.f);
|
|
}
|
|
|
|
NumChainedDeathAnims++;
|
|
NextDeathAnim_ActorTime = PawnOwner.GetActorTimeSeconds() + RandRange(0.15f, 0.33f);
|
|
}
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
MaxChainedDeathAnims=5
|
|
bDisablesWeaponFiring=true
|
|
}
|