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 72af36e..e017520 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 @@ -2,6 +2,7 @@ #include "BT/PS_AI_Behavior_BTService_EvaluateReaction.h" #include "PS_AI_Behavior_AIController.h" +#include "PS_AI_Behavior_Interface.h" #include "PS_AI_Behavior_PersonalityComponent.h" #include "PS_AI_Behavior_Definitions.h" #include "BehaviorTree/BlackboardComponent.h" @@ -27,7 +28,33 @@ void UPS_AI_Behavior_BTService_EvaluateReaction::TickNode( UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent(); if (!BB) return; - // Evaluate and apply the reaction + // ─── Check for hostility change → update TeamId dynamically ──────── + APawn* Pawn = AIC->GetPawn(); + if (Pawn && Pawn->Implements()) + { + const bool bHostile = IPS_AI_Behavior_Interface::Execute_IsBehaviorHostile(Pawn); + const EPS_AI_Behavior_NPCType NPCType = IPS_AI_Behavior_Interface::Execute_GetBehaviorNPCType(Pawn); + + // An infiltrated Enemy (hostile=false) has TeamId=1 (civilian disguise). + // When hostile flips to true, switch to TeamId=2 (enemy). + uint8 ExpectedTeamId; + switch (NPCType) + { + case EPS_AI_Behavior_NPCType::Civilian: ExpectedTeamId = 1; break; + case EPS_AI_Behavior_NPCType::Enemy: ExpectedTeamId = bHostile ? 2 : 1; break; + case EPS_AI_Behavior_NPCType::Protector: ExpectedTeamId = 3; break; + default: ExpectedTeamId = FGenericTeamId::NoTeam; break; + } + + if (AIC->GetGenericTeamId().GetId() != ExpectedTeamId) + { + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] Hostility changed: TeamId %d -> %d (hostile=%d)"), + *AIC->GetName(), AIC->GetGenericTeamId().GetId(), ExpectedTeamId, (int32)bHostile); + AIC->SetTeamId(ExpectedTeamId); + } + } + + // ─── Evaluate and apply the reaction ──────────────────────────────── const EPS_AI_Behavior_State NewState = Personality->ApplyReaction(); // Write to Blackboard 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 764dc32..678ee7c 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 @@ -27,6 +27,7 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::ExecuteTask( AActor* Target = Cast(BB->GetValueAsObject(PS_AI_Behavior_BB::ThreatActor)); if (!Target) { + UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] Attack: no ThreatActor in BB."), *AIC->GetName()); return EBTNodeResult::Failed; } @@ -36,19 +37,23 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::ExecuteTask( if (!Combat) { UE_LOG(LogPS_AI_Behavior, Warning, - TEXT("[%s] Attack task: no CombatComponent on Pawn."), *AIC->GetName()); + TEXT("[%s] Attack: no CombatComponent on Pawn."), *AIC->GetName()); return EBTNodeResult::Failed; } + UE_LOG(LogPS_AI_Behavior, Verbose, TEXT("[%s] Attack: target='%s', range=%.0f, dist=%.0f, inRange=%d, canAttack=%d"), + *AIC->GetName(), *Target->GetName(), Combat->AttackRange, + FVector::Dist(AIC->GetPawn()->GetActorLocation(), Target->GetActorLocation()), + (int32)Combat->IsInAttackRange(Target), (int32)Combat->CanAttack()); + // Try to attack immediately if in range if (Combat->IsInAttackRange(Target)) { if (Combat->CanAttack()) { Combat->ExecuteAttack(Target); - return EBTNodeResult::Succeeded; } - // In range but on cooldown — wait + // Stay InProgress — keep attacking while in combat state return EBTNodeResult::InProgress; } @@ -59,7 +64,9 @@ EBTNodeResult::Type UPS_AI_Behavior_BTTask_Attack::ExecuteTask( if (Result == EPathFollowingRequestResult::Failed) { - return EBTNodeResult::Failed; + UE_LOG(LogPS_AI_Behavior, Warning, TEXT("[%s] Attack: MoveToActor failed — no path to target."), *AIC->GetName()); + // Stay InProgress anyway — will retry next tick instead of giving up + return EBTNodeResult::InProgress; } FAttackMemory* Memory = reinterpret_cast(NodeMemory); @@ -107,9 +114,9 @@ void UPS_AI_Behavior_BTTask_Attack::TickTask( if (Combat->CanAttack()) { Combat->ExecuteAttack(Target); - FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded); } - // Else: wait for cooldown (stay InProgress) + // Stay InProgress — keep attacking (cooldown handles the rate) + // Observer Aborts on the Decorator will pull us out when state changes } else { 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 29cad55..71ac0aa 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 @@ -48,13 +48,6 @@ void APS_AI_Behavior_AIController::OnPossess(APawn* InPawn) { // Use the interface — the host project controls the storage NPCType = IPS_AI_Behavior_Interface::Execute_GetBehaviorNPCType(InPawn); - - // Also check if the interface provides a specific TeamId - const uint8 InterfaceTeamId = IPS_AI_Behavior_Interface::Execute_GetBehaviorTeamId(InPawn); - if (InterfaceTeamId != FGenericTeamId::NoTeam) - { - TeamId = InterfaceTeamId; - } } else if (PersonalityComp) { @@ -227,7 +220,16 @@ void APS_AI_Behavior_AIController::SetBehaviorState(EPS_AI_Behavior_State NewSta { if (Blackboard) { - Blackboard->SetValueAsEnum(PS_AI_Behavior_BB::State, static_cast(NewState)); + const uint8 OldVal = Blackboard->GetValueAsEnum(PS_AI_Behavior_BB::State); + const uint8 NewVal = static_cast(NewState); + if (OldVal != NewVal) + { + UE_LOG(LogPS_AI_Behavior, Log, TEXT("[%s] BB State: %s -> %s"), + *GetName(), + *UEnum::GetValueAsString(static_cast(OldVal)), + *UEnum::GetValueAsString(NewState)); + } + Blackboard->SetValueAsEnum(PS_AI_Behavior_BB::State, NewVal); } } @@ -274,16 +276,12 @@ ETeamAttitude::Type APS_AI_Behavior_AIController::GetTeamAttitudeTowards(const A if (OtherPawn) { - // Check via AIController first + // Check via AIController (NPC with our behavior system) if (const AAIController* OtherAIC = Cast(OtherPawn->GetController())) { OtherTeam = OtherAIC->GetGenericTeamId().GetId(); } - // Check via IPS_AI_Behavior interface - else if (OtherPawn->Implements()) - { - OtherTeam = IPS_AI_Behavior_Interface::Execute_GetBehaviorTeamId(const_cast(OtherPawn)); - } + // Players or other pawns without AIController → NoTeam (Neutral) } // NoTeam (255) → Neutral diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h index 40a5a4f..fa3cd11 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Behavior/Source/PS_AI_Behavior/Public/PS_AI_Behavior_Interface.h @@ -27,7 +27,6 @@ * virtual void SetBehaviorNPCType_Implementation(EPS_AI_Behavior_NPCType T) override { MyType = T; } * virtual bool IsBehaviorHostile_Implementation() const override { return bHostile; } * virtual void SetBehaviorHostile_Implementation(bool b) override { bHostile = b; } - * virtual uint8 GetBehaviorTeamId_Implementation() const override { return bHostile ? 2 : 1; } * }; */ UINTERFACE(BlueprintType, Blueprintable, meta = (DisplayName = "PS AI Behavior Interface")) @@ -69,16 +68,6 @@ public: UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") void SetBehaviorHostile(bool bNewHostile); - // ─── Team ─────────────────────────────────────────────────────────── - - /** - * Get the Team ID for perception affiliation. - * Convention: Civilian=1, Enemy=2, Protector=3, NoTeam=255. - * Infiltrated enemies return 1 (Civilian) until SetHostile(true). - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS AI Behavior") - uint8 GetBehaviorTeamId() const; - // ─── Movement ─────────────────────────────────────────────────────── /**