D1/Source/D1/CombatCharacter.cpp

893 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Interface/Interact.h"
#include "Actor/BaseWeapon.h"
#include "Components/CombatComponent.h"
#include "Components/StatsComponent.h"
#include "Definitions/CombatGameplayTags.h"
#include "Engine/DamageEvents.h"
#include "NiagaraFunctionLibrary.h"
#include "NiagaraComponent.h"
#include "DamageType/AttackDamageType.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
//////////////////////////////////////////////////////////////////////////
// ACombatCharacter
ACombatCharacter::ACombatCharacter()
{
// 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));
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
// Setting CombatComponent
CombatComponent = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
CombatComponent->OnCombatToggled.AddUObject(this, &ACombatCharacter::CharacterCombatToggled);
// Setting StateManagerComponent
StateManagerComponent = CreateDefaultSubobject<UStateManagerComponent>(TEXT("StateManagerComponent"));
StateManagerComponent->OnStateBegin.AddUObject(this, &ACombatCharacter::CharacterStateBegin);
StateManagerComponent->OnStateEnd.AddUObject(this, &ACombatCharacter::CharacterStateEnd);
StateManagerComponent->OnActionBegin.AddUObject(this, &ACombatCharacter::CharacterActionBegin);
StateManagerComponent->OnActionEnd.AddUObject(this, &ACombatCharacter::CharacterActionEnd);
// Setting StatsComponent
StatsComponent = CreateDefaultSubobject<UStatsComponent>(TEXT("StatsComponent"));
StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &ACombatCharacter::CharacterCurrentStatValueUpdated);
//Setting TargetComponent
TargetingComponent = CreateDefaultSubobject<UTargetingComponent>(TEXT("TargetingComponent"));
//Setting MovementSpeed
MovementSpeedMode = EMovementSpeedMode::Jogging;
WalkingSpeed = 200.f;
JoggingSpeed = 500.f;
SprintSpeed = 700.f;
//Settings OwnedGameplayTags
OwnedGameplayTags.AddTag(FCombatGameplayTags::Get().Character_Player);
ChargeAttackTime = 0.18f;
PelvisBoneName = TEXT("pelvis");
}
void ACombatCharacter::BeginPlay()
{
// Call the base class
Super::BeginPlay();
//Add Input Mapping Context
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
//Player have Weapon until first time : probably temp coding
FActorSpawnParameters spawnParam;
spawnParam.Owner = this;
spawnParam.Instigator = this;
ABaseEquippable* SpawnItem = Cast<ABaseEquippable>(GetWorld()->SpawnActor(Weapon, &GetActorTransform(), spawnParam));
if (SpawnItem)
SpawnItem->OnEquipped();
StatsComponent->InitializeStats();
}
float ACombatCharacter::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);
//스텟 관련 처리
StatsComponent->TakeDamageOnStat(Damage);
//앞에서 맞았는지 뒤에서 맞았는지 판별
bHitFront = UKismetMathLibrary::InRange_FloatFloat(this->GetDotProductTo(EventInstigator->GetPawn()), -0.1f, 1.f);
LastHitInfo = PointDamageEvent->HitInfo;
//play sound, effect
ApplyImpactEffect(damageTypeClass->DamageType);
if (CanReceiveHitReaction())
ApplyHitReaction(damageTypeClass->DamageType);
}
return fDamage;
}
void ACombatCharacter::ContinueAttack_Implementation()
{
if (CombatComponent->GetIsAttackSaved())
{
CombatComponent->SetIsAttackSaved(false);
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
StateManagerComponent->SetCurrentState(FGameplayTag::EmptyTag);
AttackEvent();
}
}
void ACombatCharacter::ResetAttack_Implementation()
{
CombatComponent->ResetAttack();
}
void ACombatCharacter::ResetCombat_Implementation()
{
CombatComponent->ResetAttack();
StateManagerComponent->ResetState();
StateManagerComponent->SetCurrentAction(FGameplayTag::EmptyTag);
}
FRotator ACombatCharacter::GetDesiredRotation_Implementation()
{
FVector InputVector = GetCharacterMovement()->GetLastInputVector();
if (InputVector.Equals(FVector(0, 0, 0), 0.001f))
return GetActorRotation();
else
return UKismetMathLibrary::MakeRotFromX(GetLastMovementInputVector());
}
bool ACombatCharacter::CanReceiveDamage_Implementation()
{
bool result;
result = (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
return result;
}
//////////////////////////////////////////////////////////////////////////
// Input
void ACombatCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
//Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Jumping);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Move);
//Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look);
//Interact
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ACombatCharacter::Interact);
//ToggleCombat
EnhancedInputComponent->BindAction(ToggleCombatAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleCombat);
//LightAttack
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Triggered, this, &ACombatCharacter::LightChargeAttack);
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Completed, this, &ACombatCharacter::LightAttack);
//HeavyAttack
EnhancedInputComponent->BindAction(HeavyAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::HeavyAttack);
//Dodge
EnhancedInputComponent->BindAction(DodgeAction, ETriggerEvent::Started, this, &ACombatCharacter::Dodge);
//ToggleWalk
EnhancedInputComponent->BindAction(ToggleWalkAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleWalk);
//Sprint
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &ACombatCharacter::StartSprint);
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &ACombatCharacter::StopSprint);
//ToggleLockOn
EnhancedInputComponent->BindAction(ToggleLockOnAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleLockOn);
}
}
void ACombatCharacter::SetCanMove_Implementation(bool inputCanMove)
{
bCanMove = inputCanMove;
}
void ACombatCharacter::SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode)
{
if (NewSpeedMode == MovementSpeedMode)
return;
MovementSpeedMode = NewSpeedMode;
TargetingComponent->UpdateRotationMode();
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 ACombatCharacter::Move(const FInputActionValue& Value)
{
if(!bCanMove) //Value changes SetCanMove Func Call to Animnotify
return;
// input is a Vector2D
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
void ACombatCharacter::Look(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// add yaw and pitch input to controller
if(!TargetingComponent->GetIsTargeting()) // Not Targeting
{
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
else // Targeting
AddControllerPitchInput(LookAxisVector.Y * 0.2f);
}
}
void ACombatCharacter::Jumping(const FInputActionValue& Value)
{
if (!CanJumping())
return;
StopAnimMontage();
StateManagerComponent->ResetState();
CombatComponent->ResetAttack();
Super::Jump();
}
void ACombatCharacter::Interact(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
TEnumAsByte<EObjectTypeQuery> InteractiveType = UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_GameTraceChannel1);
ObjectTypes.Add(InteractiveType);
TArray<AActor*> ActorsToIgnore;
FHitResult OutHit;
if (UKismetSystemLibrary::SphereTraceSingleForObjects(GetWorld(), GetActorLocation(), GetActorLocation(), 100.f, ObjectTypes, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, OutHit, true))
{
IInteract* interactObject = Cast<IInteract>(OutHit.GetActor());
if (interactObject)
interactObject->Interact(this);
}
}
void ACombatCharacter::ToggleCombat(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();
ToggleCombatEvent();
}
void ACombatCharacter::LightAttack(const FInputActionValue& Value)
{
if (!ResetChargeAttack())
return;
IsHeavyAttack = false;
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
CombatComponent->SetIsAttackSaved(true);
else
AttackEvent();
}
//누르고 있는 시간을 받기 위해서 FInputActionValue가 아니라 FInputActionInstance로 인자값을 받음
void ACombatCharacter::LightChargeAttack(const FInputActionInstance& Instance)
{
AttackHeldTime = Instance.GetElapsedTime();
bAttackCharged = (AttackHeldTime >= ChargeAttackTime);
if (bAttackCharged)
ChargeAttackEvent();
}
void ACombatCharacter::HeavyAttack(const FInputActionValue& Value)
{
if (bAttackCharged)
return;
IsHeavyAttack = true;
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
CombatComponent->SetIsAttackSaved(true);
else
AttackEvent();
}
void ACombatCharacter::Dodge(const FInputActionValue& Value)
{
if (CanPerformDodge())
{
PerformDodge();
ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon();
if (IsValid(pBaseWeapon))
{
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction());
}
}
}
void ACombatCharacter::ToggleWalk(const FInputActionValue& Value)
{
if (GetCombatMovementSpeedMode() != EMovementSpeedMode::Walking)
SetMovementSpeedMode(EMovementSpeedMode::Walking);
else
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
}
void ACombatCharacter::StartSprint(const FInputActionValue& Value)
{
if (CanPerformSprint())
{
SetMovementSpeedMode(EMovementSpeedMode::Sprinting);
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
if (World)
World->GetTimerManager().SetTimer(StaminaTimerHandle, this, &ACombatCharacter::SprintStaminaCost, 0.1f, true);
}
}
void ACombatCharacter::StopSprint(const FInputActionValue& Value)
{
DisableSprint();
}
void ACombatCharacter::ToggleLockOn(const FInputActionValue& Value)
{
if(!CombatComponent->GetCombatEnabled() && !TargetingComponent->GetIsTargeting())
return;
TargetingComponent->ToggleLockOn();
}
void ACombatCharacter::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 ACombatCharacter::CharacterStateEnd(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)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Disable == CharState)
{/*None*/}
}
void ACombatCharacter::CharacterActionBegin(FGameplayTag CharAction)
{
if(FGameplayTag::EmptyTag == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_GeneralAction == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Dodge == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_EnterCombat == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_ExitCombat == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_LightAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_HeavyAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_FallingAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_SprintAttack == CharAction)
{/*None*/ }
}
void ACombatCharacter::CharacterActionEnd(FGameplayTag CharAction)
{
if(FGameplayTag::EmptyTag == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_GeneralAction == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Dodge == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_EnterCombat == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_ExitCombat == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_LightAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_HeavyAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_FallingAttack == CharAction)
{/*None*/ }
else if(FCombatGameplayTags::Get().Character_Action_Attack_SprintAttack == CharAction)
{/*None*/ }
}
void ACombatCharacter::CharacterCurrentStatValueUpdated(EStats statType, float value)
{
if (!(statType == EStats::Health) || value > 0.f)
return;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dead);
}
void ACombatCharacter::CharacterCombatToggled(bool IsCombatEnabled)
{
if(IsValid(TargetingComponent))
TargetingComponent->UpdateRotationMode();
}
void ACombatCharacter::ToggleCombatEvent()
{
ABaseWeapon* baseWeapon = CombatComponent->GetMainWeapon();
if (!baseWeapon)
return;
if (!CanPerformToggleCombat())
return;
if (!CombatComponent->GetCombatEnabled())
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_EnterCombat, 0);
else
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_ExitCombat, 0);
}
void ACombatCharacter::AttackEvent()
{
if (!CanPerformAttack())
return;
if (CombatComponent->GetCombatEnabled())
{
PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount());
if (IsValid(CombatComponent->GetMainWeapon()))
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * CombatComponent->GetMainWeapon()->GetStatCostForAction());
}
else
ToggleCombatEvent();
}
void ACombatCharacter::ChargeAttackEvent()
{
if (CanPerformAttack())
{
PerformAttack(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack, CombatComponent->GetAttackCount());
ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon();
if (IsValid(pBaseWeapon))
{
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction());
}
}
}
bool ACombatCharacter::ResetChargeAttack()
{
AttackHeldTime = 0.f;
if (bAttackCharged)
{
bAttackCharged = false;
return false;
}
else
return true;
}
void ACombatCharacter::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
{
if (!GetMesh())
return;
GetMesh()->SetPhysicsLinearVelocity(GetActorForwardVector() * -InitialSpeed, false, PelvisBoneName);
}
void ACombatCharacter::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);
GetCameraBoom()->AttachToComponent(GetMesh(), rules, PelvisBoneName); //camera를 척추뼈에 붙임
GetCameraBoom()->bDoCollisionTest = false; //카메라 충돌 없게 함
GetMesh()->SetCollisionProfileName(TEXT("ragdoll"), true); //ragdoll 로 변경
GetMesh()->SetAllBodiesBelowSimulatePhysics(PelvisBoneName, true, true);
GetMesh()->SetAllBodiesBelowPhysicsBlendWeight(PelvisBoneName, 1.f);
}
void ACombatCharacter::DisableSprint()
{
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
if (World)
World->GetTimerManager().ClearTimer(StaminaTimerHandle);
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
}
void ACombatCharacter::SprintStaminaCost()
{
if (!CanPerformSprint())
{
DisableSprint();
return;
}
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -2.f);
if (StatsComponent->GetCurrentStatValue(EStats::Stamina) < 10.f)
{
DisableSprint();
return;
}
}
void ACombatCharacter::ApplyHitReaction(EDamageType InDamageType)
{
switch (InDamageType)
{
case EDamageType::None:
PerformHitStun();
break;
case EDamageType::MeleeDamage:
PerformHitStun();
break;
case EDamageType::KnockdownDamage:
PerformKnockdown();
break;
default:
break;
}
}
void ACombatCharacter::ApplyImpactEffect(EDamageType InDamageType)
{
//Play Sound
UGameplayStatics::PlaySoundAtLocation(this, HitSound, LastHitInfo.Location);
//Hit Effect
UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), HitEmitter, LastHitInfo.Location);
}
void ACombatCharacter::PerformAttack(FGameplayTag attackType, int32 attackIndex)
{
if(!CombatComponent->GetCombatEnabled())
return;
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(attackType);
int32 attackIdx = attackIndex;
if (montages.Num() <= attackIdx)
attackIdx = 0;
if (!montages.IsValidIndex(attackIdx))
return;
UAnimMontage* attackMontage = montages[attackIdx];
if (!IsValid(attackMontage))
{
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
return;
}
else
{
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Attacking);
StateManagerComponent->SetCurrentAction(attackType);
PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx >= montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
}
//인자값은 나중에 필요하면 추가 PerformDodge(int32 dodgeIndex)
void ACombatCharacter::PerformDodge()
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return;
int32 montageIndex = 0;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(FCombatGameplayTags::Get().Character_Action_Dodge);
if (montages.Num() <= montageIndex)
montageIndex = 0;
if (!montages.IsValidIndex(montageIndex))
return;
UAnimMontage* dodgeMontage = montages[montageIndex];
if (IsValid(dodgeMontage))
{
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dodging);
StateManagerComponent->SetCurrentAction(FCombatGameplayTags::Get().Character_Action_Dodge);
PlayAnimMontage(dodgeMontage);
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIndex);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
}
}
bool ACombatCharacter::PerformAction(FGameplayTag characterState, FGameplayTag characterAction, int32 montageIndex)
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return false;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(characterAction);
int32 montageIdx = montageIndex;
if (montages.Num() <= montageIdx)
montageIdx = 0;
if (!montages.IsValidIndex(montageIdx))
return false;
UAnimMontage* actionMontage = montages[montageIdx];
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(characterState);
StateManagerComponent->SetCurrentAction(characterAction);
PlayAnimMontage(actionMontage);
return true;
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
return false;
}
}
void ACombatCharacter::PerformDeath()
{
EnableRagdoll();
ApplyHitReactionPhysicsVelocity(2000.f); //충돌을 좀 더 그럴싸하게 하기 위해서 뒷방향으로 충격
if (IsValid(CombatComponent->GetMainWeapon()))
CombatComponent->GetMainWeapon()->SimulateWeaponPhysics(); //무기의 충돌킴
FTimerHandle deathTimer;
GetWorld()->GetTimerManager().SetTimer(deathTimer, FTimerDelegate::CreateLambda([&]()
{
if (IsValid(CombatComponent->GetMainWeapon()))
CombatComponent->GetMainWeapon()->Destroy();
this->Destroy();
}), 4.f, false); // 4초 후에 object 삭제
}
bool ACombatCharacter::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 ACombatCharacter::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 ACombatCharacter::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 ACombatCharacter::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 ACombatCharacter::CanPerformDodge()
{
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();
ReturnValue &= (StatsComponent->GetCurrentStatValue(EStats::Stamina) >= 10.f);
return ReturnValue;
}
bool ACombatCharacter::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 ACombatCharacter::CanReceiveHitReaction()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
return ReturnValue;
}
bool ACombatCharacter::CanPerformSprint()
{
return (FMath::IsNearlyEqual(GetVelocity().Length(), 0.f)) == false;
}
FGameplayTag ACombatCharacter::GetDesiredAttackType()
{
if (GetCharacterMovement()->IsFalling())
return FCombatGameplayTags::Get().Character_Action_Attack_FallingAttack;
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
return FCombatGameplayTags::Get().Character_Action_Attack_SprintAttack;
if (IsHeavyAttack)
return FCombatGameplayTags::Get().Character_Action_Attack_HeavyAttack;
return FCombatGameplayTags::Get().Character_Action_Attack_LightAttack;
}