Nakatomi/Source/Nakatomi/EnemyAIController.cpp

281 lines
7.7 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "EnemyAIController.h"
#include <Kismet/GameplayStatics.h>
#include "EAIState.h"
#include "NakatomiGameInstance.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Components/CapsuleComponent.h"
#include "Engine/EngineTypes.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Perception/AIPerceptionComponent.h"
AEnemyAIController::AEnemyAIController(const FObjectInitializer& object_initializer)
{
Blackboard = CreateDefaultSubobject<UBlackboardComponent>(TEXT("Blackboard"));
BehaviorTree = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("Behavior Tree"));
PerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("Perception Component"));
AIPerception = PerceptionComponent;
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("Sight Sense Config"));
SightConfig->SightRadius = 700.0f;
SightConfig->LoseSightRadius = 850.0f;
SightConfig->PeripheralVisionAngleDegrees = 90.0f;
SightConfig->SetMaxAge(5.0f);
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
HearingConfig = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("Hearing Sense Config"));
HearingConfig->HearingRange = 800.0f;
HearingConfig->SetMaxAge(3.0f);
HearingConfig->DetectionByAffiliation.bDetectEnemies = true;
HearingConfig->DetectionByAffiliation.bDetectNeutrals = true;
DamageConfig = CreateDefaultSubobject<UAISenseConfig_Damage>(TEXT("Damage Sense Config"));
DamageConfig->SetMaxAge(5.0f);
AIPerception->SetDominantSense(SightConfig->GetSenseImplementation());
AIPerception->ConfigureSense(*SightConfig);
AIPerception->ConfigureSense(*HearingConfig);
AIPerception->ConfigureSense(*DamageConfig);
}
void AEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
auto enemy = Cast<AEnemyCharacter>(InPawn);
check(enemy);
SetPerceptionComponent(*PerceptionComponent);
PerceptionComponent->OnPerceptionUpdated.AddDynamic(this, &AEnemyAIController::OnPerceptionUpdated);
enemy->GetCharacterMovement()->MaxWalkSpeed = 500.f;
enemy->GetHealthComponent()->OnDamaged.BindUFunction(this, "OnDamaged");
enemy->GetHealthComponent()->OnDeath.BindUFunction(this, "OnDeath");
if (auto behaviourTree = enemy->GetBehaviourTree())
{
Blackboard->InitializeBlackboard(*behaviourTree->BlackboardAsset);
BehaviorTree->StartTree(*behaviourTree);
Blackboard->SetValueAsObject("SelfActor", enemy);
}
Blackboard->SetValueAsEnum("State", static_cast<uint8>(EAIState::PASSIVE));
Blackboard->SetValueAsFloat("AttackRadius", enemy->AttackRadius);
Blackboard->SetValueAsFloat("DefendRadius", enemy->DefendRadius);
//ensure(enemy->GetMovementComponent()->UseAccelerationForPathFollowing());
}
void AEnemyAIController::BeginPlay()
{
Super::BeginPlay();
}
void AEnemyAIController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
AEnemyCharacter* AEnemyAIController::GetEnemyCharacter()
{
return Cast<AEnemyCharacter>(GetCharacter());
}
void AEnemyAIController::OnDamaged(FDamageInfo info)
{
if (GetEnemyCharacter()->GetHealthComponent()->GetCurrentHealth() > 0.0f)
{
// TODO: Do stuff here
}
}
void AEnemyAIController::OnDeath(FDamageInfo info)
{
// TODO: Do stuff here
auto enemy = GetEnemyCharacter();
enemy->DetachFromControllerPendingDestroy();
enemy->GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
enemy->GetCapsuleComponent()->SetCollisionResponseToAllChannels(ECR_Ignore);
enemy->GetMesh()->SetCollisionProfileName("Ragdoll");
enemy->SetActorEnableCollision(true);
enemy->GetMesh()->SetAllBodiesSimulatePhysics(true);
enemy->GetMesh()->SetSimulatePhysics(true);
enemy->GetMesh()->WakeAllRigidBodies();
enemy->GetMesh()->bBlendPhysics = true;
if (auto characterMovementComponent = enemy->GetCharacterMovement())
{
characterMovementComponent->StopMovementImmediately();
characterMovementComponent->DisableMovement();
characterMovementComponent->SetComponentTickEnabled(false);
}
enemy->SetLifeSpan(10.0f);
if (enemy->DefaultWeaponInventory.Num() > 0)
{
// TODO: This can sometimes crash, need to investigate
if (auto weaponPickup = GetWorld()->SpawnActor<AWeaponPickup>())
{
weaponPickup->SetActorLocation(enemy->GetActorLocation());
weaponPickup->SetWeapon(enemy->DefaultWeaponInventory[enemy->GetCurrentInventorySlot()]);
weaponPickup->SetWeaponProperties(*enemy->CurrentWeapon->GetWeaponProperties());
}
}
}
void AEnemyAIController::OnPerceptionUpdated(const TArray<AActor*>& actors)
{
for (AActor* actor : actors)
{
if (!actor->GetClass()->IsChildOf(APlayerCharacter::StaticClass()))
{
continue;
}
FActorPerceptionBlueprintInfo perceptionInfo;
PerceptionComponent->GetActorsPerception(actor, perceptionInfo);
for (FAIStimulus& stimulus : perceptionInfo.LastSensedStimuli)
{
if (!stimulus.IsValid() || stimulus.IsExpired() ||
static_cast<EAIState>(Blackboard->GetValueAsEnum("State")) == EAIState::DEAD)
{
continue;
}
FAISenseID SightID;
FAISenseID HearingID;
FAISenseID DamageID;
if (SightConfig)
{
SightID = SightConfig->GetSenseID();
}
if (HearingConfig)
{
HearingID = HearingConfig->GetSenseID();
}
if (DamageConfig)
{
DamageID = DamageConfig->GetSenseID();
}
if (stimulus.Type == SightID)
{
SensedSight(actor, stimulus);
}
else if (stimulus.Type == HearingID)
{
SensedHearing(actor, stimulus);
}
else if (stimulus.Type == DamageID)
{
SensedDamaged(actor, stimulus);
}
Blackboard->SetValueAsObject("TargetActor", actor);
if (stimulus.IsActive())
{
PlayerCharacter = Cast<APlayerCharacter>(actor);
SetFocus(PlayerCharacter, EAIFocusPriority::Gameplay);
}
else
{
PlayerCharacter = nullptr;
SetFocus(nullptr, EAIFocusPriority::Gameplay);
}
}
}
}
bool AEnemyAIController::TryObtainAttackToken()
{
if (HasAttackToken)
{
return true;
}
if (auto gameInstance = Cast<UNakatomiGameInstance>(UGameplayStatics::GetGameInstance(GetWorld())))
{
HasAttackToken = gameInstance->GetAIAttackTokenManager()->RequestToken();
}
return false;
}
void AEnemyAIController::TryReleaseAttackToken()
{
if (HasAttackToken)
{
if (auto gameInstance = Cast<UNakatomiGameInstance>(UGameplayStatics::GetGameInstance(GetWorld())))
{
HasAttackToken = gameInstance->GetAIAttackTokenManager()->ReleaseToken();
}
}
}
bool AEnemyAIController::GetHasAttackToken()
{
return HasAttackToken;
}
void AEnemyAIController::SetState(EAIState state)
{
Blackboard->SetValueAsEnum("State", static_cast<uint8>(state));
}
void AEnemyAIController::SetStateAsPassive()
{
SetState(EAIState::PASSIVE);
}
void AEnemyAIController::SetStateAsAttacking(AActor* target)
{
Blackboard->SetValueAsObject("TargetActor", target);
SetState(EAIState::ATTACKING);
}
AActor* AEnemyAIController::GetTargetActor()
{
return Cast<AActor>(Blackboard->GetValueAsObject("TargetActor"));
}
void AEnemyAIController::SensedSight(AActor* actor, FAIStimulus& stimulus)
{
EAIState CurrentState = static_cast<EAIState>(Blackboard->GetValueAsEnum("State"));
if (CurrentState == EAIState::PASSIVE || CurrentState == EAIState::INVESTIGATING)
{
SetStateAsAttacking(actor);
}
}
void AEnemyAIController::SensedHearing(AActor* actor, FAIStimulus& stimulus)
{
EAIState CurrentState = static_cast<EAIState>(Blackboard->GetValueAsEnum("State"));
if (CurrentState == EAIState::PASSIVE || CurrentState == EAIState::INVESTIGATING)
{
SetState(EAIState::INVESTIGATING);
Blackboard->SetValueAsVector("InvestigationLocation", stimulus.StimulusLocation);
}
}
void AEnemyAIController::SensedDamaged(AActor* actor, FAIStimulus& stimulus)
{
SetStateAsAttacking(actor);
}