ProjectM/Source/ProjectT/System/SinglePlay/Characters/NPC/SPNpcAgent.cpp

1291 lines
37 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "SPNpcAgent.h"
#include "NavigationSystem.h"
#include "NiagaraComponent.h"
#include "Animations/SPNpcAgentAnimInstance.h"
#include "Components/CapsuleComponent.h"
#include "Components/WidgetComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "ProjectT/ProjectT.h"
#include "ProjectT/Data/Gen/GenerateEnumDataTableKey.h"
#include "ProjectT/Data/Gen/GenerateEnumEffectSubTypes.h"
#include "ProjectT/Data/Gen/GenerateEnumSkillTypes.h"
#include "ProjectT/Data/Gen/GenerateStructEffect.h"
#include "ProjectT/System/Core/Characters/Player/CorePlayerCharacter.h"
#include "ProjectT/System/Core/Characters/NPC/AI/Controllers/AIControllerBase.h"
#include "ProjectT/System/Core/Characters/NPC/AI/Controllers/AIPerceptionController.h"
#include "ProjectT/System/Core/Characters/NPC/AI/Controllers/AISightController.h"
#include "ProjectT/System/Core/Characters/Skill/CoreSkill.h"
#include "ProjectT/System/Core/Common/AssetUtilsLibrary.h"
#include "ProjectT/System/Core/Common/GlobalUtilsLibrary.h"
#include "ProjectT/System/Core/Components/ObstacleFilterComponent.h"
#include "ProjectT/System/Core/Components/PathFollowComponent.h"
#include "ProjectT/System/Core/Components/VisionConeComponent.h"
#include "ProjectT/System/Core/GameModes/WorldGameMode.h"
#include "ProjectT/System/Core/Managers/CoreCheatManager.h"
#include "ProjectT/System/Core/Managers/PooledActorManager.h"
#include "ProjectT/System/SinglePlay/Components/SPNpcStatusComponent.h"
#include "ProjectT/System/SinglePlay/GameModes/SPGameModeBase.h"
#include "ProjectT/System/SinglePlay/Interfaces/SPObjectProvider.h"
#include "ProjectT/System/SinglePlay/Managers/SPEnemyVisionManager.h"
ASPNpcAgent::ASPNpcAgent() :
CurrentSpeed(ENpcSpeedType::Walk),
SkillType(ESkillTypes::None),
ChaseRangeProfileName(TEXT("ChaseRange")),
DissolveParameterName(TEXT("Dissolve")),
SkillCoolTime(0.f),
DissolveElapsedTime(0.f),
DissolveDuration(1.5f),
bRotateWhileAttack(false),
bSkillCooldownActive(false),
bDissolving(false),
CurrentUsingSkill(nullptr),
AgentAnimInstance(nullptr),
NpcActionMontage(nullptr)
{
PrimaryActorTick.bCanEverTick = true;
AIControllerClass = AAISightController::StaticClass();
GetCapsuleComponent()->SetCollisionProfileName(TEXT("Enemy"));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetAnimInstanceClass(USPNpcAgentAnimInstance::StaticClass());
GetMesh()->SetCustomDepthStencilValue(1);
WeaponStaticComponent->SetCustomDepthStencilValue(1);
WeaponStaticComponent->SetCustomDepthStencilValue(1);
HpBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HpBarWidgetComponent"));
DetectIndicatorWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("DetectIndicatorWidget"));
SphereChaseRangeComponent = CreateDefaultSubobject<UObstacleFilterComponent>(TEXT("SphereChaseRangeComponent"));
DissolveFXComponent = CreateDefaultSubobject<UNiagaraComponent>(TEXT("DissolveFXComponent"));
VisionLineMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisionLineMeshComponent"));
NMT_CHECKF(HpBarWidgetComponent && DetectIndicatorWidget && SphereChaseRangeComponent && DissolveFXComponent && VisionLineMeshComponent)
HpBarWidgetComponent->SetupAttachment(WidgetRootComponent);
HpBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
DetectIndicatorWidget->SetupAttachment(WidgetRootComponent);
DetectIndicatorWidget->SetWidgetSpace(EWidgetSpace::Screen);
SphereChaseRangeComponent->SetupAttachment(SubRootComponent);
SphereChaseRangeComponent->SetCollisionProfileName(ChaseRangeProfileName);
SphereChaseRangeComponent->SetLineThickness(1.f);
DissolveFXComponent->SetupAttachment(SubRootComponent);
DissolveFXComponent->bAutoActivate = false;
VisionLineMeshComponent->SetupAttachment(SubRootComponent);
VisionLineMeshComponent->SetRelativeLocation(FVector(0, 0, -GetCapsuleComponent()->GetScaledCapsuleHalfHeight() - 1.f));
VisionLineMeshComponent->SetHiddenInGame(true);
VisionLineMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
VisionLineMeshComponent->SetComponentTickEnabled(false);
}
void ASPNpcAgent::PostInitializeComponents()
{
Super::PostInitializeComponents();
}
void ASPNpcAgent::BeginPlay()
{
Super::BeginPlay();
InitializeMaterials();
}
void ASPNpcAgent::InitializeStatus(const FEnemyNpc InNpcStatus)
{
Super::InitializeStatus(InNpcStatus);
GetCharacterMovement()->MaxWalkSpeed = InNpcStatus.fNpcMove;
NMT_CHECKF(VisionConeComponent && NpcStatusComponent);;
VisionConeComponent->SetupSightSettings(NpcStatusComponent->GetStatusNpcSightLength(), NpcStatusComponent->GetStatusNpcSightAngle());
}
void ASPNpcAgent::Respawn()
{
Super::Respawn();
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
AgentAnimInstance->ChangeNpcNormalAnimState(ENpcNormalAnimState::Idle);
}
void ASPNpcAgent::ExecuteClickedEvent(UPrimitiveComponent* InTouchedComponent, FKey InButtonPressed)
{
if(InButtonPressed == EKeys::RightMouseButton)
{
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
NMT_CHECKF(World)
ASPGameModeBase* SPGM = UGlobalUtilsLibrary::GetGameModeChecked<ASPGameModeBase>(World);
NMT_CHECKF(SPGM)
USPEnemyVisionManager* EM = ISPObjectProvider::Execute_GetEnemyVisionManager(SPGM);
NMT_CHECKF(EM)
EM->RegisterFocused(this);
}
}
void ASPNpcAgent::ExecuteBeginCursorOverEvent(UPrimitiveComponent* InTouchedComponent)
{
GetMesh()->SetRenderCustomDepth(true);
WeaponStaticComponent->SetRenderCustomDepth(true);
WeaponDynamicComponent->SetRenderCustomDepth(true);
}
void ASPNpcAgent::ExecuteEndCursorOverEvent(UPrimitiveComponent* InTouchedComponent)
{
GetMesh()->SetRenderCustomDepth(false);
WeaponStaticComponent->SetRenderCustomDepth(false);
WeaponDynamicComponent->SetRenderCustomDepth(false);
}
void ASPNpcAgent::ResetStateAfterGameEnd()
{
Super::ResetStateAfterGameEnd();
StopSkillActionMontage();
FinishCurrentSkill();
FinishCheckInChaseRange();
}
#if WITH_EDITOR
void ASPNpcAgent::ApplyCharacterData(const FTableRowBase* RowData)
{
Super::ApplyCharacterData(RowData);
}
#endif
void ASPNpcAgent::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if(bSkillCooldownActive)
{
UpdateSkillCooldown(DeltaTime);
}
if(bDissolving)
{
UpdateDissolve(DeltaTime);
}
if(CurrentVisionMode == EVisionMode::Line)
{
UpdateVisionLineLength();
}
}
void ASPNpcAgent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
StopSkillActionMontage();
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
World->GetTimerManager().ClearTimer(CheckChaseRangeTimerHandle);
World->GetTimerManager().ClearTimer(JumpBackTimerHandle);
World->GetTimerManager().ClearTimer(EffectTimerHandle);
}
bool ASPNpcAgent::GetUseSight_Implementation()
{
return true;
}
bool ASPNpcAgent::GetUseHearing_Implementation()
{
return true;
}
FSightInfo ASPNpcAgent::GetSightInfo_Implementation()
{
NMT_CHECKF(NpcStatusComponent)
bool bNpcDataValid = !NpcStatusComponent->GetCurrentNpcStatus().IsDefault();
if(!NMT_ENSURE(bNpcDataValid))
return FSightInfo();
FSightInfo Info;
Info.SightRange = NpcStatusComponent->GetStatusNpcSightLength();
Info.SightAngle = NpcStatusComponent->GetStatusNpcSightAngle();
Info.LoseSightRange = Info.SightRange + LoseSightOffset;
Info.SightMaxAge = PerceptionMaxAge;
return Info;
}
class UBlackboardData* ASPNpcAgent::GetBlackboardData_Implementation()
{
return Blackboard;
}
class UBehaviorTree* ASPNpcAgent::GetBehaviorTree_Implementation()
{
return BehaviorTree;
}
void ASPNpcAgent::DetectTargetBySight_Implementation(AActor* InTarget, FAIStimulus InStimulus)
{
switch(CurrentState)
{
case ENpcState::Default:
{
ChangeDoubtState(EDoubtState::Increasing);
ChangeNpcState(ENpcState::Doubt);
break;
}
case ENpcState::Guard: case ENpcState::Returning:
{
ChangeNpcState(ENpcState::Chase);
ApplyStateToQuest(ENpcState::Chase);
break;
}
case ENpcState::Doubt: break;
case ENpcState::Chase: break;
case ENpcState::Attack: break;
case ENpcState::Hit: break;
case ENpcState::Dead: break;
default : NMT_LOG("Not Valid Type")
}
}
void ASPNpcAgent::ForgetTarget_Implementation(AActor* InTarget)
{
switch(CurrentState)
{
case ENpcState::Doubt:
{
GetAIControllerBase()->UpdateTargetInfo(nullptr);
ChangeNpcState(ENpcState::Default);
break;
}
case ENpcState::Default: break;
case ENpcState::Guard: break;
case ENpcState::Returning: break;
case ENpcState::Chase: break;
case ENpcState::Attack: break;
case ENpcState::Hit: break;
case ENpcState::Dead : break;
default : NMT_LOG("Not Valid Type")
}
}
FTransform ASPNpcAgent::GetViewTransform_Implementation()
{
FTransform Transform;
Transform.SetLocation(SubRootComponent->GetComponentLocation());
Transform.SetRotation(SubRootComponent->GetComponentRotation().Quaternion());
Transform.SetScale3D(FVector(1, 1, 1));
return Transform;
}
void ASPNpcAgent::InitializeProperty()
{
Super::InitializeProperty();
AgentAnimInstance = Cast<USPNpcAgentAnimInstance>(GetMesh()->GetAnimInstance());
NMT_CHECKF(AgentAnimInstance);
AgentAnimInstance->SetActionMontage(NpcActionMontage);
NMT_CHECKF(SphereChaseRangeComponent && NpcStatusComponent);
FObstacleFilterInfo FilterInfo;
FilterInfo.TraceCollisionChannels.Add(ECC_GameTraceChannel4); // NOTE : Wall
FilterInfo.TraceCollisionChannels.Add(ECC_GameTraceChannel6); // NOTE : Prop
FilterInfo.SphereRadius = NpcStatusComponent->GetStatusNpcChaseLength();
FilterInfo.CheckOutOfRange = true;
SphereChaseRangeComponent->InitializeProperty(FilterInfo);
SphereChaseRangeComponent->DeactivateFiltering();
SetSkillClass();
}
void ASPNpcAgent::ApplyDamageEvent_Implementation(AActor* InOccurActor, const FVector& InOriginLocation, const float InDamage, const TArray<FEffect>& InSkillEffects)
{
UCoreCheatManager* CCM = UGlobalUtilsLibrary::GetSharedCoreCheatManager(this);
float LocFinalDamage = CCM->GetDebugStatus().bDebugPlayerDamage? InDamage + 1000.f : InDamage;
if(NpcStatusComponent->ApplyDamage(LocFinalDamage))
{
ChangeNpcState(ENpcState::Dead);
}
else
{
if(CCM->GetDebugStatus().bDebugEnemyInvincibleStatusEffect) return;
if(InDamage > 0.f)
{
HpBarWidgetComponent->GetUserWidgetObject()->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
GetAIControllerBase()->UpdateTargetInfo(InOccurActor);
}
for(FEffect EffectType : InSkillEffects)
{
const EEffectSubTypes EffectSubType = UGlobalUtilsLibrary::GetStringToEnum<EEffectSubTypes>(EffectType.sEffect);
if(CheckEffectTime(EffectType))
{
switch(EffectSubType)
{
case EEffectSubTypes::None:
{
ChangeNpcState(ENpcState::Chase);
break;
}
case EEffectSubTypes::Hitback:
{
ChangeNpcState(ENpcState::Hit);
if(!NpcStatusComponent->IsDead())
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::HitBack);
break;
}
case EEffectSubTypes::Jumpback:
{
ChangeNpcState(ENpcState::Hit);
JumpBack(InOriginLocation);
break;
}
case EEffectSubTypes::Slow:
{
if(InDamage > 0.f && CurrentState != ENpcState::Attack)
{
SetReturnLocationFromDefault();
ChangeNpcState(ENpcState::Chase);
}
StatusSlow(EffectType.fEffectValue);
break;
}
case EEffectSubTypes::Fast:
{
if(InDamage > 0.f && CurrentState != ENpcState::Attack)
{
SetReturnLocationFromDefault();
ChangeNpcState(ENpcState::Chase);
}
StatusFast(EffectType.fEffectValue);
break;
}
case EEffectSubTypes::Bind:
{
if(InDamage > 0.f && CurrentState != ENpcState::Attack)
{
SetReturnLocationFromDefault();
ChangeNpcState(ENpcState::Chase);
}
ChangeSpeed(ENpcSpeedType::Stop, true);
break;
}
case EEffectSubTypes::Unrecog:
{
SetUnDetectTarget(true);
break;
}
case EEffectSubTypes::CogImmune: NOT_IMPLEMENTED_MSG("EEffectSubTypes::CogImmune"); break;
case EEffectSubTypes::Stun: NOT_IMPLEMENTED_MSG("EEffectSubTypes::Stun"); break;
case EEffectSubTypes::Surge: NOT_IMPLEMENTED_MSG("EEffectSubTypes::Surge"); break;
case EEffectSubTypes::HitbackImmune: NOT_IMPLEMENTED_MSG("EEffectSubTypes::HitbackImmune"); break;
case EEffectSubTypes::Heal: NOT_IMPLEMENTED_MSG("EEffectSubTypes::Heal"); break;
default: NMT_LOG("Not Valid");
}
AddEffectTimeMap(EffectType);
}
}
}
}
void ASPNpcAgent::ApplyRecoveryEvent_Implementation(const TArray<FEffect>& InReleaseEffects)
{
for(FEffect LocCurEffect : InReleaseEffects)
{
EEffectSubTypes LoCurReleaseEffect = UGlobalUtilsLibrary::GetStringToEnum<EEffectSubTypes>(LocCurEffect.sEffect);
EffectTimeMap.Remove(LoCurReleaseEffect);
if(EffectTimeMap.IsEmpty())
{
GetWorld()->GetTimerManager().ClearTimer(EffectTimerHandle);
}
switch(LoCurReleaseEffect)
{
case EEffectSubTypes::Unrecog:
{
SetUnDetectTarget(false);
break;
}
case EEffectSubTypes::Surge:
{
if(CurrentState == ENpcState::Default)
{
ChangeNpcState(ENpcState::Returning);
}
break;
}
case EEffectSubTypes::Bind:
{
if(CurrentState == ENpcState::Returning || CurrentState == ENpcState::Chase || CurrentState == ENpcState::Attack)
{
ChangeSpeed(ENpcSpeedType::Run);
}
else
{
ChangeSpeed(ENpcSpeedType::Walk);
}
break;
}
case EEffectSubTypes::Fast:
{
StatusFast(LocCurEffect.fEffectValue, true);
break;
}
case EEffectSubTypes::Slow:
{
StatusSlow(LocCurEffect.fEffectValue, true);
break;
}
case EEffectSubTypes::Hitback: NOT_IMPLEMENTED_MSG("Release EEffectSubTypes::Hitback"); break;
case EEffectSubTypes::Jumpback: NOT_IMPLEMENTED_MSG("Release EEffectSubTypes::Jumpback"); break;
case EEffectSubTypes::CogImmune: NOT_IMPLEMENTED_MSG("Release EEffectSubTypes::CogImmune"); break;
case EEffectSubTypes::None: break;
case EEffectSubTypes::Stun: break;
case EEffectSubTypes::HitbackImmune: break;
case EEffectSubTypes::Heal: break;
default: NMT_LOG("No Release Effect Type")
}
}
}
void ASPNpcAgent::StartDisableNpc()
{
Super::StartDisableNpc();
DissolveFXComponent->Activate(true);
bDissolving = true;
NotifyVisionDetection(false);
ChangeSpeed(ENpcSpeedType::Stop, true);
ClearSkillCoolTime();
FinishCurrentSkill();
FinishCheckInChaseRange();
}
void ASPNpcAgent::CompleteDisableNpc()
{
Super::CompleteDisableNpc();
AgentAnimInstance->ResetAnimState();
for(UMeshComponent* MeshComponent : AllMeshComponents)
{
if(MeshComponent == nullptr)
continue;
MeshComponent->SetScalarParameterValueOnMaterials(DissolveParameterName, 1.f);
}
}
class USPNpcAgentAnimInstance* ASPNpcAgent::GetAgentAnimInstance() const
{
NMT_CHECKF(AgentAnimInstance);
return AgentAnimInstance;
}
void ASPNpcAgent::ChangeNpcState(const ENpcState InState)
{
PrevState = CurrentState;
if(CurrentState != InState)
{
switch(InState)
{
case ENpcState::Default:
{
bCanRotateHeadToTarget = true;
if(CurrentDoubtState == EDoubtState::Increasing)
ChangeDoubtState(EDoubtState::Decreasing);
break;
}
case ENpcState::Doubt:
{
NotifyVisionDetection(true);
AgentAnimInstance->SetNormalState(true);
AgentAnimInstance->ChangeNpcNormalAnimState(ENpcNormalAnimState::Idle);
break;
}
case ENpcState::Chase:
{
if(GetAIControllerBase()->GetTargetActor())
StartCheckInChaseRange();
ChangeDoubtState(EDoubtState::None);
NotifyVisionDetection(false);
break;
}
case ENpcState::Attack:
{
if(GetAIControllerBase()->GetTargetActor())
StartCheckInChaseRange();
NotifyVisionDetection(false);
break;
}
case ENpcState::Guard:
{
bCanTriggerAlert = true;
ChangeSpeed(ENpcSpeedType::Stop);
AgentAnimInstance->SetNormalState(true);
AgentAnimInstance->ChangeNpcNormalAnimState(ENpcNormalAnimState::Idle);
break;
}
case ENpcState::Hit:
{
SetReturnLocationFromDefault();
StopSkillActionMontage();
ClearSkillCoolTime();
GetAIControllerBase()->SetEnableAttack(true);
FinishCurrentSkill();
ChangeSpeed(ENpcSpeedType::Stop, true);
SetVisionMode(EVisionMode::None);
break;
}
case ENpcState::Dead:
{
ChangeSpeed(ENpcSpeedType::Stop, true);
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::DeadStart);
ApplyStateToQuest(ENpcState::Dead);
HpBarWidgetComponent->GetUserWidgetObject()->SetVisibility(ESlateVisibility::Collapsed);
break;
}
case ENpcState::Returning:
{
break;
}
default: NMT_LOG("Not Valid ENpcState")
}
}
}
void ASPNpcAgent::ActivateCharacter(const bool InbEnable)
{
Super::ActivateCharacter(InbEnable);
DetectIndicatorWidget->SetVisibility(InbEnable);
if(InbEnable)
GetAIControllerBase()->SetCanRotateWhileAttack(bRotateWhileAttack);
}
void ASPNpcAgent::SetDetectTargetFromNoise(AActor* InTarget)
{
Super::SetDetectTargetFromNoise(InTarget);
}
void ASPNpcAgent::SetDetectLocationFromNoise(const FVector& InTargetLocation)
{
Super::SetDetectLocationFromNoise(InTargetLocation);
}
void ASPNpcAgent::ChangeSpeed(const ENpcSpeedType InSpeed, const bool InbStopImmediately)
{
float Speed = 0.f;
switch(InSpeed)
{
case ENpcSpeedType::Walk:
{
Speed = NpcStatusComponent->GetStatusNpcMove();
break;
}
case ENpcSpeedType::Run:
{
Speed = NpcStatusComponent->GetStatusNpcRun();
break;
}
case ENpcSpeedType::Stop:
{
if(InbStopImmediately)
GetCharacterMovement()->StopMovementImmediately();
if(UPathFollowComponent* PathFollowComponent = FindComponentByClass<UPathFollowComponent>())
PathFollowComponent->StopMove();
break;
}
default: NMT_LOG("Not Valid Type")
}
CurrentSpeed = InSpeed;
EffectTimeMap.Find(EEffectSubTypes::Bind) ? GetCharacterMovement()->MaxWalkSpeed = 0.f : GetCharacterMovement()->MaxWalkSpeed = Speed;
}
class ACoreSkill* ASPNpcAgent::GetCurrentSkill() const
{
return CurrentUsingSkill;
}
void ASPNpcAgent::GetSkillClass(TSubclassOf<AActor>& OutSkillClass) const
{
OutSkillClass = NpcSkillClass;
}
void ASPNpcAgent::UpdateDissolve(const float InDeltaSeconds)
{
DissolveElapsedTime += InDeltaSeconds;
float Alpha = FMath::Clamp(1.f - (DissolveElapsedTime / DissolveDuration), 0.f, 1.f);
if(FMath::IsNearlyZero(Alpha))
{
bDissolving = false;
DissolveElapsedTime = 0.f;
CompleteDisableNpc();
}
else
{
for(UMeshComponent* MeshComponent : AllMeshComponents)
{
if(MeshComponent == nullptr)
continue;
MeshComponent->SetScalarParameterValueOnMaterials(DissolveParameterName, Alpha);
}
}
}
void ASPNpcAgent::NotifyVisionDetection(const bool InbDetect)
{
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
NMT_CHECKF(World)
ASPGameModeBase* SPGM = UGlobalUtilsLibrary::GetGameModeChecked<ASPGameModeBase>(World);
NMT_CHECKF(SPGM)
USPEnemyVisionManager* EM = ISPObjectProvider::Execute_GetEnemyVisionManager(SPGM);
if(EM)
{
InbDetect ? EM->RegisterDetection(this) : EM->UnregisterDetection(this);
}
}
void ASPNpcAgent::ShowVisual_Implementation()
{
}
void ASPNpcAgent::HideVisual_Implementation()
{
}
void ASPNpcAgent::UpdateVisionLineLength()
{
AActor* Target = GetAIControllerBase()->GetTargetActor();
if(!Target)
return;
NMT_CHECKF(VisionLineMeshComponent)
FVector Start = VisionLineMeshComponent->GetComponentLocation();
FVector End = Target->GetActorLocation();
float Distance = FVector::Dist2D(Start, End);
float ScaleX = Distance / 100.f;
FRotator LookRotator = UKismetMathLibrary::FindLookAtRotation(Start, End);
VisionLineMeshComponent->SetRelativeScale3D(FVector(ScaleX, 1.f, 1.f));
VisionLineMeshComponent->SetWorldRotation(LookRotator);
}
void ASPNpcAgent::PlayActionMontage(FName InSectionName, FOnMontageBlendingOutStarted InEndedFunction)
{
NMT_CHECKF(AgentAnimInstance)
if(!NMT_ENSURE(NpcActionMontage))
return;
AgentAnimInstance->SetActionState(true);
AgentAnimInstance->Montage_Play(NpcActionMontage, 1, EMontagePlayReturnType::MontageLength, 0, true);
if(AgentAnimInstance->GetInstanceForMontage(NpcActionMontage))
{
FAnimMontageInstance* MontageInstance = AgentAnimInstance->GetInstanceForMontage(NpcActionMontage);
if(MontageInstance && MontageInstance->OnMontageBlendingOutStarted.IsBound())
{
MontageInstance->OnMontageBlendingOutStarted.Unbind();
}
}
AgentAnimInstance->Montage_SetBlendingOutDelegate(InEndedFunction);
AgentAnimInstance->Montage_JumpToSection(InSectionName);
}
void ASPNpcAgent::TryAttack()
{
if(!IsFinishSkillCoolDown())
{
NMT_LOGF("Skill CoolDown.. : %f", SkillCoolTime)
return;
}
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
AWorldGameMode* LocGameMode = UGlobalUtilsLibrary::GetGameModeChecked<AWorldGameMode>(World);
UPooledActorManager* LocPoolManager = IWorldObjectProvider::Execute_GetPooledActorManager(LocGameMode);
if(!LocPoolManager)
return;
AgentAnimInstance->ChangeNpcNormalAnimState(ENpcNormalAnimState::Idle);
ChangeSpeed(ENpcSpeedType::Stop, true);
CurrentUsingSkill = Cast<ACoreSkill>(LocPoolManager->Obtain(NpcSkillClass));
if(CurrentUsingSkill)
{
CurrentUsingSkill->SetSkillOwner(this);
StartAttack();
GetAIControllerBase()->SetEnableAttack(false);
SetHeadRotationEnable(false);
return;
}
NMT_MSG_ENSURE(0, "Fail to obtain Npc Skill");
}
void ASPNpcAgent::StartAttack()
{
FSkill SkillData = CurrentUsingSkill->GetSkillData();
ActionMontageSectionName = SkillData.sSkillName;
switch(SkillType)
{
case ESkillTypes::Melee:
{
CurrentUsingSkill->StartSkill();
SetSkillCoolTime(SkillData.fCooldown);
PlayActionMontage(FName(*ActionMontageSectionName), FOnMontageBlendingOutStarted::CreateWeakLambda(this, [this](UAnimMontage* Montage, bool Interrupted)
{
TryFinishAttack();
}));
}
break;
case ESkillTypes::Direct:
{
if(PrevState == ENpcState::Attack)
{
ContinueAttack();
return;
}
CurrentUsingSkill->StartSkill();
ActionMontageSectionName.Append("Draw");
PlayActionMontage(FName(*ActionMontageSectionName), FOnMontageBlendingOutStarted::CreateWeakLambda(this, [this](UAnimMontage* Montage, bool Interrupted)
{
ContinueAttack();
}));
}
break;
case ESkillTypes::None: NOT_IMPLEMENTED_MSG("StartAttack::None")
break;
case ESkillTypes::Buff: NOT_IMPLEMENTED_MSG("StartAttack::None")
break;
case ESkillTypes::Indirect: NOT_IMPLEMENTED_MSG("StartAttack::None")
break;
default : NMT_LOG("Not Valid Type")
}
}
void ASPNpcAgent::ContinueAttack()
{
if(CurrentUsingSkill == nullptr) return;
FSkill SkillData = CurrentUsingSkill->GetSkillData();
ActionMontageSectionName = SkillData.sSkillName;
switch(SkillType)
{
case ESkillTypes::Direct:
{
CurrentUsingSkill->ActivateSkill();
SetSkillCoolTime(SkillData.fCooldown);
ActionMontageSectionName.Append("Shoot");
PlayActionMontage(FName(*ActionMontageSectionName));
}
break;
case ESkillTypes::Melee: NOT_IMPLEMENTED_MSG("ContinueAttack::Melee")
break;
case ESkillTypes::None: NOT_IMPLEMENTED_MSG("ContinueAttack::None")
break;
case ESkillTypes::Buff: NOT_IMPLEMENTED_MSG("ContinueAttack::None")
break;
case ESkillTypes::Indirect: NOT_IMPLEMENTED_MSG("ContinueAttack::None")
break;
default : NMT_LOG("Not Valid Type")
}
}
void ASPNpcAgent::TryFinishAttack()
{
NMT_LOG("TryFinishAttack")
switch(SkillType)
{
case ESkillTypes::Melee:
{
GetAIControllerBase()->SetEnableAttack(true);
FinishCurrentSkill();
ShouldContinueAttack() ? ChangeNpcState(ENpcState::Attack) : ChangeNpcState(ENpcState::Chase);
}
break;
case ESkillTypes::Direct:
{
if(CurrentUsingSkill == nullptr)
{
GetAIControllerBase()->SetEnableAttack(true);
return;
}
FSkill SkillData = CurrentUsingSkill->GetSkillData();
ActionMontageSectionName = SkillData.sSkillName;
if(ShouldContinueAttack())
{
NMT_LOG("Attack Again")
GetAIControllerBase()->SetEnableAttack(true);
FinishCurrentSkill();
ChangeNpcState(ENpcState::Attack);
}
else
{
ActionMontageSectionName.Append("Undraw");
PlayActionMontage(FName(*ActionMontageSectionName), FOnMontageBlendingOutStarted::CreateWeakLambda(this, [this](UAnimMontage* Montage, bool Interrupted)
{
FinishAttack();
}));
}
}
break;
case ESkillTypes::None: NOT_IMPLEMENTED_MSG("ESkillTypes::None")
break;
case ESkillTypes::Buff: NOT_IMPLEMENTED_MSG("ESkillTypes::None")
break;
case ESkillTypes::Indirect: NOT_IMPLEMENTED_MSG("ESkillTypes::None")
break;
default : NMT_LOG("Not Valid Type")
}
}
void ASPNpcAgent::FinishAttack()
{
FinishCurrentSkill();
if(!GetController())
return;
GetAIControllerBase()->SetEnableAttack(true);
if(!ShouldContinueAttack())
ChangeNpcState(ENpcState::Chase);
}
void ASPNpcAgent::SetUnDetectTarget(const bool InbUnDetect)
{
Super::SetUnDetectTarget(InbUnDetect);
GetAIPerceptionController()->SetUnDetect(bUnDetect);
if(bUnDetect)
{
if(CurrentVisionMode == EVisionMode::Cone)
{
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
NMT_CHECKF(World)
ASPGameModeBase* SPGM = UGlobalUtilsLibrary::GetGameModeChecked<ASPGameModeBase>(World);
NMT_CHECKF(SPGM)
USPEnemyVisionManager* EM = ISPObjectProvider::Execute_GetEnemyVisionManager(SPGM);
NMT_CHECKF(EM)
EM->IsFocusedNpc(this) ? EM->UnregisterFocused() : EM->UnregisterDetection(this);
}
}
else
{
GetAIPerceptionController()->ForceUpdatePerception();
}
}
void ASPNpcAgent::SetVisionMode(EVisionMode InVisionMode)
{
CurrentVisionMode = InVisionMode;
NMT_CHECKF(VisionConeComponent && VisionLineMeshComponent)
switch(InVisionMode)
{
case EVisionMode::None:
{
VisionConeComponent->ToggleDraw(false);
VisionLineMeshComponent->SetHiddenInGame(true);
VisionLineMeshComponent->SetComponentTickEnabled(false);
break;
}
case EVisionMode::Cone:
{
VisionConeComponent->ToggleDraw(true);
VisionLineMeshComponent->SetHiddenInGame(true);
VisionLineMeshComponent->SetComponentTickEnabled(false);
break;
}
case EVisionMode::Line:
{
VisionConeComponent->ToggleDraw(false);
VisionLineMeshComponent->SetComponentTickEnabled(true);
VisionLineMeshComponent->SetHiddenInGame(false);
break;
}
}
}
void ASPNpcAgent::ChangeDoubtState(const EDoubtState InDoubtState)
{
Super::ChangeDoubtState(InDoubtState);
switch(InDoubtState)
{
case EDoubtState::None:
{
NotifyVisionDetection(false);
break;
}
case EDoubtState::Increasing: case EDoubtState::Decreasing: break;
default: NOT_IMPLEMENTED();
}
}
void ASPNpcAgent::InitializeMaterials()
{
TArray<UMeshComponent*> MeshComponents;
GetComponents<UMeshComponent>(MeshComponents);
for(UMeshComponent* MeshComp : MeshComponents)
{
AllMeshComponents.Add(MeshComp);
}
}
void ASPNpcAgent::StatusSlow(float InSlowValue, bool InbRecovery)
{
float LocBeforeValue = EffectTimeMap.Find(EEffectSubTypes::Slow)? LocBeforeValue = EffectTimeMap.Find(EEffectSubTypes::Slow)->EffectValue : LocBeforeValue = 0.f;
if(LocBeforeValue == InSlowValue) return;
if(!InbRecovery) InSlowValue = InSlowValue * -1.f;
float LocSlowValue = InSlowValue - LocBeforeValue;
NpcStatusComponent->AddStatusNpcMove(LocSlowValue);
NpcStatusComponent->AddStatusNpcRun(LocSlowValue);
if(CurrentSpeed == ENpcSpeedType::Walk)
GetCharacterMovement()->MaxWalkSpeed = GetNpcStatusComponent()->GetCurrentNpcStatus().fNpcMove;
else if(CurrentSpeed == ENpcSpeedType::Run)
GetCharacterMovement()->MaxWalkSpeed = GetNpcStatusComponent()->GetCurrentNpcStatus().fNpcRun;
}
void ASPNpcAgent::StatusFast(float InFastValue, bool InbRecovery)
{
float LocBeforeValue = EffectTimeMap.Find(EEffectSubTypes::Fast)? LocBeforeValue = EffectTimeMap.Find(EEffectSubTypes::Fast)->EffectValue : LocBeforeValue = 0.f;
if(LocBeforeValue == InFastValue) return;
if(InbRecovery) InFastValue = InFastValue * -1.f;
float LocFastValue = InFastValue - LocBeforeValue;
NpcStatusComponent->AddStatusNpcMove(LocFastValue);
NpcStatusComponent->AddStatusNpcRun(LocFastValue);
if(CurrentSpeed == ENpcSpeedType::Walk)
GetCharacterMovement()->MaxWalkSpeed = GetNpcStatusComponent()->GetCurrentNpcStatus().fNpcMove;
else if(CurrentSpeed == ENpcSpeedType::Run)
GetCharacterMovement()->MaxWalkSpeed = GetNpcStatusComponent()->GetCurrentNpcStatus().fNpcRun;
}
void ASPNpcAgent::JumpBack(const FVector& InTargetLocation)
{
FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), InTargetLocation);
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::JumpBack);
float JumpPower = 100.f;
FVector JumpDir = (GetActorLocation() - InTargetLocation).GetSafeNormal();
FVector JumpBackVector = JumpPower * JumpDir;
FVector LaunchVelocity;
SetActorRotation(LookAtRotation);
UGlobalUtilsLibrary::SuggestProjectileVelocityAtLocation(GetWorld(), LaunchVelocity, GetActorLocation(),
GetActorLocation() + JumpBackVector, 40.f, 0.f, 1.f);
LaunchCharacter(LaunchVelocity, true, true);
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
if(!World->GetTimerManager().IsTimerActive(JumpBackTimerHandle))
{
World->GetTimerManager().SetTimer(JumpBackTimerHandle, this, &ASPNpcAgent::CheckFallingFromJumpBack, World->GetDeltaSeconds(), true, 0.2f);
}
}
void ASPNpcAgent::CheckFallingFromJumpBack()
{
if(!GetMovementComponent()->IsFalling() && GetWorld()->GetTimerManager().IsTimerActive(JumpBackTimerHandle))
{
GetWorld()->GetTimerManager().ClearTimer(JumpBackTimerHandle);
JumpBackTimerHandle.Invalidate();
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::FallDown);
// RelocateOnNavMesh();
}
}
bool ASPNpcAgent::ShouldContinueAttack() const
{
AActor* Target = GetAIControllerBase()->GetTargetActor();
if(Target && IsTargetInAttackRange(Target))
{
if(SkillType == ESkillTypes::Direct)
{
NMT_CHECKF(NpcStatusComponent)
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(ECC_GameTraceChannel4); // NOTE : Wall
ObjectQueryParams.AddObjectTypesToQuery(ECC_GameTraceChannel6); // NOTE : Prop
FHitResult HitResult;
FVector EndTraceLocation = GetActorLocation() + GetActorForwardVector() * NpcStatusComponent->GetStatusNpcAttackLength();
GetWorld()->LineTraceSingleByObjectType(HitResult, GetActorLocation(), EndTraceLocation, ObjectQueryParams, FCollisionQueryParams());
return !HitResult.bBlockingHit;
}
return true;
}
return false;
}
void ASPNpcAgent::DetermineNextStateAfterAbnormal()
{
ENpcAbnormalAnimState AbnormalAnimState = AgentAnimInstance->GetNpcAbnormalAnimState();
switch(AbnormalAnimState)
{
case ENpcAbnormalAnimState::HitBack:
{
if(!NpcStatusComponent->IsDead())
{
AgentAnimInstance->SetNormalState(true);
ChangeNpcState(ENpcState::Chase);
}
break;
}
case ENpcAbnormalAnimState::FallDown:
{
if(!NpcStatusComponent->IsDead())
{
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::GetUp);
}
else
{
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::DeadLoop);
ChangeNpcState(ENpcState::Dead);
}
break;
}
case ENpcAbnormalAnimState::GetUp:
{
AgentAnimInstance->SetNormalState(true);
AActor* Target = GetAIControllerBase()->GetTargetActor();
if(Target)
{
IsTargetInAttackRange(Target) ? ChangeNpcState(ENpcState::Attack) : ChangeNpcState(ENpcState::Chase);
}
else
{
ChangeNpcState(ENpcState::Chase);
}
break;
}
case ENpcAbnormalAnimState::DeadStart:
{
AgentAnimInstance->ChangeNpcAbnormalAnimState(ENpcAbnormalAnimState::DeadLoop);
GetMesh()->bPauseAnims = true;
break;
}
case ENpcAbnormalAnimState::DeadLoop: case ENpcAbnormalAnimState::JumpBack: case ENpcAbnormalAnimState::Stun: NOT_IMPLEMENTED()
break;
default: NMT_LOG("Not Valid")
}
}
void ASPNpcAgent::SetCurrentUsingSkill(class ACoreSkill* InSkill)
{
CurrentUsingSkill = InSkill;
}
void ASPNpcAgent:: FinishCurrentSkill()
{
if(CurrentUsingSkill)
{
NMT_LOG("FinishCurrentSkill")
CurrentUsingSkill->FinishSkill();
CurrentUsingSkill = nullptr;
}
}
void ASPNpcAgent::SetSkillCoolTime(const float InCoolTime)
{
SkillCoolTime = InCoolTime;
bSkillCooldownActive = true;
}
class AAIPerceptionController* ASPNpcAgent::GetAIPerceptionController() const
{
AAIPerceptionController* AIC = Cast<AAIPerceptionController>(GetController());
NMT_CHECKF(AIC)
return AIC;
}
bool ASPNpcAgent::IsFinishSkillCoolDown() const
{
return SkillCoolTime <= 0.f;
}
bool ASPNpcAgent::IsTargetInAttackRange(const AActor* InTarget) const
{
NMT_CHECKF(NpcStatusComponent)
return GetHorizontalDistanceTo(InTarget) <= NpcStatusComponent->GetStatusNpcAttackLength();
}
void ASPNpcAgent::StartCheckInChaseRange()
{
NMT_CHECKF(SphereChaseRangeComponent)
if(!SphereChaseRangeComponent->OnExitedSphereRange.IsBound())
{
SphereChaseRangeComponent->ActivateFiltering();
SphereChaseRangeComponent->OnEnterSphereRange.AddWeakLambda(this, [this](AActor* Target)
{
GetAIControllerBase()->UpdateTargetInfo(Target);
});
SphereChaseRangeComponent->OnExitedSphereRange.AddWeakLambda(this, [this]()
{
GetAIControllerBase()->UpdateTargetInfo(nullptr);
});
}
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
if(!World->GetTimerManager().IsTimerActive(CheckChaseRangeTimerHandle))
World->GetTimerManager().SetTimer(CheckChaseRangeTimerHandle, this, &ASPNpcAgent::CheckTargetInChaseRange, World->GetDeltaSeconds(), true);
}
void ASPNpcAgent::StopSkillActionMontage()
{
NMT_CHECKF(AgentAnimInstance)
if(!NMT_ENSURE(NpcActionMontage))
return;
AgentAnimInstance->SetActionState(false);
if(AgentAnimInstance->GetInstanceForMontage(NpcActionMontage))
{
FAnimMontageInstance* MontageInstance = AgentAnimInstance->GetInstanceForMontage(NpcActionMontage);
if(MontageInstance && MontageInstance->OnMontageBlendingOutStarted.IsBound())
{
MontageInstance->OnMontageBlendingOutStarted.Unbind();
}
}
AgentAnimInstance->Montage_Stop(0.f, NpcActionMontage);
}
void ASPNpcAgent::SetSkillClass()
{
UDataTable* SkillDataTable = UAssetUtilsLibrary::LoadDataTableByEnumKey(EDataTableKey::Skill);
if(!NMT_ENSURE(SkillDataTable))
return;
NMT_CHECKF(NpcStatusComponent)
FString SkillRowName = FString::FromInt(NpcStatusComponent->GetStatusNpcAttackId());
FSkill* SkillDataRow = SkillDataTable->FindRow<FSkill>(*SkillRowName, TEXT("Not Found FSkill"));
if(!NMT_ENSURE(SkillDataRow))
return;
FString TempPath = SkillDataRow->sSkillBpPath;
if(!CHECK_FREE(TempPath))
{
FString ClassPath = UAssetUtilsLibrary::AssetReferencePathToClassPath(TempPath);
UClass* LoadedClass = StaticLoadClass(UObject::StaticClass(), nullptr, *ClassPath);
NMT_CHECKF(LoadedClass)
NpcSkillClass = TSubclassOf<ACoreSkill>(LoadedClass);
SkillType = UGlobalUtilsLibrary::GetStringToEnum<ESkillTypes>(*SkillDataRow->sSkillType);
bRotateWhileAttack = SkillType == ESkillTypes::Melee;
}
}
void ASPNpcAgent::ClearSkillCoolTime()
{
bSkillCooldownActive = false;
SkillCoolTime = 0.f;
}
void ASPNpcAgent::UpdateSkillCooldown(const float InDeltaSeconds)
{
if(SkillCoolTime < 0.f)
{
ClearSkillCoolTime();
if(SkillType == ESkillTypes::Direct)
TryFinishAttack();
}
SkillCoolTime -= InDeltaSeconds;
}
void ASPNpcAgent::CheckTargetInChaseRange()
{
if(SphereChaseRangeComponent)
{
AActor* TargetActor = GetAIControllerBase()->GetTargetActor();
if(TargetActor == nullptr)
return;
TArray<AActor*> DetectedActor = SphereChaseRangeComponent->GetNonBlockedActors(TargetActor->GetActorLocation(), GetActorLocation());
ACorePlayerCharacter* DetectedPlayer = nullptr;
for (AActor* Actor : DetectedActor)
{
if(ACorePlayerCharacter* Player = Cast<ACorePlayerCharacter>(Actor))
DetectedPlayer = Player;
}
if(DetectedPlayer == nullptr)
{
NMT_LOG("DetectedPlayer is empty. Player is behind wall")
FinishCheckInChaseRange();
}
}
}
void ASPNpcAgent::FinishCheckInChaseRange()
{
if(GetWorld()->GetTimerManager().IsTimerActive(CheckChaseRangeTimerHandle) && SphereChaseRangeComponent)
{
NMT_LOG("FinishCheckInChaseRange")
GetWorld()->GetTimerManager().ClearTimer(CheckChaseRangeTimerHandle);
CheckChaseRangeTimerHandle.Invalidate();
GetAIControllerBase()->UpdateTargetInfo(nullptr);
SphereChaseRangeComponent->DeactivateFiltering();
SphereChaseRangeComponent->OnExitedSphereRange.Clear();
if(CurrentState != ENpcState::Attack && CurrentState != ENpcState::Hit)
{
ChangeNpcState(ENpcState::Chase);
}
}
}
void ASPNpcAgent::RelocateOnNavMesh()
{
// FIXME : 현재 미사용. 테스트 필요
UWorld* World = UGlobalUtilsLibrary::GetValidWorld(this);
NMT_CHECKF(World);
UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(World);
NMT_CHECKF(NavSys);
float CapsuleRadius, CapsuleHalfHeight;
GetCapsuleComponent()->GetScaledCapsuleSize(CapsuleRadius, CapsuleHalfHeight);
FVector Extent(CapsuleRadius, CapsuleRadius, CapsuleHalfHeight);
FNavLocation OutLocation;
bool bOnNavMesh = NavSys->ProjectPointToNavigation(GetActorLocation(), OutLocation, Extent);
if(bOnNavMesh)
{
NMT_LOG("On Nav Mesh")
}
else
{
NMT_LOG("NOT On Nav Mesh")
Extent += FVector(50.0, 50.0, 0.0);
if(NavSys->ProjectPointToNavigation(GetActorLocation(), OutLocation, Extent))
{
SetActorLocation(OutLocation);
}
}
}