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 164cc95..6472f6b 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 0b5801b..f4671b9 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.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_Behavior.pdb index 4e0813b..637d738 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 70a69f0..e8a819c 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.pdb b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Binaries/Win64/UnrealEditor-PS_AI_BehaviorEditor.pdb index d565db5..7349624 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_EvaluateReaction.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_EvaluateReaction.cpp index 8ec530c..f7aadfb 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_EvaluateReaction.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Private/BT/PS_AI_Behavior_BTService_EvaluateReaction.cpp @@ -25,6 +25,9 @@ void UPS_AI_Behavior_BTService_EvaluateReaction::TickNode( APS_AI_Behavior_AIController* AIC = Cast(OwnerComp.GetAIOwner()); if (!AIC) { UE_LOG(LogPS_AI_Behavior, Warning, TEXT("EvaluateReaction: no AIC!")); return; } + // Scripted state: external control — don't touch the state + if (AIC->GetBehaviorState() == EPS_AI_Behavior_State::Scripted) { return; } + UPS_AI_Behavior_PersonalityComponent* Personality = AIC->GetPersonalityComponent(); if (!Personality) { UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] EvaluateReaction: no PersonalityComponent!"), *AIC->GetName()); return; } 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 cb0ad86..979d20e 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 @@ -25,6 +25,9 @@ void UPS_AI_Behavior_BTService_UpdateThreat::TickNode( APS_AI_Behavior_AIController* AIC = Cast(OwnerComp.GetAIOwner()); if (!AIC) return; + // Scripted state: external control — don't accumulate threat + if (AIC->GetBehaviorState() == EPS_AI_Behavior_State::Scripted) { return; } + UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent(); if (!BB) return; 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 ff81b07..8e8699d 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 @@ -80,12 +80,24 @@ void APS_AI_Behavior_AIController::OnPossess(APawn* InPawn) } SetupBlackboard(); - StartBehavior(); + + if (bAutoStartBehavior) + { + StartBehavior(); + } + else + { + // Stay in Scripted state — no BT, no services, controlled externally + SetBehaviorState(EPS_AI_Behavior_State::Scripted); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] bAutoStartBehavior=false — entering Scripted state."), + *GetName()); + } + TryBindConversationAgent(); TryBindGazeComponent(); - UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Possessed Pawn '%s' — BT started, TeamId=%d."), - *GetName(), *InPawn->GetName(), TeamId); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Possessed Pawn '%s' — TeamId=%d, AutoStart=%d."), + *GetName(), *InPawn->GetName(), TeamId, (int32)bAutoStartBehavior); } void APS_AI_Behavior_AIController::OnUnPossess() @@ -227,24 +239,84 @@ void APS_AI_Behavior_AIController::SetupBlackboard() void APS_AI_Behavior_AIController::StartBehavior() { + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] StartBehavior called. Pawn=%s, HasBB=%d"), + *GetName(), + GetPawn() ? *GetPawn()->GetName() : TEXT("null"), + Blackboard != nullptr); + + // Check if a BT was previously loaded and stopped (StopBehavior case) + UBehaviorTreeComponent* BTComp = Cast(GetBrainComponent()); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] BrainComponent=%s, BTComp=%s, CurrentTree=%s, IsRunning=%d"), + *GetName(), + GetBrainComponent() ? *GetBrainComponent()->GetName() : TEXT("null"), + BTComp ? *BTComp->GetName() : TEXT("null"), + (BTComp && BTComp->GetCurrentTree()) ? *BTComp->GetCurrentTree()->GetName() : TEXT("null"), + BTComp ? (int32)BTComp->IsRunning() : -1); + + if (BTComp && BTComp->GetCurrentTree() && !BTComp->IsRunning()) + { + BTComp->RestartLogic(); + + if (GetBehaviorState() == EPS_AI_Behavior_State::Scripted) + { + SetBehaviorState(EPS_AI_Behavior_State::Idle); + } + + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] StartBehavior — BT restarted (RestartLogic). IsRunning=%d"), + *GetName(), (int32)BTComp->IsRunning()); + return; + } + + // First-time start: load and run the BT UBehaviorTree* BTToRun = BehaviorTreeAsset; + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] BehaviorTreeAsset=%s"), + *GetName(), BTToRun ? *BTToRun->GetName() : TEXT("null")); // Fallback: get from personality profile if (!BTToRun && PersonalityComp && PersonalityComp->Profile) { BTToRun = PersonalityComp->Profile->DefaultBehaviorTree.LoadSynchronous(); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Loaded from Profile: %s"), + *GetName(), BTToRun ? *BTToRun->GetName() : TEXT("null")); } - if (BTToRun) - { - RunBehaviorTree(BTToRun); - } - else + if (!BTToRun) { UE_LOG(LogPS_AI_Behavior, Warning, - TEXT("[%s] No BehaviorTree assigned and none in PersonalityProfile — NPC will be inert."), + TEXT("[%s] StartBehavior FAILED — No BehaviorTree assigned and none in PersonalityProfile."), *GetName()); + return; } + + const bool bSuccess = RunBehaviorTree(BTToRun); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] RunBehaviorTree('%s') returned %d"), + *GetName(), *BTToRun->GetName(), (int32)bSuccess); + + // Check post-run state + BTComp = Cast(GetBrainComponent()); + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Post-run: BTComp=%s, IsRunning=%d, BB=%s"), + *GetName(), + BTComp ? *BTComp->GetName() : TEXT("null"), + BTComp ? (int32)BTComp->IsRunning() : -1, + Blackboard ? TEXT("valid") : TEXT("null")); + + if (bSuccess && GetBehaviorState() == EPS_AI_Behavior_State::Scripted) + { + SetBehaviorState(EPS_AI_Behavior_State::Idle); + } +} + +void APS_AI_Behavior_AIController::StopBehavior() +{ + if (UBrainComponent* Brain = GetBrainComponent()) + { + Brain->StopLogic(TEXT("Scripted")); + } + + StopMovement(); + SetBehaviorState(EPS_AI_Behavior_State::Scripted); + + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] StopBehavior — BT stopped, entering Scripted state."), *GetName()); } void APS_AI_Behavior_AIController::SetBehaviorState(EPS_AI_Behavior_State NewState) @@ -274,6 +346,12 @@ void APS_AI_Behavior_AIController::SetBehaviorState(EPS_AI_Behavior_State NewSta } } + // ─── Scripted: stop movement, NPC stays alive ────────────── + if (NewState == EPS_AI_Behavior_State::Scripted) + { + StopMovement(); + } + // ─── Dead: shut down all AI systems ───────────────────────── if (NewState == EPS_AI_Behavior_State::Dead) { 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 b503fcd..f171d06 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 @@ -72,6 +72,11 @@ EPS_AI_Behavior_State UPS_AI_Behavior_PersonalityComponent::EvaluateReaction() c return EPS_AI_Behavior_State::Dead; } + if (CurrentState == EPS_AI_Behavior_State::Scripted) + { + return EPS_AI_Behavior_State::Scripted; + } + const float Courage = GetTrait(EPS_AI_Behavior_TraitAxis::Courage); const float Aggressivity = GetTrait(EPS_AI_Behavior_TraitAxis::Aggressivity); const float Caution = GetTrait(EPS_AI_Behavior_TraitAxis::Caution); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h index 5a072b2..9081275 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_AIController.h @@ -59,6 +59,10 @@ public: // ─── Configuration ────────────────────────────────────────────────── + /** If false, the Behavior Tree does NOT start on possess. Call StartBehavior() manually. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior") + bool bAutoStartBehavior = true; + /** Behavior Tree to run. If null, uses the Profile's DefaultBehaviorTree. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior") TObjectPtr BehaviorTreeAsset; @@ -107,6 +111,14 @@ public: UFUNCTION(BlueprintCallable, Category = "PS AI Behavior|Blackboard") EPS_AI_Behavior_State GetBehaviorState() const; + /** Start (or restart) the Behavior Tree. If state is Scripted, transitions to Idle. */ + UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + void StartBehavior(); + + /** Stop the Behavior Tree and enter Scripted state. NPC stays alive and perceptible. */ + UFUNCTION(BlueprintCallable, Category = "PS AI Behavior") + void StopBehavior(); + protected: virtual void OnPossess(APawn* InPawn) override; virtual void OnUnPossess() override; @@ -126,9 +138,6 @@ private: /** Initialize Blackboard with required keys. */ void SetupBlackboard(); - /** Start the Behavior Tree (from asset or profile). */ - void StartBehavior(); - /** * Attempt to bind to PS_AI_ConvAgent_ElevenLabsComponent if present on the Pawn. * Uses UObject reflection — no compile-time dependency on PS_AI_ConvAgent. diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Definitions.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Definitions.h index f8aa571..d0c8942 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Definitions.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Definitions.h @@ -43,6 +43,7 @@ enum class EPS_AI_Behavior_State : uint8 Fleeing UMETA(DisplayName = "Fleeing"), TakingCover UMETA(DisplayName = "Taking Cover"), Dead UMETA(DisplayName = "Dead"), + Scripted UMETA(DisplayName = "Scripted"), }; /**