565 lines
16 KiB
Ucode
565 lines
16 KiB
Ucode
//=============================================================================
|
|
// AICommand
|
|
//=============================================================================
|
|
// Base class for AICommands (based on GOW2-GOW3)
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
//=============================================================================
|
|
|
|
class AICommand extends GameAICommand
|
|
within KFAIController
|
|
native(AI)
|
|
DependsOn(KFPawn)
|
|
dependson(KFAiBehaviorTypes);
|
|
|
|
/** Reference to the KFAIController using this command */
|
|
var KFAIController AIOwner;
|
|
/** When a command is paused, the new command is cached here until the original command resumes */
|
|
var AICommand CachedChildCommand;
|
|
/** Can the AI perform an attack when using this command? */
|
|
var bool bAllowedToAttack;
|
|
/** When command is active, AILog category from this command will be overridden to this name */
|
|
var string OverrideLogCategory;
|
|
/** When true, this command can interrupt the pause AICommand */
|
|
var bool bInterruptPauseCommand;
|
|
/** When true, this command can will disable the movement plugin on push and enable on popped*/
|
|
var bool bDisableMovementPluginOnPushed;
|
|
|
|
cpptext
|
|
{
|
|
virtual void TickCommand(FLOAT DeltaTime);
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Initialization
|
|
********************************************************************************************* */
|
|
|
|
/** Cache AIOwner */
|
|
function PrePushed( GameAIController AI )
|
|
{
|
|
AIOwner = Outer;
|
|
|
|
Super.PrePushed( AI );
|
|
}
|
|
|
|
function Pushed()
|
|
{
|
|
if( bUsePluginsForMovement && MovementPlugin != none && bDisableMovementPluginOnPushed )
|
|
{
|
|
MovementPlugin.DisablePlugin();
|
|
}
|
|
Super.Pushed();
|
|
}
|
|
|
|
/** Clear AIOwner */
|
|
function PostPopped()
|
|
{
|
|
AIOwner = none;
|
|
Super.PostPopped();
|
|
}
|
|
|
|
/** Command has been paused by another command */
|
|
function Paused(GameAICommand NewCommand)
|
|
{
|
|
`AILog( GetFuncName()$" ["$self$"] setting CachedChildCommand to "$NewCommand, 'Command_Base' );
|
|
CachedChildCommand = AICommand( NewCommand );
|
|
|
|
Super.Paused(NewCommand);
|
|
}
|
|
|
|
/** Command has resumed execution */
|
|
function Resumed( Name OldCommandName )
|
|
{
|
|
`AILog( GetFuncName()$" Command resumed, setting CachedChildCommand to none", 'Command_Base' );
|
|
CachedChildCommand = none;
|
|
|
|
Super.Resumed( OldCommandName );
|
|
}
|
|
|
|
/** Command is being popped off the stack */
|
|
function Popped()
|
|
{
|
|
if( bUsePluginsForMovement && MovementPlugin != none && bDisableMovementPluginOnPushed )
|
|
{
|
|
MovementPlugin.EnablePlugin();
|
|
}
|
|
|
|
if( MyKFPawn != none && MyKFPawn.IsAliveAndWell() )
|
|
{
|
|
UpdateCommandHistory();
|
|
`AILog( GetFuncName()$" Command Popped, setting CachedChildCommand to none", 'Command_Base' );
|
|
}
|
|
CachedChildCommand = none;
|
|
|
|
if( AIOwner != none )
|
|
{
|
|
AIOwner.NotifyCommandFinished( self );
|
|
}
|
|
|
|
Super.Popped();
|
|
}
|
|
|
|
/** Called from TickCommand and is used to call special moves and functions ontop of an AI Command */
|
|
event HandleAICommandSpecialAction();
|
|
|
|
/** Can this command be pushed by special moves? */
|
|
function bool AllowPushOfDefaultCommandForSpecialMove( ESpecialMove SM )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** Access the parent AICommand, if any exists */
|
|
function AICommandBase GetParentCommand()
|
|
{
|
|
local GameAICommand ParentCommand;
|
|
|
|
ParentCommand = CommandList;
|
|
while( ParentCommand != none && ParentCommand.ChildCommand != self )
|
|
{
|
|
ParentCommand = ParentCommand.ChildCommand;
|
|
}
|
|
|
|
if( ParentCommand != none )
|
|
{
|
|
return ParentCommand;
|
|
}
|
|
|
|
return none;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Notifications
|
|
* Important event notifications are often forwarded from KFAIController to the top level
|
|
* AICommand and on to any child commands.
|
|
********************************************************************************************* */
|
|
|
|
function bool NotifyHearNoise( float Loudness, Actor NoiseMaker, optional Name NoiseType )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() Loudness: "$Loudness$" NoiseMaker: "$NoiseMaker$" Type: "$NoiseType, 'HearNoise' );
|
|
return CachedChildCommand.NotifyHearNoise( Loudness, NoiseMaker, NoiseType );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool NotifyCombatBehaviorChange( name BehaviorName, bool bEnabled )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() BehaviorName: "$BehaviorName$" bEnabled: "$bEnabled );
|
|
return CachedChildCommand.NotifyCombatBehaviorChange( BehaviorName, bEnabled );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Allows AICommands to reject switching enemy to NewEnemy */
|
|
function bool CanChangeEnemy( Pawn NewEnemy )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
return CachedChildCommand.CanChangeEnemy( NewEnemy );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Notification from controller that enemy has changed. Sends OldEnemy to support movement goal re-evaluations. */
|
|
function NotifyEnemyChanged( optional Pawn OldEnemy )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
CachedChildCommand.NotifyEnemyChanged( OldEnemy );
|
|
}
|
|
}
|
|
|
|
/** Notification that a pending door is now open */
|
|
function NotifyDoorOpened()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'NotifyDoorOpened' );
|
|
CachedChildCommand.NotifyDoorOpened();
|
|
}
|
|
}
|
|
|
|
/** Notification from controller that enemy has become visible */
|
|
function bool NotifyPlayerBecameVisible( Pawn VisiblePlayer )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() Seen: "$VisiblePlayer$" notifying "$CachedChildCommand$" and letting it handle the event.", 'SeePlayer' );
|
|
return CachedChildCommand.NotifyPlayerBecameVisible( VisiblePlayer );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification from controller that enemy is no longer visible */
|
|
function bool NotifyEnemyNotVisible()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'EnemyNotVisible' );
|
|
return CachedChildCommand.NotifyEnemyNotVisible();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification from controller that my pawn's base has changed */
|
|
function bool NotifyBaseChange( actor NewBase, vector NewFloor )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
//`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'HitWall' );
|
|
if( CachedChildCommand.NotifyBaseChange( NewBase, NewFloor ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification from controller that my pawn has landed */
|
|
function bool NotifyLanded( vector HitNormal, actor FloorActor )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() HitNormal:"$HitNormal$" FloorActor:"$FloorActor$" notifying "$CachedChildCommand$" and letting it handle the event.", 'PathWarning' );
|
|
return CachedChildCommand.NotifyLanded( HitNormal, FloorActor );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification that my current enemy is surrounded by other NPCs */
|
|
function bool EnemyIsSurrounded()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'PathWarning' );
|
|
return CachedChildCommand.EnemyIsSurrounded();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification from controller that my pawn has collided with an obstruction (during a latent move) */
|
|
function bool NotifyHitWall( vector HitNormal, actor Wall )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'HitWall' );
|
|
return CachedChildCommand.NotifyHitWall( HitNormal, Wall );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification from controller that my pawn has collided with an obstruction while falling (during a latent move) */
|
|
function bool NotifyFallingHitWall( vector HitNormal, actor Wall )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'HitWall' );
|
|
return CachedChildCommand.NotifyFallingHitWall( HitNormal, Wall );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification that a nearby Husk has become suicidal */
|
|
function bool NotifyHuskSuicide( KFPawn_Monster Husk )
|
|
{
|
|
return false;
|
|
|
|
if( CachedAICommandList != none )
|
|
{
|
|
if( CachedAICommandList.NotifyHuskSuicide( Husk ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification that my pawn has bumped into something - usually another NPC */
|
|
function bool NotifyBump( actor Other, vector HitNormal )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'BumpEvent' );
|
|
if( CachedChildCommand.NotifyBump( Other, HitNormal ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool NotifyLatentPostPhysWalking()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'PostPhysWalking' );
|
|
if( CachedChildCommand.NotifyLatentPostPhysWalking() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool NotifyTouch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal)
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'TouchEvent' );
|
|
if( CachedChildCommand.NotifyTouch( Other, OtherComp, HitLocation, HitNormal ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool NotifyMovingOnToDestructibleEdge( KFDestructibleActor TheDestructiblePathObject, out KFNavMeshMovementStepData EdgeData, out Vector outMove2Point )
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'NotifyZedStuck' );
|
|
if( CachedChildCommand.NotifyMovingOnToDestructibleEdge( TheDestructiblePathObject, EdgeData, outMove2Point ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function bool NotifyNpcTerminallyStuck()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'NotifyNpcTerminallyStuck' );
|
|
if( CachedChildCommand.NotifyNpcTerminallyStuck() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool NotifyNpcInGrannyMode()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() notifying "$CachedChildCommand$" and letting it handle the event.", 'NotifyNpcInGrannyMode' );
|
|
if( CachedChildCommand.NotifyNpcInGrannyMode() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Ignore timer transitions to other commands? */
|
|
function bool ShouldIgnoreTimeTransitions()
|
|
{
|
|
local bool bShouldIgnore;
|
|
|
|
if(CachedChildCommand != none)
|
|
{
|
|
bShouldIgnore = CachedChildCommand.ShouldIgnoreTimeTransitions();
|
|
}
|
|
|
|
if( bShouldIgnore )
|
|
{
|
|
`AILog( GetFuncName()$"() returning TRUE - ignoring time transitions", 'CombatTransitions' );
|
|
}
|
|
return bShouldIgnore;
|
|
}
|
|
|
|
/** Checks to see if we're allowed to fire our weapon in this command, passing through to children until blocked. */
|
|
function bool IsAllowedToAttack()
|
|
{
|
|
local bool bCanAttack;
|
|
|
|
bCanAttack = ( bAllowedToAttack && (CachedChildCommand == None || CachedChildCommand.IsAllowedToAttack()) );
|
|
|
|
if( !bCanAttack )
|
|
{
|
|
`AILog( GetFuncName()$"() returning false because !bAllowedToAttack" );
|
|
}
|
|
|
|
return bCanAttack;
|
|
}
|
|
|
|
/** Checks if Pawn P exists and is still alive */
|
|
function bool IsPawnAlive( Pawn P )
|
|
{
|
|
if( P == none )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( P.Controller == none || !P.IsAliveAndWell() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Checks if Controller C exists, its pawn exists and is still alive */
|
|
function bool IsControllerAlive( Controller C )
|
|
{
|
|
if( C == none )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( C.Pawn == none || !C.Pawn.IsAliveAndWell() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Should the AI re-evaluate their current target and possibly choose a different one? */
|
|
function bool ShouldSelectTarget()
|
|
{
|
|
if( CachedChildCommand != None )
|
|
{
|
|
return CachedChildCommand.ShouldSelectTarget();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function AdjustEnemyRating(out float out_Rating, Pawn EnemyPawn)
|
|
{
|
|
if (CachedChildCommand != None)
|
|
{
|
|
CachedChildCommand.AdjustEnemyRating(out_Rating, EnemyPawn);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Pathfinding & Movement
|
|
********************************************************************************************* */
|
|
|
|
/** Called from native code during latent movement, gives NPC a chance to find direct path to goal */
|
|
function FindDirectPath()
|
|
{
|
|
`AILog( GetFuncName(), 'Move_DirectPath' );
|
|
|
|
if (CachedChildCommand != None)
|
|
{
|
|
`AILog( GetFuncName()$"(), [I am "$self$"] letting "$CachedChildCommand$" decide what to do about it.", 'Move_DirectPath' );
|
|
CachedChildCommand.FindDirectPath();
|
|
}
|
|
}
|
|
|
|
/** Called from native code during latent movement when current move is considered unreachable */
|
|
function bool MoveUnreachable( Vector AttemptedDest, Actor AttemptedTarget )
|
|
{
|
|
if( AttemptedTarget != none )
|
|
{
|
|
`AILog( GetFuncName()$" AttemptedTarget: "$AttemptedTarget, 'PathWarning' );
|
|
}
|
|
|
|
if( CachedChildCommand != None )
|
|
{
|
|
`AILog( GetFuncName()$"() letting "$CachedChildCommand$" decide what to do about it.", 'PathWarning' );
|
|
return CachedChildCommand.MoveUnreachable( AttemptedDest, AttemptedTarget );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Notification that NPC's path needs to be updated */
|
|
function NotifyNeedRepath()
|
|
{
|
|
if(CachedChildCommand != none)
|
|
{
|
|
`AILog( GetFuncName()$"() "$self$" called, letting "$CachedChildCommand$" decide what to do about it.", 'PathWarning' );
|
|
CachedChildCommand.NotifyNeedRepath();
|
|
}
|
|
else
|
|
{
|
|
`AILog( GetFuncName()$"() "$self$" called.", 'PathWarning' );
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* Debugging
|
|
********************************************************************************************* */
|
|
|
|
/** Update command history (debugging) */
|
|
function UpdateCommandHistory()
|
|
{
|
|
local int i;
|
|
|
|
if( Outer.Pawn != none )
|
|
{
|
|
for( i = 0; i < KFAICommandHistory.Length; i++ )
|
|
{
|
|
if( KFAICommandHistory[i].CmdName != "" && KFAICommandHistory[i].CmdName == string( Name ) )
|
|
{
|
|
if( Status == 'Aborted' )
|
|
{
|
|
KFAICommandHistory[i].bAborted = true;
|
|
}
|
|
if( Status == 'Failure')
|
|
{
|
|
KFAICommandHistory[i].bFailure = true;
|
|
}
|
|
if( Status == 'Success')
|
|
{
|
|
KFAICommandHistory[i].bSuccess = true;
|
|
}
|
|
|
|
UpdateHistoryString( "Status: "$Status );
|
|
//HistoryString = "Status: "$Status;
|
|
KFAICommandHistory[i].Duration = `TimeSince(KFAICommandHistory[i].TimeStamp);
|
|
KFAICommandHistory[i].VerboseString = HistoryString;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Update the command's HistoryString, which is output when DumpCommandHistory() is called */
|
|
function UpdateHistoryString( string AddString )
|
|
{
|
|
if( Outer.Pawn != none )
|
|
{
|
|
if( CommandHistoryNum > 0 )
|
|
{
|
|
HistoryString = HistoryString$" "$AddString;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Debug event to draw command-specific text to HUD */
|
|
simulated event DrawDebug( HUD H, Name Category )
|
|
{
|
|
if( ChildCommand != None )
|
|
{
|
|
ChildCommand.DrawDebug( H,Category );
|
|
}
|
|
}
|
|
|
|
/** Used when dumping command history to log file */
|
|
event string GetDebugVerboseText()
|
|
{
|
|
return HistoryString;
|
|
}
|
|
|
|
/** Is this AICommand permitted to interrupt an active AICommand_Pause? */
|
|
static function bool CanInterruptPauseCommand()
|
|
{
|
|
return default.bInterruptPauseCommand;
|
|
}
|
|
|
|
DefaultProperties
|
|
{
|
|
bAllowedToAttack=true
|
|
bInterruptPauseCommand=false
|
|
bDisableMovementPluginOnPushed=true
|
|
}
|