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. */