//============================================================================= // AICommand_Evade //============================================================================= // Handles starting/stopping evade special move. Can be used for a variety // of reasons, but main purpose is to evade away from grenades/projectiles. // // Use KFAIController.DoEvade() to start this action. // // Typically, this is what happens: // 1) Player's GetAdjustedAim() sets the player's ShotTarget to be the pawn // the player is aiming at. // 2) Projectile.PreBeginPlay() calls ReceiveProjectileWarning() for the // ShotTarget. // 3) KFAIController.ReceiveProjectileWarning() starts a brief timer which goes // on to start the evade command. It also warns other Zeds within a radius // of about 900 units, calling HandleProjectileWarning() for each notified // NPC. Conditions are taken into account prior to warning anothehr Zed, // like ensuring that they are somewhat facing the instigator's location. // //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class AICommand_Evade extends AICommand_SpecialMove within KFAIController; /** Direction in which to evade */ var byte EvadeDirection; /** Optional delay before starting the evade special move */ var float EvadeDelay; /** SpecialMoveFlags used to determine which animation to play */ var byte SMFlags; /** Determines whether to use a regular evade, or an "evade in fear" (different set of animations) */ var bool bFrightened; /** If true, NPC will turn to face the threat before starting the special moe */ var bool bTurnToThreat; /** There used to be a check when the special move completes, to see if another immediate evade is necessary. This code is currently commented out (see SpecialMoveFinished() below). RepeatDistSq was used with this. */ var float RepeatDistSq; /** If TRUE, AI will use the LookAt system to glance at what they're evading */ var bool bLookAtDangerInstigator; /** Location to look at if bLookAtDangerInstigator is TRUE */ var vector LookAtLocation; /** Previous value of MyKFPawn.bIsHeadTrackingActive */ var bool bOldHeadTrackingActive; /** Simple constructor that pushes a new instance of the command for the AI */ static function bool Evade( KFAIController AI, byte Direction, optional float InEvadeDelay, optional bool InFrightened, optional bool InTurnToThreat, optional vector InLookAtLocation ) { local AICommand_Evade Cmd; if( AI != None ) { Cmd = new(AI) class'AICommand_Evade'; if( Cmd != None ) { Cmd.EvadeDirection = Direction; Cmd.EvadeDelay = InEvadeDelay; Cmd.SMFlags = Direction; Cmd.bFrightened = InFrightened; Cmd.bTurnToThreat = InTurnToThreat; if( InLookAtLocation != vect(0,0,0) ) { Cmd.bLookAtDangerInstigator = true; Cmd.LookAtLocation = InLookAtLocation; } AI.PushCommand( Cmd ); return true; } } return false; } /** Build debug string */ event String GetDumpString() { return super.GetDumpString()@"Dir:"@EvadeDirection; } function Pushed() { AIActionStatus = "Evading!"; Super.Pushed(); AIZeroMovementVariables(); // Keep pawn's rotation as desired SetDesiredRotation( Pawn.Rotation ); SetFocalPoint( vect(0,0,0) ); Focus = none; /** Optionally pause in the 'Wait' state - use to hit reactions so a cluster of Zeds don't all attempt to evade at the same instant */ if( EvadeDelay > 0.f ) { GotoState( 'Wait' ); } else { GotoState( 'Command_SpecialMove' ); } } function Popped() { AIActionStatus = "Done Evading"; AIZeroMovementVariables(); if( MyKFPawn != none ) { MyKFPawn.StopLookingAtPawn(); } Focus = none; // Disable head tracking if( bLookAtDangerInstigator && MyKFPawn != none && MyKFPawn.bCanHeadTrack) { MyKFPawn.bIsHeadTrackingActive = bOldHeadTrackingActive; MyKFPawn.MyLookAtInfo.ForcedLookAtLocation = vect(0,0,0); } Super.Popped(); } function bool CanEvade() { return false; } state Wait { /** Look at what we're evading if bLookAtDangerInstigator=TRUE */ function BeginState( name PreviousStateName ) { if( bLookAtDangerInstigator && MyKFPawn.bCanHeadTrack ) { // Head tracking if( MyKFPawn.IK_Look_Head == none ) { MyKFPawn.IK_Look_Head = SkelControlLookAt( MyKFPawn.Mesh.FindSkelControl('HeadLook') ); } bOldHeadTrackingActive = MyKFPawn.bIsHeadTrackingActive; MyKFPawn.bIsHeadTrackingActive = true; MyKFPawn.MyLookAtInfo.LookAtPct = 1.f; MyKFPawn.MyLookAtInfo.BlendOut = 0.33f; MyKFPawn.MyLookAtInfo.BlendIn = fMax( EvadeDelay * 0.95f, 0.2f ); MyKFPawn.MyLookAtInfo.ForcedLookAtLocation = LookAtLocation; } } Begin: Sleep( EvadeDelay ); GotoState('Command_SpecialMove'); } state Command_SpecialMove { /** Determine whether to use a "fear" evade or a regular evade. */ function ESpecialMove GetSpecialMove() { /** Not every Zed has an evade-in-fear special move */ if( bFrightened && MyKFPawn.CanDoSpecialMove(SM_Evade_Fear) ) { return SM_Evade_Fear; } else { return SM_Evade; } } /** Pack the animflags, passing in EvadeDirection to get the appropriate animation sequence */ function byte GetSpecialMoveFlags( ESpecialMove InSpecialMove ) { return class'KFSM_Evade'.static.PackAnimFlag( EvadeDirection ); } /** Optionally don't begin the special move until rotation is complete */ function bool ShouldFinishRotation() { return bTurnToThreat; } /** Begin executing the special move */ function bool ExecuteSpecialMove() { SpecialMove = GetSpecialMove(); `AILog( GetFuncName()$"()"@SpecialMove, 'Command_SpecialMove' ); if( SpecialMove != SM_None && (!bShouldCheckSpecialMove || MyKFPawn.CanDoSpecialMove( SpecialMove )) ) { MyKFPawn.DoSpecialMove(SpecialMove, true, GetInteractionPawn(), GetSpecialMoveFlags(GetSpecialMove()) ); AIActionStatus = "SpecialMove: "$MyKFPawn.SpecialMoves[SpecialMove]; return true; } else { return false; } } function FinishedSpecialMove() { // local byte BestDir; // // if( EvadeFromActor != none && Projectile(EvadeFromActor) != none && VSizeSq(Pawn.Location - EvadeFromActor.Location) <= RepeatDistSq ) // { // BestDir = GetBestEvadeDir( EvadeFromActor.Location, Enemy ); // EvadeDirection = BestDir; // SMFlags = BestDir; // TimeOutDelaySeconds = default.TimeOutDelaySeconds; // GotoState( GetStateName(), 'Begin', true ); // } // else // { super.FinishedSpecialMove(); // } } } defaultproperties { bAllowedToAttack=false bShouldCheckSpecialMove=true RepeatDistSq=250000.f }