//============================================================================= // 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 }