Files
UnrealEngine/Engine/Source/Runtime/AIModule/Private/AIController.cpp
2025-05-18 13:04:45 +08:00

1207 lines
37 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AIController.h"
#include "CollisionQueryParams.h"
#include "NavigationSystem.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/PhysicsVolume.h"
#include "Engine/Canvas.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "Tasks/AITask.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h"
#include "Perception/AIPerceptionComponent.h"
#include "VisualLogger/VisualLoggerTypes.h"
#include "VisualLogger/VisualLogger.h"
#include "GameplayTaskResource.h"
#include "AIResources.h"
#include "AIModuleLog.h"
#include "Kismet/GameplayStatics.h"
#include "DisplayDebugHelpers.h"
#include "BehaviorTree/BehaviorTree.h"
#include "GameplayTasksComponent.h"
#include "Tasks/GameplayTask_ClaimResource.h"
#include "NetworkingDistanceConstants.h"
#include "Navigation/PathFollowingComponent.h"
#include "NavFilters/NavigationQueryFilter.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AIController)
//----------------------------------------------------------------------//
// AAIController
//----------------------------------------------------------------------//
bool AAIController::bAIIgnorePlayers = false;
DECLARE_CYCLE_STAT(TEXT("MoveTo"), STAT_MoveTo, STATGROUP_AI);
DEFINE_LOG_CATEGORY(LogAINavigation);
AAIController::AAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bSetControlRotationFromPawnOrientation = true;
PathFollowingComponent = CreateOptionalDefaultSubobject<UPathFollowingComponent>(TEXT("PathFollowingComponent"));
if (PathFollowingComponent)
{
PathFollowingComponent->OnRequestFinished.AddUObject(this, &AAIController::OnMoveCompleted);
}
bSkipExtraLOSChecks = true;
bWantsPlayerState = false;
TeamID = FGenericTeamId::NoTeam;
bStartAILogicOnPossess = false;
bStopAILogicOnUnposses = true;
}
void AAIController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UpdateControlRotation(DeltaTime);
}
void AAIController::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (bWantsPlayerState && IsValidChecked(this) && (GetNetMode() != NM_Client))
{
InitPlayerState();
}
if (BrainComponent == nullptr)
{
BrainComponent = FindComponentByClass<UBrainComponent>();
}
if (Blackboard == nullptr)
{
Blackboard = FindComponentByClass<UBlackboardComponent>();
}
#if ENABLE_VISUAL_LOG
for (UActorComponent* Component : GetComponents())
{
if (Component)
{
REDIRECT_OBJECT_TO_VLOG(Component, this);
}
}
#endif // ENABLE_VISUAL_LOG
}
void AAIController::PostRegisterAllComponents()
{
Super::PostRegisterAllComponents();
// cache PerceptionComponent if not already set
// note that it's possible for an AI to not have a perception component at all
if (!IsValid(PerceptionComponent))
{
PerceptionComponent = FindComponentByClass<UAIPerceptionComponent>();
}
}
void AAIController::Reset()
{
Super::Reset();
if (PathFollowingComponent)
{
PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::OwnerFinished | FPathFollowingResultFlags::ForcedScript);
}
}
void AAIController::DisplayDebug(UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplay, float& YL, float& YPos)
{
Super::DisplayDebug(Canvas, DebugDisplay, YL, YPos);
if (DebugDisplay.IsDisplayOn(NAME_AI))
{
if (PathFollowingComponent)
{
PathFollowingComponent->DisplayDebug(Canvas, DebugDisplay, YL, YPos);
}
AActor* FocusActor = GetFocusActor();
if (FocusActor)
{
FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager;
DisplayDebugManager.DrawString(FString::Printf(TEXT(" Focus %s"), *FocusActor->GetName()));
}
}
}
#if ENABLE_VISUAL_LOG
void AAIController::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
FVisualLogStatusCategory MyCategory;
MyCategory.Category = TEXT("AI Controller");
MyCategory.Add(TEXT("Pawn"), GetNameSafe(GetPawn()));
AActor* FocusActor = GetFocusActor();
MyCategory.Add(TEXT("Focus"), GetDebugName(FocusActor));
if (FocusActor == nullptr)
{
MyCategory.Add(TEXT("Focus Location"), TEXT_AI_LOCATION(GetFocalPoint()));
}
Snapshot->Status.Add(MyCategory);
if (GetPawn())
{
Snapshot->Location = GetPawn()->GetActorLocation();
}
if (PathFollowingComponent)
{
PathFollowingComponent->DescribeSelfToVisLog(Snapshot);
}
if (BrainComponent != nullptr)
{
BrainComponent->DescribeSelfToVisLog(Snapshot);
}
if (PerceptionComponent != nullptr)
{
PerceptionComponent->DescribeSelfToVisLog(Snapshot);
}
}
#endif // ENABLE_VISUAL_LOG
void AAIController::SetFocalPoint(FVector NewFocus, EAIFocusPriority::Type InPriority)
{
// clear out existing
ClearFocus(InPriority);
// now set new focus
if (InPriority >= FocusInformation.Priorities.Num())
{
FocusInformation.Priorities.SetNum(InPriority + 1);
}
FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[InPriority];
FocusItem.Position = NewFocus;
}
FVector AAIController::GetFocalPointForPriority(EAIFocusPriority::Type InPriority) const
{
FVector Result = FAISystem::InvalidLocation;
if (InPriority < FocusInformation.Priorities.Num())
{
const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[InPriority];
AActor* FocusActor = FocusItem.Actor.Get();
if (FocusActor)
{
Result = GetFocalPointOnActor(FocusActor);
}
else
{
Result = FocusItem.Position;
}
}
return Result;
}
FVector AAIController::GetFocalPoint() const
{
FVector Result = FAISystem::InvalidLocation;
// find focus with highest priority
for (int32 Index = FocusInformation.Priorities.Num() - 1; Index >= 0; --Index)
{
const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[Index];
AActor* FocusActor = FocusItem.Actor.Get();
if (FocusActor)
{
Result = GetFocalPointOnActor(FocusActor);
break;
}
else if (FAISystem::IsValidLocation(FocusItem.Position))
{
Result = FocusItem.Position;
break;
}
}
return Result;
}
AActor* AAIController::GetFocusActor() const
{
AActor* FocusActor = nullptr;
for (int32 Index = FocusInformation.Priorities.Num() - 1; Index >= 0; --Index)
{
const FFocusKnowledge::FFocusItem& FocusItem = FocusInformation.Priorities[Index];
FocusActor = FocusItem.Actor.Get();
if (FocusActor)
{
break;
}
else if (FAISystem::IsValidLocation(FocusItem.Position))
{
break;
}
}
return FocusActor;
}
FVector AAIController::GetFocalPointOnActor(const AActor *Actor) const
{
return Actor != nullptr ? Actor->GetActorLocation() : FAISystem::InvalidLocation;
}
void AAIController::K2_SetFocus(AActor* NewFocus)
{
SetFocus(NewFocus, EAIFocusPriority::Gameplay);
}
void AAIController::K2_SetFocalPoint(FVector NewFocus)
{
SetFocalPoint(NewFocus, EAIFocusPriority::Gameplay);
}
void AAIController::K2_ClearFocus()
{
ClearFocus(EAIFocusPriority::Gameplay);
}
void AAIController::SetFocus(AActor* NewFocus, EAIFocusPriority::Type InPriority)
{
// clear out existing
ClearFocus(InPriority);
// now set new
if (NewFocus)
{
if (InPriority >= FocusInformation.Priorities.Num())
{
FocusInformation.Priorities.SetNum(InPriority + 1);
}
FocusInformation.Priorities[InPriority].Actor = NewFocus;
}
}
void AAIController::ClearFocus(EAIFocusPriority::Type InPriority)
{
if (InPriority < FocusInformation.Priorities.Num())
{
FocusInformation.Priorities[InPriority].Actor = nullptr;
FocusInformation.Priorities[InPriority].Position = FAISystem::InvalidLocation;
}
}
void AAIController::SetPerceptionComponent(UAIPerceptionComponent& InPerceptionComponent)
{
if (PerceptionComponent != nullptr)
{
UE_VLOG(this, LogAIPerception, Warning, TEXT("Setting perception component while AIController already has one!"));
}
PerceptionComponent = &InPerceptionComponent;
}
bool AAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const
{
if (Other == nullptr)
{
return false;
}
if (ViewPoint.IsZero())
{
FRotator ViewRotation;
GetActorEyesViewPoint(ViewPoint, ViewRotation);
// if we still don't have a view point we simply fail
if (ViewPoint.IsZero())
{
return false;
}
}
FVector TargetLocation = Other->GetTargetLocation(GetPawn());
FCollisionQueryParams CollisionParams(SCENE_QUERY_STAT(LineOfSight), true, this->GetPawn());
CollisionParams.AddIgnoredActor(Other);
bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams);
if (!bHit)
{
return true;
}
// if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI)
// then don't go any further as it likely will not be tracing to the correct location
const APawn * OtherPawn = Cast<const APawn>(Other);
if (!OtherPawn && Cast<UCapsuleComponent>(Other->GetRootComponent()) == NULL)
{
return false;
}
const FVector OtherActorLocation = Other->GetActorLocation();
const FVector::FReal DistSq = (OtherActorLocation - ViewPoint).SizeSquared();
if (DistSq > FARSIGHTTHRESHOLDSQUARED)
{
return false;
}
if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED))
{
return false;
}
float OtherRadius, OtherHeight;
Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight);
if (!bAlternateChecks || !bLOSflag)
{
//try viewpoint to head
bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams);
if (!bHit)
{
return true;
}
}
if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag))
{
// only check sides if width of other is significant compared to distance
if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f)
{
return false;
}
//try checking sides - look at dist to four side points, and cull furthest and closest
FVector Points[4];
Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0);
Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0);
Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0);
Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0);
int32 IndexMin = 0;
int32 IndexMax = 0;
FVector::FReal CurrentMax = (Points[0] - ViewPoint).SizeSquared();
FVector::FReal CurrentMin = CurrentMax;
for (int32 PointIndex = 1; PointIndex<4; PointIndex++)
{
const FVector::FReal NextSize = (Points[PointIndex] - ViewPoint).SizeSquared();
if (NextSize > CurrentMin)
{
CurrentMin = NextSize;
IndexMax = PointIndex;
}
else if (NextSize < CurrentMax)
{
CurrentMax = NextSize;
IndexMin = PointIndex;
}
}
for (int32 PointIndex = 0; PointIndex<4; PointIndex++)
{
if ((PointIndex != IndexMin) && (PointIndex != IndexMax))
{
bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams);
if (!bHit)
{
return true;
}
}
}
}
return false;
}
void AAIController::ActorsPerceptionUpdated(const TArray<AActor*>& UpdatedActors)
{
}
DEFINE_LOG_CATEGORY_STATIC(LogTestAI, All, All);
void AAIController::UpdateControlRotation(float DeltaTime, bool bUpdatePawn)
{
APawn* const MyPawn = GetPawn();
if (MyPawn)
{
FRotator NewControlRotation = GetControlRotation();
// Look toward focus
const FVector FocalPoint = GetFocalPoint();
if (FAISystem::IsValidLocation(FocalPoint))
{
NewControlRotation = (FocalPoint - MyPawn->GetPawnViewLocation()).Rotation();
}
else if (bSetControlRotationFromPawnOrientation)
{
NewControlRotation = MyPawn->GetActorRotation();
}
// Don't pitch view unless looking at another pawn
if (NewControlRotation.Pitch != 0 && Cast<APawn>(GetFocusActor()) == nullptr)
{
NewControlRotation.Pitch = 0.f;
}
SetControlRotation(NewControlRotation);
if (bUpdatePawn)
{
const FRotator CurrentPawnRotation = MyPawn->GetActorRotation();
if (CurrentPawnRotation.Equals(NewControlRotation, 1e-3f) == false)
{
MyPawn->FaceRotation(NewControlRotation, DeltaTime);
}
}
}
}
void AAIController::OnPossess(APawn* InPawn)
{
// don't even try possessing pending-kill pawns
if (InPawn != nullptr && !IsValid(InPawn))
{
return;
}
Super::OnPossess(InPawn);
if (GetPawn() == nullptr || InPawn == nullptr)
{
return;
}
// not calling UpdateNavigationComponents() anymore. The PathFollowingComponent
// is now observing newly possessed pawns (via OnNewPawn)
if (PathFollowingComponent)
{
PathFollowingComponent->Initialize();
}
if (bWantsPlayerState)
{
ChangeState(NAME_Playing);
}
// a Pawn controlled by AI _requires_ a GameplayTasksComponent, so if Pawn
// doesn't have one we need to create it
if (CachedGameplayTasksComponent == nullptr)
{
UGameplayTasksComponent* GTComp = InPawn->FindComponentByClass<UGameplayTasksComponent>();
if (GTComp == nullptr)
{
GTComp = NewObject<UGameplayTasksComponent>(InPawn, TEXT("GameplayTasksComponent"));
GTComp->RegisterComponent();
}
CachedGameplayTasksComponent = GTComp;
}
if (CachedGameplayTasksComponent && !CachedGameplayTasksComponent->OnClaimedResourcesChange.Contains(this, GET_FUNCTION_NAME_CHECKED(AAIController, OnGameplayTaskResourcesClaimed)))
{
CachedGameplayTasksComponent->OnClaimedResourcesChange.AddDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed);
}
if (Blackboard && Blackboard->GetBlackboardAsset())
{
InitializeBlackboard(*Blackboard, *Blackboard->GetBlackboardAsset());
}
if (bStartAILogicOnPossess && BrainComponent)
{
BrainComponent->StartLogic();
}
}
void AAIController::OnUnPossess()
{
APawn* CurrentPawn = GetPawn();
Super::OnUnPossess();
if (PathFollowingComponent)
{
PathFollowingComponent->Cleanup();
}
const bool bGameplayTaskCleanupRequired = (CachedGameplayTasksComponent && (CachedGameplayTasksComponent->GetOwner() == CurrentPawn));
// End all tasks owned by the controller before cleaning up the BrainComponent
// since it might need to update its resource lock (see OnGameplayTaskResourcesClaimed)
if (bGameplayTaskCleanupRequired)
{
CachedGameplayTasksComponent->EndAllResourceConsumingTasksOwnedBy(*this);
}
if (bStopAILogicOnUnposses)
{
CleanupBrainComponent();
}
// Can now unregister callback and clear the cache
if (bGameplayTaskCleanupRequired)
{
CachedGameplayTasksComponent->OnClaimedResourcesChange.RemoveDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed);
CachedGameplayTasksComponent = nullptr;
}
}
void AAIController::SetPawn(APawn* InPawn)
{
Super::SetPawn(InPawn);
if (Blackboard)
{
const UBlackboardData* BBAsset = Blackboard->GetBlackboardAsset();
if (BBAsset)
{
const FBlackboard::FKey SelfKey = BBAsset->GetKeyID(FBlackboard::KeySelf);
if (SelfKey != FBlackboard::InvalidKey)
{
Blackboard->SetValue<UBlackboardKeyType_Object>(SelfKey, GetPawn());
}
}
}
}
EPathFollowingRequestResult::Type AAIController::MoveToActor(AActor* Goal, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass, bool bAllowPartialPaths)
{
// abort active movement to keep only one request running
if (PathFollowingComponent && PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle)
{
PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest
, FAIRequestID::CurrentRequest, EPathFollowingVelocityMode::Keep);
}
FAIMoveRequest MoveReq(Goal);
MoveReq.SetUsePathfinding(bUsePathfinding);
MoveReq.SetAllowPartialPath(bAllowPartialPaths);
MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass);
MoveReq.SetAcceptanceRadius(AcceptanceRadius);
MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap);
MoveReq.SetCanStrafe(bCanStrafe);
return MoveTo(MoveReq);
}
EPathFollowingRequestResult::Type AAIController::MoveToLocation(const FVector& Dest, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass, bool bAllowPartialPaths)
{
// abort active movement to keep only one request running
if (PathFollowingComponent && PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle)
{
PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest
, FAIRequestID::CurrentRequest, EPathFollowingVelocityMode::Keep);
}
FAIMoveRequest MoveReq(Dest);
MoveReq.SetUsePathfinding(bUsePathfinding);
MoveReq.SetAllowPartialPath(bAllowPartialPaths);
MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation);
MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass);
MoveReq.SetAcceptanceRadius(AcceptanceRadius);
MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap);
MoveReq.SetCanStrafe(bCanStrafe);
return MoveTo(MoveReq);
}
void AAIController::MergePaths(const FNavPathSharedPtr& InitialPath, FNavPathSharedPtr& InOutMergedPath) const
{
if (!InitialPath.IsValid() || !InitialPath->IsValid())
{
UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: InitialPath is Invalid"), __FUNCTION__);
return;
}
if (!InOutMergedPath.IsValid() || !InOutMergedPath->IsValid())
{
UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: InOutMergedPath is Invalid"), __FUNCTION__);
return;
}
const TArray<FNavPathPoint>& InitialPathPoints = InitialPath->GetPathPoints();
TArray<FNavPathPoint>& InOutPathPoints = InOutMergedPath->GetPathPoints();
if (!InitialPathPoints.Last().Location.Equals(InOutPathPoints[0].Location))
{
UE_VLOG_UELOG(this, LogAINavigation, Error, TEXT("%hs: last %s and first %s points don't match."), __FUNCTION__, *InitialPathPoints.Last().Location.ToString(), *InOutPathPoints[0].Location.ToString());
return;
}
// We don't want to keep path points that have already been traversed, so only merge the points starting from "CurrentPathIndex".
const int32 StartingPointIndex = PathFollowingComponent ? PathFollowingComponent->GetCurrentPathIndex() : 0;
if (StartingPointIndex < InitialPathPoints.Num())
{
InOutPathPoints.Insert(&InitialPathPoints[StartingPointIndex], InitialPathPoints.Num() - StartingPointIndex - 1, 0);
}
}
FPathFollowingRequestResult AAIController::MoveTo(const FAIMoveRequest& MoveRequest, FNavPathSharedPtr* OutPath)
{
// both MoveToActor and MoveToLocation can be called from blueprints/script and should keep only single movement request at the same time.
// this function is entry point of all movement mechanics - do NOT abort in here, since movement may be handled by AITasks, which support stacking
SCOPE_CYCLE_COUNTER(STAT_MoveTo);
UE_VLOG(this, LogAINavigation, Log, TEXT("MoveTo: %s"), *MoveRequest.ToString());
FPathFollowingRequestResult ResultData;
ResultData.Code = EPathFollowingRequestResult::Failed;
if (MoveRequest.IsValid() == false)
{
UE_VLOG(this, LogAINavigation, Error, TEXT("MoveTo request failed due MoveRequest not being valid. Most probably desired Goal Actor not longer exists. MoveRequest: '%s'"), *MoveRequest.ToString());
return ResultData;
}
if (PathFollowingComponent == nullptr)
{
UE_VLOG(this, LogAINavigation, Error, TEXT("MoveTo request failed due missing PathFollowingComponent"));
return ResultData;
}
bool bCanRequestMove = true;
bool bAlreadyAtGoal = false;
if (!MoveRequest.IsMoveToActorRequest())
{
if (MoveRequest.GetGoalLocation().ContainsNaN() || FAISystem::IsValidLocation(MoveRequest.GetGoalLocation()) == false)
{
UE_VLOG(this, LogAINavigation, Error, TEXT("AAIController::MoveTo: Destination is not valid! Goal(%s)"), TEXT_AI_LOCATION(MoveRequest.GetGoalLocation()));
bCanRequestMove = false;
}
// fail if projection to navigation is required but it failed
if (bCanRequestMove && MoveRequest.IsProjectingGoal())
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
const FNavAgentProperties& AgentProps = GetNavAgentPropertiesRef();
FNavLocation ProjectedLocation;
if (NavSys && !NavSys->ProjectPointToNavigation(MoveRequest.GetGoalLocation(), ProjectedLocation, INVALID_NAVEXTENT, &AgentProps))
{
if (MoveRequest.IsUsingPathfinding())
{
UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh"));
}
else
{
UE_VLOG_LOCATION(this, LogAINavigation, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("AAIController::MoveTo failed to project destination location to navmesh, path finding is disabled perhaps disable goal projection ?"));
}
bCanRequestMove = false;
}
MoveRequest.UpdateGoalLocation(ProjectedLocation.Location);
}
bAlreadyAtGoal = bCanRequestMove && PathFollowingComponent->HasReached(MoveRequest);
}
else
{
bAlreadyAtGoal = bCanRequestMove && PathFollowingComponent->HasReached(MoveRequest);
}
if (bAlreadyAtGoal)
{
UE_VLOG(this, LogAINavigation, Log, TEXT("MoveTo: already at goal!"));
ResultData.MoveId = PathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Success);
ResultData.Code = EPathFollowingRequestResult::AlreadyAtGoal;
}
else if (bCanRequestMove)
{
FPathFindingQuery PFQuery;
bool bShouldMergePaths = false;
FVector StartLocation = GetNavAgentLocation();
if (MoveRequest.ShouldStartFromPreviousPath())
{
FNavPathSharedPtr CurrentPath = PathFollowingComponent->GetPath();
if (CurrentPath.IsValid() && CurrentPath->IsValid() && CurrentPath->GetPathPoints().Num() > 0)
{
StartLocation = CurrentPath->GetPathPoints().Last();
bShouldMergePaths = true;
}
}
const bool bValidQuery = BuildPathfindingQuery(MoveRequest, StartLocation, PFQuery);
if (bValidQuery)
{
FNavPathSharedPtr Path;
FindPathForMoveRequest(MoveRequest, PFQuery, Path);
if (bShouldMergePaths && Path.IsValid())
{
// Merge the newly generated path with the current one
MergePaths(PathFollowingComponent->GetPath(), Path);
}
const FAIRequestID RequestID = Path.IsValid() ? RequestMove(MoveRequest, Path) : FAIRequestID::InvalidRequest;
if (RequestID.IsValid())
{
bAllowStrafe = MoveRequest.CanStrafe();
ResultData.MoveId = RequestID;
ResultData.Code = EPathFollowingRequestResult::RequestSuccessful;
if (OutPath)
{
*OutPath = Path;
}
}
}
}
if (ResultData.Code == EPathFollowingRequestResult::Failed)
{
ResultData.MoveId = PathFollowingComponent->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid);
}
return ResultData;
}
FAIRequestID AAIController::RequestMove(const FAIMoveRequest& MoveRequest, FNavPathSharedPtr Path)
{
uint32 RequestID = FAIRequestID::InvalidRequest;
if (PathFollowingComponent)
{
RequestID = PathFollowingComponent->RequestMove(MoveRequest, Path);
}
return RequestID;
}
bool AAIController::PauseMove(FAIRequestID RequestToPause)
{
if (PathFollowingComponent != NULL && RequestToPause.IsEquivalent(PathFollowingComponent->GetCurrentRequestId()))
{
PathFollowingComponent->PauseMove(RequestToPause, EPathFollowingVelocityMode::Reset);
return true;
}
return false;
}
bool AAIController::ResumeMove(FAIRequestID RequestToResume)
{
if (PathFollowingComponent != NULL && RequestToResume.IsEquivalent(PathFollowingComponent->GetCurrentRequestId()))
{
PathFollowingComponent->ResumeMove(RequestToResume);
return true;
}
return false;
}
void AAIController::StopMovement()
{
// @note FPathFollowingResultFlags::ForcedScript added to make AITask_MoveTo instances
// not ignore OnRequestFinished notify that's going to be sent out due to this call
if (PathFollowingComponent)
{
PathFollowingComponent->AbortMove(*this, FPathFollowingResultFlags::MovementStop | FPathFollowingResultFlags::ForcedScript);
}
}
bool AAIController::ShouldPostponePathUpdates() const
{
if (PathFollowingComponent && PathFollowingComponent->HasStartedNavLinkMove())
{
return true;
}
return Super::ShouldPostponePathUpdates();
}
bool AAIController::BuildPathfindingQuery(const FAIMoveRequest& MoveRequest, FPathFindingQuery& OutQuery) const
{
return BuildPathfindingQuery(MoveRequest, GetNavAgentLocation(), OutQuery);
}
bool AAIController::BuildPathfindingQuery(const FAIMoveRequest& MoveRequest, const FVector& StartLocation, FPathFindingQuery& OutQuery) const
{
bool bResult = false;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
const ANavigationData* NavData = (NavSys == nullptr) ? nullptr :
MoveRequest.IsUsingPathfinding() ? NavSys->GetNavDataForProps(GetNavAgentPropertiesRef(), GetNavAgentLocation()) :
NavSys->GetAbstractNavData();
if (NavData)
{
FVector GoalLocation = MoveRequest.GetGoalLocation();
if (MoveRequest.IsMoveToActorRequest())
{
const INavAgentInterface* NavGoal = Cast<const INavAgentInterface>(MoveRequest.GetGoalActor());
if (NavGoal)
{
const FVector Offset = NavGoal->GetMoveGoalOffset(this);
GoalLocation = FQuatRotationTranslationMatrix(MoveRequest.GetGoalActor()->GetActorQuat(), NavGoal->GetNavAgentLocation()).TransformPosition(Offset);
}
else
{
GoalLocation = MoveRequest.GetGoalActor()->GetActorLocation();
}
}
FSharedConstNavQueryFilter NavFilter = UNavigationQueryFilter::GetQueryFilter(*NavData, this, MoveRequest.GetNavigationFilter());
OutQuery = FPathFindingQuery(*this, *NavData, StartLocation, GoalLocation, NavFilter);
OutQuery.SetAllowPartialPaths(MoveRequest.IsUsingPartialPaths());
OutQuery.SetRequireNavigableEndLocation(MoveRequest.IsNavigableEndLocationRequired());
if (MoveRequest.IsApplyingCostLimitFromHeuristic())
{
const float HeuristicScale = NavFilter->GetHeuristicScale();
OutQuery.CostLimit = FPathFindingQuery::ComputeCostLimitFromHeuristic(OutQuery.StartLocation, OutQuery.EndLocation, HeuristicScale, MoveRequest.GetCostLimitFactor(), MoveRequest.GetMinimumCostLimit());
}
if (PathFollowingComponent)
{
PathFollowingComponent->OnPathfindingQuery(OutQuery);
}
bResult = true;
}
else
{
if (NavSys == nullptr)
{
UE_VLOG(this, LogAINavigation, Warning, TEXT("Unable AAIController::BuildPathfindingQuery due to no NavigationSystem present. Note that even pathfinding-less movement requires presence of NavigationSystem."));
}
else
{
UE_VLOG(this, LogAINavigation, Warning, TEXT("Unable to find NavigationData instance while calling AAIController::BuildPathfindingQuery"));
}
}
return bResult;
}
void AAIController::FindPathForMoveRequest(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query, FNavPathSharedPtr& OutPath) const
{
SCOPE_CYCLE_COUNTER(STAT_AI_Overall);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
FPathFindingResult PathResult = NavSys->FindPathSync(Query);
if (PathResult.Result != ENavigationQueryResult::Error)
{
if (PathResult.IsSuccessful() && PathResult.Path.IsValid())
{
if (MoveRequest.IsMoveToActorRequest())
{
PathResult.Path->SetGoalActorObservation(*MoveRequest.GetGoalActor(), 100.0f);
}
PathResult.Path->EnableRecalculationOnInvalidation(true);
OutPath = PathResult.Path;
}
}
else
{
UE_VLOG(this, LogAINavigation, Error, TEXT("Trying to find path to %s resulted in Error")
, MoveRequest.IsMoveToActorRequest() ? *GetNameSafe(MoveRequest.GetGoalActor()) : *MoveRequest.GetGoalLocation().ToString());
UE_VLOG_SEGMENT(this, LogAINavigation, Error, GetPawn() ? GetPawn()->GetActorLocation() : FAISystem::InvalidLocation
, MoveRequest.GetGoalLocation(), FColor::Red, TEXT("Failed move to %s"), *GetNameSafe(MoveRequest.GetGoalActor()));
}
}
}
// DEPRECATED FUNCTION SUPPORT
bool AAIController::PreparePathfinding(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query)
{
return BuildPathfindingQuery(MoveRequest, Query);
}
// DEPRECATED FUNCTION SUPPORT
FAIRequestID AAIController::RequestPathAndMove(const FAIMoveRequest& MoveRequest, FPathFindingQuery& Query)
{
FAIRequestID MoveId = FAIRequestID::InvalidRequest;
FNavPathSharedPtr FoundPath;
FindPathForMoveRequest(MoveRequest, Query, FoundPath);
if (FoundPath.IsValid())
{
MoveId = RequestMove(MoveRequest, FoundPath);
}
return MoveId;
}
EPathFollowingStatus::Type AAIController::GetMoveStatus() const
{
return (PathFollowingComponent) ? PathFollowingComponent->GetStatus() : EPathFollowingStatus::Idle;
}
bool AAIController::HasPartialPath() const
{
return (PathFollowingComponent != NULL) && (PathFollowingComponent->HasPartialPath());
}
bool AAIController::IsFollowingAPath() const
{
return (PathFollowingComponent != nullptr) && (PathFollowingComponent->GetStatus() != EPathFollowingStatus::Idle);
}
IPathFollowingAgentInterface* AAIController::GetPathFollowingAgent() const
{
return PathFollowingComponent;
}
FVector AAIController::GetImmediateMoveDestination() const
{
return (PathFollowingComponent) ? PathFollowingComponent->GetCurrentTargetLocation() : FVector::ZeroVector;
}
void AAIController::SetMoveBlockDetection(bool bEnable)
{
if (PathFollowingComponent)
{
PathFollowingComponent->SetBlockDetectionState(bEnable);
}
}
void AAIController::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
ReceiveMoveCompleted.Broadcast(RequestID, Result.Code);
OnMoveCompleted(RequestID, Result.Code);
}
void AAIController::OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type Result)
{
// deprecated
}
FAIRequestID AAIController::GetCurrentMoveRequestID() const
{
return GetPathFollowingComponent() ? GetPathFollowingComponent()->GetCurrentRequestId() : FAIRequestID::InvalidRequest;
}
bool AAIController::RunBehaviorTree(UBehaviorTree* BTAsset)
{
// @todo: find BrainComponent and see if it's BehaviorTreeComponent
// Also check if BTAsset requires BlackBoardComponent, and if so
// check if BB type is accepted by BTAsset.
// Spawn BehaviorTreeComponent if none present.
// Spawn BlackBoardComponent if none present, but fail if one is present but is not of compatible class
if (BTAsset == NULL)
{
UE_VLOG(this, LogBehaviorTree, Warning, TEXT("RunBehaviorTree: Unable to run NULL behavior tree"));
return false;
}
bool bSuccess = true;
// see if need a blackboard component at all
UBlackboardComponent* BlackboardComp = Blackboard;
if (BTAsset->BlackboardAsset && (Blackboard == nullptr || Blackboard->IsCompatibleWith(BTAsset->BlackboardAsset) == false))
{
bSuccess = UseBlackboard(BTAsset->BlackboardAsset, BlackboardComp);
}
if (bSuccess)
{
UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(BrainComponent);
if (BTComp == NULL)
{
UE_VLOG(this, LogBehaviorTree, Log, TEXT("RunBehaviorTree: spawning BehaviorTreeComponent.."));
BTComp = NewObject<UBehaviorTreeComponent>(this, TEXT("BTComponent"));
BTComp->RegisterComponent();
REDIRECT_OBJECT_TO_VLOG(BTComp, this);
}
// make sure BrainComponent points at the newly created BT component
BrainComponent = BTComp;
check(BTComp != NULL);
BTComp->StartTree(*BTAsset, EBTExecutionMode::Looped);
}
return bSuccess;
}
void AAIController::CleanupBrainComponent()
{
if (BrainComponent)
{
BrainComponent->Cleanup();
}
}
void AAIController::ClaimTaskResource(TSubclassOf<UGameplayTaskResource> ResourceClass)
{
if (CachedGameplayTasksComponent)
{
const uint8 ResourceID = UGameplayTaskResource::GetResourceID(ResourceClass);
if (ScriptClaimedResources.HasID(ResourceID) == false)
{
ScriptClaimedResources.AddID(ResourceID);
UE_VLOG(this, LogGameplayTasks, Log, TEXT("ClaimTaskResource %s"), *GetNameSafe(*ResourceClass));
IGameplayTaskOwnerInterface& AsTaskOwner = *this;
UGameplayTask_ClaimResource* ResourceTask = UGameplayTask_ClaimResource::ClaimResource(AsTaskOwner, ResourceClass, uint8(EAITaskPriority::High), ResourceClass->GetFName());
if (ResourceTask)
{
CachedGameplayTasksComponent->AddTaskReadyForActivation(*ResourceTask);
}
UE_CVLOG(ResourceTask == nullptr, this, LogGameplayTasks, Warning, TEXT("ClaimTaskResource failed to create UGameplayTask_ClaimResource instance"));
}
}
}
void AAIController::UnclaimTaskResource(TSubclassOf<UGameplayTaskResource> ResourceClass)
{
if (CachedGameplayTasksComponent)
{
const uint8 ResourceID = UGameplayTaskResource::GetResourceID(ResourceClass);
if (ScriptClaimedResources.HasID(ResourceID) == true)
{
ScriptClaimedResources.RemoveID(ResourceID);
UE_VLOG(this, LogGameplayTasks, Log, TEXT("UnclaimTaskResource %s"), *GetNameSafe(*ResourceClass));
UGameplayTask* ResourceTask = CachedGameplayTasksComponent->FindResourceConsumingTaskByName(ResourceClass->GetFName());
if (ResourceTask)
{
ResourceTask->EndTask();
}
UE_CVLOG(ResourceTask == nullptr, this, LogGameplayTasks, Warning, TEXT("UnclaimTaskResource failed to find UGameplayTask_ClaimResource instance"));
}
}
}
bool AAIController::InitializeBlackboard(UBlackboardComponent& BlackboardComp, UBlackboardData& BlackboardAsset)
{
check(BlackboardComp.GetOwner() == this);
if (BlackboardComp.InitializeBlackboard(BlackboardAsset))
{
// find the "self" key and set it to our pawn
const FBlackboard::FKey SelfKey = BlackboardAsset.GetKeyID(FBlackboard::KeySelf);
if (SelfKey != FBlackboard::InvalidKey)
{
BlackboardComp.SetValue<UBlackboardKeyType_Object>(SelfKey, GetPawn());
}
OnUsingBlackBoard(&BlackboardComp, &BlackboardAsset);
return true;
}
return false;
}
bool AAIController::UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent)
{
if (BlackboardAsset == nullptr)
{
UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: trying to use NULL Blackboard asset. Ignoring"));
return false;
}
bool bSuccess = true;
Blackboard = FindComponentByClass<UBlackboardComponent>();
if (Blackboard == nullptr)
{
Blackboard = NewObject<UBlackboardComponent>(this, TEXT("BlackboardComponent"));
REDIRECT_OBJECT_TO_VLOG(Blackboard, this);
if (Blackboard != nullptr)
{
bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset);
Blackboard->RegisterComponent();
}
}
else if (Blackboard->GetBlackboardAsset() == nullptr)
{
bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset);
}
else if (Blackboard->GetBlackboardAsset() != BlackboardAsset)
{
// @todo this behavior should be opt-out-able.
UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: requested blackboard %s while already has %s instantiated. Forcing new BB.")
, *GetNameSafe(BlackboardAsset), *GetNameSafe(Blackboard->GetBlackboardAsset()));
bSuccess = InitializeBlackboard(*Blackboard, *BlackboardAsset);
}
BlackboardComponent = Blackboard;
return bSuccess;
}
bool AAIController::ShouldSyncBlackboardWith(const UBlackboardComponent& OtherBlackboardComponent) const
{
return Blackboard != nullptr
&& Blackboard->GetBlackboardAsset() != nullptr
&& OtherBlackboardComponent.GetBlackboardAsset() != nullptr
&& Blackboard->GetBlackboardAsset()->IsRelatedTo(*OtherBlackboardComponent.GetBlackboardAsset());
}
bool AAIController::SuggestTossVelocity(FVector& OutTossVelocity, FVector Start, FVector End, float TossSpeed, bool bPreferHighArc, float CollisionRadius, bool bOnlyTraceUp)
{
// pawn's physics volume gets 2nd priority
APhysicsVolume const* const PhysicsVolume = GetPawn() ? GetPawn()->GetPhysicsVolume() : nullptr;
float const GravityOverride = PhysicsVolume ? PhysicsVolume->GetGravityZ() : 0.f;
ESuggestProjVelocityTraceOption::Type const TraceOption = bOnlyTraceUp ? ESuggestProjVelocityTraceOption::OnlyTraceWhileAscending : ESuggestProjVelocityTraceOption::TraceFullPath;
UGameplayStatics::FSuggestProjectileVelocityParameters VelocityParams = UGameplayStatics::FSuggestProjectileVelocityParameters(this, Start, End, TossSpeed);
VelocityParams.bFavorHighArc = bPreferHighArc;
VelocityParams.CollisionRadius = CollisionRadius;
VelocityParams.OverrideGravityZ = GravityOverride;
VelocityParams.TraceOption = TraceOption;
return UGameplayStatics::SuggestProjectileVelocity(VelocityParams, OutTossVelocity);
}
FString AAIController::GetDebugIcon() const
{
if (BrainComponent == NULL || BrainComponent->IsRunning() == false)
{
return TEXT("/Engine/EngineResources/AICON-Red.AICON-Red");
}
return TEXT("/Engine/EngineResources/AICON-Green.AICON-Green");
}
void AAIController::OnGameplayTaskResourcesClaimed(FGameplayResourceSet NewlyClaimed, FGameplayResourceSet FreshlyReleased)
{
if (BrainComponent)
{
const uint8 LogicID = UGameplayTaskResource::GetResourceID<UAIResource_Logic>();
if (NewlyClaimed.HasID(LogicID))
{
BrainComponent->LockResource(EAIRequestPriority::Logic);
}
else if (FreshlyReleased.HasID(LogicID))
{
BrainComponent->ClearResourceLock(EAIRequestPriority::Logic);
}
}
}
void AAIController::SetPathFollowingComponent(UPathFollowingComponent* NewPFComponent)
{
PathFollowingComponent = NewPFComponent;
#if ENABLE_VISUAL_LOG
if (NewPFComponent)
{
REDIRECT_OBJECT_TO_VLOG(NewPFComponent, this);
}
#endif // ENABLE_VISUAL_LOG
}
//----------------------------------------------------------------------//
// IGenericTeamAgentInterface
//----------------------------------------------------------------------//
void AAIController::SetGenericTeamId(const FGenericTeamId& NewTeamID)
{
if (TeamID != NewTeamID)
{
TeamID = NewTeamID;
// @todo notify perception system that a controller changed team ID
}
}