Compare commits
2 Commits
59a23e61db
...
98f0dbdce5
| Author | SHA1 | Date | |
|---|---|---|---|
| 98f0dbdce5 | |||
| 25abd59512 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -227,8 +227,12 @@ void UPS_AI_Behavior_BTTask_Attack::TickTask(
|
|||||||
{
|
{
|
||||||
// No LOS and idle → find a firing position
|
// No LOS and idle → find a firing position
|
||||||
// Check if investigation (last known position) is active
|
// Check if investigation (last known position) is active
|
||||||
|
// ClearValue sets vectors to FAISystem::InvalidLocation, not zero — must check both
|
||||||
const FVector LastKnownPos = BB->GetValueAsVector(PS_AI_Behavior_BB::LastKnownTargetPosition);
|
const FVector LastKnownPos = BB->GetValueAsVector(PS_AI_Behavior_BB::LastKnownTargetPosition);
|
||||||
if (!LastKnownPos.IsZero())
|
const bool bHasLastKnown = !LastKnownPos.IsZero() &&
|
||||||
|
!LastKnownPos.ContainsNaN() &&
|
||||||
|
LastKnownPos.X < 1e30f; // Filter out FLT_MAX / InvalidLocation
|
||||||
|
if (bHasLastKnown)
|
||||||
{
|
{
|
||||||
// Investigation mode: go to last known position
|
// Investigation mode: go to last known position
|
||||||
AIC->MoveToLocation(
|
AIC->MoveToLocation(
|
||||||
@ -336,6 +340,7 @@ void UPS_AI_Behavior_BTTask_Attack::TickTask(
|
|||||||
*AIC->GetName());
|
*AIC->GetName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::AbortTask(
|
EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::AbortTask(
|
||||||
@ -427,20 +432,9 @@ void UPS_AI_Behavior_BTTask_Attack::OnFiringPositionQueryFinished(
|
|||||||
|
|
||||||
AActor* Target = WeakTarget.Get();
|
AActor* Target = WeakTarget.Get();
|
||||||
|
|
||||||
if (Result.IsValid() && Result->IsSuccessful())
|
// Fallback lambda: advance directly toward target
|
||||||
|
auto FallbackAdvance = [&](const TCHAR* Reason)
|
||||||
{
|
{
|
||||||
const FVector FiringPos = Result->GetItemAsLocation(0);
|
|
||||||
AIC->MoveToLocation(
|
|
||||||
FiringPos, 50.0f, /*bStopOnOverlap=*/true,
|
|
||||||
/*bUsePathfinding=*/true, /*bProjectGoal=*/false, /*bCanStrafe=*/false);
|
|
||||||
Memory->bSeekingFiringPos = true;
|
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Attack: EQS found firing position %s"),
|
|
||||||
*AIC->GetName(), *FiringPos.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// EQS found nothing — fallback: advance toward target
|
|
||||||
if (Target)
|
if (Target)
|
||||||
{
|
{
|
||||||
const float MidRange = (Memory->MinRange + Memory->MaxRange) * 0.5f;
|
const float MidRange = (Memory->MinRange + Memory->MaxRange) * 0.5f;
|
||||||
@ -449,9 +443,32 @@ void UPS_AI_Behavior_BTTask_Attack::OnFiringPositionQueryFinished(
|
|||||||
/*bUsePathfinding=*/true, /*bProjectGoal=*/true, /*bCanStrafe=*/false);
|
/*bUsePathfinding=*/true, /*bProjectGoal=*/true, /*bCanStrafe=*/false);
|
||||||
Memory->bSeekingFiringPos = true;
|
Memory->bSeekingFiringPos = true;
|
||||||
}
|
}
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Attack: %s, fallback advance toward target"),
|
||||||
|
*AIC->GetName(), Reason);
|
||||||
|
};
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Attack: EQS no result, fallback advance"),
|
if (Result.IsValid() && Result->IsSuccessful())
|
||||||
*AIC->GetName());
|
{
|
||||||
|
const FVector FiringPos = Result->GetItemAsLocation(0);
|
||||||
|
const EPathFollowingRequestResult::Type MoveResult = AIC->MoveToLocation(
|
||||||
|
FiringPos, 50.0f, /*bStopOnOverlap=*/true,
|
||||||
|
/*bUsePathfinding=*/true, /*bProjectGoal=*/true, /*bCanStrafe=*/false);
|
||||||
|
|
||||||
|
if (MoveResult == EPathFollowingRequestResult::RequestSuccessful)
|
||||||
|
{
|
||||||
|
Memory->bSeekingFiringPos = true;
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Attack: EQS moving to firing position %s"),
|
||||||
|
*AIC->GetName(), *FiringPos.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// AlreadyAtGoal or Failed — EQS position is too close or unreachable
|
||||||
|
FallbackAdvance(TEXT("EQS position unreachable or too close"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FallbackAdvance(TEXT("EQS no result"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -178,6 +178,17 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
||||||
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
||||||
|
|
||||||
|
// Crouch if the cover point requires it
|
||||||
|
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] CoverShootCycle: at cover, ducking for %.1fs"),
|
UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] CoverShootCycle: at cover, ducking for %.1fs"),
|
||||||
*AIC->GetName(), Memory->PhaseDuration);
|
*AIC->GetName(), Memory->PhaseDuration);
|
||||||
}
|
}
|
||||||
@ -240,13 +251,12 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No better cover → stay, retry next cycle
|
// No better cover found and no LOS → abandon cover, fall back to Attack task
|
||||||
Memory->SubState = EPS_AI_Behavior_CombatSubState::AtCover;
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
Memory->PhaseDuration = FMath::RandRange(Memory->EffCoverMin, Memory->EffCoverMax);
|
TEXT("[%s] CoverShootCycle: no LOS and no advancing cover → abandoning cover for Attack fallback"),
|
||||||
Memory->Timer = Memory->PhaseDuration;
|
*AIC->GetName());
|
||||||
Memory->CycleCount = 0;
|
FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
|
||||||
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
return;
|
||||||
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -255,13 +265,15 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
Memory->SubState = EPS_AI_Behavior_CombatSubState::Peeking;
|
Memory->SubState = EPS_AI_Behavior_CombatSubState::Peeking;
|
||||||
Memory->PhaseDuration = FMath::RandRange(Memory->EffPeekMin, Memory->EffPeekMax);
|
Memory->PhaseDuration = FMath::RandRange(Memory->EffPeekMin, Memory->EffPeekMax);
|
||||||
Memory->Timer = Memory->PhaseDuration;
|
Memory->Timer = Memory->PhaseDuration;
|
||||||
|
Memory->LOSCheckTimer = 0.3f;
|
||||||
|
|
||||||
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
||||||
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::Peeking));
|
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::Peeking));
|
||||||
|
|
||||||
// Start attacking
|
// Stand up to shoot
|
||||||
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
{
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, false);
|
||||||
IPS_AI_Behavior_Interface::Execute_BehaviorStartAttack(Pawn, Target);
|
IPS_AI_Behavior_Interface::Execute_BehaviorStartAttack(Pawn, Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +286,40 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
// ─── PEEKING: Shooting at target ────────────────────────────────
|
// ─── PEEKING: Shooting at target ────────────────────────────────
|
||||||
case EPS_AI_Behavior_CombatSubState::Peeking:
|
case EPS_AI_Behavior_CombatSubState::Peeking:
|
||||||
{
|
{
|
||||||
|
// Continuous LOS check while peeking — stop shooting if target hides
|
||||||
|
Memory->LOSCheckTimer -= DeltaSeconds;
|
||||||
|
if (Memory->LOSCheckTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
Memory->LOSCheckTimer = 0.3f; // check every 0.3s
|
||||||
|
const bool bStillHasLOS = UPS_AI_Behavior_Statics::HasLineOfSight(
|
||||||
|
Pawn->GetWorld(), Pawn, Target, 150.0f);
|
||||||
|
if (!bStillHasLOS)
|
||||||
|
{
|
||||||
|
// Target hid — stop shooting, crouch back to cover
|
||||||
|
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_BehaviorStopAttack(Pawn);
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Log,
|
||||||
|
TEXT("[%s] CoverShootCycle: lost LOS during peek → back to cover"),
|
||||||
|
*AIC->GetName());
|
||||||
|
|
||||||
|
Memory->SubState = EPS_AI_Behavior_CombatSubState::AtCover;
|
||||||
|
Memory->PhaseDuration = FMath::RandRange(Memory->EffCoverMin, Memory->EffCoverMax);
|
||||||
|
Memory->Timer = Memory->PhaseDuration;
|
||||||
|
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
||||||
|
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Memory->Timer -= DeltaSeconds;
|
Memory->Timer -= DeltaSeconds;
|
||||||
if (Memory->Timer <= 0.0f)
|
if (Memory->Timer <= 0.0f)
|
||||||
{
|
{
|
||||||
@ -353,6 +399,17 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
||||||
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
||||||
|
|
||||||
|
// Re-crouch if cover requires it
|
||||||
|
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose,
|
UE_LOG(LogPS_AI_Behavior, Verbose,
|
||||||
TEXT("[%s] CoverShootCycle: ducking back (cycle %d/%d)"),
|
TEXT("[%s] CoverShootCycle: ducking back (cycle %d/%d)"),
|
||||||
*AIC->GetName(), Memory->CycleCount, Memory->EffMaxCycles);
|
*AIC->GetName(), Memory->CycleCount, Memory->EffMaxCycles);
|
||||||
@ -375,6 +432,17 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
|
|||||||
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
BB->SetValueAsEnum(PS_AI_Behavior_BB::CombatSubState,
|
||||||
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
static_cast<uint8>(EPS_AI_Behavior_CombatSubState::AtCover));
|
||||||
|
|
||||||
|
// Crouch if the new cover point requires it
|
||||||
|
if (Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] CoverShootCycle: arrived at new cover, ducking"),
|
UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] CoverShootCycle: arrived at new cover, ducking"),
|
||||||
*AIC->GetName());
|
*AIC->GetName());
|
||||||
}
|
}
|
||||||
@ -391,11 +459,12 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_CoverShootCycle::AbortTask(
|
|||||||
{
|
{
|
||||||
AIC->StopMovement();
|
AIC->StopMovement();
|
||||||
|
|
||||||
// Stop attacking if we were peeking
|
// Stop attacking and stand up if we were peeking/crouching
|
||||||
APawn* Pawn = AIC->GetPawn();
|
APawn* Pawn = AIC->GetPawn();
|
||||||
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
{
|
{
|
||||||
IPS_AI_Behavior_Interface::Execute_BehaviorStopAttack(Pawn);
|
IPS_AI_Behavior_Interface::Execute_BehaviorStopAttack(Pawn);
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,11 +477,12 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::OnTaskFinished(
|
|||||||
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp.GetAIOwner());
|
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp.GetAIOwner());
|
||||||
if (AIC)
|
if (AIC)
|
||||||
{
|
{
|
||||||
// Stop attacking
|
// Stop attacking and stand up
|
||||||
APawn* Pawn = AIC->GetPawn();
|
APawn* Pawn = AIC->GetPawn();
|
||||||
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
{
|
{
|
||||||
IPS_AI_Behavior_Interface::Execute_BehaviorStopAttack(Pawn);
|
IPS_AI_Behavior_Interface::Execute_BehaviorStopAttack(Pawn);
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release cover point
|
// Release cover point
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "BT/PS_AI_Behavior_BTTask_FindCover.h"
|
#include "BT/PS_AI_Behavior_BTTask_FindCover.h"
|
||||||
#include "PS_AI_Behavior_AIController.h"
|
#include "PS_AI_Behavior_AIController.h"
|
||||||
|
#include "PS_AI_Behavior_Interface.h"
|
||||||
#include "PS_AI_Behavior_CoverPoint.h"
|
#include "PS_AI_Behavior_CoverPoint.h"
|
||||||
#include "PS_AI_Behavior_PersonalityComponent.h"
|
#include "PS_AI_Behavior_PersonalityComponent.h"
|
||||||
#include "PS_AI_Behavior_Definitions.h"
|
#include "PS_AI_Behavior_Definitions.h"
|
||||||
@ -11,6 +12,7 @@
|
|||||||
#include "CollisionQueryParams.h"
|
#include "CollisionQueryParams.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "EngineUtils.h"
|
#include "EngineUtils.h"
|
||||||
|
#include "EnvironmentQuery/EnvQueryManager.h"
|
||||||
|
|
||||||
UPS_AI_Behavior_BTTask_FindCover::UPS_AI_Behavior_BTTask_FindCover()
|
UPS_AI_Behavior_BTTask_FindCover::UPS_AI_Behavior_BTTask_FindCover()
|
||||||
{
|
{
|
||||||
@ -120,7 +122,16 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindCover::ExecuteTask(
|
|||||||
|
|
||||||
BB->SetValueAsVector(PS_AI_Behavior_BB::CoverLocation, BestCoverPos);
|
BB->SetValueAsVector(PS_AI_Behavior_BB::CoverLocation, BestCoverPos);
|
||||||
|
|
||||||
// Navigate to cover
|
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
||||||
|
|
||||||
|
// If we have a refinement EQS query and a manual CoverPoint, refine the position
|
||||||
|
if (RefinementQuery && ChosenPoint)
|
||||||
|
{
|
||||||
|
RunRefinementQuery(OwnerComp, NodeMemory, AIC->GetPawn(), BestCoverPos);
|
||||||
|
return EBTNodeResult::InProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to cover directly (no refinement)
|
||||||
const EPathFollowingRequestResult::Type Result = AIC->MoveToLocation(
|
const EPathFollowingRequestResult::Type Result = AIC->MoveToLocation(
|
||||||
BestCoverPos, AcceptanceRadius, /*bStopOnOverlap=*/true,
|
BestCoverPos, AcceptanceRadius, /*bStopOnOverlap=*/true,
|
||||||
/*bUsePathfinding=*/true, /*bProjectDestinationToNavigation=*/true,
|
/*bUsePathfinding=*/true, /*bProjectDestinationToNavigation=*/true,
|
||||||
@ -136,7 +147,6 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindCover::ExecuteTask(
|
|||||||
return EBTNodeResult::Succeeded;
|
return EBTNodeResult::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
|
||||||
Memory->bMoveRequested = true;
|
Memory->bMoveRequested = true;
|
||||||
return EBTNodeResult::InProgress;
|
return EBTNodeResult::InProgress;
|
||||||
}
|
}
|
||||||
@ -145,6 +155,7 @@ void UPS_AI_Behavior_BTTask_FindCover::TickTask(
|
|||||||
UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
|
UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
|
||||||
{
|
{
|
||||||
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
||||||
|
if (Memory->bEQSRunning) return; // Waiting for EQS callback
|
||||||
if (!Memory->bMoveRequested) return;
|
if (!Memory->bMoveRequested) return;
|
||||||
|
|
||||||
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp.GetAIOwner());
|
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp.GetAIOwner());
|
||||||
@ -153,6 +164,23 @@ void UPS_AI_Behavior_BTTask_FindCover::TickTask(
|
|||||||
if (AIC->GetMoveStatus() == EPathFollowingStatus::Idle)
|
if (AIC->GetMoveStatus() == EPathFollowingStatus::Idle)
|
||||||
{
|
{
|
||||||
Memory->bMoveRequested = false;
|
Memory->bMoveRequested = false;
|
||||||
|
|
||||||
|
// Crouch at cover if the point requires it
|
||||||
|
APawn* Pawn = AIC->GetPawn();
|
||||||
|
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
|
||||||
|
if (BB)
|
||||||
|
{
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
|
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +193,13 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindCover::AbortTask(
|
|||||||
{
|
{
|
||||||
AIC->StopMovement();
|
AIC->StopMovement();
|
||||||
|
|
||||||
|
// Stand up if crouching
|
||||||
|
APawn* Pawn = AIC->GetPawn();
|
||||||
|
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Release any claimed cover point
|
// Release any claimed cover point
|
||||||
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
|
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
|
||||||
if (BB)
|
if (BB)
|
||||||
@ -173,7 +208,7 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindCover::AbortTask(
|
|||||||
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
if (Point)
|
if (Point)
|
||||||
{
|
{
|
||||||
Point->Release(AIC->GetPawn());
|
if (Pawn) Point->Release(Pawn);
|
||||||
BB->ClearValue(PS_AI_Behavior_BB::CoverPoint);
|
BB->ClearValue(PS_AI_Behavior_BB::CoverPoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,9 +321,108 @@ float UPS_AI_Behavior_BTTask_FindCover::EvaluateCoverQuality(
|
|||||||
|
|
||||||
FString UPS_AI_Behavior_BTTask_FindCover::GetStaticDescription() const
|
FString UPS_AI_Behavior_BTTask_FindCover::GetStaticDescription() const
|
||||||
{
|
{
|
||||||
return FString::Printf(TEXT("Find cover within %.0fcm\nManual %s + Procedural (%d candidates)\nBonus: +%.0f%%"),
|
return FString::Printf(TEXT("Find cover within %.0fcm\nManual %s + Procedural (%d candidates)\nBonus: +%.0f%%\nRefinement: %s"),
|
||||||
SearchRadius,
|
SearchRadius,
|
||||||
CoverPointType == EPS_AI_Behavior_CoverPointType::Cover ? TEXT("Cover") : TEXT("Hiding"),
|
CoverPointType == EPS_AI_Behavior_CoverPointType::Cover ? TEXT("Cover") : TEXT("Hiding"),
|
||||||
NumCandidates,
|
NumCandidates,
|
||||||
ManualPointBonus * 100.0f);
|
ManualPointBonus * 100.0f,
|
||||||
|
RefinementQuery ? *RefinementQuery->GetName() : TEXT("None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── EQS Refinement ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void UPS_AI_Behavior_BTTask_FindCover::RunRefinementQuery(
|
||||||
|
UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory,
|
||||||
|
APawn* Pawn, const FVector& CoverCenter)
|
||||||
|
{
|
||||||
|
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
||||||
|
|
||||||
|
UWorld* World = Pawn->GetWorld();
|
||||||
|
UEnvQueryManager* EQSManager = UEnvQueryManager::GetCurrent(World);
|
||||||
|
if (!EQSManager)
|
||||||
|
{
|
||||||
|
// Fallback: move directly to the cover center
|
||||||
|
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp.GetAIOwner());
|
||||||
|
if (AIC)
|
||||||
|
{
|
||||||
|
AIC->MoveToLocation(CoverCenter, AcceptanceRadius, true, true, true, false);
|
||||||
|
Memory->bMoveRequested = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory->bEQSRunning = true;
|
||||||
|
|
||||||
|
FEnvQueryRequest Request(RefinementQuery, Pawn);
|
||||||
|
Request.Execute(EEnvQueryRunMode::SingleResult,
|
||||||
|
FQueryFinishedSignature::CreateUObject(this,
|
||||||
|
&UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished,
|
||||||
|
&OwnerComp, NodeMemory, CoverCenter));
|
||||||
|
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] FindCover: EQS refinement query launched around %s"),
|
||||||
|
*Pawn->GetName(), *CoverCenter.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
|
||||||
|
TSharedPtr<FEnvQueryResult> Result,
|
||||||
|
UBehaviorTreeComponent* OwnerComp, uint8* NodeMemory,
|
||||||
|
FVector OriginalCoverPos)
|
||||||
|
{
|
||||||
|
if (!OwnerComp || !NodeMemory) return;
|
||||||
|
|
||||||
|
FCoverMemory* Memory = reinterpret_cast<FCoverMemory*>(NodeMemory);
|
||||||
|
Memory->bEQSRunning = false;
|
||||||
|
|
||||||
|
APS_AI_Behavior_AIController* AIC = Cast<APS_AI_Behavior_AIController>(OwnerComp->GetAIOwner());
|
||||||
|
if (!AIC || !AIC->GetPawn()) return;
|
||||||
|
|
||||||
|
FVector FinalPos = OriginalCoverPos; // Fallback to original position
|
||||||
|
|
||||||
|
if (Result.IsValid() && Result->IsSuccessful())
|
||||||
|
{
|
||||||
|
FinalPos = Result->GetItemAsLocation(0);
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refined position %s (was %s)"),
|
||||||
|
*AIC->GetName(), *FinalPos.ToString(), *OriginalCoverPos.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement failed, using original cover position"),
|
||||||
|
*AIC->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update BB with refined position
|
||||||
|
UBlackboardComponent* BB = OwnerComp->GetBlackboardComponent();
|
||||||
|
if (BB)
|
||||||
|
{
|
||||||
|
BB->SetValueAsVector(PS_AI_Behavior_BB::CoverLocation, FinalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to the refined (or original) position
|
||||||
|
const EPathFollowingRequestResult::Type MoveResult = AIC->MoveToLocation(
|
||||||
|
FinalPos, AcceptanceRadius, true, true, true, false);
|
||||||
|
|
||||||
|
if (MoveResult == EPathFollowingRequestResult::Failed)
|
||||||
|
{
|
||||||
|
FinishLatentTask(*OwnerComp, EBTNodeResult::Failed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MoveResult == EPathFollowingRequestResult::AlreadyAtGoal)
|
||||||
|
{
|
||||||
|
// Crouch at cover if needed
|
||||||
|
APawn* Pawn = AIC->GetPawn();
|
||||||
|
if (Pawn && Pawn->Implements<UPS_AI_Behavior_Interface>())
|
||||||
|
{
|
||||||
|
const APS_AI_Behavior_CoverPoint* CoverPt =
|
||||||
|
Cast<APS_AI_Behavior_CoverPoint>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
|
||||||
|
if (CoverPt && CoverPt->bCrouch)
|
||||||
|
{
|
||||||
|
IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FinishLatentTask(*OwnerComp, EBTNodeResult::Succeeded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory->bMoveRequested = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,7 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Attack|LOS", meta = (ClampMin = "0.1", ClampMax = "1.0"))
|
UPROPERTY(EditAnywhere, Category = "Attack|LOS", meta = (ClampMin = "0.1", ClampMax = "1.0"))
|
||||||
float LOSCheckInterval = 0.2f;
|
float LOSCheckInterval = 0.2f;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EQS query asset for finding a firing position with clear LOS.
|
* EQS query asset for finding a firing position with clear LOS.
|
||||||
* Compose in editor: OnCircle generator + LineOfSight filter + Distance tests.
|
* Compose in editor: OnCircle generator + LineOfSight filter + Distance tests.
|
||||||
|
|||||||
@ -87,6 +87,7 @@ private:
|
|||||||
float PhaseDuration = 0.0f;
|
float PhaseDuration = 0.0f;
|
||||||
int32 CycleCount = 0;
|
int32 CycleCount = 0;
|
||||||
bool bMoveRequested = false;
|
bool bMoveRequested = false;
|
||||||
|
float LOSCheckTimer = 0.0f; // cooldown for LOS checks during Peeking
|
||||||
|
|
||||||
// Effective durations (modulated by personality)
|
// Effective durations (modulated by personality)
|
||||||
float EffPeekMin = 2.0f;
|
float EffPeekMin = 2.0f;
|
||||||
|
|||||||
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "BehaviorTree/BTTaskNode.h"
|
#include "BehaviorTree/BTTaskNode.h"
|
||||||
|
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||||
#include "PS_AI_Behavior_Definitions.h"
|
#include "PS_AI_Behavior_Definitions.h"
|
||||||
#include "PS_AI_Behavior_BTTask_FindCover.generated.h"
|
#include "PS_AI_Behavior_BTTask_FindCover.generated.h"
|
||||||
|
|
||||||
class APS_AI_Behavior_CoverPoint;
|
class APS_AI_Behavior_CoverPoint;
|
||||||
|
class UEnvQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BT Task: Find a cover position and navigate to it.
|
* BT Task: Find a cover position and navigate to it.
|
||||||
@ -70,6 +72,19 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, Category = "Cover|Advancement", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
UPROPERTY(EditAnywhere, Category = "Cover|Advancement", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||||
float AdvancementBias = 0.0f;
|
float AdvancementBias = 0.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional EQS query to refine the exact cover position around a selected CoverPoint.
|
||||||
|
* The query runs centered on the chosen CoverPoint and picks the best nearby spot.
|
||||||
|
* Use OnCircle generator (small radius ~200cm) + CoverQuality test + Distance tests.
|
||||||
|
* If null, the NPC goes directly to the CoverPoint's location (no refinement).
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Cover|EQS Refinement")
|
||||||
|
TObjectPtr<UEnvQuery> RefinementQuery;
|
||||||
|
|
||||||
|
/** Radius around the CoverPoint for EQS refinement search (cm). */
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Cover|EQS Refinement", meta = (ClampMin = "100.0", ClampMax = "500.0"))
|
||||||
|
float RefinementRadius = 200.0f;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
|
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
|
||||||
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
|
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
|
||||||
@ -80,6 +95,7 @@ private:
|
|||||||
struct FCoverMemory
|
struct FCoverMemory
|
||||||
{
|
{
|
||||||
bool bMoveRequested = false;
|
bool bMoveRequested = false;
|
||||||
|
bool bEQSRunning = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual uint16 GetInstanceMemorySize() const override { return sizeof(FCoverMemory); }
|
virtual uint16 GetInstanceMemorySize() const override { return sizeof(FCoverMemory); }
|
||||||
@ -98,4 +114,13 @@ private:
|
|||||||
APS_AI_Behavior_CoverPoint* FindBestManualCoverPoint(
|
APS_AI_Behavior_CoverPoint* FindBestManualCoverPoint(
|
||||||
const UWorld* World, const FVector& NpcLoc, const FVector& ThreatLoc,
|
const UWorld* World, const FVector& NpcLoc, const FVector& ThreatLoc,
|
||||||
EPS_AI_Behavior_NPCType NPCType, float& OutScore) const;
|
EPS_AI_Behavior_NPCType NPCType, float& OutScore) const;
|
||||||
|
|
||||||
|
/** Run EQS refinement query around the chosen CoverPoint. */
|
||||||
|
void RunRefinementQuery(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory,
|
||||||
|
APawn* Pawn, const FVector& CoverCenter);
|
||||||
|
|
||||||
|
/** Callback when the refinement EQS query completes. */
|
||||||
|
void OnRefinementQueryFinished(TSharedPtr<FEnvQueryResult> Result,
|
||||||
|
UBehaviorTreeComponent* OwnerComp, uint8* NodeMemory,
|
||||||
|
FVector OriginalCoverPos);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -142,6 +142,18 @@ public:
|
|||||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior")
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior")
|
||||||
bool CanBehaviorAttack(AActor* Target) const;
|
bool CanBehaviorAttack(AActor* Target) const;
|
||||||
|
|
||||||
|
// ─── Stance ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order the Pawn to crouch or stand up.
|
||||||
|
* Called by the cover system when entering/leaving a cover point with bCrouch set.
|
||||||
|
* The Pawn implements this however it wants (CharacterMovement->Crouch, animation, etc.).
|
||||||
|
*
|
||||||
|
* @param bCrouch True = crouch, False = stand up.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior")
|
||||||
|
void SetBehaviorCrouch(bool bCrouch);
|
||||||
|
|
||||||
// ─── Combat Style ───────────────────────────────────────────────────
|
// ─── Combat Style ───────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user