//============================================================================= // AICommand_SpecialMove //============================================================================= // Base AICommand for KFSpecialMove state-related AI handling (based on GOW2-3) //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC //============================================================================= class AICommand_SpecialMove extends AICommand within KFAIController native(AI); /** Special move this command is monitoring */ var ESpecialMove SpecialMove; /** Starting state override - default is 'Command_SpecialMove' */ var name DefaultStartState; /** Time after special move is complete that command should terminate */ var float TerminationTime; /** Whether we should check if the special move is valid (CanDoSpecialMove()) */ var bool bShouldCheckSpecialMove; /** Should update Pawn's anchor when starting a special move */ var bool bUpdateStartAnchor; /** Should update Pawn's anchor when popping after a successful special move */ var bool bUpdateAnchorOnSuccess; /** Time in seconds we should wait before considering this special move as timed out **/ var float TimeOutDelaySeconds; /** Time we should sleep if the special move fails */ var float FailureSleepTimeSeconds; /** Destination special move should end at */ var vector EndDest; /** Command will forcibly attempt to begin SpecialMove even if it's not considered to be ready after this duration */ var float FailSafeReadyTime; /** If true, command will attempt to begin the special move even if it's not considered to be ready */ var bool bForceReady; var int MaxExecuteSMAttemptsBeforeAbort; var int ExecuteSMCount; var bool bWaitForLanding; var object Observer; /********************************************************************************************* * Push/Pause/Pop ********************************************************************************************* */ function Pushed() { Super.Pushed(); `AILog( self$" Pushed", 'Command_SpecialMove' ); LockdownAI(); if( DefaultStartState != '' ) { GotoState( DefaultStartState ); } } function Resumed( Name OldCommandName ) { `AILog( self$" Resumed, previous command: "$OldCommandName, 'Command_SpecialMove' ); ExecuteSMCount = 0; Super.Resumed( OldCommandName ); LockdownAI(); } function Paused( GameAICommand NewCommand ) { `AILog( self$" Paused by command "$NewCommand, 'Command_SpecialMove' ); Super.Paused( NewCommand ); UnlockAI(); } function Popped() { local int Idx; local NavigationPoint NewAnchor; Super.Popped(); `AILog( "Popped()", 'Command_SpecialMove' ); UnlockAI(); ClearTimeout(); if( bUpdateAnchorOnSuccess ) { // If successful if( Status == 'Success' ) { NewAnchor = GetUpdatedAnchor(); if( NewAnchor != None ) { // Update pawn's anchor Pawn.SetAnchor( NewAnchor ); // Remove anything in the route cache up to our new movetarget Idx = RouteCache.Find( NewAnchor ); if( Idx != INDEX_NONE ) { RouteCache_RemoveIndex( 0, Idx + 1 ); } } } } } function ClearTimeout() { ClearTimer( nameof(self.SpecialMoveTimeout), self ); } /********************************************************************************************* * Pathfinding & Movement ********************************************************************************************* */ function NavigationPoint GetStartAnchor() { return None; } function NavigationPoint GetUpdatedAnchor() { return None; } function LockdownAI() { `AILog( GetFuncName(), 'Command_SpecialMove' ); bPreparingMove = true; // Don't move until move done AIZeroMovementVariables(); } function UnlockAI() { `AILog( GetFuncName(), 'Command_SpecialMove' ); // Turn off flags bPreparingMove = false; bPreciseDestination = false; if (Pawn!=none) { Pawn.LockDesiredRotation(false); } } /********************************************************************************************* * SpecialMove events ********************************************************************************************* */ /** The SpecialMove has timed out - handle specifics in child classes */ function SpecialMoveTimeout(); /** Return the number of seconds to wait before forcibly attempting to start the special move */ function float GetFailSafeReadyTime() { return FailSafeReadyTime; } /** If the SpecialMove hasn't been started after FailSafeReadyTime seconds, this timer forces the command to attempt to do the SpecialMove. */ function Timer_FailSafeReadyTriggered() { `AILog( self$" Failsafe triggered for special move action", 'Command_SpecialMove' ); bForceReady = true; } /********************************************************************************************* * Special Move & State ********************************************************************************************* */ state Command_SpecialMove `DEBUGSTATE { /** Overridden in child states */ function bool SetupSpecialMove() { return true; } function FinishedSpecialMove(); function ESpecialMove GetSpecialMove(); function float GetPostSpecialMoveSleepTime(); function float GetPreSpecialMoveSleepTime(); function bool IsReady() { return true; } function bool SetupMoveToEndDest(); function bool ShouldFinishRotation(); function bool ShouldFinishPostRotation(); function KFPawn GetInteractionPawn(); /** Optionally replace the Pawn's anchor navigation point */ event BeginState( Name PreviousStateName ) { if( bUpdateStartAnchor ) { Pawn.SetAnchor( GetStartAnchor() ); } } function byte GetSpecialMoveFlags( ESpecialMove InSpecialMove ) { if( MyKFPawn != none && MyKFPawn.CanDoSpecialMove(InSpecialMove) ) { return MyKFPawn.SpecialMoves[InSpecialMove].PackFlagsBase(MyKFPawn); } return 255; } /** Can this command be pushed by special moves? */ function bool AllowPushOfDefaultCommandForSpecialMove( ESpecialMove SM ) { // if we're handling this special move, then NO! return ( SM != GetSpecialMove() ); } /** Begin executing the special move */ function bool ExecuteSpecialMove() { SpecialMove = GetSpecialMove(); `AILog( GetFuncName()$"()"@SpecialMove, 'Command_SpecialMove' ); /** Warning! if bShouldCheckSpecialMove is false and InternalCanDoSpecialMove returns false, the special move will begin! This is old code, so I'm not changing it for the moment. @TODO: FIX! */ if( SpecialMove != SM_None && (!bShouldCheckSpecialMove || MyKFPawn.CanDoSpecialMove( SpecialMove )) ) { MyKFPawn.DoSpecialMove(SpecialMove, true, GetInteractionPawn(), GetSpecialMoveFlags(SpecialMove)); AIActionStatus = "SpecialMove: "$MyKFPawn.SpecialMoves[SpecialMove]; return true; } else { return false; } } /** Checks circumstances in which to consider special move completed */ function bool IsSpecialMoveComplete() { if( !bPreparingMove || MyKFPawn == None || MyKFPawn.SpecialMove == SM_None ) { return true; } return false; } /** Special move has timed out, fail the command and end it */ function SpecialMoveTimeout() { `AILog( GetFuncName()$" Special move timed out - failing and aborting", 'Command_SpecialMove' ); if( MyKFPawn.SpecialMove == SpecialMove ) { MyKFPawn.EndSpecialMove(); } UpdateHistoryString( "[F] SM TimedOut" ); Status = 'Failure'; AbortCommand( self ); } /** Notification that special move execution failed */ function OnFailedToDoSpecialMove() { `AILog( self$" Failed to do special move", 'Command_SpecialMove' ); UpdateHistoryString( "[F] SM Aborted" ); } Begin: `AILog( self$" --"@GetStateName()$":"$class@"-- BEGIN LABEL", 'Command_SpecialMove' ); if( bWaitForLanding && MyKFPawn.Physics == PHYS_Falling ) { /** Don't execute any more state code until I've landed */ WaitForLanding(); } if( !SetupSpecialMove() ) { `AILog( self$" Setup Special Move failed", 'Command_SpecialMove' ); Goto( 'Abort' ); } if( ShouldFinishRotation() && !Pawn.ReachedDesiredRotation() ) { /** Rotate to desired rotation (optional) and don't execute more state code until finished */ FinishRotation(); } /** Handle optional delay prior to starting special move and don't execute more state code until the delay is over */ if( GetPreSpecialMoveSleepTime() > 0 ) { Sleep( GetPreSpecialMoveSleepTime() ); } /** Optionally wait until IsReady() returns true (it does by default) */ bForceReady = false; SetTimer( GetFailSafeReadyTime(), false, nameof(Timer_FailSafeReadyTriggered), self ); while( !IsReady() && !bForceReady ) { `AILog( self$" Waiting for ready", 'Command_SpecialMove' ); Sleep(0.1f); } ClearTimer( nameof(Timer_FailSafeReadyTriggered), self ); /** Try to start the special move */ if( ExecuteSpecialMove() ) { /** Handle optional timeout in case the special move takes too long, gets stuck, etc. */ SetTimer( TimeOutDelaySeconds, false, nameof(self.SpecialMoveTimeOut), self ); /** Check to see if special move is finished every 0.1 seconds */ do { `AILog( self$" Waiting for SM to end TimeOutDelaySeconds: "$TimeoutDelaySeconds, 'Command_SpecialMove' ); Sleep( 0.1f ); } until( IsSpecialMoveComplete() ); /** Optionally wait until finished rotating after special move is done */ if( ShouldFinishPostRotation() ) { FinishRotation(); } /** Haven't used this, but not ruling it out - optional move after special move is done */ if( SetupMoveToEndDest() ) { /** Latent move to EndDest, won't execute more state code until move is complete/interrupted/fails */ MoveTo( EndDest ); } UpdateHistoryString( "SM Ended at "$WorldInfo.TimeSeconds ); FinishedSpecialMove(); /** Handle optional delay after special move is completed */ TerminationTime = WorldInfo.TimeSeconds + GetPostSpecialMoveSleepTime(); while( TerminationTime > WorldInfo.TimeSeconds ) { Sleep( 0.1f ); } /** Done! */ Status = 'Success'; } /** Handle optional retries after a failure */ else if( MaxExecuteSMAttemptsBeforeAbort > 0 && MaxExecuteSMAttemptsBeforeAbort >= ExecuteSMCount ) { FinishRotation(); Sleep( 0.1f ); ExecuteSMCount++; Goto( 'Begin' ); } else { Abort: `AILog( "Abort label", 'Command_SpecialMove' ); OnFailedToDoSpecialMove(); Status = 'Failure'; /** Handle optional delay after failure, won't execute more state code until delay is finished */ Sleep( FailureSleepTimeSeconds ); } /** Exit the command */ `AILog( "Calling PopCommand() at bottom of state code", 'Command_SpecialMove' ); PopCommand( self ); } defaultproperties { DefaultStartState=Command_SpecialMove bWaitForLanding=true bAllowedToAttack=false bIgnoreStepAside=true TimeOutDelaySeconds=10.0f FailureSleepTimeSeconds=0.5f MaxExecuteSMAttemptsBeforeAbort=5 FailSafeReadyTime=5.f }