370 lines
11 KiB
Ucode
370 lines
11 KiB
Ucode
//=============================================================================
|
|
// 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
|
|
}
|