Compare commits

..

No commits in common. "391a35ac2cb8c2c10dec09c85acfe1e4f78180a2" and "86d3ae118df19911ac8f22bf743ad307f2156ccd" have entirely different histories.

36 changed files with 50 additions and 288 deletions

View File

@ -76,31 +76,17 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode(
if (ThreatActor)
{
// Target switched by perception (higher score)
// Target switched by perception (higher score) → reset LOS tracking
if (ThreatActor != CurrentBBTarget)
{
const EPS_AI_Behavior_State CurrentState = AIC->GetBehaviorState();
Memory->TimeSinceLOS = 0.0f;
Memory->bHadLOS = true;
Memory->bInvestigating = false;
Memory->LastVisiblePosition = ThreatActor->GetActorLocation();
BB->ClearValue(PS_AI_Behavior_BB::LastKnownTargetPosition);
// During TakingCover: don't switch target — cover is positioned against current threat.
// The flanking check will handle cover invalidation if needed.
if (CurrentBBTarget && CurrentState == EPS_AI_Behavior_State::TakingCover)
{
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("[%s] UpdateThreat: suppressed target switch to '%s' — in TakingCover (keeping '%s')"),
*AIC->GetName(), *ThreatActor->GetName(), *CurrentBBTarget->GetName());
ThreatActor = CurrentBBTarget; // Keep current target
}
else
{
Memory->TimeSinceLOS = 0.0f;
Memory->bHadLOS = true;
Memory->bInvestigating = false;
Memory->LastVisiblePosition = ThreatActor->GetActorLocation();
BB->ClearValue(PS_AI_Behavior_BB::LastKnownTargetPosition);
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] UpdateThreat: target switched to '%s', LOS tracking reset"),
*AIC->GetName(), *ThreatActor->GetName());
}
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] UpdateThreat: target switched to '%s', LOS tracking reset"),
*AIC->GetName(), *ThreatActor->GetName());
}
BB->SetValueAsObject(PS_AI_Behavior_BB::ThreatActor, ThreatActor);

View File

@ -220,15 +220,8 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
if (Memory->LOSCheckTimer <= 0.0f)
{
Memory->LOSCheckTimer = 0.5f;
// Check if threat can see NPC at chest height (not feet)
const FVector ThreatEye = Target->GetActorLocation() + FVector(0, 0, 60.0f);
const FVector NpcChest = Pawn->GetActorLocation() + FVector(0, 0, 60.0f);
FHitResult FlankHit;
FCollisionQueryParams FlankParams(SCENE_QUERY_STAT(FlankLOS), true);
FlankParams.AddIgnoredActor(Target);
FlankParams.AddIgnoredActor(Pawn);
const bool bThreatHasLOS = !Pawn->GetWorld()->LineTraceSingleByChannel(
FlankHit, ThreatEye, NpcChest, ECC_Visibility, FlankParams);
const bool bThreatHasLOS = UPS_AI_Behavior_Statics::HasLineOfSight(
Pawn->GetWorld(), Target, Pawn, 60.0f);
if (bThreatHasLOS)
{
UE_LOG(LogPS_AI_Behavior, Log,
@ -476,14 +469,6 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::TickTask(
DrawDebugString(World, Memory->FiringPosition + FVector(0, 0, 50.0f),
TEXT("FIRE"), nullptr, FColor::Red, 0.0f, true);
}
else if (Memory->SubState == EPS_AI_Behavior_CombatSubState::Peeking)
{
// No firing position — shooting in place
DrawDebugSphere(World, Pawn->GetActorLocation() + FVector(0, 0, 30.0f),
15.0f, 6, FColor::Orange, false, 0.0f);
DrawDebugString(World, Pawn->GetActorLocation() + FVector(0, 0, 50.0f),
TEXT("IN PLACE"), nullptr, FColor::Orange, 0.0f, true);
}
}
#endif
}
@ -702,23 +687,8 @@ void UPS_AI_Behavior_BTTask_CoverShootCycle::OnFiringPositionQueryFinished(
// EQS failed → fallback: peek in place if LOS exists
Memory->bHasFiringPosition = false;
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("[%s] CoverShootCycle: firing position EQS FAILED at cover %s — items=%d"),
*AIC->GetName(), *CoverLoc.ToString(), NumItems);
#if ENABLE_DRAW_DEBUG
if (bDebugDraw)
{
UWorld* World = Pawn->GetWorld();
const FVector PawnLoc = Pawn->GetActorLocation();
DrawDebugSphere(World, PawnLoc + FVector(0, 0, 30.0f),
20.0f, 8, FColor::Orange, false, 5.0f);
DrawDebugString(World, PawnLoc + FVector(0, 0, 55.0f),
TEXT("NO FIRE POS"), nullptr, FColor::Orange, 5.0f, true);
}
#endif
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] CoverShootCycle: firing position EQS failed, peeking in place"),
*AIC->GetName());
if (Target)
{

View File

@ -440,8 +440,18 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
// Log all items with their scores for debugging
const int32 NumSurvived = Result->Items.Num();
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement — %d items survived, refined %s → %s"),
*AIC->GetName(), NumSurvived, *OriginalCoverPos.ToString(), *FinalPos.ToString());
UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FindCover: EQS refinement — %d items survived filters:"),
*AIC->GetName(), NumSurvived);
for (int32 i = 0; i < NumSurvived; ++i)
{
if (!Result->Items[i].IsValid()) continue;
const FVector Loc = Result->GetItemAsLocation(i);
const float ItemScore = Result->GetItemScore(i);
UE_LOG(LogPS_AI_Behavior, Warning, TEXT(" [%d] %s score=%.3f %s"),
i, *Loc.ToString(), ItemScore, (i == 0) ? TEXT("← CHOSEN") : TEXT(""));
}
UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FindCover: refined %s → %s"),
*AIC->GetName(), *OriginalCoverPos.ToString(), *FinalPos.ToString());
#if ENABLE_DRAW_DEBUG
if (bDebugDraw)
@ -477,26 +487,8 @@ void UPS_AI_Behavior_BTTask_FindCover::OnRefinementQueryFinished(
}
else
{
const int32 NumItems = Result.IsValid() ? Result->Items.Num() : 0;
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("[%s] FindCover: EQS refinement FAILED — items=%d, valid=%s"),
*AIC->GetName(), NumItems,
Result.IsValid() ? TEXT("yes") : TEXT("no"));
#if ENABLE_DRAW_DEBUG
if (bDebugDraw)
{
UWorld* World = AIC->GetWorld();
if (World)
{
// Show that refinement failed — orange box at original position
DrawDebugBox(World, OriginalCoverPos + FVector(0, 0, 50.0f),
FVector(20.0f), FColor::Orange, false, 5.0f, 0, 2.5f);
DrawDebugString(World, OriginalCoverPos + FVector(0, 0, 75.0f),
TEXT("NO REFINE"), nullptr, FColor::Orange, 5.0f, true);
}
}
#endif
UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] FindCover: EQS refinement failed, using original cover position"),
*AIC->GetName());
}
// Update BB with refined position

View File

@ -6,7 +6,6 @@
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "EnvironmentQuery/Items/EnvQueryItemType_Point.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"
void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
@ -23,39 +22,19 @@ void UPS_AI_Behavior_EQSContext_CoverLocation::ProvideContext(
UBlackboardComponent* BB = AIC->GetBlackboardComponent();
if (!BB) return;
// Use CoverPoint actor location (center of cover geometry) for circle generation.
// Fallback to CoverLocation vector if no actor.
FVector RawLoc = FVector::ZeroVector;
const AActor* CoverPointActor = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
if (CoverPointActor)
// Prefer the original CoverPoint actor location (center of cover geometry)
// over the refined CoverLocation (which may be offset behind cover)
const AActor* CoverPoint = Cast<AActor>(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint));
if (CoverPoint)
{
RawLoc = CoverPointActor->GetActorLocation();
}
else
{
RawLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverPoint->GetActorLocation());
return;
}
if (RawLoc.IsZero()) return;
// Project to navmesh so the EQS generator works correctly
// (cover points inside geometry or above navmesh need projection)
FVector FinalLoc = RawLoc;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(QuerierPawn->GetWorld());
if (NavSys)
// Fallback to CoverLocation vector (procedural cover, no actor)
const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation);
if (!CoverLoc.IsZero())
{
FNavLocation NavLoc;
if (NavSys->ProjectPointToNavigation(RawLoc, NavLoc, FVector(200.0f, 200.0f, 200.0f)))
{
FinalLoc = NavLoc.Location;
}
UEnvQueryItemType_Point::SetContextHelper(ContextData, CoverLoc);
}
UE_LOG(LogPS_AI_Behavior, Verbose,
TEXT("[EQSContext_CoverLocation] %s '%s' raw=%s → nav=%s"),
CoverPointActor ? TEXT("CoverPoint") : TEXT("CoverLocation"),
CoverPointActor ? *CoverPointActor->GetName() : TEXT("(vector)"),
*RawLoc.ToString(), *FinalLoc.ToString());
UEnvQueryItemType_Point::SetContextHelper(ContextData, FinalLoc);
}

View File

@ -7,7 +7,6 @@
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
#include "CollisionQueryParams.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"
UPS_AI_Behavior_EQSTest_CoverQuality::UPS_AI_Behavior_EQSTest_CoverQuality()
{
@ -39,20 +38,6 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(CoverQualityEQS), true);
// Ignore the threat actor itself (e.g. AimTargetActor sphere blocks its own traces)
TArray<AActor*> ThreatActors;
if (QueryInstance.PrepareContext(UPS_AI_Behavior_EQSContext_Threat::StaticClass(), ThreatActors))
{
for (AActor* A : ThreatActors)
{
TraceParams.AddIgnoredActor(A);
for (AActor* Parent = A->GetAttachParentActor(); Parent; Parent = Parent->GetAttachParentActor())
{
TraceParams.AddIgnoredActor(Parent);
}
}
}
// Compute height steps
TArray<float> TraceHeights;
if (NumTraceHeights == 1)
@ -101,7 +86,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
{
const float HitDist = FVector::Dist(CandidatePos, Hit.ImpactPoint);
UE_LOG(LogPS_AI_Behavior, Verbose,
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: HIT '%s' dist=%.0fcm"),
It.GetIndex(), Height, Lateral,
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
@ -115,7 +100,7 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
}
else
{
UE_LOG(LogPS_AI_Behavior, Verbose,
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("CoverQuality[%d] h=%.0f lat=%.0f: NO HIT"),
It.GetIndex(), Height, Lateral);
}
@ -124,26 +109,12 @@ void UPS_AI_Behavior_EQSTest_CoverQuality::RunTest(FEnvQueryInstance& QueryInsta
const float Score = BlockedCount / static_cast<float>(TotalTraces);
UE_LOG(LogPS_AI_Behavior, Verbose,
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("CoverQuality[%d] at %s → score=%.2f (blocked=%d/%d)"),
It.GetIndex(), *CandidatePos.ToString(),
Score, (int32)BlockedCount, TotalTraces);
#if ENABLE_DRAW_DEBUG
if (bDrawDebug)
{
// Color: red(0) → yellow(0.5) → green(1)
const uint8 R = static_cast<uint8>(FMath::Lerp(255.0f, 0.0f, FMath::Clamp(Score, 0.0f, 1.0f)));
const uint8 G = static_cast<uint8>(FMath::Lerp(0.0f, 255.0f, FMath::Clamp(Score, 0.0f, 1.0f)));
DrawDebugBox(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 20.0f),
FVector(8.0f), FColor(R, G, 0), false, 5.0f);
DrawDebugString(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 35.0f),
FString::Printf(TEXT("%.0f%%"), Score * 100.0f), nullptr,
FColor(R, G, 0), 5.0f, true);
}
#endif
It.SetScore(TestPurpose, FilterType, Score, FloatValueMin.GetValue(), FloatValueMax.GetValue());
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
}
}

View File

@ -2,12 +2,10 @@
#include "EQS/PS_AI_Behavior_EQSTest_LineOfSight.h"
#include "EQS/PS_AI_Behavior_EQSContext_Threat.h"
#include "PS_AI_Behavior_Definitions.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h"
#include "CollisionQueryParams.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"
UPS_AI_Behavior_EQSTest_LineOfSight::UPS_AI_Behavior_EQSTest_LineOfSight()
{
@ -39,91 +37,20 @@ void UPS_AI_Behavior_EQSTest_LineOfSight::RunTest(FEnvQueryInstance& QueryInstan
FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(LOSTestEQS), true);
// Ignore the threat actor itself (e.g. AimTargetActor sphere blocks its own traces)
TArray<AActor*> ThreatActors;
if (QueryInstance.PrepareContext(UPS_AI_Behavior_EQSContext_Threat::StaticClass(), ThreatActors))
{
for (AActor* A : ThreatActors)
{
TraceParams.AddIgnoredActor(A);
// Also ignore parent chain (AimTarget → Character capsule)
for (AActor* Parent = A->GetAttachParentActor(); Parent; Parent = Parent->GetAttachParentActor())
{
TraceParams.AddIgnoredActor(Parent);
}
}
}
// Lateral offsets: center + optional left/right perpendicular to threat direction
TArray<float> LateralOffsets;
LateralOffsets.Add(0.0f);
if (LateralSpread > 0.0f)
{
LateralOffsets.Add(-LateralSpread);
LateralOffsets.Add(LateralSpread);
}
const int32 TotalTraces = LateralOffsets.Num();
const FVector TraceEnd = ThreatLoc + FVector(0, 0, TargetHeightOffset);
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
{
const FVector CandidatePos = GetItemLocation(QueryInstance, It.GetIndex());
const FVector TraceStart = CandidatePos + FVector(0, 0, TraceHeight);
// Direction from candidate to threat (2D) and its perpendicular
const FVector DirToThreat = (ThreatLoc - CandidatePos).GetSafeNormal2D();
const FVector LateralDir = FVector::CrossProduct(FVector::UpVector, DirToThreat);
FHitResult Hit;
const bool bBlocked = World->LineTraceSingleByChannel(
Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
int32 ClearCount = 0;
for (float Lateral : LateralOffsets)
{
const FVector TraceStart = CandidatePos + FVector(0, 0, TraceHeight) + LateralDir * Lateral;
const FVector TraceEnd = ThreatLoc + FVector(0, 0, TargetHeightOffset) + LateralDir * Lateral;
FHitResult Hit;
const bool bBlocked = World->LineTraceSingleByChannel(
Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
if (!bBlocked)
{
ClearCount++;
}
else
{
UE_LOG(LogPS_AI_Behavior, Verbose,
TEXT("LOS[%d] lat=%.0f: BLOCKED by '%s' at %s (dist=%.0fcm)"),
It.GetIndex(), Lateral,
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
*Hit.ImpactPoint.ToString(),
FVector::Dist(CandidatePos, Hit.ImpactPoint));
}
#if ENABLE_DRAW_DEBUG
if (bDrawDebug)
{
const FColor Color = bBlocked ? FColor::Red : FColor::Green;
DrawDebugLine(const_cast<UWorld*>(World), TraceStart, TraceEnd,
Color, false, 5.0f, 0, 0.5f);
}
#endif
}
// Score: ratio of clear traces (1.0 = all clear, 0.0 = all blocked)
const float Score = static_cast<float>(ClearCount) / static_cast<float>(TotalTraces);
UE_LOG(LogPS_AI_Behavior, Verbose,
TEXT("LOS[%d] at %s → clear=%d/%d score=%.2f"),
It.GetIndex(), *CandidatePos.ToString(),
ClearCount, TotalTraces, Score);
#if ENABLE_DRAW_DEBUG
if (bDrawDebug)
{
const uint8 G = static_cast<uint8>(Score * 255.0f);
DrawDebugSphere(const_cast<UWorld*>(World), CandidatePos + FVector(0, 0, 20.0f),
10.0f, 6, FColor(255 - G, G, 0), false, 5.0f);
}
#endif
It.SetScore(TestPurpose, FilterType, Score, FloatValueMin.GetValue(), FloatValueMax.GetValue());
// Score: 1.0 = clear LOS (not blocked), 0.0 = blocked
const float Score = bBlocked ? 0.0f : 1.0f;
It.SetScore(TestPurpose, FilterType, Score, 0.0f, 1.0f);
}
}

View File

@ -16,7 +16,6 @@
#include "GameFramework/SpectatorPawn.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "EngineUtils.h"
UPS_AI_Behavior_PerceptionComponent::UPS_AI_Behavior_PerceptionComponent()
{
@ -271,53 +270,6 @@ AActor* UPS_AI_Behavior_PerceptionComponent::GetHighestThreatActor(
TArray<AActor*> PerceivedActors;
GetCurrentlyPerceivedActors(nullptr, PerceivedActors); // All senses at once
// ─── Omniscient awareness for high-priority target types ────────
// Protectors (police, player) are always known if within sight radius,
// even outside the perception cone. This ensures enemies prioritize
// dangerous threats they're aware of (e.g. a cop standing next to them).
if (TargetPriority.Num() > 0)
{
const AAIController* OwnerAIC = Cast<AAIController>(GetOwner());
const APawn* OwnerPawn = OwnerAIC ? OwnerAIC->GetPawn() : nullptr;
if (OwnerPawn)
{
const EPS_AI_Behavior_TargetType TopPriority = TargetPriority[0];
const UPS_AI_Behavior_Settings* Settings = GetDefault<UPS_AI_Behavior_Settings>();
const float AwarenessRadius = Settings->DefaultSightRadius;
const FVector OwnerLoc = OwnerPawn->GetActorLocation();
for (TActorIterator<APawn> It(GetWorld()); It; ++It)
{
APawn* CandidatePawn = *It;
if (!CandidatePawn || CandidatePawn == OwnerPawn) continue;
// Already perceived → skip
if (PerceivedActors.Contains(CandidatePawn)) continue;
// Check distance
const float Dist = FVector::Dist(OwnerLoc, CandidatePawn->GetActorLocation());
if (Dist > AwarenessRadius) continue;
// Check if this actor matches the top priority type
const EPS_AI_Behavior_TargetType CandidateType = ClassifyActor(CandidatePawn);
const EPS_AI_Behavior_TargetType MappedType =
(CandidateType == EPS_AI_Behavior_TargetType::Player)
? EPS_AI_Behavior_TargetType::Protector
: CandidateType;
if (MappedType == TopPriority)
{
PerceivedActors.Add(CandidatePawn);
UE_LOG(LogPS_AI_Behavior, Log,
TEXT("[%s] Omniscient awareness: added '%s' (type=%d, dist=%.0f) — top priority target"),
*OwnerPawn->GetName(), *CandidatePawn->GetName(),
static_cast<int32>(CandidateType), Dist);
}
}
}
}
if (PerceivedActors.Num() == 0)
{
return nullptr;

View File

@ -25,11 +25,11 @@ public:
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "1", ClampMax = "5"))
int32 NumTraceHeights = 3;
/** Minimum height for the lowest trace (cm relative to item — items are at navmesh level). */
/** Minimum height for the lowest trace (cm relative to item — items are at capsule center, ~90cm above ground). */
UPROPERTY(EditAnywhere, Category = "Cover")
float MinTraceHeight = 0.0f;
/** Maximum height for the highest trace (cm relative to item — 70 ≈ knee-to-waist from navmesh). */
/** Maximum height for the highest trace (cm relative to item — 70 ≈ top of head from capsule center). */
UPROPERTY(EditAnywhere, Category = "Cover")
float MaxTraceHeight = 70.0f;
@ -48,9 +48,6 @@ public:
UPROPERTY(EditAnywhere, Category = "Cover", meta = (ClampMin = "0.0", ClampMax = "100.0"))
float LateralSpread = 30.0f;
/** Draw debug boxes for each candidate with color-coded score. */
UPROPERTY(EditAnywhere, Category = "Debug")
bool bDrawDebug = false;
protected:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;

View File

@ -31,18 +31,6 @@ public:
UPROPERTY(EditDefaultsOnly, Category = "LOS", meta = (ClampMin = "0.0"))
float TargetHeightOffset = 100.0f;
/**
* Lateral offset (cm) for side traces perpendicular to threat direction.
* 0 = center trace only, 30 = adds traces at ±30cm.
* Requires wider clear LOS, not just a narrow slit past cover edge.
*/
UPROPERTY(EditAnywhere, Category = "LOS", meta = (ClampMin = "0.0", ClampMax = "100.0"))
float LateralSpread = 0.0f;
/** Draw debug spheres and LOS lines for each candidate. */
UPROPERTY(EditAnywhere, Category = "Debug")
bool bDrawDebug = false;
protected:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
virtual FText GetDescriptionTitle() const override;