You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
770 lines
24 KiB
770 lines
24 KiB
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "MCharacter.h"
|
|
|
|
#include "DrawDebugHelpers.h"
|
|
#include "MPlayerMech.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "GameFramework/PawnMovementComponent.h"
|
|
#include "MechDefence/MechDefence.h"
|
|
#include "MWeapon.h"
|
|
#include "MHealthComponent.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "EngineUtils.h"
|
|
#include "MConstructableBuilding.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Components/AudioComponent.h"
|
|
#include "MGameplayActor.h"
|
|
#include "MInteractableActor.h"
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
|
|
static TAutoConsoleVariable<int32> CVAR_DebugPlayerDrawing(
|
|
TEXT("MDEF.DebugPlayerDrawing"),
|
|
0,
|
|
TEXT("Draw debug visualizations for the player character"),
|
|
ECVF_Cheat);
|
|
|
|
static TAutoConsoleVariable<int32> CVAR_GodMode(
|
|
TEXT("MDEF.GodMode"),
|
|
0,
|
|
TEXT("Make the player character invincible"),
|
|
ECVF_Cheat);
|
|
|
|
AMCharacter::AMCharacter()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
|
|
CameraComponent->SetupAttachment(GetCapsuleComponent());
|
|
CameraComponent->SetRelativeLocation(FVector(0.f, 0.f, BaseEyeHeight));
|
|
CameraComponent->bUsePawnControlRotation = true;
|
|
|
|
PlayerArms = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("PlayerArms"));
|
|
PlayerArms->SetupAttachment(CameraComponent);
|
|
PlayerArms->SetRelativeLocation(FVector(0.f, 0.f, -155.f));
|
|
PlayerArms->SetRelativeRotation(FRotator(2.f, -15.f, 5.f));
|
|
|
|
GetMovementComponent()->GetNavAgentPropertiesRef().bCanCrouch = true;
|
|
|
|
HealthComponent = CreateDefaultSubobject<UMHealthComponent>(TEXT("HealthComponent"));
|
|
|
|
AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComponent"));
|
|
AudioComponent->SetupAttachment(RootComponent);
|
|
|
|
MaxInteractableDistance = 1000.f;
|
|
HoveredInteractableActor = nullptr;
|
|
bDied = false;
|
|
CurrentWeapon = nullptr;
|
|
bWantsToZoom = false;
|
|
TargetFOV = 45.f;
|
|
ZoomInterpolateSpeed = 20.f;
|
|
DefaultFOV = CameraComponent->FieldOfView;
|
|
OutOfSafeZoneDamageInterval = 1.f;
|
|
OutOfSafeZoneDamage = 10.f;
|
|
SafeZoneHealAmount = 10.f;
|
|
SafeZoneHealInterval = 0.5f;
|
|
SafeZoneHealDelay = 5.f;
|
|
bIsInSafeZone = false;
|
|
bIsFirstTimeHeal = true;
|
|
MinimumMovementVelocityThreshold = 100.f;
|
|
MaxStamina = 100.f;
|
|
CurrentStamina = MaxStamina;
|
|
StaminaConsumeAmount = 5.f;
|
|
StaminaConsumeInterval = 0.5f;
|
|
StaminaRestoreAmount = 10.f;
|
|
StaminaRestoreInterval = 0.5f;
|
|
StaminaRestoreDelay = 2.f;
|
|
SprintMultiplier = 2.f;
|
|
bIsSprinting = false;
|
|
bCanSprint = true;
|
|
ArmRotateSpeed = 5.f;
|
|
HiddenArmPitch = -60.f;
|
|
HiddenArmLocation = FVector(-20, -5, -200);
|
|
|
|
FAutoConsoleVariableSink CVarConsoleVarSink(FConsoleCommandDelegate::CreateUObject(this, &AMCharacter::HandleCVARChanged));
|
|
}
|
|
|
|
void AMCharacter::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
DefaultArmPitch = PlayerArms->GetRelativeRotation().Pitch;
|
|
DefaultArmLocation = PlayerArms->GetRelativeLocation();
|
|
|
|
HealthComponent->OnHealthChanged.AddDynamic(this, &AMCharacter::HandleHealthChanged);
|
|
if(CVAR_GodMode.GetValueOnGameThread() > 0)
|
|
HealthComponent->bIsInvincible = true;
|
|
|
|
// Spawn weapons
|
|
FActorSpawnParameters SpawnParameters;
|
|
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
Weapons.SetNumZeroed((int32)EWeaponType::MaxWeaponType);
|
|
|
|
if(PrimaryWeaponClass)
|
|
{
|
|
AMWeapon* PrimaryWeapon = GetWorld()->SpawnActor<AMWeapon>(PrimaryWeaponClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters);
|
|
if(PrimaryWeapon)
|
|
{
|
|
PrimaryWeapon->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, PrimaryWeapon->GetPlayerAttachmentSocketName());
|
|
Weapons[(int)EWeaponType::PrimaryWeapon] = PrimaryWeapon;
|
|
PrimaryWeapon->SetOwner(this);
|
|
PrimaryWeapon->SetActorEnableCollision(false);
|
|
OnWeaponEquipped.AddDynamic(PrimaryWeapon, &AMWeapon::HandleWeaponEquipped);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Primary Weapon Class not specified. Please set primary weapon class"));
|
|
}
|
|
|
|
if(ScannerClass)
|
|
{
|
|
AMWeapon* Scanner = GetWorld()->SpawnActor<AMWeapon>(ScannerClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters);
|
|
if(Scanner)
|
|
{
|
|
Scanner->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, Scanner->GetPlayerAttachmentSocketName());
|
|
Weapons[(int)EWeaponType::Scanner] = Scanner;
|
|
Scanner->SetOwner(this);
|
|
Scanner->SetActorHiddenInGame(true);
|
|
Scanner->SetActorTickEnabled(false);
|
|
Scanner->SetActorEnableCollision(false);
|
|
OnWeaponEquipped.AddDynamic(Scanner, &AMWeapon::HandleWeaponEquipped);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Scanner Class not specified. Please set scanner class"));
|
|
}
|
|
|
|
if(BlowTorchClass)
|
|
{
|
|
AMWeapon* BlowTorch = GetWorld()->SpawnActor<AMWeapon>(BlowTorchClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters);
|
|
if(BlowTorch)
|
|
{
|
|
BlowTorch->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, BlowTorch->GetPlayerAttachmentSocketName());
|
|
Weapons[(int)EWeaponType::BlowTorch] = BlowTorch;
|
|
BlowTorch->SetOwner(this);
|
|
BlowTorch->SetActorHiddenInGame(true);
|
|
BlowTorch->SetActorTickEnabled(false);
|
|
BlowTorch->SetActorEnableCollision(false);
|
|
OnWeaponEquipped.AddDynamic(BlowTorch, &AMWeapon::HandleWeaponEquipped);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("BlowTorch Class not specified. Please set blowtorch class"));
|
|
}
|
|
|
|
if(TabletClass)
|
|
{
|
|
AMWeapon* Tablet = GetWorld()->SpawnActor<AMWeapon>(TabletClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParameters);
|
|
if(Tablet)
|
|
{
|
|
Tablet->AttachToComponent(PlayerArms, FAttachmentTransformRules::SnapToTargetNotIncludingScale, Tablet->GetPlayerAttachmentSocketName());
|
|
Weapons[(int)EWeaponType::Tablet] = Tablet;
|
|
Tablet->SetOwner(this);
|
|
Tablet->SetActorHiddenInGame(true);
|
|
Tablet->SetActorTickEnabled(false);
|
|
Tablet->SetActorEnableCollision(false);
|
|
OnWeaponEquipped.AddDynamic(Tablet, &AMWeapon::HandleWeaponEquipped);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Tablet Class not specified. Please set tablet class"));
|
|
}
|
|
|
|
EquipWeapon((uint8)EWeaponType::PrimaryWeapon);
|
|
|
|
|
|
// Safezone checks
|
|
UCapsuleComponent* PlayerCapsuleComponent = GetCapsuleComponent();
|
|
if(PlayerCapsuleComponent)
|
|
{
|
|
PlayerCapsuleComponent->OnComponentBeginOverlap.AddDynamic(this, &AMCharacter::HandleCapsuleBeginOverlap);
|
|
PlayerCapsuleComponent->OnComponentEndOverlap.AddDynamic(this, &AMCharacter::HandleCapsuleEndOverlap);
|
|
}
|
|
|
|
// GetWorldTimerManager().SetTimerForNextTick(this, &AMCharacter::CheckIfInSafeZone);
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::CheckIfInSafeZone, 2.f);
|
|
}
|
|
|
|
void AMCharacter::HandleCVARChanged()
|
|
{
|
|
bool GodMode = CVAR_GodMode.GetValueOnGameThread() > 0;
|
|
HealthComponent->bIsInvincible = GodMode;
|
|
|
|
UWorld* CurrentWorld = GetWorld();
|
|
if(CurrentWorld)
|
|
{
|
|
for(TActorIterator<AMPlayerMech> It(CurrentWorld); It; ++It)
|
|
{
|
|
AMPlayerMech* PlayerVehicle = *It;
|
|
UMHealthComponent* HC = Cast<UMHealthComponent>(PlayerVehicle->GetComponentByClass(UMHealthComponent::StaticClass()));
|
|
if(HC)
|
|
{
|
|
HC->bIsInvincible = GodMode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::CheckIfInSafeZone()
|
|
{
|
|
TArray<AActor*> OverlappingActors;
|
|
UCapsuleComponent* ActorCapsule = GetCapsuleComponent();
|
|
if(ActorCapsule)
|
|
{
|
|
ActorCapsule->GetOverlappingActors(OverlappingActors, SafeZoneClass);
|
|
if(OverlappingActors.Num() == 0)
|
|
{
|
|
if(bIsInSafeZone)
|
|
LeaveSafeZone();
|
|
}
|
|
else if(OverlappingActors.Num() > 0)
|
|
{
|
|
for(AActor* OverlappedActor : OverlappingActors)
|
|
{
|
|
IGameplayTagAssetInterface* GameplayTagAsset = Cast<IGameplayTagAssetInterface>(OverlappingActors[0]);
|
|
if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags))
|
|
{
|
|
//Check if the safezone is constructed then determine if we are inside a constructed or underconstruction safezone
|
|
AMConstructableBuilding* ConstructableBuilding = Cast<AMConstructableBuilding>(OverlappedActor);
|
|
if(ConstructableBuilding)
|
|
{
|
|
if(ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed)
|
|
{
|
|
if(!bIsInSafeZone) // If we're not in safe zone yet, enter it
|
|
{
|
|
EnterSafeZone(OverlappedActor);
|
|
}
|
|
else
|
|
{
|
|
// If we're already in safe zone, check if we need healing
|
|
FTimerManager& TimerManager = GetWorldTimerManager();
|
|
if(!HealthComponent->IsAtMaxHealth() && !TimerManager.IsTimerActive(TimerHandle_ApplySafeZoneHeal))
|
|
{
|
|
TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay);
|
|
|
|
}
|
|
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// We are inside a safezone but it isnt constructed
|
|
if(bIsInSafeZone)
|
|
LeaveSafeZone();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::MoveForward(float ScaleValue)
|
|
{
|
|
AddMovementInput(GetActorForwardVector(), ScaleValue);
|
|
}
|
|
|
|
void AMCharacter::MoveRight(float ScaleValue)
|
|
{
|
|
AddMovementInput(GetActorRightVector(), ScaleValue);
|
|
}
|
|
|
|
void AMCharacter::StartCrouch()
|
|
{
|
|
Crouch();
|
|
}
|
|
|
|
void AMCharacter::StopCrouch()
|
|
{
|
|
UnCrouch();
|
|
}
|
|
|
|
void AMCharacter::StartFire()
|
|
{
|
|
if(CurrentWeapon)
|
|
{
|
|
if(CurrentWeapon->StartFire())
|
|
{
|
|
UAnimInstance* AnimInstance = PlayerArms->GetAnimInstance();
|
|
if(AnimInstance && FireAnimation)
|
|
{
|
|
AnimInstance->PlaySlotAnimationAsDynamicMontage(FireAnimation, "Arms", 0.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::StopFire()
|
|
{
|
|
if(CurrentWeapon)
|
|
{
|
|
CurrentWeapon->StopFire();
|
|
}
|
|
}
|
|
|
|
void AMCharacter::Reload()
|
|
{
|
|
if(CurrentWeapon && !CurrentWeapon->IsReloading())
|
|
{
|
|
CurrentWeapon->Reload();
|
|
UAnimInstance* AnimInstance = PlayerArms->GetAnimInstance();
|
|
if(AnimInstance && ReloadAnimation)
|
|
{
|
|
AnimInstance->PlaySlotAnimationAsDynamicMontage(ReloadAnimation, "Arms", 0.f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::StartZoom()
|
|
{
|
|
bWantsToZoom = true;
|
|
}
|
|
|
|
void AMCharacter::StopZoom()
|
|
{
|
|
bWantsToZoom = false;
|
|
}
|
|
|
|
void AMCharacter::StartSprint()
|
|
{
|
|
if(bCanSprint && !bIsSprinting)
|
|
{
|
|
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
|
|
if(MovementComponent)
|
|
{
|
|
MovementComponent->MaxWalkSpeed *= SprintMultiplier;
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_RestoreStamina);
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ConsumeStamina, this, &AMCharacter::ConsumeStamina, StaminaConsumeInterval);
|
|
bIsSprinting = true;
|
|
const FVector CurrentLocation = GetActorLocation();
|
|
PreviousLocation = FVector2D(CurrentLocation.X, CurrentLocation.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::StopSprint()
|
|
{
|
|
if(bIsSprinting)
|
|
{
|
|
bIsSprinting = false;
|
|
bCanSprint = CurrentStamina > 0.f ? true : false;
|
|
UCharacterMovementComponent* MovementComponent = GetCharacterMovement();
|
|
if(MovementComponent)
|
|
{
|
|
MovementComponent->MaxWalkSpeed /= SprintMultiplier;
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_ConsumeStamina);
|
|
GetWorldTimerManager().SetTimer(TimerHandle_RestoreStamina, this, &AMCharacter::RestoreStamina, StaminaRestoreInterval, false, StaminaRestoreDelay);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::ConsumeStamina()
|
|
{
|
|
const FVector CurrentLocation = GetActorLocation();
|
|
const FVector2D CurrentLocation2D = FVector2D(CurrentLocation.X, CurrentLocation.Y);
|
|
if(PreviousLocation != CurrentLocation2D)
|
|
{
|
|
CurrentStamina = FMath::Max(0.f, CurrentStamina - StaminaConsumeAmount);
|
|
PreviousLocation = CurrentLocation2D;
|
|
}
|
|
|
|
if(CurrentStamina > 0.f)
|
|
{
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ConsumeStamina, this, &AMCharacter::ConsumeStamina, StaminaConsumeInterval);
|
|
}
|
|
else
|
|
{
|
|
StopSprint();
|
|
}
|
|
}
|
|
|
|
void AMCharacter::RestoreStamina()
|
|
{
|
|
CurrentStamina = FMath::Min(MaxStamina, CurrentStamina + StaminaRestoreAmount);
|
|
if(CurrentStamina < MaxStamina)
|
|
{
|
|
GetWorldTimerManager().SetTimer(TimerHandle_RestoreStamina, this, &AMCharacter::RestoreStamina, StaminaRestoreInterval);
|
|
}
|
|
else
|
|
{
|
|
bCanSprint = true;
|
|
}
|
|
}
|
|
|
|
void AMCharacter::EnterSafeZone(AActor* SafeZoneActor)
|
|
{
|
|
OnCharacterEnterSafeZone.Broadcast(this, SafeZoneActor);
|
|
FTimerManager& TimerManager = GetWorldTimerManager();
|
|
|
|
if(!HealthComponent->IsAtMaxHealth())
|
|
TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay);
|
|
|
|
bIsInSafeZone = true;
|
|
|
|
if(SafeZoneEnterSound)
|
|
UGameplayStatics::PlaySound2D(this, SafeZoneEnterSound);
|
|
|
|
if(TimerManager.IsTimerActive(TimerHandle_ApplyOutOfSafeZoneDamage))
|
|
TimerManager.ClearTimer(TimerHandle_ApplyOutOfSafeZoneDamage);
|
|
}
|
|
|
|
void AMCharacter::LeaveSafeZone()
|
|
{
|
|
if(bIsMounted)
|
|
return;
|
|
|
|
bIsInSafeZone = false;
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ApplyOutOfSafeZoneDamage, this, &AMCharacter::ApplyOutOfSafeZoneDamage, OutOfSafeZoneDamageInterval, true);
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_ApplySafeZoneHeal);
|
|
OnCharacterLeaveSafeZone.Broadcast(this);
|
|
|
|
if(SafeZoneLeaveSound)
|
|
UGameplayStatics::PlaySound2D(this, SafeZoneLeaveSound);
|
|
}
|
|
|
|
void AMCharacter::StartHidingEquippedItem()
|
|
{
|
|
bWantsToHideEquippedItem = true;
|
|
}
|
|
|
|
void AMCharacter::StopHidingEquippedItem()
|
|
{
|
|
bWantsToHideEquippedItem = false;
|
|
}
|
|
|
|
void AMCharacter::SetTargetFOV(float NewTargetFOV)
|
|
{
|
|
if(NewTargetFOV != DefaultFOV)
|
|
{
|
|
TargetFOV = NewTargetFOV;
|
|
bWantsToZoom = true;
|
|
}
|
|
}
|
|
|
|
void AMCharacter::ResetToDefaultFOV()
|
|
{
|
|
TargetFOV = DefaultFOV;
|
|
bWantsToZoom = false;
|
|
}
|
|
|
|
void AMCharacter::ApplyOutOfSafeZoneDamage()
|
|
{
|
|
if(!bIsInSafeZone)
|
|
{
|
|
if(!HealthComponent->IsDead())
|
|
{
|
|
UGameplayStatics::ApplyDamage(this, OutOfSafeZoneDamage, GetController(), this, OutOfSafeZoneDamageClass);
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ApplyOutOfSafeZoneDamage, this, &AMCharacter::ApplyOutOfSafeZoneDamage, OutOfSafeZoneDamageInterval);
|
|
}
|
|
else
|
|
{
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_ApplyOutOfSafeZoneDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::ApplySafeZoneHealing()
|
|
{
|
|
if(!HealthComponent->IsAtMaxHealth())
|
|
{
|
|
HealthComponent->Heal(SafeZoneHealAmount);
|
|
if(bIsFirstTimeHeal)
|
|
{
|
|
if(HealingStartSound)
|
|
UGameplayStatics::PlaySound2D(this, HealingStartSound);
|
|
bIsFirstTimeHeal = false;
|
|
}
|
|
else
|
|
{
|
|
if(HealingInProgressSound)
|
|
UGameplayStatics::PlaySound2D(this, HealingInProgressSound);
|
|
}
|
|
|
|
}
|
|
|
|
if(HealthComponent->IsAtMaxHealth())
|
|
{
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_ApplySafeZoneHeal);
|
|
bIsFirstTimeHeal = true;
|
|
if(HealingCompletedSound)
|
|
UGameplayStatics::PlaySound2D(this, HealingCompletedSound);
|
|
}
|
|
else
|
|
{
|
|
GetWorldTimerManager().SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval);
|
|
}
|
|
}
|
|
|
|
void AMCharacter::Interact()
|
|
{
|
|
if(!HoveredInteractableActor)
|
|
return;
|
|
|
|
IGameplayTagAssetInterface* GameplayTagAsset = Cast<IGameplayTagAssetInterface>(HoveredInteractableActor);
|
|
if(GameplayTagAsset && GameplayTagAsset->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Interactable.Mountable")))
|
|
{
|
|
APlayerController* PC = Cast<APlayerController>(GetController());
|
|
if(PC)
|
|
{
|
|
PC->Possess(Cast<APawn>(HoveredInteractableActor));
|
|
AMPlayerMech* Mech = Cast<AMPlayerMech>(HoveredInteractableActor);
|
|
if(Mech)
|
|
{
|
|
bIsMounted = true;
|
|
EnterSafeZone(Mech);
|
|
Mech->MountPlayerCharacter(this);
|
|
|
|
// Disable outline
|
|
IMInteractableActor* InteractableActor = Cast<IMInteractableActor>(HoveredInteractableActor);
|
|
if(InteractableActor)
|
|
{
|
|
UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent();
|
|
if(InteractableActorMesh)
|
|
{
|
|
InteractableActorMesh->SetRenderCustomDepth(false);
|
|
}
|
|
}
|
|
}
|
|
UE_LOG(LogTemp, Log, TEXT("Player Possessed %s"), *HoveredInteractableActor->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::HandleCapsuleBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
|
|
{
|
|
IGameplayTagAssetInterface* GameplayTagAsset = Cast<IGameplayTagAssetInterface>(OtherActor);
|
|
if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && !bIsInSafeZone)
|
|
{
|
|
AMConstructableBuilding* ConstructableBuilding = Cast<AMConstructableBuilding>(OtherActor);
|
|
if(ConstructableBuilding && ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed)
|
|
{
|
|
EnterSafeZone(OtherActor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::HandleCapsuleEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
|
|
{
|
|
IGameplayTagAssetInterface* GameplayTagAsset = Cast<IGameplayTagAssetInterface>(OtherActor);
|
|
if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(SafeZoneTags) && bIsInSafeZone)
|
|
{
|
|
// Check again just to make sure, we might have left one safe zone but might still be inside
|
|
// another. This can happen if the perimeters of both safezone overlap. We're still technically
|
|
// inside 'A' safe zone even if we might have left this one. This check is to make sure this
|
|
// scenario is correctly handled.
|
|
AMConstructableBuilding* ConstructableBuilding = Cast<AMConstructableBuilding>(OtherActor);
|
|
if(ConstructableBuilding && ConstructableBuilding->GetBuildingState() == EBuildingState::Constructed)
|
|
{
|
|
CheckIfInSafeZone();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
// Check for hovered interactable objects
|
|
FHitResult TraceHit;
|
|
FVector TraceStartLocation = CameraComponent->GetComponentLocation();
|
|
FVector TraceEndLocation;
|
|
TraceEndLocation = TraceStartLocation + CameraComponent->GetComponentRotation().Vector() * MaxInteractableDistance;
|
|
FCollisionQueryParams CollisionParams;
|
|
CollisionParams.AddIgnoredActor(this);
|
|
|
|
AActor* PreviousHoveredInteractableActor = HoveredInteractableActor;
|
|
bool bShouldUnHover = false;
|
|
if(GetWorld()->LineTraceSingleByChannel(TraceHit, TraceStartLocation, TraceEndLocation, TRACE_CHANNEL_Interactable, CollisionParams))
|
|
{
|
|
AActor* NewHoveredInteractableActor = TraceHit.Actor.Get();
|
|
if(NewHoveredInteractableActor && NewHoveredInteractableActor != HoveredInteractableActor)
|
|
{
|
|
//Hover new actor
|
|
HoveredInteractableActor = NewHoveredInteractableActor;
|
|
bShouldUnHover = true;
|
|
IMInteractableActor* InteractableActor = Cast<IMInteractableActor>(HoveredInteractableActor);
|
|
if(InteractableActor)
|
|
{
|
|
UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent();
|
|
if(InteractableActorMesh)
|
|
{
|
|
InteractableActorMesh->SetRenderCustomDepth(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HoveredInteractableActor = nullptr;
|
|
bShouldUnHover = true;
|
|
}
|
|
|
|
//Unhover previous interactable actor
|
|
if(PreviousHoveredInteractableActor && bShouldUnHover)
|
|
{
|
|
IMInteractableActor* InteractableActor = Cast<IMInteractableActor>(PreviousHoveredInteractableActor);
|
|
if(InteractableActor)
|
|
{
|
|
UMeshComponent* InteractableActorMesh = InteractableActor->GetHoveredMeshComponent();
|
|
if(InteractableActorMesh)
|
|
{
|
|
InteractableActorMesh->SetRenderCustomDepth(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(CVAR_DebugPlayerDrawing.GetValueOnGameThread() > 0)
|
|
DrawDebugLine(GetWorld(), TraceStartLocation, TraceEndLocation, FColor::Green, false, 1.f, 0, 1.f);
|
|
|
|
|
|
// Zoom in/out
|
|
float NewTargetFOV = bWantsToZoom ? TargetFOV : DefaultFOV;
|
|
if(CameraComponent->FieldOfView != NewTargetFOV)
|
|
{
|
|
float NewFOV = FMath::FInterpTo(CameraComponent->FieldOfView, NewTargetFOV, DeltaTime, ZoomInterpolateSpeed);
|
|
CameraComponent->SetFieldOfView(NewFOV);
|
|
}
|
|
|
|
// Hide/Show equipped item
|
|
float TargetPitch = bWantsToHideEquippedItem ? HiddenArmPitch : DefaultArmPitch;
|
|
float CurrentPitch = PlayerArms->GetRelativeRotation().Pitch;
|
|
if(CurrentPitch != TargetPitch)
|
|
{
|
|
float NewPitch = FMath::FInterpTo(CurrentPitch, TargetPitch, DeltaTime, ArmRotateSpeed);
|
|
FRotator NewRotation = PlayerArms->GetRelativeRotation();
|
|
NewRotation.Pitch = NewPitch;
|
|
PlayerArms->SetRelativeRotation(NewRotation);
|
|
}
|
|
|
|
FVector TargetArmLocation = bWantsToHideEquippedItem ? HiddenArmLocation : DefaultArmLocation;
|
|
FVector CurrentLocation = PlayerArms->GetRelativeLocation();
|
|
if(CurrentLocation != TargetArmLocation)
|
|
{
|
|
FVector NewLocation = FMath::VInterpTo(CurrentLocation, TargetArmLocation, DeltaTime, ArmRotateSpeed);
|
|
PlayerArms->SetRelativeLocation(NewLocation);
|
|
}
|
|
|
|
|
|
// Movement Sounds
|
|
float CurrentVelocity = GetVelocity().Size();
|
|
UPawnMovementComponent* MC = GetMovementComponent();
|
|
if(MC && CurrentVelocity > MinimumMovementVelocityThreshold && MC->IsMovingOnGround())
|
|
{
|
|
if(MC->IsCrouching() && CrouchWalkSound && !AudioComponent->IsPlaying())
|
|
{
|
|
AudioComponent->Stop();
|
|
AudioComponent->SetSound(CrouchWalkSound);
|
|
AudioComponent->Play();
|
|
}
|
|
else if(WalkSound && !AudioComponent->IsPlaying())
|
|
{
|
|
AudioComponent->Stop();
|
|
AudioComponent->SetSound(WalkSound);
|
|
AudioComponent->Play();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
|
{
|
|
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
|
|
|
PlayerInputComponent->BindAxis("MoveForward", this, &AMCharacter::MoveForward);
|
|
PlayerInputComponent->BindAxis("MoveRight", this, &AMCharacter::MoveRight);
|
|
|
|
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
|
|
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
|
|
|
|
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
|
|
|
|
PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &AMCharacter::StartCrouch);
|
|
PlayerInputComponent->BindAction("Crouch", IE_Released, this, &AMCharacter::StopCrouch);
|
|
|
|
PlayerInputComponent->BindAction("Dismount", IE_Pressed, this, &AMCharacter::Interact);
|
|
|
|
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AMCharacter::StartFire);
|
|
PlayerInputComponent->BindAction("Fire", IE_Released, this, &AMCharacter::StopFire);
|
|
|
|
// PlayerInputComponent->BindAction("SecondaryFire", IE_Pressed, this, &AMCharacter::StartZoom);
|
|
// PlayerInputComponent->BindAction("SecondaryFire", IE_Released, this, &AMCharacter::StopZoom);
|
|
|
|
PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMCharacter::StartSprint);
|
|
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMCharacter::StopSprint);
|
|
|
|
PlayerInputComponent->BindAction("Reload", IE_Pressed, this, &AMCharacter::Reload);
|
|
}
|
|
|
|
void AMCharacter::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
|
|
{
|
|
TagContainer = ActorTags;
|
|
}
|
|
|
|
void AMCharacter::EquipWeapon(uint8 Weapon)
|
|
{
|
|
EWeaponType WeaponType = (EWeaponType)Weapon;
|
|
if((CurrentWeapon && CurrentWeapon->WeaponType == WeaponType) || WeaponType >= EWeaponType::MaxWeaponType)
|
|
return;
|
|
|
|
if(Weapons[Weapon])
|
|
{
|
|
AMWeapon* PreviousWeapon = nullptr;
|
|
if(CurrentWeapon)
|
|
{
|
|
if(CurrentWeapon->IsReloading())
|
|
CurrentWeapon->CancelReload();
|
|
CurrentWeapon->SetActorHiddenInGame(true);
|
|
CurrentWeapon->SetActorTickEnabled(false);
|
|
PreviousWeapon = CurrentWeapon;
|
|
}
|
|
|
|
CurrentWeapon = Weapons[Weapon];
|
|
CurrentWeapon->SetActorHiddenInGame(false);
|
|
CurrentWeapon->SetActorTickEnabled(true);
|
|
|
|
OnWeaponEquipped.Broadcast(this, CurrentWeapon, PreviousWeapon);
|
|
}
|
|
}
|
|
|
|
void AMCharacter::HandleHealthChanged(UMHealthComponent* HealthComp, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
|
|
{
|
|
if(Health <= 0.f && !bDied)
|
|
{
|
|
bDied = true;
|
|
GetMovementComponent()->StopMovementImmediately();
|
|
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
DetachFromControllerPendingDestroy();
|
|
SetLifeSpan(10.f);
|
|
}
|
|
else
|
|
{
|
|
// If we received damage, reset the healing timer
|
|
if(HealthDelta < 0.f)
|
|
{
|
|
FTimerManager& TimerManager = GetWorldTimerManager();
|
|
if(TimerManager.IsTimerActive(TimerHandle_ApplySafeZoneHeal))
|
|
TimerManager.SetTimer(TimerHandle_ApplySafeZoneHeal, this, &AMCharacter::ApplySafeZoneHealing, SafeZoneHealInterval, false, SafeZoneHealDelay);
|
|
|
|
if(HitByBulletSound)
|
|
UGameplayStatics::PlaySound2D(this, HitByBulletSound);
|
|
}
|
|
|
|
CheckIfInSafeZone();
|
|
}
|
|
}
|
|
|
|
float AMCharacter::GetStaminaRemappedInRange(float Min, float Max)
|
|
{
|
|
const FVector2D InputRange(0.f, MaxStamina);
|
|
const FVector2D OutputRange(Min, Max);
|
|
return FMath::GetMappedRangeValueClamped(InputRange, OutputRange, CurrentStamina);
|
|
}
|
|
|