From cfade2a265ef953d2b47576408b1a062fc32432e Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Mon, 1 Jun 2026 09:40:56 +0200 Subject: [PATCH] PS_AI_Behavior: make spline StopFollowing a firm, BT-respected stop StopFollowing() now sets a bManuallyStopped flag (cleared only by an explicit StartFollowing/StartFollowingAtDistance). The FindAndFollowSpline and FollowSpline BT tasks bail while it is set, so the BT no longer auto-resumes a manual stop on the next tick. ResumeFollowing() also respects the flag. PauseFollowing() is unchanged (BT may resume). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...AI_Behavior_BTTask_FindAndFollowSpline.cpp | 20 +++++++++++++++++++ .../BT/PS_AI_Behavior_BTTask_FollowSpline.cpp | 6 ++++++ ...PS_AI_Behavior_SplineFollowerComponent.cpp | 10 ++++++++-- .../PS_AI_Behavior_SplineFollowerComponent.h | 7 +++++++ 4 files changed, 41 insertions(+), 2 deletions(-) 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 c8b109b..939d4d5 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 @@ -32,6 +32,14 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FindAndFollowSpline::ExecuteTask( return EBTNodeResult::Failed; } + // Respect a manual StopFollowing(): do not search for or resume a spline until + // the caller explicitly StartFollowing() again. Without this, the task would + // resume any CurrentSpline and override the stop on the very next tick. + if (Follower->bManuallyStopped) + { + return EBTNodeResult::Failed; + } + // Debug: log state on entry if (UPS_AI_Behavior_Statics::IsDebugEnabled(AIC->GetPawn())) { @@ -268,6 +276,18 @@ void UPS_AI_Behavior_BTTask_FindAndFollowSpline::TickTask( return; } + // Manual StopFollowing() requested mid-approach: cancel the move-to-spline and bail. + if (UPS_AI_Behavior_SplineFollowerComponent* StopCheck = + AIC->GetPawn()->FindComponentByClass()) + { + if (StopCheck->bManuallyStopped) + { + AIC->StopMovement(); + FinishLatentTask(OwnerComp, EBTNodeResult::Failed); + return; + } + } + // Check if we've reached the spline if (AIC->GetMoveStatus() == EPathFollowingStatus::Idle) { diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FollowSpline.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FollowSpline.cpp index a86f056..94a261e 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FollowSpline.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTTask_FollowSpline.cpp @@ -24,6 +24,12 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_FollowSpline::ExecuteTask( AIC->GetPawn()->FindComponentByClass(); if (!Follower) return EBTNodeResult::Failed; + // Respect a manual StopFollowing(): refuse to resume until an explicit restart. + if (Follower->bManuallyStopped) + { + return EBTNodeResult::Failed; + } + if (!Follower->CurrentSpline) { UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] FollowSpline: no current spline set."), diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_SplineFollowerComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_SplineFollowerComponent.cpp index 03e8771..80345ae 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_SplineFollowerComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/PS_AI_Behavior_SplineFollowerComponent.cpp @@ -58,6 +58,7 @@ bool UPS_AI_Behavior_SplineFollowerComponent::StartFollowingAtDistance( CurrentDistance = FMath::Clamp(StartDistance, 0.0f, Spline->GetSplineLength()); bMovingForward = bForward; bIsFollowing = true; + bManuallyStopped = false; // an explicit Start lifts any previous manual stop LastHandledJunctionIndex = -1; UE_LOG(LogPS_AI_Behavior, Warning, @@ -82,18 +83,23 @@ bool UPS_AI_Behavior_SplineFollowerComponent::StartFollowingAtDistance( void UPS_AI_Behavior_SplineFollowerComponent::StopFollowing() { bIsFollowing = false; - UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] Stopped following spline."), + // Firm, manual stop: the Behavior Tree spline tasks will refuse to re-start + // following until StartFollowing()/StartFollowingAtDistance() is called again. + bManuallyStopped = true; + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] StopFollowing: manual stop (BT will not auto-resume)."), GetOwner() ? *GetOwner()->GetName() : TEXT("?")); } void UPS_AI_Behavior_SplineFollowerComponent::PauseFollowing() { + // Pause is NOT a manual stop — the BT is allowed to resume it. bIsFollowing = false; } void UPS_AI_Behavior_SplineFollowerComponent::ResumeFollowing() { - if (CurrentSpline) + // Do not resume over a manual stop — only an explicit Start lifts that. + if (CurrentSpline && !bManuallyStopped) { bIsFollowing = true; } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h index 4468eb2..40f2b46 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_SplineFollowerComponent.h @@ -106,6 +106,13 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Spline Follower|Runtime") bool bIsFollowing = false; + /** Set by StopFollowing(): a firm, manual stop. While true, the Behavior Tree + * spline tasks refuse to (re)start following — only an explicit StartFollowing() + * / StartFollowingAtDistance() clears it. This is what distinguishes a deliberate + * stop from PauseFollowing() (which the BT is allowed to auto-resume). */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spline Follower|Runtime") + bool bManuallyStopped = false; + // ─── Delegates ────────────────────────────────────────────────────── /** Fired when approaching a junction. Use to make custom spline selection. */