709 lines
22 KiB
C++
709 lines
22 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "AI/MasterAI.h"
|
|
|
|
#include "AIController.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "Components/InputComponent.h"
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
#include "GameFramework/Controller.h"
|
|
#include "Components/CombatComponent.h"
|
|
#include "Components/StatsComponent.h"
|
|
#include "Components/StateManagerComponent.h"
|
|
#include "Definitions/CombatGameplayTags.h"
|
|
#include "Engine/DamageEvents.h"
|
|
#include "NiagaraFunctionLibrary.h"
|
|
#include "BehaviorTree/BlackboardComponent.h"
|
|
#include "Components/CollisionComponent.h"
|
|
#include "Components/TimelineComponent.h"
|
|
#include "Components/WidgetComponent.h"
|
|
#include "DamageType/AttackDamageType.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Perception/AISense_Damage.h"
|
|
|
|
AMasterAI::AMasterAI()
|
|
{
|
|
// Set size for collision capsule
|
|
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
|
|
|
// Don't rotate when the controller rotates. Let that just affect the camera.
|
|
bUseControllerRotationPitch = false;
|
|
bUseControllerRotationYaw = false;
|
|
bUseControllerRotationRoll = false;
|
|
|
|
// Configure character movement
|
|
GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...
|
|
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // ...at this rotation rate
|
|
|
|
// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
|
|
// instead of recompiling to adjust them
|
|
GetCharacterMovement()->MaxAcceleration = 1500.f;
|
|
GetCharacterMovement()->BrakingFrictionFactor = 1.f;
|
|
GetCharacterMovement()->bUseSeparateBrakingFriction = true; //Sprint 시 애니메이션이 끊기는 거 방지
|
|
GetCharacterMovement()->JumpZVelocity = 700.f;
|
|
GetCharacterMovement()->AirControl = 0.35f;
|
|
GetCharacterMovement()->MaxWalkSpeed = 500.f;
|
|
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
|
|
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
|
|
GetCharacterMovement()->GravityScale = 1.75f;
|
|
|
|
GetMesh()->SetRelativeLocationAndRotation(FVector(0.f, 0.f, -100.f), FRotator(0.f, -90.f, 0.f));
|
|
|
|
//Setting Widget class is Editor to Blueprint
|
|
TargetingWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("TargetingWidget"));
|
|
static ConstructorHelpers::FClassFinder<UUserWidget> WidgetRef(TEXT("/Game/CombatSystem/UI/WBP_LockOn.WBP_LockOn_C"));
|
|
if(WidgetRef.Class)
|
|
{
|
|
TargetingWidgetComponent->SetWidgetClass(WidgetRef.Class);
|
|
TargetingWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
|
|
TargetingWidgetComponent->SetDrawSize({20.f, 20.f});
|
|
TargetingWidgetComponent->SetupAttachment(GetMesh());
|
|
TargetingWidgetComponent->SetRelativeLocation(FVector(0.f, 0.f, 40.f));
|
|
TargetingWidgetComponent->SetVisibility(false);
|
|
}
|
|
|
|
// Setting CombatComponent
|
|
CombatComponent = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
|
|
|
|
// Setting StateManagerComponent
|
|
StateManagerComponent = CreateDefaultSubobject<UStateManagerComponent>(TEXT("StateManagerComponent"));
|
|
StateManagerComponent->OnStateBegin.AddUObject(this, &AMasterAI::CharacterStateBegin);
|
|
StateManagerComponent->OnStateEnd.AddUObject(this, &AMasterAI::CharacterStateEnd);
|
|
|
|
// Setting StatsComponent
|
|
StatsComponent = CreateDefaultSubobject<UStatsComponent>(TEXT("StatsComponent"));
|
|
StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &AMasterAI::CharacterCurrentStatValueUpdated);
|
|
StatsComponent->SetBaseStatValue(EStats::Health, 100.f);
|
|
StatsComponent->SetMaxStatValue(EStats::Health, 100.f);
|
|
StatsComponent->SetBaseStatValue(EStats::Armor, 0.f);
|
|
StatsComponent->SetMaxStatValue(EStats::Armor, 100.f);
|
|
StatsComponent->SetBaseStatValue(EStats::Damage, 25.f);
|
|
StatsComponent->SetMaxStatValue(EStats::Damage, 300.f);
|
|
StatsComponent->InitializeStats();
|
|
|
|
// Setting WeaponCollisionComponent
|
|
// Setting OnHit Func is Child Class
|
|
MainCollisionComponent = CreateDefaultSubobject<UCollisionComponent>(TEXT("MainCollisionComponent"));
|
|
MainCollisionComponent->SetTraceRaius(20.f);
|
|
MainCollisionComponent->SetStartSocketName(TEXT("WeaponStart"));
|
|
MainCollisionComponent->SetEndSocketName(TEXT("WeaponEnd"));
|
|
TArray<TEnumAsByte<EObjectTypeQuery>> InputCollisionObjectTypes;
|
|
InputCollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn));
|
|
MainCollisionComponent->SetCollisionObjectTypes(InputCollisionObjectTypes);
|
|
MainCollisionComponent->SetDrawDebugType(EDrawDebugTrace::None);
|
|
FGameplayTagContainer GameplayTags(FCombatGameplayTags::Get().Character_Enemy);
|
|
MainCollisionComponent->SetGameplayTagsIgnore(GameplayTags);
|
|
|
|
SubCollisionComponent = CreateDefaultSubobject<UCollisionComponent>(TEXT("SubCollisionComponent"));
|
|
SubCollisionComponent->SetTraceRaius(20.f);
|
|
SubCollisionComponent->SetStartSocketName(TEXT("WeaponStart"));
|
|
SubCollisionComponent->SetEndSocketName(TEXT("WeaponEnd"));
|
|
TArray<TEnumAsByte<EObjectTypeQuery>> LeftInputCollisionObjectTypes;
|
|
LeftInputCollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn));
|
|
SubCollisionComponent->SetCollisionObjectTypes(LeftInputCollisionObjectTypes);
|
|
SubCollisionComponent->SetDrawDebugType(EDrawDebugTrace::None);
|
|
FGameplayTagContainer LeftGameplayTags(FCombatGameplayTags::Get().Character_Enemy);
|
|
SubCollisionComponent->SetGameplayTagsIgnore(LeftGameplayTags);
|
|
|
|
//Settings OwnedGameplayTags
|
|
OwnedGameplayTags.AddTag(FCombatGameplayTags::Get().Character_Enemy);
|
|
|
|
//Setting MovementSpeed
|
|
MovementSpeedMode = EMovementSpeedMode::Jogging;
|
|
WalkingSpeed = 200.f;
|
|
JoggingSpeed = 500.f;
|
|
SprintSpeed = 700.f;
|
|
|
|
PelvisBoneName = TEXT("pelvis");
|
|
|
|
//Setting Timeline
|
|
RotateToTargetTimeLineComponent = CreateDefaultSubobject<UTimelineComponent>(TEXT("RotateToTargetTimeLineComponent"));
|
|
static ConstructorHelpers::FObjectFinder<UCurveFloat> UCurveObj(TEXT("/Game/CombatSystem/Blueprints/Timeline/RotateToTargetCurve.RotateToTargetCurve"));
|
|
if(UCurveObj.Succeeded())
|
|
CurveFloatTimeline = UCurveObj.Object;
|
|
}
|
|
|
|
void AMasterAI::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// BP의 BeginPlay가 먼저 호출됨 (ReceiveBeginPlay)
|
|
// 따라서 생성자로 올림
|
|
//StatsComponent->InitializeStats();
|
|
|
|
//Setting Timeline
|
|
FOnTimelineFloat TimelineFloatCallback;
|
|
TimelineFloatCallback.BindUFunction(this, FName("RotateToTargetUpdate"));
|
|
RotateToTargetTimeLineComponent->SetTimelineLength(0.25f);
|
|
RotateToTargetTimeLineComponent->SetLooping(false);
|
|
if(CurveFloatTimeline)
|
|
RotateToTargetTimeLineComponent->AddInterpFloat(CurveFloatTimeline, TimelineFloatCallback);
|
|
}
|
|
|
|
float AMasterAI::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
|
|
{
|
|
float fDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
|
|
|
|
UAttackDamageType* damageTypeClass = Cast<UAttackDamageType>(DamageEvent.DamageTypeClass->GetDefaultObject());
|
|
if(!IsValid(damageTypeClass))
|
|
return false;
|
|
|
|
if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
|
|
{
|
|
const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);
|
|
APawn* Attacker = EventInstigator->GetPawn();
|
|
|
|
//스텟 관련 처리
|
|
StatsComponent->TakeDamageOnStat(Damage);
|
|
|
|
//데미지 관련 AI 처리
|
|
UAISense_Damage::ReportDamageEvent(GetWorld(), this, Attacker, Damage, PointDamageEvent->HitInfo.Location, PointDamageEvent->HitInfo.Location);
|
|
|
|
//앞에서 맞았는지 뒤에서 맞았는지 판별
|
|
bHitFront = UKismetMathLibrary::InRange_FloatFloat(this->GetDotProductTo(Attacker), -0.1f, 1.f);
|
|
LastHitInfo = PointDamageEvent->HitInfo;
|
|
|
|
//play sound, effect
|
|
ApplyImpactEffect(damageTypeClass->DamageType);
|
|
|
|
if (CanReceiveHitReaction())
|
|
ApplyHitReaction(damageTypeClass->DamageType);
|
|
}
|
|
return fDamage;
|
|
}
|
|
|
|
void AMasterAI::ContinueAttack_Implementation()
|
|
{
|
|
if (CombatComponent->GetIsAttackSaved())
|
|
{
|
|
CombatComponent->SetIsAttackSaved(false);
|
|
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
|
|
StateManagerComponent->SetCurrentState(FGameplayTag::EmptyTag);
|
|
AttackEvent();
|
|
}
|
|
}
|
|
|
|
void AMasterAI::ResetAttack_Implementation()
|
|
{
|
|
CombatComponent->ResetAttack();
|
|
}
|
|
|
|
void AMasterAI::ResetCombat_Implementation()
|
|
{
|
|
CombatComponent->ResetAttack();
|
|
StateManagerComponent->ResetState();
|
|
StateManagerComponent->SetCurrentAction(FGameplayTag::EmptyTag);
|
|
}
|
|
|
|
FRotator AMasterAI::GetDesiredRotation_Implementation()
|
|
{
|
|
FVector InputVector = GetCharacterMovement()->GetLastInputVector();
|
|
if (InputVector.Equals(FVector(0, 0, 0), 0.001f))
|
|
return GetActorRotation();
|
|
else
|
|
return UKismetMathLibrary::MakeRotFromX(GetLastMovementInputVector());
|
|
}
|
|
|
|
bool AMasterAI::CanReceiveDamage_Implementation()
|
|
{
|
|
bool result = (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
|
|
result &= !bEnableIFrame;
|
|
return result;
|
|
}
|
|
|
|
void AMasterAI::SetCanMove_Implementation(bool inputCanMove)
|
|
{
|
|
bCanMove = inputCanMove;
|
|
}
|
|
|
|
void AMasterAI::ActivateCollision_Implementation(ECollisionPart CollisionPart)
|
|
{
|
|
switch (CollisionPart)
|
|
{
|
|
case ECollisionPart::MainWeapon:
|
|
MainCollisionComponent->ActivateCollision();
|
|
break;
|
|
case ECollisionPart::OffhandWeapon:
|
|
SubCollisionComponent->ActivateCollision();
|
|
break;
|
|
case ECollisionPart::RightFoot:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AMasterAI::DeactivateCollision_Implementation(ECollisionPart CollisionPart)
|
|
{
|
|
switch (CollisionPart)
|
|
{
|
|
case ECollisionPart::MainWeapon:
|
|
MainCollisionComponent->DeactivateCollision();
|
|
break;
|
|
case ECollisionPart::OffhandWeapon:
|
|
SubCollisionComponent->DeactivateCollision();
|
|
break;
|
|
case ECollisionPart::RightFoot:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AMasterAI::SetIFrame_Implementation(bool InputEnableIFrame)
|
|
{
|
|
bEnableIFrame = InputEnableIFrame;
|
|
}
|
|
|
|
void AMasterAI::RotateToTarget_Implementation()
|
|
{
|
|
RotateToTargetTL();
|
|
}
|
|
|
|
float AMasterAI::PerformAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex)
|
|
{
|
|
TArray<UAnimMontage*> montages = GetActionMontage(ActionTag);
|
|
int32 montageIdx = MontageIndex;
|
|
|
|
if(bRandomIndex)
|
|
montageIdx = FMath::RandRange(0, montages.Num() - 1);
|
|
else if (montages.Num() <= montageIdx)
|
|
montageIdx = 0;
|
|
|
|
if (!montages.IsValidIndex(montageIdx))
|
|
return false;
|
|
|
|
UAnimMontage* actionMontage = montages[montageIdx];
|
|
|
|
float actionDuration = 0.f;
|
|
if (IsValid(actionMontage))
|
|
{
|
|
StateManagerComponent->SetCurrentState(StateTag);
|
|
StateManagerComponent->SetCurrentAction(ActionTag);
|
|
if(!IsValid(GetMesh()->GetAnimInstance()))
|
|
return 0.f;
|
|
actionDuration = GetMesh()->GetAnimInstance()->Montage_Play(actionMontage);
|
|
}
|
|
else
|
|
{
|
|
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
|
|
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
|
|
}
|
|
return actionDuration;
|
|
}
|
|
|
|
float AMasterAI::PerformAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex)
|
|
{
|
|
TArray<UAnimMontage*> montages = GetActionMontage(AttackType);
|
|
int32 attackIdx = AttackIndex;
|
|
|
|
if(bRandomIndex)
|
|
attackIdx = FMath::RandRange(0, montages.Num() - 1);
|
|
else if (montages.Num() <= attackIdx)
|
|
attackIdx = 0;
|
|
|
|
if (!montages.IsValidIndex(attackIdx))
|
|
return 0.f;
|
|
|
|
UAnimMontage* attackMontage = montages[attackIdx];
|
|
float attackDuration = 0.f;
|
|
if (!IsValid(attackMontage))
|
|
{
|
|
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
|
|
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
|
|
}
|
|
else
|
|
{
|
|
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Attacking);
|
|
StateManagerComponent->SetCurrentAction(AttackType);
|
|
|
|
if(!IsValid(GetMesh()->GetAnimInstance()))
|
|
return 0.f;
|
|
attackDuration = GetMesh()->GetAnimInstance()->Montage_Play(attackMontage);
|
|
|
|
int idx = attackIdx + 1;
|
|
if (idx >= montages.Num())
|
|
idx = 0;
|
|
|
|
CombatComponent->SetAttackCount(idx);
|
|
}
|
|
return attackDuration;
|
|
}
|
|
|
|
bool AMasterAI::CanBeTargeted()
|
|
{
|
|
return (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
|
|
}
|
|
|
|
void AMasterAI::OnTargeted(bool bIsTargeted)
|
|
{
|
|
TargetingWidgetComponent->SetVisibility(bIsTargeted);
|
|
}
|
|
|
|
void AMasterAI::OnTargetSet(AActor* NewTarget)
|
|
{
|
|
TargetActor = NewTarget;
|
|
}
|
|
|
|
UPrimitiveComponent* AMasterAI::GetWeaponMesh()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void AMasterAI::SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode)
|
|
{
|
|
if (NewSpeedMode == MovementSpeedMode)
|
|
return;
|
|
|
|
MovementSpeedMode = NewSpeedMode;
|
|
|
|
switch (MovementSpeedMode)
|
|
{
|
|
case EMovementSpeedMode::Walking:
|
|
GetCharacterMovement()->MaxWalkSpeed = WalkingSpeed;
|
|
break;
|
|
case EMovementSpeedMode::Jogging:
|
|
GetCharacterMovement()->MaxWalkSpeed = JoggingSpeed;
|
|
break;
|
|
case EMovementSpeedMode::Sprinting:
|
|
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AMasterAI::CharacterStateBegin(FGameplayTag CharState)
|
|
{
|
|
if (FGameplayTag::EmptyTag == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Dodging == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_GeneralActionState == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Dead == CharState)
|
|
{
|
|
PerformDeath();
|
|
}
|
|
else if (FCombatGameplayTags::Get().Character_State_Disable == CharState)
|
|
{/*None*/}
|
|
}
|
|
|
|
void AMasterAI::CharacterStateEnd(FGameplayTag CharState)
|
|
{
|
|
if (FGameplayTag::EmptyTag == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
|
|
{
|
|
StopRotateToTargetTL();
|
|
}
|
|
else if (FCombatGameplayTags::Get().Character_State_Dodging == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_GeneralActionState == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Dead == CharState)
|
|
{/*None*/}
|
|
else if (FCombatGameplayTags::Get().Character_State_Disable == CharState)
|
|
{/*None*/}
|
|
}
|
|
|
|
void AMasterAI::CharacterCurrentStatValueUpdated(EStats statType, float value)
|
|
{
|
|
if (!(statType == EStats::Health) || value > 0.f)
|
|
return;
|
|
|
|
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dead);
|
|
}
|
|
|
|
void AMasterAI::AttackEvent()
|
|
{
|
|
if (!CanPerformAttack())
|
|
return;
|
|
|
|
if (CombatComponent->GetCombatEnabled())
|
|
PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount(), false);
|
|
}
|
|
|
|
void AMasterAI::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
|
|
{
|
|
if (!GetMesh())
|
|
return;
|
|
|
|
GetMesh()->SetPhysicsLinearVelocity(GetActorForwardVector() * -InitialSpeed, false, PelvisBoneName);
|
|
}
|
|
|
|
void AMasterAI::EnableRagdoll()
|
|
{
|
|
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None, 0); //movement 더 이상 없게 바꿈
|
|
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore); //충돌무시
|
|
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); //카메라 충돌무시
|
|
FAttachmentTransformRules rules(EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, true);
|
|
GetMesh()->SetCollisionProfileName(TEXT("ragdoll"), true); //ragdoll 로 변경
|
|
GetMesh()->SetAllBodiesBelowSimulatePhysics(PelvisBoneName, true, true);
|
|
GetMesh()->SetAllBodiesBelowPhysicsBlendWeight(PelvisBoneName, 1.f);
|
|
}
|
|
|
|
void AMasterAI::DisableSprint()
|
|
{
|
|
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
|
|
if (World)
|
|
World->GetTimerManager().ClearTimer(StaminaTimerHandle);
|
|
|
|
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
|
|
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
|
|
}
|
|
|
|
void AMasterAI::SprintStaminaCost()
|
|
{
|
|
if (!CanPerformSprint())
|
|
{
|
|
DisableSprint();
|
|
return;
|
|
}
|
|
|
|
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -2.f);
|
|
if (StatsComponent->GetCurrentStatValue(EStats::Stamina) < 10.f)
|
|
{
|
|
DisableSprint();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void AMasterAI::ApplyHitReaction(EDamageType InDamageType)
|
|
{
|
|
switch (InDamageType)
|
|
{
|
|
case EDamageType::None:
|
|
PerformHitStun();
|
|
break;
|
|
case EDamageType::MeleeDamage:
|
|
PerformHitStun();
|
|
break;
|
|
case EDamageType::KnockdownDamage:
|
|
PerformKnockdown();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AMasterAI::ApplyImpactEffect(EDamageType InDamageType)
|
|
{
|
|
//Play Sound
|
|
UGameplayStatics::PlaySoundAtLocation(this, HitSound, LastHitInfo.Location);
|
|
|
|
//Hit Effect
|
|
UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), HitEmitter, LastHitInfo.Location);
|
|
}
|
|
|
|
void AMasterAI::RotateToTargetTL()
|
|
{
|
|
RotateToTargetTimeLineComponent->PlayFromStart();
|
|
}
|
|
|
|
void AMasterAI::StopRotateToTargetTL()
|
|
{
|
|
RotateToTargetTimeLineComponent->Stop();
|
|
}
|
|
|
|
void AMasterAI::RotateToTargetUpdate(float Value)
|
|
{
|
|
if(!IsValid(TargetActor))
|
|
return;
|
|
|
|
const FRotator CurrentRotator = GetActorRotation();
|
|
FVector StartVector = GetActorLocation();
|
|
FVector TargetVector = TargetActor->GetActorLocation();
|
|
FRotator TargetRotator = UKismetMathLibrary::FindLookAtRotation(StartVector, TargetVector);
|
|
|
|
UWorld* WorldPointer = GetWorld();
|
|
if(!WorldPointer)
|
|
return;
|
|
FRotator NewRotator = FMath::Lerp<FRotator, float>(CurrentRotator, TargetRotator, Value);
|
|
NewRotator.Roll = CurrentRotator.Roll;
|
|
NewRotator.Pitch = CurrentRotator.Pitch;
|
|
SetActorRotation(NewRotator);
|
|
}
|
|
|
|
void AMasterAI::PerformDeath()
|
|
{
|
|
if(IsDead())
|
|
return;
|
|
SetDead(true);
|
|
|
|
if(!DeathAnimations.IsEmpty())
|
|
{
|
|
const int randomIdx = FMath::RandRange(0, DeathAnimations.Num() - 1);
|
|
PlayAnimMontage(DeathAnimations[randomIdx]);
|
|
}
|
|
else
|
|
EnableRagdoll();
|
|
|
|
if(bHitFront) //충돌을 좀 더 그럴싸하게 하기 위해서 피격방향으로 충격
|
|
ApplyHitReactionPhysicsVelocity(2000.f);
|
|
else
|
|
ApplyHitReactionPhysicsVelocity(-2000.f);
|
|
|
|
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
|
|
|
FTimerHandle deathTimer;
|
|
GetWorld()->GetTimerManager().SetTimer(deathTimer, FTimerDelegate::CreateLambda([&]()
|
|
{
|
|
this->Destroy();
|
|
}), 4.f, false); // 4초 후에 object 삭제
|
|
}
|
|
|
|
bool AMasterAI::PerformHitStun()
|
|
{
|
|
UAnimMontage* hitMontage = nullptr;
|
|
if(bHitFront)
|
|
hitMontage = HitStunFrontMontage;
|
|
else
|
|
hitMontage = HitStunBackMontage;
|
|
|
|
if(!IsValid(hitMontage))
|
|
return false;
|
|
|
|
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Disable);
|
|
PlayAnimMontage(hitMontage);
|
|
return true;
|
|
}
|
|
|
|
bool AMasterAI::PerformKnockdown()
|
|
{
|
|
UAnimMontage* hitMontage = nullptr;
|
|
if(bHitFront)
|
|
hitMontage = KnockdownFrontMontage;
|
|
else
|
|
hitMontage = KnockdownBackMontage;
|
|
|
|
if(!IsValid(hitMontage))
|
|
return false;
|
|
|
|
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Disable);
|
|
PlayAnimMontage(hitMontage);
|
|
return true;
|
|
}
|
|
|
|
bool AMasterAI::CanPerformToggleCombat()
|
|
{
|
|
bool ReturnValue = true;
|
|
|
|
FGameplayTagContainer inputContainer;
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Attacking);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
|
|
|
|
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
|
|
ReturnValue &= !GetCharacterMovement()->IsFalling();
|
|
return ReturnValue;
|
|
}
|
|
|
|
bool AMasterAI::CanPerformAttack()
|
|
{
|
|
bool ReturnValue = true;
|
|
|
|
FGameplayTagContainer inputContainer;
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Attacking);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
|
|
|
|
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
|
|
ReturnValue &= (StatsComponent->GetCurrentStatValue(EStats::Stamina) >= 10.f);
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
bool AMasterAI::CanJumping()
|
|
{
|
|
bool ReturnValue = true;
|
|
|
|
FGameplayTagContainer inputContainer;
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
|
|
|
|
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
|
|
ReturnValue &= !GetCharacterMovement()->IsFalling();
|
|
return ReturnValue;
|
|
}
|
|
|
|
bool AMasterAI::CanReceiveHitReaction()
|
|
{
|
|
bool ReturnValue = true;
|
|
|
|
FGameplayTagContainer inputContainer;
|
|
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
|
|
|
|
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
|
|
ReturnValue &= !IsDead();
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
bool AMasterAI::CanPerformSprint()
|
|
{
|
|
return (FMath::IsNearlyEqual(GetVelocity().Length(), 0.f)) == false;
|
|
}
|
|
|
|
FGameplayTag AMasterAI::GetDesiredAttackType()
|
|
{
|
|
if (GetCharacterMovement()->IsFalling())
|
|
return FCombatGameplayTags::Get().Character_Action_Attack_FallingAttack;
|
|
|
|
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
|
|
return FCombatGameplayTags::Get().Character_Action_Attack_SprintAttack;
|
|
|
|
return FCombatGameplayTags::Get().Character_Action_Attack_LightAttack;
|
|
}
|
|
|
|
TArray<UAnimMontage*> AMasterAI::GetActionMontage(FGameplayTag characterAction)
|
|
{
|
|
TArray<UAnimMontage*> outputArr;
|
|
|
|
if (FCombatGameplayTags::Get().Character_Action_Attack_CloseRange == characterAction)
|
|
outputArr = CloseRangeAttackMontage;
|
|
else if (FCombatGameplayTags::Get().Character_Action_Attack_MediumRange == characterAction)
|
|
outputArr = MediumRangeAttackMontage;
|
|
else if (FCombatGameplayTags::Get().Character_Action_Attack_RareAttack == characterAction)
|
|
outputArr = RareAttackMontage;
|
|
else if (FCombatGameplayTags::Get().Character_Action_Dodge == characterAction)
|
|
outputArr = DodgeMontage;
|
|
else if (FCombatGameplayTags::Get().Character_Action_EnterCombat == characterAction)
|
|
outputArr.Add(EnterCombat);
|
|
else if (FCombatGameplayTags::Get().Character_Action_ExitCombat == characterAction)
|
|
outputArr.Add(ExitCombat);
|
|
else if (FCombatGameplayTags::Get().Character_Action_Intro == characterAction)
|
|
outputArr.Add(Intro);
|
|
|
|
return outputArr;
|
|
}
|
|
|
|
void AMasterAI::SetDead(bool Condition)
|
|
{
|
|
if(bIsDead == Condition)
|
|
return;
|
|
|
|
bIsDead = Condition;
|
|
AAIController* aiController = Cast<AAIController>(GetController());
|
|
if(aiController)
|
|
{
|
|
UBlackboardComponent* BlackboardComponent = aiController->GetBlackboardComponent();
|
|
if(BlackboardComponent)
|
|
BlackboardComponent->SetValueAsBool(TEXT("bIsDead"), bIsDead);
|
|
}
|
|
}
|
|
|
|
bool AMasterAI::IsDead()
|
|
{
|
|
return bIsDead;
|
|
} |