/** * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class GameAICmd_Hover_MoveToGoal_Mesh extends GameAICommand; var transient Actor Find; var float Radius; var transient bool bWasFiring; var float DesiredHoverHeight; var transient float CurrentHoverHeight; var float SubGoalReachDist; /** how close to get to the enemy (only valid of bCompleteMove is TRUE) */ var transient float GoalDistance; /** current vector destination */ var transient vector IntermediatePoint; var transient vector LastMovePoint; var transient int NumMovePointFails; var int MaxMovePointFails; var transient Vector FallbackDest; var transient Actor MoveToActor; /** location of MoveToActor last time we did pathfinding */ var BasedPosition LastMoveTargetPathLocation; /** storage of initial desired move location */ var transient vector InitialFinalDestination; /** is this AI on 'final approach' ( i.e. moving directly to it's end-goal destination )*/ var bool bFinalApproach; /** TRUE when we are trying to get back on the mesh */ var bool bFallbackMoveToMesh; /** Simple constructor that pushes a new instance of the command for the AI */ static function bool HoverToGoal( GameAIController AI, Actor InGoal, float InGoalDistance, float InHoverHeight ) { local GameAICmd_Hover_MoveToGoal_Mesh Cmd; if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly) { Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh'; if( Cmd != None ) { Cmd.GoalDistance = InGoalDistance; Cmd.MoveToActor = InGoal; Cmd.InitialFinalDestination = InGoal.GetDestination(AI); Cmd.DesiredHoverHeight = InHoverHeight; Cmd.CurrentHoverHeight = InHoverHeight; AI.PushCommand( Cmd ); return TRUE; } } return FALSE; } static function bool HoverToPoint( GameAIController AI, vector InPoint, float InGoalDistance, float InHoverHeight ) { local GameAICmd_Hover_MoveToGoal_Mesh Cmd; if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly) { Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh'; if( Cmd != None ) { Cmd.GoalDistance = InGoalDistance; Cmd.MoveToActor = none; Cmd.InitialFinalDestination = InPoint; Cmd.DesiredHoverHeight = InHoverHeight; Cmd.CurrentHoverHeight = InHoverHeight; AI.PushCommand( Cmd ); return TRUE; } } return FALSE; } static function bool HoverBackToMesh( GameAIController AI ) { local GameAICmd_Hover_MoveToGoal_Mesh Cmd; if( AI != None && AI.Pawn != None && AI.Pawn.bCanFly) { Cmd = new(AI) class'GameAICmd_Hover_MoveToGoal_Mesh'; if( Cmd != None ) { Cmd.bFallbackMoveToMesh=true; AI.PushCommand( Cmd ); return TRUE; } } return FALSE; } function Pushed() { Super.Pushed(); if( bFallbackMoveToMesh ) { `AILog("Going into breadcrumb fallback state to get back onto navmesh CurLoc:"@Pawn.Location); GotoState('Fallback_Breadcrumbs'); return; } if( !NavigationHandle.ComputeValidFinalDestination(InitialFinalDestination) ) { `AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (ComputeValidFinalDestination returned FALSE)"); GotoState('DelayFailure'); } else if( !NavigationHandle.SetFinalDestination(InitialFinalDestination) ) { `AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (SetFinalDestination returned FALSE)"); GotoState('DelayFailure'); } else { GotoState('Moving'); } } function Popped() { Super.Popped(); // Check for any latent move actions ClearLatentAction( class'SeqAct_AIMoveToActor', Status != 'Success' ); // clear the route cache to make sure any intermediate claims are destroyed NavigationHandle.PathCache_Empty(); // explicitly clear velocity/accel as they aren't necessarily // cleared by the physics code if( Pawn != None ) { Pawn.ZeroMovementVariables(); Pawn.DestinationOffset = 0.f; } ReachedMoveGoal(); } function Tick( float DeltaTime ) { Super.Tick(DeltaTime); if( ShouldUpdateBreadCrumbs() ) { NavigationHandle.UpdateBreadCrumbs(Pawn.Location); } NavigationHandle.DrawBreadCrumbs(); } function bool HandlePathObstruction(Actor BlockedBy) { // ! intentionally does not pass on to children MoveTimer = -1.f; // kills latent moveto's GotoState('Fallback_Breadcrumbs'); return false; } state DelayFailure { function bool HandlePathObstruction(Actor BlockedBy); Begin: Sleep( 0.5f ); Status = 'Failure'; PopCommand( self ); } state MoveDown `DEBUGSTATE { // find a safe altitude to fly to function vector GetMoveDest() { local vector HitLocation; local vector HitNormal; local vector Dest; local actor HitActor; // find the poly we're above and try to fly to that if(NavigationHandle.LineCheck(Pawn.Location, Pawn.Location + vect(0.f,0.f,-4096.f),vect(5,5,5),HitLocation,HitNormal)) { // if we didn't hit the mesh for some reason, trace against geo HitActor = Trace(HitLocation,HitNormal,Pawn.Location + vect(0,0,-4096.f),Pawn.Location); if(HitActor == none) { `AILog(GetFuncName()@"Could not find surface to adjust height to!"); return Pawn.Location; } } Dest = HitLocation; Dest.Z += Pawn.GetCollisionHeight() * 1.5f; return Dest; } Begin: `AILog("Moving down!"); MoveTo(GetMoveDest()); Sleep(1.0f); GotoState('Moving'); }; function ReEvaluatePath() { /* `AILog(GetFuncName()@bReevaluatePath); if( HasReachedGoal() ) { Status = 'Success'; PopCommand(self); } else if( bReevaluatePath && Pawn != None && MovePointIsValid() ) { //debug `AILog( "Move continued... Goal"@MoveGoal@"Anchor"@Pawn.Anchor); NavigationHandle.PathCache_Empty(); `AILog(GetFuncName()@"disabling bReevaluatePath"); bReevaluatePath = FALSE; // Restart the movement state GotoState( 'Moving', 'Begin' ); }*/ } /** Check if AI has successfully reached the given goal */ function bool HasReachedGoal() { if( Pawn == None ) { return TRUE; } `AILog(GetFuncName()@bFinalApproach@MoveToActor); if(bFinalApproach && MoveToActor != None) { return Pawn.ReachedDestination(MoveToActor); } if( BP2Vect( NavigationHandle.FinalDestination ) != vect(0,0,0) ) { if( VSize(BP2Vect(NavigationHandle.FinalDestination)-Pawn.Location) < GoalDistance ) { return TRUE; } return Pawn.ReachedPoint( BP2Vect(NavigationHandle.FinalDestination), None ); } return FALSE; } function bool ShouldUpdateBreadCrumbs() { return true; } state Moving `DEBUGSTATE { final function float GetMoveDestinationOffset() { // return a negative destination offset so we get closer to our points (yay) if( bFinalApproach ) { return GoalDistance; } else { return (SubGoalReachDist - Pawn.GetCollisionRadius()); } } CheckMove: //debug `AILog("CHECKMOVE TAG"); if( HasReachedGoal() ) { Goto( 'ReachedGoal' ); } Begin: `AILog("BEGIN TAG"@GetStateName()); if( Enemy != none ) { Radius = Pawn.GetCollisionRadius() + Enemy.GetCollisionRadius(); } Radius = FMax(Radius, GoalDistance); NavigationHandle.SetFinalDestination(InitialFinalDestination); if( NavigationHandle.PointReachable(BP2Vect(NavigationHandle.FinalDestination)) ) { IntermediatePoint = BP2Vect(NavigationHandle.FinalDestination); } else { if( MoveToActor != None ) { // update final dest in case target moved if( !NavigationHandle.SetFinalDestination(MoveToActor.GetDestination(Outer)) ) { `AILog("ABORTING! Final destination"@InitialFinalDestination@"is not reachable! (SetFinalDestination returned FALSE)"); Goto('FailedMove'); } } if( !GeneratePathToLocation( BP2Vect(NavigationHandle.FinalDestination),GoalDistance, TRUE ) ) { //debug `AILog("Couldn't generate path to location"@BP2Vect(NavigationHandle.FinalDestination)@"from"@Pawn.Location); //`AILog("Retrying with mega debug on"); //NavigationHandle.bDebugConstraintsAndGoalEvals = TRUE; //NavigationHandle.bUltraVerbosePathDebugging = TRUE; //GeneratePathToLocation( BP2Vect(NavigationHandle.FinalDestination),GoalDistance, TRUE ); GotoState( 'Fallback_Breadcrumbs' ); } //debug `AILog( "Generated path..." ); `AILog( "Found path!" @ `showvar(BP2Vect(NavigationHandle.FinalDestination)), 'Move' ); if( !NavigationHandle.GetNextMoveLocation( IntermediatePoint, SubGoalReachDist ) ) { //debug `AILog("Generated path, but couldn't retrieve next move location?"); Goto( 'FailedMove' ); } } if( MoveToActor != None ) { Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(Outer)); } while( TRUE ) { //debug `AILog( "Still moving to"@IntermediatePoint, 'Loop' ); bFinalApproach = VSizeSq(IntermediatePoint - BP2Vect(NavigationHandle.FinalDestination)) < 1.0; `AILog("Calling MoveTo -- "@IntermediatePoint); // if on our final move to an Actor, send it in directly so we account for any movement it does if( bFinalApproach && MoveToActor != None ) { `AILog(" - Final approach to" @ MoveToActor $ ", using MoveToward()"); Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(outer)); NavigationHandle.SetFinalDestination(MoveToActor.GetDestination(outer)); MoveToward(MoveToActor, Enemy, GetMoveDestinationOffset(), FALSE); } else { // if we weren't given a focus, default to looking where we're going if( Enemy == None ) { SetFocalPoint(IntermediatePoint); } // use a negative offset so we get closer to our points! MoveTo(IntermediatePoint, Enemy, GetMoveDestinationOffset() ); } `AILog("MoveTo Finished -- "@IntermediatePoint); // if( bReevaluatePath ) // { // ReEvaluatePath(); // } if( HasReachedGoal() ) { Goto( 'CheckMove' ); } // if we are moving towards a moving target, repath every time we successfully reach the next node // as that Pawn's movement may cause the best path to change else if( MoveToActor != None && VSize(MoveToActor.GetDestination(Outer) - BP2Vect(LastMoveTargetPathLocation)) > 512.0 ) { Vect2BP(LastMoveTargetPathLocation, MoveToActor.GetDestination(outer)); `AILog("Repathing because target moved:" @ MoveToActor); Goto('CheckMove'); } else if( !NavigationHandle.GetNextMoveLocation( IntermediatePoint, SubGoalReachDist ) ) { `AILog( "Couldn't get next move location" ); if (!bFinalApproach && ((MoveToActor != None) ? /*NavigationHandle.*/ActorReachable(MoveToActor) : /*NavigationHandle.*/PointReachable(BP2Vect(NavigationHandle.FinalDestination)))) { `AILog("Target is directly reachable; try direct move"); IntermediatePoint =((MoveToActor != None) ? MoveToActor.GetDestination(outer) : BP2Vect(NavigationHandle.FinalDestination)); Sleep(RandRange(0.1,0.175)); } else { Sleep(0.1f); `AILog("GetNextMoveLocation returned false, and finaldest is not directly reachable"); Goto('FailedMove'); } } else { if(VSize(IntermediatePoint-LastMovePoint) < (Pawn.GetCollisionRadius() * 0.1f) ) { NumMovePointFails++; // DrawDebugBox(Pawn.Location, vect(2,2,2) * NumMovePointFails, 255, 255, 255, true ); `AILog("WARNING: Got same move location... something's wrong?!"@`showvar(LastMovePoint)@`showvar(IntermediatePoint)@"Delta"@VSize(LastMovePoint-IntermediatePoint)@"ChkDist"@(Pawn.GetCollisionRadius() * 0.1f)); } else { NumMovePointFails=0; } LastMovePoint = IntermediatePoint; if(NumMovePointFails >= MaxMovePointFails && MaxMovePointFails >= 0) { `AILog("ERROR: Got same move location 5x in a row.. something's wrong! bailing from this move"); Goto('FailedMove'); } else { //debug `AILog( "NextMove"@IntermediatePoint@`showvar(NumMovePointFails) ); } } } //debug `AILog( "Reached end of move loop??" ); Goto( 'CheckMove' ); FailedMove: `AILog( "Failed move. Now ZeroMovementVariables" ); MoveTo(Pawn.Location); Pawn.ZeroMovementVariables(); GotoState('DelayFailure'); ReachedGoal: //debug `AILog("Reached move point:"@BP2Vect(NavigationHandle.FinalDestination)@VSize(Pawn.Location-BP2Vect(NavigationHandle.FinalDestination))); Status = 'Success'; PopCommand( self ); } /** * this state will follow our breadcrumbs backward until we are back in the mesh, and then transition back to moving, or go to other fallback states * if we run out of breadcrumbs and are not yet back in the mesh */ state Fallback_Breadcrumbs `DEBUGSTATE { function bool ShouldUpdateBreadCrumbs() { return false; } function bool HandlePathObstruction(Actor BlockedBy) { Pawn.SetLocation(IntermediatePoint); MoveTimer=-1; GotoState('Fallback_Breadcrumbs','Begin'); return true; } Begin: `AILog("trying to move back along breadcrumb path"); if( NavigationHandle.GetNextBreadCrumb(IntermediatePoint) ) { `AILog("Moving to breadcrumb pos:"$IntermediatePoint); // GameAIOwner.DrawDebugLine(Pawn.Location,IntermediatePoint,255,0,0,TRUE); // GameAIOwner.DrawDebugLine(IntermediatePoint+vect(0,0,100),IntermediatePoint,255,0,0,TRUE); MoveToDirectNonPathPos(IntermediatePoint); if( !NavigationHandle.IsAnchorInescapable() ) { GotoState('Moving'); } Sleep(0.1); Goto('Begin'); } else if( !NavigationHandle.IsAnchorInescapable()) { GotoState('Moving','Begin'); } else { GotoState('Fallback_FindNearbyMeshPoint'); } } state Fallback_FindNearbyMeshPoint `DEBUGSTATE { function bool FindAPointWhereICanHoverTo( out Vector out_FallbackDest, float Inradius, optional float minRadius=0, optional float entityRadius = 32, optional bool bDirectOnly=true, optional int MaxPoints=-1,optional float ValidHitBoxSize) { local Vector Retval; local array poses; // local int i; local vector extent; local vector validhitbox; Extent.X = entityRadius; Extent.Y = entityRadius; Extent.Z = entityRadius; validhitbox = vect(1,1,1) * ValidHitBoxSize; NavigationHandle.GetValidPositionsForBox(Pawn.Location,Inradius,Extent,bDirectOnly,poses,MaxPoints,minRadius,validhitbox); // for(i=0;i 0) { Retval = Poses[Rand(Poses.Length)]; // if for some reason we have a 0,0,0 vect that is never going to be correct if( VSize(Retval) == 0.0f ) { out_FallbackDest = vect(0,0,0); return FALSE; } `AILog( `showvar(Retval) ); out_FallbackDest = Retval; return TRUE; } out_FallbackDest = vect(0,0,0); return FALSE; } function bool ShouldUpdateBreadCrumbs() { return false; } Begin: `AILog( "Fallback! We now try MoveTo directly to a point that is avail to us" ); if( !FindAPointWhereICanHoverTo( FallbackDest, 2048 ) ) { GotoState('MoveDown'); } else { MoveToDirectNonPathPos( FallbackDest,, SubGoalReachDist ); Sleep( 0.5f ); if( bFallbackMoveToMesh ) { GotoState('DelaySuccess'); } else { GotoState('Moving','Begin'); } } } /** Allows subclasses to determine if our enemy is based on an interp actor or not **/ function bool IsEnemyBasedOnInterpActor( Pawn InEnemy ) { return FALSE; } event DrawDebug( HUD H, Name Category ) { Super.DrawDebug( H, Category ); if(Category != 'Pathing') { return; } // BLUE DrawDebugLine( Pawn.Location, GetDestinationPosition(), 0, 0, 255 ); // GREEN DrawDebugLine( Pawn.Location, BP2Vect(NavigationHandle.FinalDestination), 0, 255, 0 ); NavigationHandle.DrawPathCache( vect(0,0,15) ); } defaultproperties { SubGoalReachDist=128.0 MaxMovePointFails=5 }