//============================================================================= // KFSM_MeleeAttack //============================================================================= // Handles close-range melee attacks, and base class for all NPC melee attacks. // An attack is considered to be a melee attack if there are no projectiles // involved (i.e, just play an animation-based attack) //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class KFSM_MeleeAttack extends KFSM_PlaySingleAnim native(SpecialMoves); /** If Interruptible, is this attack affected by parry stumble */ var transient bool bCannotBeParried; static function byte PackFlagsBase(KFPawn P) { local byte StrikeFlags; local KFPawn_Monster KFPM; KFPM = KFPawn_Monster(P); if ( KFPM == none || P.MyKFAIC == none ) { return 255; } if( KFPM != none && KFPM.PawnAnimInfo != none ) { StrikeFlags = KFPM.PawnAnimInfo.GetStrikeFlags(P.MyKFAIC.PendingAnimStrikeIndex); if( StrikeFlags != 255 ) { //class'AICommand_Attack_Melee'.static.Melee( self, Enemy, StrikeFlags ); P.MyKFAIC.UpdatePendingStrike(); } } return StrikeFlags; } /** Unpack Special move flags */ function UnpackSpecialMoveFlags() { local byte AtkIndex, AtkVariant; AtkIndex = KFPOwner.SpecialMoveFlags & 15; AtkVariant = KFPOwner.SpecialMoveFlags >> 4; // setup next attack animation based on SpecialMoveFlags AnimName = KFPOwner.PawnAnimInfo.InitMeleeSpecialMove(self, AtkIndex, AtkVariant); // for now all non-RootMotion are also interruptible bCanBeInterrupted = (bCanBeInterrupted || !bUseRootMotion); } /** * Can a new special move override this one before it is finished? * This is only if CanDoSpecialMove() == TRUE && !bForce when starting it. */ function bool CanOverrideMoveWith( Name NewMove ) { if ( bCanBeInterrupted && (NewMove == 'KFSM_Stunned' || NewMove == 'KFSM_Stumble' || NewMove == 'KFSM_Knockdown' || NewMove == 'KFSM_Frozen') ) { return TRUE; // for NotifyAttackParried } return FALSE; } function SpecialMoveStarted( bool bForced, name PrevMove ) { local KFPawn_Monster KFPM; bUseRootMotion = false; bCanBeInterrupted = false; UnpackSpecialMoveFlags(); KFPM = KFPawn_Monster(KFPOwner); if ( KFPM != none && KFPM.bCanCloak && KFPM.bIsCloaking ) { KFPM.SetCloaked(false); } Super.SpecialMoveStarted( bForced, PrevMove ); } function SpecialMoveEnded(Name PrevMove, Name NextMove) { local KFPawn_Monster KFPM; PawnOwner.ClearTimer(nameof(InterruptCheckTimer), self); // prevent this anim from triggering melee impact notifies during AbortBlendOutTime if ( bCanBeInterrupted && KFPOwner.BodyStanceNodes[AnimStance].bIsPlayingCustomAnim ) { KFPOwner.BodyStanceNodes[AnimStance].SetNotifyWeightThreshold(0.8f); } KFPM = KFPawn_Monster(KFPOwner); if ( KFPM != none ) { if( KFPM.bCanCloak && KFPM.bCloakOnMeleeEnd && !KFPM.bIsCloaking ) { KFPM.SetCloaked(true); } KFPM.MeleeSpecialMoveEnded(); } Super.SpecialMoveEnded(PrevMove, NextMove); } /** Called from SpecialMoveStarted */ function PlayAnimation() { local float InterruptTime; if( AnimName == '' ) { `warn( KFPOwner$" "$GetFuncName()$" "$self$" attempting special move attack but the AttackAnims array is empty!" ); return; } `AILog_Ext( GetFuncName()$" "$self$" chose attack animation "$AnimName, 'Command_SpecialMove', AIOwner ); if( bCanBeInterrupted ) { InterruptTime = KFSkeletalMeshComponent(PawnOwner.Mesh).GetAnimInterruptTime(AnimName); PawnOwner.SetTimer(InterruptTime, false, nameof(InterruptCheckTimer), self); } PlaySpecialMoveAnim( AnimName, AnimStance, BlendInTime, BlendOutTime, KFPOwner.AttackSpeedModifier); } /** Notification from the pawn that a medium (aka gun) or heavy (aka melee) affliction has been activated */ function NotifyHitReactionInterrupt() { // only non-RM attacks can be aborted if ( bCanBeInterrupted ) { KFPOwner.EndSpecialMove(); // Reset melee timer after interrupt if( AIOwner != None ) { AIOwner.LastGetStrikeTime = -1; } } } /** Called by owning AI Command when AI behavior decides to stop this attack */ function AbortedByAICommand() { local KFPawn_Monster KFPMOwner; if ( bCanBeInterrupted ) { if( !KFPOwner.IsTimerActive( nameof(InterruptCheckTimer), self) ) { KFPMOwner = KFPawn_Monster(KFPOwner); KFPOwner.EndSpecialMove(); if( KFPMOwner != none ) { KFPMOwner.NotifyAnimInterrupt(); } if( AIOwner != None ) { // Reset melee timer after interrupt AIOwner.LastGetStrikeTime = -1; } } } } /** Called when it's safe to interrupt this animation */ function InterruptCheckTimer() { local AICommand_Attack_Melee MeleeCommand; local KFPawn_Monster KFPMOwner; if ( bCanBeInterrupted && AIOwner != None ) { MeleeCommand = AICommand_Attack_Melee(AIOwner.GetActiveCommand()); if( MeleeCommand != none && MeleeCommand.bAttackAborted ) { KFPMOwner = KFPawn_Monster(KFPOwner); if( KFPMOwner != none ) { KFPMOwner.NotifyAnimInterrupt(); } KFPOwner.EndSpecialMove(); // Reset melee timer after interrupt AIOwner.LastGetStrikeTime = -1; } } } /** Called when owning pawn has damage parried during this move */ function bool CanInterruptWithParry() { return !bCannotBeParried; } defaultproperties { BlendInTime=0.2f BlendOutTime=0.33f // --------------------------------------------- // Animations bUseRootMotion=false AbortBlendOutTime=0.5f // --------------------------------------------- // SpecialMove Handle=KFSM_MeleeAttack bDisableSteering=false bDisableMovement=true bUseHigherMeshSmoothingThreshold=true }