diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.dll b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.dll index b8642a2..f55ea13 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.dll and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.dll differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.exp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.exp index 8acac19..0de7db8 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.exp and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.exp differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.exe b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.exe index 52e6e9e..23fb2c1 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.exe and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.exe differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.pdb index 8695149..8958b16 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_0.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.exe b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.exe index b383d63..bba697a 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.exe and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.exe differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.pdb index a7a5b2d..f7642b4 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_1.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.exe b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.exe index caef3cb..7a019c8 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.exe and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.exe differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.pdb index dff3741..3f7fc82 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_2.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.exe b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.exe index 8bfaca5..3f930d4 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.exe and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.exe differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.pdb index 5c7bf48..66c2043 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.patch_3.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.pdb index 486e792..16bcc47 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.dll b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.dll index e386ca4..19c234d 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.dll and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.dll differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.exp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.exp index 996d269..4f83f9f 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.exp and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.exp differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.patch_0.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.patch_0.pdb index 1d3fe88..12073ee 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.patch_0.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.patch_0.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.pdb index 4233b28..a7d6083 100644 Binary files a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.pdb and b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.pdb differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_UpdateThreat.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_UpdateThreat.cpp index ba2cb1d..7aa640b 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_UpdateThreat.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_UpdateThreat.cpp @@ -4,6 +4,7 @@ #include "PS_AI_Behavior_AIController.h" #include "PS_AI_Behavior_PerceptionComponent.h" #include "PS_AI_Behavior_PersonalityComponent.h" +#include "PS_AI_Behavior_PersonalityProfile.h" #include "PS_AI_Behavior_Statics.h" #include "PS_AI_Behavior_Settings.h" #include "PS_AI_Behavior_Definitions.h" @@ -34,6 +35,8 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode( return; } + UPS_AI_Behavior_PersonalityComponent* Personality = AIC->GetPersonalityComponent(); + // Debug: check what perception sees TArray PerceivedActors; Perception->GetCurrentlyPerceivedActors(nullptr, PerceivedActors); @@ -60,8 +63,13 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode( const float StoredThreat = BB->GetValueAsFloat(PS_AI_Behavior_BB::ThreatLevel); // Apply decay when no threat, or take the max of new vs decayed - const UPS_AI_Behavior_Settings* Settings = GetDefault(); - const float DecayedThreat = FMath::Max(0.0f, StoredThreat - Settings->ThreatDecayRate * DeltaSeconds); + // Decay rate comes from PersonalityProfile (per-archetype), fallback to global Settings + float DecayRate = GetDefault()->ThreatDecayRate; + if (Personality && Personality->Profile) + { + DecayRate = Personality->Profile->ThreatDecayRate; + } + const float DecayedThreat = FMath::Max(0.0f, StoredThreat - DecayRate * DeltaSeconds); const float FinalThreat = FMath::Max(RawThreat, DecayedThreat); BB->SetValueAsFloat(PS_AI_Behavior_BB::ThreatLevel, FinalThreat); @@ -261,7 +269,6 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode( } // Sync to PersonalityComponent - UPS_AI_Behavior_PersonalityComponent* Personality = AIC->GetPersonalityComponent(); if (Personality) { Personality->PerceivedThreatLevel = FinalThreat; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_Attack.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_Attack.cpp index a92d81a..85418da 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_Attack.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_Attack.cpp @@ -103,17 +103,19 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::ExecuteTask( const float ApproachRange = (Memory->CombatType == EPS_AI_Behavior_CombatType::Melee) ? Memory->MinRange * 0.5f : (Memory->MinRange + Memory->MaxRange) * 0.5f; + // Start attack immediately (draw weapon, enter combat stance) + // regardless of range or LOS — the NPC is committed to combat + if (Pawn->Implements()) + { + IPS_AI_Behavior_Interface::Execute_BehaviorStartAttack(Pawn, Target); + Memory->bAttacking = true; + } + // Check if already in range const float DistToTarget = FVector::Dist(Pawn->GetActorLocation(), Target->GetActorLocation()); if (DistToTarget <= Memory->MaxRange) { Memory->bInRange = true; - // Only start attacking if we have LOS (ranged) or always (melee) - if (Memory->bHasLOS && Pawn->Implements()) - { - IPS_AI_Behavior_Interface::Execute_BehaviorStartAttack(Pawn, Target); - Memory->bAttacking = true; - } } // Initial move toward target if not in range diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_CoverShootCycle.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_CoverShootCycle.cpp index 6fb8e19..76e4de3 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_CoverShootCycle.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_CoverShootCycle.cpp @@ -132,6 +132,13 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_CoverShootCycle::ExecuteTask( // Face the threat while in cover AIC->SetFocus(Target); + // Start attack immediately (draw weapon, enter combat stance) + APawn* Pawn = AIC->GetPawn(); + if (Pawn && Pawn->Implements()) + { + IPS_AI_Behavior_Interface::Execute_BehaviorStartAttack(Pawn, Target); + } + if (Result == EPathFollowingRequestResult::AlreadyAtGoal) { Memory->SubState = EPS_AI_Behavior_CombatSubState::AtCover; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindAndFollowSpline.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindAndFollowSpline.cpp index 07ce145..521c268 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindAndFollowSpline.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindAndFollowSpline.cpp @@ -148,34 +148,64 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindAndFollowSpline::ExecuteTask( if (bWalkToSpline && GapToSpline > AcceptanceRadius) { - // Walk to spline first via NavMesh - const EPathFollowingRequestResult::Type Result = AIC->MoveToLocation( - SplinePoint, AcceptanceRadius, /*bStopOnOverlap=*/true, - /*bUsePathfinding=*/true, /*bProjectDestinationToNavigation=*/true, - /*bCanStrafe=*/false); + // Try to reach the closest spline point. If blocked, sample other points along the spline. + const float SplineLength = ClosestSpline->GetSplineLength(); + const int32 NumSamples = 8; + const float SampleStep = SplineLength / NumSamples; - if (Result == EPathFollowingRequestResult::Failed) + // Start with the closest point, then try evenly spaced samples + TArray DistancesToTry; + DistancesToTry.Add(DistAlongSpline); + for (int32 i = 1; i <= NumSamples; ++i) { - // Can't reach via NavMesh — try starting anyway (snap) - const FVector Fwd = AIC->GetPawn()->GetActorForwardVector(); - const FVector SpDir = ClosestSpline->GetWorldDirectionAtDistance(DistAlongSpline); - Follower->StartFollowingAtDistance(ClosestSpline, DistAlongSpline, - FVector::DotProduct(Fwd, SpDir) >= 0.0f); - return EBTNodeResult::Succeeded; + const float D = FMath::Fmod(DistAlongSpline + i * SampleStep, SplineLength); + DistancesToTry.Add(D); } - if (Result == EPathFollowingRequestResult::AlreadyAtGoal) + float ReachableDist = -1.0f; + FVector ReachablePoint = FVector::ZeroVector; + + for (float TestDist : DistancesToTry) { - const FVector Fwd = AIC->GetPawn()->GetActorForwardVector(); - const FVector SpDir = ClosestSpline->GetWorldDirectionAtDistance(DistAlongSpline); - Follower->StartFollowingAtDistance(ClosestSpline, DistAlongSpline, - FVector::DotProduct(Fwd, SpDir) >= 0.0f); - return EBTNodeResult::Succeeded; + const FVector TestPoint = ClosestSpline->GetWorldLocationAtDistance(TestDist); + const EPathFollowingRequestResult::Type TestResult = AIC->MoveToLocation( + TestPoint, AcceptanceRadius, true, true, true, false); + + if (TestResult == EPathFollowingRequestResult::AlreadyAtGoal) + { + const FVector Fwd = AIC->GetPawn()->GetActorForwardVector(); + const FVector SpDir = ClosestSpline->GetWorldDirectionAtDistance(TestDist); + Follower->StartFollowingAtDistance(ClosestSpline, TestDist, + FVector::DotProduct(Fwd, SpDir) >= 0.0f); + return EBTNodeResult::Succeeded; + } + + if (TestResult != EPathFollowingRequestResult::Failed) + { + ReachableDist = TestDist; + ReachablePoint = TestPoint; + break; // Found a reachable point, use it + } + + // This point is blocked, try next + AIC->StopMovement(); } - // Store the spline to connect to after reaching it + if (ReachableDist < 0.0f) + { + UE_LOG(LogPS_AI_Behavior, Log, + TEXT("[%s] FindAndFollowSpline: no reachable point on spline '%s' (%d samples tested)"), + *AIC->GetName(), *ClosestSpline->GetName(), DistancesToTry.Num()); + return EBTNodeResult::Failed; + } + + UE_LOG(LogPS_AI_Behavior, Log, + TEXT("[%s] FindAndFollowSpline: walking to reachable spline point at dist=%.0f (gap=%.0fcm)"), + *AIC->GetName(), ReachableDist, + FVector::Dist(AIC->GetPawn()->GetActorLocation(), ReachablePoint)); + Follower->CurrentSpline = ClosestSpline; - Follower->CurrentDistance = DistAlongSpline; + Follower->CurrentDistance = ReachableDist; FFindSplineMemory* Memory = reinterpret_cast(NodeMemory); Memory->bMovingToSpline = true; diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindCover.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindCover.cpp index d41b801..fac5577 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindCover.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FindCover.cpp @@ -207,21 +207,51 @@ void UPS_AI_Behavior_BTTask_FindCover::TickTask( if (AIC->GetMoveStatus() == EPathFollowingStatus::Idle) { + // Verify we actually reached the cover — pathfinding can stop early (blocked by other NPCs, etc.) + UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent(); + APawn* Pawn = AIC->GetPawn(); + if (BB && Pawn) + { + const FVector CoverLoc = BB->GetValueAsVector(PS_AI_Behavior_BB::CoverLocation); + const float DistToCover = FVector::Dist2D(Pawn->GetActorLocation(), CoverLoc); + + UE_LOG(LogPS_AI_Behavior, Log, + TEXT("[%s] FindCover: MoveStatus=Idle, dist2D=%.0fcm, acceptance=%.0fcm, coverLoc=%s, pawnLoc=%s"), + *AIC->GetName(), DistToCover, AcceptanceRadius, + *CoverLoc.ToString(), *Pawn->GetActorLocation().ToString()); + + if (DistToCover > AcceptanceRadius * 2.0f) + { + // Still too far — retry movement + const EPathFollowingRequestResult::Type Retry = AIC->MoveToLocation( + CoverLoc, AcceptanceRadius, true, true, true, false); + + if (Retry == EPathFollowingRequestResult::Failed) + { + // Can't reach at all — give up + Memory->bMoveRequested = false; + FinishLatentTask(OwnerComp, EBTNodeResult::Failed); + return; + } + + if (Retry != EPathFollowingRequestResult::AlreadyAtGoal) + { + return; // Keep waiting for retry movement + } + // AlreadyAtGoal — fall through to success + } + } + Memory->bMoveRequested = false; // Crouch at cover if the point requires it - APawn* Pawn = AIC->GetPawn(); - if (Pawn && Pawn->Implements()) + if (Pawn && Pawn->Implements() && BB) { - UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent(); - if (BB) + const APS_AI_Behavior_CoverPoint* CoverPt = + Cast(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint)); + if (CoverPt && CoverPt->bCrouch) { - const APS_AI_Behavior_CoverPoint* CoverPt = - Cast(BB->GetValueAsObject(PS_AI_Behavior_BB::CoverPoint)); - if (CoverPt && CoverPt->bCrouch) - { - IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true); - } + IPS_AI_Behavior_Interface::Execute_SetBehaviorCrouch(Pawn, true); } } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp index 0df463c..68a2f27 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_AIController.cpp @@ -204,6 +204,7 @@ void APS_AI_Behavior_AIController::SetupBlackboard() Blackboard->SetValueAsInt(PS_AI_Behavior_BB::PatrolIndex, 0); Blackboard->SetValueAsEnum(PS_AI_Behavior_BB::State, static_cast(EPS_AI_Behavior_State::Idle)); + Blackboard->SetValueAsBool(PS_AI_Behavior_BB::PreferCover, false); } } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PerceptionComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PerceptionComponent.cpp index 55bfcae..a841d2a 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PerceptionComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PerceptionComponent.cpp @@ -96,7 +96,7 @@ APawn* UPS_AI_Behavior_PerceptionComponent::FindOwningPawn(AActor* Actor) return ActorAsPawn; } - // Not a Pawn — walk up Owner/Instigator chain to find the owning Pawn + // Not a Pawn — walk up Owner/Instigator/Attachment chain to find the owning Pawn AActor* Current = Actor; for (int32 Depth = 0; Depth < 4; ++Depth) // Safety limit { @@ -108,14 +108,29 @@ APawn* UPS_AI_Behavior_PerceptionComponent::FindOwningPawn(AActor* Actor) // Try Owner AActor* OwnerActor = Current->GetOwner(); - if (!OwnerActor || OwnerActor == Current) break; - - if (APawn* OwnerPawn = Cast(OwnerActor)) + if (OwnerActor && OwnerActor != Current) { - return OwnerPawn; + if (APawn* OwnerPawn = Cast(OwnerActor)) + { + return OwnerPawn; + } + Current = OwnerActor; + continue; } - Current = OwnerActor; // Continue up the chain + // Try attachment parent (ChildActorComponent → parent Character) + AActor* ParentActor = Current->GetAttachParentActor(); + if (ParentActor && ParentActor != Current) + { + if (APawn* ParentPawn = Cast(ParentActor)) + { + return ParentPawn; + } + Current = ParentActor; + continue; + } + + break; // No more chain to walk } // Fallback: could not resolve to a Pawn. diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PersonalityComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PersonalityComponent.cpp index 43e3528..a8676a2 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PersonalityComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_PersonalityComponent.cpp @@ -93,13 +93,32 @@ EPS_AI_Behavior_State UPS_AI_Behavior_PersonalityComponent::EvaluateReaction() c PerceivedThreatLevel, Aggressivity, Courage, Caution, EffectiveAttackThresh, EffectiveFleeThresh, AlertThresh); - // Decision cascade - if (PerceivedThreatLevel >= EffectiveFleeThresh && Courage < 0.7f) + // Decision cascade — with hysteresis to prevent state flickering. + // Once in a state, require a larger threshold drop to leave it. + const float Hysteresis = 0.15f; + + if (CurrentState == EPS_AI_Behavior_State::Fleeing) + { + // Stay fleeing until threat drops well below the threshold + if (PerceivedThreatLevel >= (EffectiveFleeThresh - Hysteresis)) + { + return EPS_AI_Behavior_State::Fleeing; + } + } + else if (PerceivedThreatLevel >= EffectiveFleeThresh && Courage < 0.7f) { return EPS_AI_Behavior_State::Fleeing; } - if (PerceivedThreatLevel >= EffectiveAttackThresh && Aggressivity > 0.0f) + if (CurrentState == EPS_AI_Behavior_State::Combat) + { + // Stay in combat until threat drops well below the threshold + if (PerceivedThreatLevel >= (EffectiveAttackThresh - Hysteresis)) + { + return EPS_AI_Behavior_State::Combat; + } + } + else if (PerceivedThreatLevel >= EffectiveAttackThresh && Aggressivity > 0.0f) { return EPS_AI_Behavior_State::Combat; } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h index df2e089..d23a18b 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_PersonalityProfile.h @@ -79,6 +79,15 @@ public: meta = (ClampMin = "0.0", ClampMax = "1.0")) float AlertThreshold = 0.15f; + /** + * How fast the perceived threat level decays per second when no active threat is perceived. + * Low values = NPC stays scared/alert longer (civilians). + * High values = NPC calms down quickly (trained soldiers). + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Personality|Reaction", + meta = (ClampMin = "0.001", ClampMax = "1.0")) + float ThreatDecayRate = 0.05f; + // ─── Target Priority (Combat) ─────────────────────────────────────── /**