D1/Source/D1/CombatPlayerCharacter.cpp

1069 lines
35 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatPlayerCharacter.h"
#include "CombatPlayerController.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 "Actor/BaseConsumable.h"
#include "Components/EquipmentComponent.h"
#include "DamageType/AttackDamageType.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "UI/CombatHUD.h"
#include "UI/UI_MainHUD.h"
#include "UI/UI_PotionAmountText.h"
//////////////////////////////////////////////////////////////////////////
// ACombatCharacter
ACombatPlayerCharacter::ACombatPlayerCharacter()
{
// 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, &ACombatPlayerCharacter::CharacterCombatToggled);
// Setting StateManagerComponent
StateManagerComponent = CreateDefaultSubobject<UStateManagerComponent>(TEXT("StateManagerComponent"));
StateManagerComponent->OnStateBegin.AddUObject(this, &ACombatPlayerCharacter::CharacterStateBegin);
StateManagerComponent->OnStateEnd.AddUObject(this, &ACombatPlayerCharacter::CharacterStateEnd);
// Setting StatsComponent
StatsComponent = CreateDefaultSubobject<UStatsComponent>(TEXT("StatsComponent"));
StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &ACombatPlayerCharacter::CharacterCurrentStatValueUpdated);
StatsComponent->SetBaseStatValue(EStats::Health, 100.f);
StatsComponent->SetMaxStatValue(EStats::Health, 100.f);
StatsComponent->SetBaseStatValue(EStats::Stamina, 100.f);
StatsComponent->SetMaxStatValue(EStats::Stamina, 100.f);
StatsComponent->SetBaseStatValue(EStats::Armor, 0.f);
StatsComponent->SetMaxStatValue(EStats::Armor, 100.f);
StatsComponent->InitializeStats();
//Setting TargetComponent
TargetingComponent = CreateDefaultSubobject<UTargetingComponent>(TEXT("TargetingComponent"));
//Setting EquipmentComponent
EquipmentComponent = CreateDefaultSubobject<UEquipmentComponent>(TEXT("EquipmentComponent"));
//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");
//Setting Timeline
RotateToTargetTimeLineComponent = CreateDefaultSubobject<UTimelineComponent>(TEXT("RotateToTargetTimeLineComponent"));
}
void ACombatPlayerCharacter::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);
}
}
EquipmentComponent->InitializeEquipment();
//Setting Timeline - if you set on Constructor, Can not get Curve
FOnTimelineFloat TimelineFloatCallback;
TimelineFloatCallback.BindUFunction(this, FName("RotateToTargetUpdate"));
RotateToTargetTimeLineComponent->SetTimelineLength(5.0f);
RotateToTargetTimeLineComponent->SetLooping(false);
if(CurveFloatTimeline)
RotateToTargetTimeLineComponent->AddInterpFloat(CurveFloatTimeline, TimelineFloatCallback);
}
void ACombatPlayerCharacter::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
}
float ACombatPlayerCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float fDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
const 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);
ReceiveDamage(fDamage, PointDamageEvent, damageTypeClass, EventInstigator);
}
else if(DamageEvent.IsOfType(FRadialDamageEvent::ClassID))
{
const FRadialDamageEvent* RadialDamageEvent = static_cast<const FRadialDamageEvent*>(&DamageEvent);
ReceiveDamage(fDamage, RadialDamageEvent, damageTypeClass, EventInstigator);
}
return fDamage;
}
void ACombatPlayerCharacter::ContinueAttack_Implementation()
{
if (CombatComponent->GetIsAttackSaved())
{
CombatComponent->SetIsAttackSaved(false);
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
StateManagerComponent->SetCurrentState(FGameplayTag::EmptyTag);
AttackEvent();
}
}
void ACombatPlayerCharacter::ResetAttack_Implementation()
{
CombatComponent->ResetAttack();
}
void ACombatPlayerCharacter::ResetCombat_Implementation()
{
CombatComponent->ResetAttack();
StateManagerComponent->ResetState();
StateManagerComponent->SetCurrentAction(FGameplayTag::EmptyTag);
if(CanPerformBlock())
CombatComponent->SetBlockingState(true);
}
FRotator ACombatPlayerCharacter::GetDesiredRotation_Implementation()
{
FVector InputVector = GetCharacterMovement()->GetLastInputVector();
if (InputVector.Equals(FVector(0, 0, 0), 0.001f))
return GetActorRotation();
else
return UKismetMathLibrary::MakeRotFromX(GetLastMovementInputVector());
}
bool ACombatPlayerCharacter::CanReceiveDamage_Implementation()
{
bool result = (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
result &= !bEnableIFrame;
return result;
}
//////////////////////////////////////////////////////////////////////////
// Input
void ACombatPlayerCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
//Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACombatPlayerCharacter::Jumping);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACombatPlayerCharacter::Move);
//Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACombatPlayerCharacter::Look);
//Interact
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::Interact);
//ToggleCombat
EnhancedInputComponent->BindAction(ToggleCombatInputAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::ToggleCombatAction);
//LightAttack
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Triggered, this, &ACombatPlayerCharacter::LightChargeAttack);
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Completed, this, &ACombatPlayerCharacter::LightAttack);
//HeavyAttack
EnhancedInputComponent->BindAction(HeavyAttackAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::HeavyAttack);
//Dodge
EnhancedInputComponent->BindAction(DodgeAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::Dodge);
//ToggleWalk
EnhancedInputComponent->BindAction(ToggleWalkAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::ToggleWalk);
//Sprint
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::StartSprint);
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &ACombatPlayerCharacter::StopSprint);
//ToggleLockOn
EnhancedInputComponent->BindAction(ToggleLockOnAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::ToggleLockOn);
//Block
EnhancedInputComponent->BindAction(BlockAction, ETriggerEvent::Triggered, this, &ACombatPlayerCharacter::Blocking);
EnhancedInputComponent->BindAction(BlockAction, ETriggerEvent::Completed, this, &ACombatPlayerCharacter::StopBlocking);
//Use Item
EnhancedInputComponent->BindAction(UseItemAction, ETriggerEvent::Started, this, &ACombatPlayerCharacter::UseItem);
}
}
void ACombatPlayerCharacter::SetCanMove_Implementation(bool inputCanMove)
{
bCanMove = inputCanMove;
}
void ACombatPlayerCharacter::ActivateCollision_Implementation(ECollisionPart CollisionPart)
{
ABaseWeapon* weapon = CombatComponent->GetMainWeapon();
if(IsValid(weapon))
weapon->ActivateCollision(CollisionPart);
}
void ACombatPlayerCharacter::DeactivateCollision_Implementation(ECollisionPart CollisionPart)
{
ABaseWeapon* weapon = CombatComponent->GetMainWeapon();
if(IsValid(weapon))
weapon->DeactivateCollision(CollisionPart);
}
void ACombatPlayerCharacter::SetIFrame_Implementation(bool InputEnableIFrame)
{
bEnableIFrame = InputEnableIFrame;
}
float ACombatPlayerCharacter::PerformAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex)
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return false;
TArray<UAnimMontage*> montages = CurrentWeapon->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 %d is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
}
return actionDuration;
}
float ACombatPlayerCharacter::PerformAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex)
{
if(!CombatComponent->GetCombatEnabled())
return 0.f;
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return 0.f;
TArray<UAnimMontage*> montages = CurrentWeapon->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 ACombatPlayerCharacter::PerformCustomAction(FGameplayTag ActionTag, FGameplayTag StateTag, UAnimMontage* InMontage, float fMontagePlayRate, bool bAutoReset)
{
UAnimMontage* actionMontage = InMontage;
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(StateTag);
StateManagerComponent->SetCurrentAction(ActionTag);
if(!IsValid(GetMesh()->GetAnimInstance()))
return false;
GetMesh()->GetAnimInstance()->Montage_Play(actionMontage, fMontagePlayRate);
}
else
{
FString str = FString::Printf(TEXT("PerformCustomAction is NOT VALID!!"));
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
return false;
}
return true;
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::SetPotionUI(UUserWidget* potionUI)
{
PotionUI = potionUI;
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::Jumping(const FInputActionValue& Value)
{
if (!CanJumping())
return;
StopAnimMontage();
StateManagerComponent->ResetState();
CombatComponent->ResetAttack();
Super::Jump();
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::ToggleCombatAction(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();
ToggleCombatEvent();
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::LightChargeAttack(const FInputActionInstance& Instance)
{
AttackHeldTime = Instance.GetElapsedTime();
bAttackCharged = (AttackHeldTime >= ChargeAttackTime);
if (bAttackCharged)
ChargeAttackEvent();
}
void ACombatPlayerCharacter::HeavyAttack(const FInputActionValue& Value)
{
if (bAttackCharged)
return;
IsHeavyAttack = true;
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
CombatComponent->SetIsAttackSaved(true);
else
AttackEvent();
}
void ACombatPlayerCharacter::Dodge(const FInputActionValue& Value)
{
if (CanPerformDodge())
{
PerformAction(FCombatGameplayTags::Get().Character_Action_Dodge, FCombatGameplayTags::Get().Character_State_Dodging, 0);
ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon();
if (IsValid(pBaseWeapon))
{
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction());
}
}
}
void ACombatPlayerCharacter::ToggleWalk(const FInputActionValue& Value)
{
if (GetCombatMovementSpeedMode() != EMovementSpeedMode::Walking)
SetMovementSpeedMode(EMovementSpeedMode::Walking);
else
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
}
void ACombatPlayerCharacter::StartSprint(const FInputActionValue& Value)
{
if (CanPerformSprint())
{
SetMovementSpeedMode(EMovementSpeedMode::Sprinting);
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
if (World)
World->GetTimerManager().SetTimer(StaminaTimerHandle, this, &ACombatPlayerCharacter::SprintStaminaCost, 0.1f, true);
}
}
void ACombatPlayerCharacter::StopSprint(const FInputActionValue& Value)
{
DisableSprint();
}
void ACombatPlayerCharacter::ToggleLockOn(const FInputActionValue& Value)
{
TargetingComponent->ToggleLockOn();
}
void ACombatPlayerCharacter::Blocking(const FInputActionValue& Value)
{
bIsBlockPressed = true;
if(CanPerformBlock())
{
CombatComponent->SetBlockingState(true);
}
}
void ACombatPlayerCharacter::StopBlocking(const FInputActionValue& Value)
{
bIsBlockPressed = false;
CombatComponent->SetBlockingState(false);
}
void ACombatPlayerCharacter::UseItem(const FInputActionValue& Value)
{
if(CanUseItem())
EquipmentComponent->PerformActionFromItem(FCombatGameplayTags::Get().Item_Consumable);
}
void ACombatPlayerCharacter::CharacterStateBegin(FGameplayTag CharState)
{
if (FGameplayTag::EmptyTag == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
{
RotateToTarget();
}
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 ACombatPlayerCharacter::CharacterStateEnd(FGameplayTag CharState)
{
if (FGameplayTag::EmptyTag == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
{
StopRotateToTarget();
}
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 ACombatPlayerCharacter::CharacterCurrentStatValueUpdated(EStats statType, float value)
{
if (!(statType == EStats::Health) || value > 0.f)
return;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dead);
}
void ACombatPlayerCharacter::CharacterCombatToggled(bool IsCombatEnabled)
{
if(IsValid(TargetingComponent))
TargetingComponent->UpdateRotationMode();
}
void ACombatPlayerCharacter::ToggleCombatEvent()
{
ABaseWeapon* baseWeapon = CombatComponent->GetMainWeapon();
if (!baseWeapon)
return;
if (!CanPerformToggleCombat())
return;
if (!CombatComponent->GetCombatEnabled())
PerformAction(FCombatGameplayTags::Get().Character_Action_EnterCombat, FCombatGameplayTags::Get().Character_State_GeneralActionState, 0);
else
PerformAction(FCombatGameplayTags::Get().Character_Action_ExitCombat, FCombatGameplayTags::Get().Character_State_GeneralActionState, 0);
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::ChargeAttackEvent()
{
if (!CanPerformAttack())
return;
if(CombatComponent->GetCombatEnabled())
{
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 ACombatPlayerCharacter::ResetChargeAttack()
{
AttackHeldTime = 0.f;
if (bAttackCharged)
{
bAttackCharged = false;
return false;
}
else
return true;
}
void ACombatPlayerCharacter::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
{
if (!GetMesh())
return;
GetMesh()->SetPhysicsLinearVelocity(GetActorForwardVector() * -InitialSpeed, false, PelvisBoneName);
}
void ACombatPlayerCharacter::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 ACombatPlayerCharacter::DisableSprint()
{
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
if (World)
World->GetTimerManager().ClearTimer(StaminaTimerHandle);
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
}
void ACombatPlayerCharacter::SprintStaminaCost()
{
if (!CanPerformSprint())
{
DisableSprint();
return;
}
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -2.f);
if (StatsComponent->GetCurrentStatValue(EStats::Stamina) < 10.f)
{
DisableSprint();
return;
}
}
void ACombatPlayerCharacter::ApplyHitReaction(EDamageType InDamageType)
{
if(WasHitBlocked())
{
PerformBlock();
}
else
{
switch (InDamageType)
{
case EDamageType::None:
PerformHitStun();
break;
case EDamageType::MeleeDamage:
PerformHitStun();
break;
case EDamageType::KnockdownDamage:
PerformKnockdown();
break;
default:
break;
}
}
}
void ACombatPlayerCharacter::ApplyImpactEffect(EDamageType InDamageType)
{
//Play Sound
UGameplayStatics::PlaySoundAtLocation(this, HitSound, LastHitInfo.Location);
//Hit Effect
UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), HitEmitter, LastHitInfo.Location);
}
void ACombatPlayerCharacter::ReceiveDamage(float Damage, const FPointDamageEvent* pointDamageEvent, const UAttackDamageType* damageTypeClass, AController* eventInstigator)
{
//앞에서 맞았는지 뒤에서 맞았는지 판별
bHitFront = UKismetMathLibrary::InRange_FloatFloat(this->GetDotProductTo(eventInstigator->GetPawn()), -0.1f, 1.f);
LastHitInfo = pointDamageEvent->HitInfo;
//공격을 막았을 경우
if(WasHitBlocked())
Damage = 0.f; //막았을 때는 데미지 처리안함
else
ApplyImpactEffect(damageTypeClass->DamageType); //play sound, effect
//스텟 관련 처리
StatsComponent->TakeDamageOnStat(Damage);
if (CanReceiveHitReaction())
ApplyHitReaction(damageTypeClass->DamageType);
}
void ACombatPlayerCharacter::ReceiveDamage(float Damage, const FRadialDamageEvent* radialDamageEvent, const UAttackDamageType* damageTypeClass, AController* eventInstigator)
{
//앞에서 맞았는지 뒤에서 맞았는지 판별
bHitFront = UKismetMathLibrary::InRange_FloatFloat(this->GetDotProductTo(eventInstigator->GetPawn()), -0.1f, 1.f);
LastHitInfo = radialDamageEvent->ComponentHits[0];
//공격을 막았을 경우
if(WasHitBlocked())
Damage = 0.f; //막았을 때는 데미지 처리안함
else
ApplyImpactEffect(damageTypeClass->DamageType); //play sound, effect
//스텟 관련 처리
StatsComponent->TakeDamageOnStat(Damage);
if (CanReceiveHitReaction())
ApplyHitReaction(damageTypeClass->DamageType);
}
void ACombatPlayerCharacter::RotateToTarget()
{
RotateToTargetTimeLineComponent->PlayFromStart();
}
void ACombatPlayerCharacter::StopRotateToTarget()
{
RotateToTargetTimeLineComponent->Stop();
}
void ACombatPlayerCharacter::RotateToTargetUpdate(float Value)
{
if(!IsValid(TargetingComponent->GetTargetActor()))
return;
const FRotator CurrentRotator = GetActorRotation();
FVector StartVector = GetActorLocation();
FVector TargetVector = TargetingComponent->GetTargetActor()->GetActorLocation();
FRotator TargetRotator = UKismetMathLibrary::FindLookAtRotation(StartVector, TargetVector);
UWorld* WorldPointer = GetWorld();
if(!WorldPointer)
return;
double deltaTime = UGameplayStatics::GetWorldDeltaSeconds(WorldPointer);
FRotator NewRotator = FMath::RInterpTo(CurrentRotator, TargetRotator, deltaTime, RotateToTargetInterpSpeed);
NewRotator.Roll = CurrentRotator.Roll;
NewRotator.Pitch = CurrentRotator.Pitch;
SetActorRotation(NewRotator);
}
void ACombatPlayerCharacter::PerformDeath()
{
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);
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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::PerformBlock()
{
if(!IsValid(CombatComponent->GetShieldWeapon()))
return false;
TArray<UAnimMontage*> hitMontage = CombatComponent->GetShieldWeapon()->GetActionMontage(FCombatGameplayTags::Get().Character_Action_Attack_Blocking);
if(hitMontage.Num() <= 0)
return false;
int32 indexNum = FMath::RandRange(0, hitMontage.Num() - 1);
if(!hitMontage.IsValidIndex(indexNum))
return false;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Blocking);
PlayAnimMontage(hitMontage[indexNum]);
return true;
}
bool ACombatPlayerCharacter::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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::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 ACombatPlayerCharacter::CanReceiveHitReaction()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
return ReturnValue;
}
bool ACombatPlayerCharacter::CanPerformSprint()
{
return (FMath::IsNearlyEqual(GetVelocity().Length(), 0.f)) == false;
}
bool ACombatPlayerCharacter::CanPerformBlock()
{
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 &= CombatComponent->GetCombatEnabled();
ReturnValue &= IsValid(CombatComponent->GetShieldWeapon());
ReturnValue &= bIsBlockPressed; //Block Key를 눌렀는가?
return ReturnValue;
}
bool ACombatPlayerCharacter::CanUseItem()
{
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;
}
FGameplayTag ACombatPlayerCharacter::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;
}
bool ACombatPlayerCharacter::WasHitBlocked()
{
if(IsValid(CombatComponent))
return (CombatComponent->GetIsBlocking() && bHitFront);
else
return false;
}