// Copyright Epic Games, Inc. All Rights Reserved.
#include "ForestryCharacter.h"
#include "DebugMessages.h"
#include "Animation/AnimInstance.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "EnhancedInputComponent.h"
#include "InputActionValue.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Forestry.h"
AForestryCharacter::AForestryCharacter()
{
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);
// Create the first person mesh that will be viewed only by this character's owner
FirstPersonMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("First Person Mesh"));
FirstPersonMesh->SetupAttachment(GetMesh());
FirstPersonMesh->SetOnlyOwnerSee(true);
FirstPersonMesh->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::FirstPerson;
FirstPersonMesh->SetCollisionProfileName(FName("NoCollision"));
// Create the Camera Component
FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("First Person Camera"));
FirstPersonCameraComponent->SetupAttachment(FirstPersonMesh, FName("head"));
FirstPersonCameraComponent->SetRelativeLocationAndRotation(FVector(-2.8f, 5.89f, 0.0f), FRotator(0.0f, 90.0f, -90.0f));
FirstPersonCameraComponent->bUsePawnControlRotation = true;
FirstPersonCameraComponent->bEnableFirstPersonFieldOfView = true;
FirstPersonCameraComponent->bEnableFirstPersonScale = true;
FirstPersonCameraComponent->FirstPersonFieldOfView = 70.0f;
FirstPersonCameraComponent->FirstPersonScale = 0.6f;
// configure the character comps
GetMesh()->SetOwnerNoSee(true);
GetMesh()->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::WorldSpaceRepresentation;
GetCapsuleComponent()->SetCapsuleSize(34.0f, 96.0f);
// Interactable Searcher
InteractableSearcher = CreateDefaultSubobject<UInteractableSearcher>("Interactable Searcher");
// Statistics Component
StatisticsComponent = CreateDefaultSubobject<UStatisticsComponent>("Statistics");
// Flashlight Component
FlashlightComponent = CreateDefaultSubobject<UFlashlightComponent>("Flashlight");
// Crouch Helper
CrouchHelper = CreateDefaultSubobject<UCrouchHelper>("Crouch Helper");
CrouchHelper->Setup(GetCapsuleComponent());
// Configure character movement
GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
GetCharacterMovement()->AirControl = 0.5f;
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
// Physics Handle
PhysicsHandleComponent = CreateDefaultSubobject<UPhysicsHandleComponent>("PhysicsHandle");
PhysicsHandleComponent->LinearStiffness = 2000.f;
PhysicsHandleComponent->AngularStiffness = 2000.f;
PhysicsHandleComponent->LinearDamping = 100.f;
PhysicsHandleComponent->AngularDamping = 100.f;
}
bool AForestryCharacter::CanStand() const
{
if (!GetCapsuleComponent()) return false;
UCapsuleComponent* Capsule = GetCapsuleComponent();
float CurrentHalfHeight = Capsule->GetUnscaledCapsuleHalfHeight();
float DesiredHalfHeight = CrouchHelper->GetStandHeight();
if (CurrentHalfHeight >= DesiredHalfHeight)
return true;
float Radius = Capsule->GetUnscaledCapsuleRadius();
FVector Start = Capsule->GetComponentLocation() + FVector(0,0,CurrentHalfHeight);
FVector End = Capsule->GetComponentLocation() + FVector(0,0,DesiredHalfHeight);
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
bool bHit = GetWorld()->SweepSingleByChannel(
Hit,
Start,
End,
FQuat::Identity,
ECC_Visibility,
FCollisionShape::MakeCapsule(Radius, DesiredHalfHeight - CurrentHalfHeight),
Params
);
return !bHit;
}
void AForestryCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
// Crouching
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Started, this, &AForestryCharacter::DoCrouchStart);
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoCrouchEnd);
// Sprinting
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AForestryCharacter::DoSprintStart);
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoSprintEnd);
// Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AForestryCharacter::DoJumpStart);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &AForestryCharacter::DoJumpEnd);
// Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AForestryCharacter::MoveInput);
// Looking/Aiming
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AForestryCharacter::LookInput);
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AForestryCharacter::LookInput);
if (InteractableSearcher) InteractableSearcher->SetupPlayerInput(EnhancedInputComponent);
if (FlashlightComponent) FlashlightComponent->SetupPlayerInput(EnhancedInputComponent);
}
else
{
UE_LOG(LogForestry, Error, TEXT("'%s' Failed to find an Enhanced Input Component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
}
}
void AForestryCharacter::TickActor(float DeltaTime, ELevelTick TickType, FActorTickFunction& ThisTickFunction)
{
Super::TickActor(DeltaTime, TickType, ThisTickFunction);
if (bIsCrouching)
{
if (bWannaStand)
{
if (!CanStand()) return;
bIsCrouching = false;
bWannaStand = false;
CrouchHelper->Stand();
ChangeMoveSpeed(WalkSpeed);
}
}
}
void AForestryCharacter::MoveInput(const FInputActionValue& Value)
{
// get the Vector2D move axis
FVector2D MovementVector = Value.Get<FVector2D>();
// pass the axis values to the move input
DoMove(MovementVector.X, MovementVector.Y);
// bool bIsMoving = MovementVector != FVector2D::ZeroVector;
}
void AForestryCharacter::LookInput(const FInputActionValue& Value)
{
// get the Vector2D look axis
FVector2D LookAxisVector = Value.Get<FVector2D>();
// pass the axis values to the aim input
DoAim(LookAxisVector.X, LookAxisVector.Y);
}
void AForestryCharacter::ChangeMoveSpeed(const float Speed) const
{
GetCharacterMovement()->MaxWalkSpeed = Speed;
}
void AForestryCharacter::DoAim(float Yaw, float Pitch)
{
if (GetController())
{
// pass the rotation inputs
AddControllerYawInput(Yaw);
AddControllerPitchInput(Pitch);
}
}
void AForestryCharacter::DoMove(float Right, float Forward)
{
if (GetController())
{
// pass the move inputs
AddMovementInput(GetActorRightVector(), Right);
AddMovementInput(GetActorForwardVector(), Forward);
}
}
void AForestryCharacter::DoJumpStart()
{
// pass Jump to the character
if (!bIsCrouching) Jump();
}
void AForestryCharacter::DoJumpEnd()
{
// pass StopJumping to the character
StopJumping();
}
void AForestryCharacter::DoSprintStart()
{
if (StatisticsComponent->GetStatisticCurrentValue(EStatisticType::STAMINA) < MinStaminaToSprint)
{
// TODO: Notify that stamina is too low!
printk(110, "Character: Stamina is too low to sprint!")
return;
}
if (bIsCrouching) return;
if (!GetCharacterMovement()->IsMovingOnGround()) return;
bIsSprinting = true;
StatisticsComponent->SetDrainMultiplierForType(EStatisticType::STAMINA, StatisticsComponent->StaminaSprintMultiplier);
StatisticsComponent->SetDrainMultiplierForType(EStatisticType::THIRST, StatisticsComponent->ThirstSprintMultiplier);
ChangeMoveSpeed(SprintSpeed);
}
void AForestryCharacter::DoSprintEnd()
{
if (!bIsSprinting) return;
bIsSprinting = false;
StatisticsComponent->SetDrainMultiplierForType(EStatisticType::STAMINA, StatisticsComponent->DefaultMultiplier);
StatisticsComponent->SetDrainMultiplierForType(EStatisticType::THIRST, StatisticsComponent->DefaultMultiplier);
ChangeMoveSpeed(WalkSpeed);
}
void AForestryCharacter::DoCrouchStart()
{
if (bIsCrouching) return;
if (bIsSprinting)
DoSprintEnd();
bWannaStand = false;
bIsCrouching = true;
CrouchHelper->Crouch();
ChangeMoveSpeed(CrouchSpeed);
}
void AForestryCharacter::DoCrouchEnd()
{
if (!bIsCrouching) return;
if (!bWannaStand) bWannaStand = true;
}