//============================================================================= // KFSpecialMoveHandler //============================================================================= // Manages starting and stopping special moves for pawns //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Andrew "Strago" Ladenberger //============================================================================= class KFSpecialMove extends GameSpecialMove native(SpecialMoves) abstract; /** Owner of this special move */ var KFPawn KFPOwner; var KFPlayerController PCOwner; var KFAIController AIOwner; var transient Object AISpecialOwner; var transient ESpecialMove SMIndex; /********************************************************************************************* * Animation *********************************************************************************************/ /** Cached reference to the current AnimNodeSlot used for cleanup */ var AnimNodeSlot ActiveSlotNode; /** If set, this special move will override the value for Pawn::MaxSpeedModifier() */ var bool bOverridePawnSpeedModifier; /** If set, hit reactions will still play while special move is active */ var bool bAllowHitReactions; /** If set, HandleMomentum is skipped when taking damage */ var bool bAllowMomentumPush; /** If set, never interrupt this move for a wander. Instead try to wander at the end of the move * NOTE: this variable could potentially be replaced with bDisableMovement. This is not currently * done in case it breaks other special moves */ var bool bCanOnlyWanderAtEnd; /** If set, allow weapon anims to be played for third-person pawns (weapon attachment) */ var bool bAllowThirdPersonWeaponAnims; /** If set, sets Pawn.RotationRate to CustomRotationRate for the duration of the move */ var bool bUseCustomRotationRate; var rotator CustomRotationRate; /** Used when deferring special move animations till post sync due to physics calls. */ var name DeferredSeqName; /** Default root motion mode to be used for this move */ var ERootMotionMode SMRootMotionMode; /** When TRUE, tells our physics smoothing code to use a higher threshold when in root motion. */ var bool bUseHigherMeshSmoothingThreshold; /********************************************************************************************* * Weapon firing *********************************************************************************************/ /** Weapon firing should cease/be disabled while this special move is being performed */ var bool bDisablesWeaponFiring; /** * For SpecialMove interactions. Only InteractionPawn allowed to damage me. * Note: this is reset to FALSE in SpecialMoveStarted. */ var bool bOnlyInteractionPawnCanDamageMe; /** Can change InteractionPawn before execution (InternalCanDoSpecialMove) */ var bool bCanModifyInteractionPawn; /** Can be used by input functions */ var bool bPendingStopFire; /** Flags used by button-activated special moves */ const FLAG_SpecialMoveButtonPressed = 253; const FLAG_SpecialMoveButtonReleased = 254; /** Sets a custom interpolated 3rd person view offset if we've got a third person camera */ var bool bUseCustomThirdPersonViewOffset; var ViewOffsetData CustomThirdPersonViewOffset; var float ViewOffsetInterpTime; var bool bRestoredCameraDefaults; /** Custom interpolated camera FOV */ var float CustomCameraFOV; var float CameraFOVTransitionTime; /********************************************************************************************* * GearSpecialMove *********************************************************************************************/ /** Flag to allow/disallow headtracking while in this special move. */ var const bool bDisableHeadTracking; /** AI should ignore notifications */ var const bool bDisableAI; /** Disable movement (but doesn't necessarily stop physics, just blocks inputs). */ var bool bDisableMovement; var bool bMovementDisabled; /** Lock Pawn Rotation, prevent it from being affects by Controller.Rotation */ var const bool bLockPawnRotation; var bool bPawnRotationLocked; var bool bDisableLook; /** Weapon goes down and can't shoot, be switched, can't quick heal, etc */ var bool bDisableWeaponInteraction; /** if > 0.0 after this many seconds the AI's command is popped and the special move is aborted */ var float AITimeout; var bool bDisableAIAttackRangeChecks; var bool bDisableSteering; /** If TRUE, disables turning in place animations & code. Pawn will always face his rotation. */ var const bool bDisableTurnInPlace; /** If set to anything above 0, scales the turn in place anim rate by its value */ var const float CustomTurnInPlaceAnimRate; var const bool bDisablePhysics; /** If TRUE, disables physics adjustments on the clients. Where drastic velocity changes are needed, this can help with rubberbanding */ var const bool bServerOnlyPhysics; var bool bAllowFireAnims; var bool bShouldDeferToPostTick; var AICommand_PushedBySM AICommand; /** Default AICommand to push on AI when SpecialMove starts */ var class DefaultAICommandClass; cpptext { /** Affect pawn owner movement speed */ virtual FLOAT GetMovementSpeedModifier() {return 1.f;} /** Using cpptext since GameSpecialMove's version is a final function. */ virtual void ForcePawnRotation( APawn* P, FRotator NewRotation,UBOOL bIgnoreHumanController=FALSE ); } /********************************************************************************************* * Functions *********************************************************************************************/ /** Uses traceflags identical to pawn moves, checks to see if the path is clear for pawns to move through */ native static function bool IsPawnPathClear( Actor TraceInstigator, Pawn TraceToPawn, vector TraceEnd, vector TraceStart, optional vector TraceExtent, optional bool bIgnorePawns, optional bool bTraceComplex ); /** Generic PackFlags to be implemented by subclasses. (see also PackSMFlags for advanced moves) */ static function byte PackFlagsBase(KFPawn P); /** Overridden to lock desired rotation (see GOW3) */ //native function ForcePawnRotation(Pawn P, Rotator NewRotation); function bool NotifyBump(Actor Other, vector HitNormal); /** Called when the interaction pawn is updated after the special move has started */ function InteractionPawnUpdated(); /** Called on some player-controlled moves when a firemode input has been pressed */ function SpecialMoveButtonRetriggered(); /** Called on some player-controlled moves when a firemode input has been released */ function SpecialMoveButtonReleased(); /** * Checks to see if this Special Move can be done. */ protected function bool InternalCanDoSpecialMove() { return (PawnOwner.Physics != PHYS_RigidBody); } function InitSpecialMove( Pawn InPawn, Name InHandle ) { PawnOwner = InPawn; KFPOwner = KFPawn(InPawn); if ( PawnOwner.Controller != none ) { PCOwner = KFPlayerController(PawnOwner.Controller); if( PCOwner == none ) { AIOwner = KFAIController(PawnOwner.Controller); } } bPendingStopFire = false; } function SpecialMoveStarted( bool bForced, Name PrevMove ) { local AICommand AIOwnerActiveCommand; local KFWeapon KFW; // Make sure we're taken out of ironsights if( PCOwner != none && KFPOwner.Weapon != none ) { KFW = KFWeapon( KFPOwner.Weapon ); if( KFW != none ) { KFW.SetIronSights( false ); } } SMIndex = KFPOwner.SpecialMove; // Push AICommand if it is defined. if( AIOwner != None ) { if( DefaultAICommandClass != None && AIOwner.MyKFPawn != None ) { AIOwnerActiveCommand == AICommand(AIOwner.GetActiveCommand()); if(( AIOwnerActiveCommand == none || AIOwnerActiveCommand.AllowPushOfDefaultCommandForSpecialMove(KFPOwner.SpecialMove))) { AICommand = DefaultAICommandClass.static.PushSpecialMoveCommand(AIOwner); } } AIOwner.NotifySpecialMoveStarted( self ); } if( PCOwner != None ) { if( bDisableLook ) { PCOwner.IgnoreLookInput(TRUE); } // Set camera offset if( bUseCustomThirdPersonViewOffset && PCOwner.PlayerCamera != none && PCOwner.PlayerCamera.CameraStyle == 'ThirdPerson' ) { KFThirdPersonCamera(KFPlayerCamera(PCOwner.PlayerCamera).ThirdPersonCam).SetViewOffset( CustomThirdPersonViewOffset, true, ViewOffsetInterpTime ); } // Set camera FOV if( CustomCameraFOV > 0.f ) { PCOwner.HandleTransitionFOV( CustomCameraFOV, CameraFOVTransitionTime ); } bRestoredCameraDefaults = false; } if( bDisableWeaponInteraction && KFPOwner.Weapon != none && KFWeapon(KFPOwner.Weapon) != none ) { KFWeapon(KFPOwner.Weapon).SetSimplePutDown(true); } if( bDisableTurnInPlace ) { KFPOwner.bDisableTurnInPlace = TRUE; } if( bDisableMovement ) { SetMovementLock(TRUE); } // If Pawn rotation should be locked, make sure it is. if( bLockPawnRotation ) { SetLockPawnRotation(TRUE); } if( bDisablePhysics ) { PawnOwner.ZeroMovementVariables(); if (PawnOwner.Role == ROLE_Authority || PawnOwner.IsLocallyControlled()) { PawnOwner.SetPhysics(PHYS_None); } } if( bUseCustomRotationRate ) { PawnOwner.RotationRate = CustomRotationRate; } if( CustomTurnInPlaceAnimRate > 0.f ) { KFPOwner.TurnInPlaceAnimRate = CustomTurnInPlaceAnimRate; } } function SpecialMoveEnded(Name PrevMove, Name NextMove) { // Clear pending stop fire flag bPendingStopFire = false; // Clear timers. PawnOwner.ClearTimer(nameof(AbortSpecialMove), Self); // AI set special move command. if( AIOwner != None && DefaultAICommandClass != None && AICommand != None ) { if( AIOwner.GetActiveCommand() == AICommand ) { AICommand.Status = 'Success'; AIOwner.PopCommand(AICommand); } // Clear reference. AICommand = None; } if( PCOwner != None ) { if( bDisableLook ) { PCOwner.IgnoreLookInput(FALSE); } RestoreCameraDefaults(); } if( KFPOwner != none ) { KFPOwner.NotifySpecialMoveEnded( self, SMIndex ); if( bUseCustomRotationRate ) { KFPOwner.RotationRate = KFPOwner.default.RotationRate; } if( KFPOwner.TurnInPlaceAnimRate != KFPOwner.default.TurnInPlaceAnimRate ) { KFPOwner.TurnInPlaceAnimRate = KFPOwner.default.TurnInPlaceAnimRate; } if( bDisableTurnInPlace ) { KFPOwner.bDisableTurnInPlace = KFPOwner.default.bDisableTurnInPlace; } if( bDisableWeaponInteraction && KFPOwner.Weapon != none && KFWeapon(KFPOwner.Weapon) != none ) { KFWeapon(KFPOwner.Weapon).SetSimplePutDown(false); } // If we are in a state that should have left us wandering, begin wandering at the end of this move if( bCanOnlyWanderAtEnd && KFPOwner != none ) { if( AIOwner != none && KFPOwner.IsHeadless() ) { AIOwner.DoHeadlessWander(); } else if(KFPOwner.ShouldBeWandering()) { KFPOwner.CausePanicWander(); } } } if ( bDisablePhysics && (PawnOwner.Role == ROLE_Authority || PawnOwner.IsLocallyControlled()) ) { PawnOwner.SetPhysics(PHYS_Falling); } // If movement was disabled, toggle it back on if( bMovementDisabled ) { SetMovementLock(FALSE); } if (AIOwner != none) { AIOwner.NotifySpecialMoveEnded(self); } AISpecialOwner = none; // If Pawn rotation was locked in default properties, unlock it. // Otherwise we're in manual mode, so let the special subclass handle everything. if( bPawnRotationLocked ) { SetLockPawnRotation(FALSE); } // Disable end of animation notification, so it doesn't interfere with future special moves // using that same animation channel. if ( ActiveSlotNode != None ) { ActiveSlotNode.SetActorAnimEndNotification( FALSE ); ActiveSlotNode = None; } } /** Restores camera to its previous values */ function RestoreCameraDefaults() { if( bRestoredCameraDefaults || PCOwner == none ) { return; } if( bUseCustomThirdPersonViewOffset && PCOwner.PlayerCamera != none && PCOwner.PlayerCamera.CameraStyle == 'ThirdPerson' ) { if( KFPawn_Monster(KFPOwner) != none ) { KFThirdPersonCamera(KFPlayerCamera(PCOwner.PlayerCamera).ThirdPersonCam).SetViewOffset( KFPawn_Monster(KFPOwner).ThirdPersonViewOffset, true, ViewOffsetInterpTime ); } } // Return camera fov to default if( CustomCameraFOV > 0.f ) { PCOwner.HandleTransitionFOV( PCOwner.DefaultFOV, CameraFOVTransitionTime ); } bRestoredCameraDefaults = true; } /** Locks or Unlocks Pawn movement */ final function SetMovementLock(bool bEnable) { if( bMovementDisabled != bEnable ) { bMovementDisabled = bEnable; if (PCOwner != None) { PCOwner.IgnoreMoveInput(bEnable); // we might be in between the input reading and PlayerMove(), so we need to zero the inputs // to make sure a stray tick of motion doesn't get in if (PCOwner.PlayerInput != None) { PCOwner.PlayerInput.aForward = 0.f; PCOwner.PlayerInput.aStrafe = 0.f; PCOwner.PlayerInput.aUp = 0.f; } } else if( AIOwner != none ) { AIOwner.bPreparingMove = bEnable; } // Set acceleration to zero if( bEnable ) { PawnOwner.Acceleration = Vect(0,0,0); } } } /** * Can be overridden to modify sprint speed when the move is running. Must be implemented in subclasses (see KFSM_Block)! * Network: SERVER */ function float GetSprintSpeedModifier() { return 1.0f; }; function EnableRootMotion() { // Turn on root motion on animation node KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneAxisOption( RBA_Translate, RBA_Translate, RBA_Translate ); // Turn on Root motion on mesh. KFPOwner.Mesh.RootMotionMode = SMRootMotionMode; } function DisableRootMotion() { // Restore default root motion mode KFPOwner.Mesh.RootMotionMode = PawnOwner.Mesh.default.RootMotionMode; // Turn off Root motion on animation node KFPOwner.BodyStanceNodes[EAS_FullBody].SetRootBoneAxisOption(RBA_Discard, RBA_Discard, RBA_Discard); } /** Overridden to restrict movement when on the ground */ function Tick( float DeltaTime ) { super.Tick( DeltaTime ); if( bMovementDisabled && KFPOwner != none && KFPOwner.Role == ROLE_Authority && KFPOwner.Physics == PHYS_Walking ) { KFPOwner.Velocity.X = 0.f; KFPOwner.Velocity.Y = 0.f; KFPOwner.Acceleration = vect(0,0,0); } } /** * Locks or UnLocks Pawn rotation. * When Pawn rotation is locked, Pawn will stop rotating to match the controller's Yaw. */ final function SetLockPawnRotation(bool bLock) { if( bPawnRotationLocked != bLock ) { bPawnRotationLocked = bLock; // If we were locked, then interpolate back to new (unlocked) rotation if( !bLock ) { //PawnOwner.InterpolatePawnRotation(); } } } /** Allows special moves to alter view rotation */ function ProcessViewRotation( float DeltaTime, out rotator out_ViewRotation, out Rotator out_DeltaRot ); /** Play a CameraAnim */ function CameraAnimInst PlayCameraAnim ( Pawn PawnToPlay, CameraAnim InCameraAnim, optional float Rate=1.f, optional float Scale=1.f, optional float BlendInTime, optional float BlendOutTime, optional bool bLoop, optional bool bRandomStartTime, optional float Duration, optional bool bSingleInstance, optional bool bSkipSpaceCheck ) { local KFPlayerController KFPC; KFPC = KFPlayerController(PawnToPlay.Controller); // Only play camera animations on local human players if (KFPC == None || !PawnToPlay.IsLocallyControlled() || KFPC.PlayerCamera == None || InCameraAnim == None) { return None; } if (!bSkipSpaceCheck && !KFPC.CameraAnimHasEnoughSpace(InCameraAnim, Scale)) { `log("PlayCameraAnim failed CameraAnimHasEnoughSpace:" @ KFPC.CameraAnimHasEnoughSpace(InCameraAnim, Scale)); return None; } if (PawnOwner != None && PawnOwner.WorldInfo.GRI.bMatchIsOver) { return None; } // Play camera animation return KFPC.PlayerCamera.PlayCameraAnim(InCameraAnim, Rate, Scale, BlendInTime, BlendOutTime, bLoop, bRandomStartTime, Duration, bSingleInstance); } /* Play a custom animation by name */ simulated function float PlaySpecialMoveAnim( name AnimName, EAnimSlotStance AnimStance, optional float BlendInTime=0.2f, optional float BlendOutTime=0.2f, optional float Rate=1.f, optional bool bLooping ) { local float Duration; `log("PlaySpecialMoveAnim '"$AnimName$"' on"@KFPOwner@" SM="$self, KFPOwner.bLogCustomAnim); Duration = KFPOwner.PlayBodyAnim(AnimName, AnimStance, Rate, BlendInTime, BlendOutTime, bLooping); // Make sure animation is valid (otherwise this move could break) if ( Duration <= 0 && KFPOwner.IsDoingSpecialMove() ) { // Failed to play animation, fall back to just setting a timer to exit the special move. `warn("Failed to play" @ AnimName @ "on special move" @ Self @ "on Pawn" @ PawnOwner); PawnOwner.SetTimer(0.25f, FALSE, nameof(AbortSpecialMove), Self); return 0; } // Enable OnAnimEnd notification (auto-reset by the slot node) if ( !bLooping ) { ActiveSlotNode = KFPOwner.BodyStanceNodes[AnimStance]; ActiveSlotNode.SetActorAnimEndNotification( TRUE ); } return Duration; } /** Timer used to exist gracefully if something has gone wrong */ function AbortSpecialMove() { KFPOwner.EndSpecialMove(); } /** * 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) { // By default end this special move. KFPOwner.EndSpecialMove(); } /** * This is a callback function that will be kicked off from KFPawn.uc OnAnimEnd. Some animations cause * physics to happen when they end so we defer certain end animations till post tick. */ function DeferOnAnimEnd() { if( KFPOwner.SpecialMoves[KFPOwner.SpecialMove] == self) { AnimEndNotify(None, 0, 0); } } /** Called from KFPawn::OnAnimNotifyParticleSystemSpawned() */ function OnAnimNotifyParticleSystemSpawned( const AnimNotify_PlayParticleEffect AnimNotifyData, ParticleSystemComponent PSC ); /********************************************************************************************* * Gameplay notications/hooks *********************************************************************************************/ /** Called by owning AI Command when AI behavior decides to stop this attack */ function AbortedByAICommand(); /** Notification from the pawn that damage was taken during move */ function NotifyOwnerTakeHit(class DamageType, vector HitLoc, vector HitDir, Controller InstigatedBy); /** Notification from the pawn that a medium (aka gun) or heavy (aka melee) affliction has been activated */ function NotifyHitReactionInterrupt(); /** Called when owning pawn has damage parried during this move */ function bool CanInterruptWithParry(); /** Called when owner swaps to gore mesh during this move */ function OnGoreMeshSwap(); /** Server notification that the pawn has been EMP disrupted */ function OnEMPDisrupted() { if( KFPOwner != none && KFPOwner.IsHumanControlled() ) { KFPOwner.EndSpecialMove(); } } /** Used for changing attack target (non claimable, switch to other enemy) */ event ModifyInteractionPawn(out KFPawn OtherPawn); /** Allows special moves to control the crosshair */ function bool CanDrawCrosshair() { return true; } /** Allows special move to alter aim rotation */ function bool GetSMAimRotation( out rotator AimRot ); defaultproperties { Handle=KFSpecialMove SMRootMotionMode=RMM_Accel bDisableSteering=true bDisableAIAttackRangeChecks=true bAllowHitReactions=false bCanOnlyWanderAtEnd=false bShouldDeferToPostTick=false bUseHigherMeshSmoothingThreshold=false CustomTurnInPlaceAnimRate=0.f // Camera view offset/FOV bUseCustomThirdPersonViewOffset=false CustomCameraFOV=0.f CameraFOVTransitionTime=1.f }