//============================================================================= // 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, 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, 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 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 }