super-duper-spoon/Source/Shooter/ShooterCharacter.cpp

1010 lines
26 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "ShooterCharacter.h"
#include "Weapon.h"
#include "Item.h"
#include "Camera/CameraComponent.h"
#include "Components/SphereComponent.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/WidgetComponent.h"
#include "Engine/SkeletalMeshSocket.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
#include "Sound/SoundCue.h"
#include "Ammo.h"
// Sets default values
AShooterCharacter::AShooterCharacter() :
// Base rates for turning/looking up
BaseTurnRate(45.f),
BaseLookUpRate(45.f),
// Turn rates for aiming/not aiming
HipTurnRate(90.f),
HipLookUpRate(90.f),
AimingTurnRate(20.f),
AimingLookUpRate(20.f),
// Mouse look sensitivity scale factors
MouseHipTurnRate(1.0f),
MouseHipLookUpRate(1.0f),
MouseAimingTurnRate(0.6f),
MouseAimingLookUpRate(0.6f),
// True when aiming the weapon
bAiming(false),
// Camera field of view values
CameraDefaultFOV(0.f), // Set in BeginPlay
CameraZoomedFOV(20.f),
CameraCurrentFOV(0.f),
ZoomInterpSpeed(20.f),
// Crosshair spread factors
CrosshairSpreadMultiplier(0.f),
CrosshairVelocityFactor(0.f),
CrosshairInAirFactor(0.f),
CrosshairAimFactor(0.f),
CrosshairShootingFactor(0.f),
// Bullet fire timer variables
ShootTimeDuration(0.05f),
bFiringBullet(false),
// Automatic fire
bFireButtonPressed(false),
bShouldFire(true),
AutomaticFireRate(0.1f),
// Item trace variables
bShouldTraceForItems(false),
// Camera interp location variables
CameraInterpDistance(150.f),
CameraInterpElevation(55.f),
// Starting ammo amounts
Starting9mmAmmo(85),
StartingARAmmo(120),
// Combat variables
CombatState(ECombatState::ECS_Unoccupied),
bCrouching(false),
BaseMovementSpeed(650.f),
CrouchMovementSpeed(300.f),
StandingCapsuleHalfHeight(88.f),
CrouchingCapsuleHalfHeight(54.f),
BaseGroundFriction(2.f),
CrouchingGroundFriction(100.f),
bAimingButtonPressed(false),
bShouldPlayPickupSound(true),
bShouldPlayEquipSound(true),
PickupSoundResetTime(0.2f),
EquipSoundResetTime(0.2f)
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create a camera boom (pulls in towards the character if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
// The camera follows at this distance behind the character
CameraBoom->TargetArmLength = 240.f;
// Rotate the arm based on the controller
CameraBoom->bUsePawnControlRotation = true;
CameraBoom->SocketOffset = FVector(0.f, 35.f, 70.f);
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
// Camera does not rotate relative to arm
FollowCamera->bUsePawnControlRotation = false;
// Don't rotate when the controller does. Let the controller only affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = true;
bUseControllerRotationRoll = false;
GetCharacterMovement()->bOrientRotationToMovement = false; // Character moves in the direction of input....
GetCharacterMovement()->RotationRate = FRotator(0.f, 540.f, 0.f); // ... at this rotation rate
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
// Create Hand Scene Component
HandSceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("HandSceneComp"));
// Create interpolation components
WeaponInterpComp = CreateDefaultSubobject<USceneComponent>(TEXT("Weapon Interpolation Component"));
WeaponInterpComp->SetupAttachment(GetFollowCamera());
InterpComp1 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 1"));
InterpComp1->SetupAttachment(GetFollowCamera());
InterpComp2 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 2"));
InterpComp2->SetupAttachment(GetFollowCamera());
InterpComp3 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 3"));
InterpComp3->SetupAttachment(GetFollowCamera());
InterpComp4 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 4"));
InterpComp4->SetupAttachment(GetFollowCamera());
InterpComp5 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 5"));
InterpComp5->SetupAttachment(GetFollowCamera());
InterpComp6 = CreateDefaultSubobject<USceneComponent>(TEXT("Interpolation Component 6"));
InterpComp6->SetupAttachment(GetFollowCamera());
}
// Called when the game starts or when spawned
void AShooterCharacter::BeginPlay()
{
Super::BeginPlay();
if (FollowCamera)
{
CameraDefaultFOV = FollowCamera->FieldOfView;
CameraCurrentFOV = CameraDefaultFOV;
}
// Spawn the default weapon and equip it
EquipWeapon(SpawnDefaultWeapon());
InitializeAmmoMap();
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
// Create FInterpLocation structs for each interp location. Add to array
InitializeInterpLocations();
}
// Called to bind functionality to input
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
check(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", this, &AShooterCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AShooterCharacter::MoveRight);
PlayerInputComponent->BindAxis("TurnRate", this, &AShooterCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUpRate", this, &AShooterCharacter::LookUpAtRate);
PlayerInputComponent->BindAxis("Turn", this, &AShooterCharacter::Turn);
PlayerInputComponent->BindAxis("LookUp", this, &AShooterCharacter::LookUp);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AShooterCharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAction("FireButton", IE_Pressed, this,
&AShooterCharacter::FireButtonPressed);
PlayerInputComponent->BindAction("FireButton", IE_Released, this,
&AShooterCharacter::FireButtonReleased);
PlayerInputComponent->BindAction("Aiming", IE_Pressed, this,
&AShooterCharacter::AimingButtonPressed);
PlayerInputComponent->BindAction("Aiming", IE_Released, this,
&AShooterCharacter::AimingButtonReleased);
PlayerInputComponent->BindAction("Select", IE_Pressed, this,
&AShooterCharacter::SelectButtonPressed);
PlayerInputComponent->BindAction("Select", IE_Released, this,
&AShooterCharacter::SelectButtonReleased);
PlayerInputComponent->BindAction("Reload", IE_Pressed, this,
&AShooterCharacter::ReloadButtonPressed);
PlayerInputComponent->BindAction("Crouch", IE_Pressed, this,
&AShooterCharacter::CrouchButtonPressed);
}
// Called every frame
void AShooterCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Handle interpolation for zoom when aiming
CameraInterpZoom(DeltaTime);
// Change look sensitivity based on aiming
SetLookRates();
// Calculate crosshair spread multiplier
CalculateCrosshairSpread(DeltaTime);
TraceForItems();
// Interpolate capsule half height based on crouching/standing
InterpCapsuleHalfHeight(DeltaTime);
}
void AShooterCharacter::MoveForward(float Value)
{
if (Controller && (Value != 0))
{
// Find out which way is forward
const FRotator Rotation{ Controller->GetControlRotation() };
const FRotator YawRotation{ 0, Rotation.Yaw, 0 };
const FVector Direction{ FRotationMatrix{YawRotation}.GetUnitAxis(EAxis::X) };
AddMovementInput(Direction, Value);
}
}
void AShooterCharacter::MoveRight(float Value)
{
if (Controller && (Value != 0))
{
// Find out which way is forward
const FRotator Rotation{ Controller->GetControlRotation() };
const FRotator YawRotation{ 0, Rotation.Yaw, 0 };
const FVector Direction{ FRotationMatrix{YawRotation}.GetUnitAxis(EAxis::Y) };
AddMovementInput(Direction, Value);
}
}
void AShooterCharacter::TurnAtRate(float Rate)
{
// Calculate delta for this frame from the rate information
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds()); // def/sec * sec/frame
}
void AShooterCharacter::LookUpAtRate(float Rate)
{
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds()); // def/sec * sec/frame
}
void AShooterCharacter::Turn(float Value)
{
float TurnScaleFactor;
if (bAiming)
{
TurnScaleFactor = MouseAimingTurnRate;
}
else
{
TurnScaleFactor = MouseHipTurnRate;
}
AddControllerYawInput(Value * TurnScaleFactor);
}
void AShooterCharacter::LookUp(float Value)
{
float LookUpScaleFactor;
if (bAiming)
{
LookUpScaleFactor = MouseAimingLookUpRate;
}
else
{
LookUpScaleFactor = MouseHipLookUpRate;
}
AddControllerPitchInput(Value * LookUpScaleFactor);
}
void AShooterCharacter::AimingButtonPressed()
{
bAimingButtonPressed = true;
if (CombatState != ECombatState::ECS_Reloading)
{
Aim();
}
}
void AShooterCharacter::AimingButtonReleased()
{
bAimingButtonPressed = false;
StopAiming();
}
void AShooterCharacter::Aim()
{
bAiming = true;
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
}
void AShooterCharacter::StopAiming()
{
bAiming = false;
if (!bCrouching)
{
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
}
}
void AShooterCharacter::PickupAmmo(AAmmo* Ammo)
{
// Check to see if AmmoMap contains Ammo's AmmoType
if (AmmoMap.Find(Ammo->GetAmmoType()))
{
// Get amount of ammo in our AmmoMap for Ammo's type
int32 AmmoCount{ AmmoMap[Ammo->GetAmmoType()] };
AmmoCount += Ammo->GetItemCount();
// Set amount of ammo in our AmmoMap for Ammo's type
AmmoMap[Ammo->GetAmmoType()] = AmmoCount;
}
if (EquippedWeapon->GetAmmoType() == Ammo->GetAmmoType())
{
// Check to see if the gun is empty
if (EquippedWeapon->GetAmmo() == 0)
{
ReloadWeapon();
}
}
Ammo->Destroy();
}
void AShooterCharacter::InitializeInterpLocations()
{
struct FInterpLocation WeaponLocation{ WeaponInterpComp, 0 };
InterpLocations.Add(WeaponLocation);
struct FInterpLocation InerpLoc1{ InterpComp1, 0 };
InterpLocations.Add(InerpLoc1);
struct FInterpLocation InerpLoc2{ InterpComp2, 0 };
InterpLocations.Add(InerpLoc2);
struct FInterpLocation InerpLoc3{ InterpComp3, 0 };
InterpLocations.Add(InerpLoc3);
struct FInterpLocation InerpLoc4{ InterpComp4, 0 };
InterpLocations.Add(InerpLoc4);
struct FInterpLocation InerpLoc5{ InterpComp5, 0 };
InterpLocations.Add(InerpLoc5);
struct FInterpLocation InerpLoc6{ InterpComp6, 0 };
InterpLocations.Add(InerpLoc6);
}
void AShooterCharacter::ResetPickupSoundTimer()
{
bShouldPlayPickupSound = true;
}
void AShooterCharacter::ResetEquipSoundTimer()
{
bShouldPlayEquipSound = true;
}
int32 AShooterCharacter::GetInterpLocationIndex()
{
int32 LowestIndex = 1;
int32 LowestCount = INT_MAX;
for (int32 i = 1; i < InterpLocations.Num(); ++i)
{
if (InterpLocations[i].ItemCount < LowestCount)
{
LowestIndex = i;
LowestCount = InterpLocations[i].ItemCount;
}
}
return LowestIndex;
}
void AShooterCharacter::IncrementInterpLocItemCount(int32 Index, int32 Amount)
{
if (Amount < -1 || Amount > 1) return;
if (Index < InterpLocations.Num())
{
InterpLocations[Index].ItemCount += Amount;
}
}
void AShooterCharacter::StartPickupSoundTimer()
{
bShouldPlayPickupSound = false;
GetWorldTimerManager().SetTimer(PickupSoundTimer, this, &AShooterCharacter::ResetPickupSoundTimer, PickupSoundResetTime);
}
void AShooterCharacter::StartEquipSoundTimer()
{
bShouldPlayEquipSound = false;
GetWorldTimerManager().SetTimer(EquipSoundTimer, this, &AShooterCharacter::ResetEquipSoundTimer, EquipSoundResetTime);
}
void AShooterCharacter::CameraInterpZoom(float DeltaTime)
{
// Set current camera field of view
if (bAiming)
{
// Interpolate to zoomed FOV
CameraCurrentFOV = FMath::FInterpTo(CameraCurrentFOV, CameraZoomedFOV,
DeltaTime, ZoomInterpSpeed);
}
else
{
// Interpolate to default FOV
CameraCurrentFOV = FMath::FInterpTo(CameraCurrentFOV, CameraDefaultFOV,
DeltaTime, ZoomInterpSpeed);
}
if (FollowCamera)
{
FollowCamera->SetFieldOfView(CameraCurrentFOV);
}
}
void AShooterCharacter::SetLookRates()
{
if (bAiming)
{
BaseTurnRate = AimingTurnRate;
BaseLookUpRate = AimingLookUpRate;
}
else
{
BaseTurnRate = HipTurnRate;
BaseLookUpRate = HipLookUpRate;
}
}
void AShooterCharacter::FireButtonPressed()
{
bFireButtonPressed = true;
FireWeapon();
}
void AShooterCharacter::FireButtonReleased()
{
bFireButtonPressed = false;
}
void AShooterCharacter::FireWeapon()
{
if (!EquippedWeapon) return;
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (!WeaponHasAmmo()) return;
PlayFireSound();
SendBullet();
PlayGunFireMontage();
EquippedWeapon->DecrementAmmo();
StartCrosshairBulletFire();
StartFireTimer();
}
void AShooterCharacter::StartFireTimer()
{
CombatState = ECombatState::ECS_FireTimerInProgress;
GetWorldTimerManager().SetTimer(AutoFireTimer, this, &AShooterCharacter::AutoFireReset,
AutomaticFireRate);
}
void AShooterCharacter::AutoFireReset()
{
CombatState = ECombatState::ECS_Unoccupied;
if (WeaponHasAmmo())
{
if (bFireButtonPressed)
{
FireWeapon();
}
}
else
{
ReloadWeapon();
}
}
void AShooterCharacter::PlayFireSound()
{
// Play fire sound
if (FireSound)
{
UGameplayStatics::PlaySound2D(this, FireSound);
}
}
void AShooterCharacter::SendBullet()
{
// Send bullet
const USkeletalMeshSocket* BarrelSocket = EquippedWeapon->GetItemMesh()->GetSocketByName("BarrelSocket");
if (!BarrelSocket) return;
const FTransform SocketTransform = BarrelSocket->GetSocketTransform(EquippedWeapon->GetItemMesh());
if (MuzzleFlash)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlash, SocketTransform);
}
FVector BeamEnd;
bool bBeamEnd = GetBeamEndLocation(SocketTransform.GetLocation(), BeamEnd);
if (!bBeamEnd) return;
// Spawn impact particles after updating BeamEndPoint
if (ImpactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticles,
BeamEnd);
}
// Spawn smoke trail particles
if (BeamParticles)
{
UParticleSystemComponent* Beam = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BeamParticles,
SocketTransform);
if (Beam)
{
Beam->SetVectorParameter("Target", BeamEnd);
}
}
}
void AShooterCharacter::PlayGunFireMontage()
{
// Play gun fire montage
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && HipFireMontage)
{
AnimInstance->Montage_Play(HipFireMontage);
AnimInstance->Montage_JumpToSection(FName("StartFire"));
}
}
bool AShooterCharacter::GetBeamEndLocation(const FVector& MuzzleSocketLocation, FVector& OutBeamLocation)
{
// Check for crosshair trace hit
FHitResult CrosshairHitResult;
bool bCrosshairHit = TraceUnderCrosshairs(CrosshairHitResult, OutBeamLocation);
// Perform a second trace, this time from the gun barrel
FHitResult WeaponTraceHit;
const FVector WeaponTraceStart{ MuzzleSocketLocation };
const FVector StartToEnd{ OutBeamLocation - MuzzleSocketLocation };
const FVector WeaponTraceEnd{ MuzzleSocketLocation + StartToEnd * 1.25f };
GetWorld()->LineTraceSingleByChannel(WeaponTraceHit, WeaponTraceStart, WeaponTraceEnd,
ECollisionChannel::ECC_Visibility);
if (WeaponTraceHit.bBlockingHit) // Object between barrel and BeamEndPoint ?
{
OutBeamLocation = WeaponTraceHit.Location;
return true;
}
return false;
}
bool AShooterCharacter::TraceUnderCrosshairs(FHitResult& OutHitResult, FVector& OutHitLocation)
{
// Get Viewport Size
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
// Get screen space location of crosshairs
FVector2D CrosshairLocation{ ViewportSize.X / 2.f, ViewportSize.Y / 2.f };
// Get world position and direction of crosshairs
FVector CrosshairWorldPosition;
FVector CrosshairWorldDirection;
bool bScreenToWorld = UGameplayStatics::DeprojectScreenToWorld(
UGameplayStatics::GetPlayerController(this, 0),
CrosshairLocation, CrosshairWorldPosition, CrosshairWorldDirection);
if (bScreenToWorld)
{
// Trace from Crosshair world location outward
const FVector Start{ CrosshairWorldPosition };
const FVector End{ Start + CrosshairWorldDirection * 50'000.f };
OutHitLocation = End;
GetWorld()->LineTraceSingleByChannel(OutHitResult, Start, End, ECollisionChannel::ECC_Visibility);
if (OutHitResult.bBlockingHit)
{
OutHitLocation = OutHitResult.Location;
return true;
}
}
return false;
}
void AShooterCharacter::StartCrosshairBulletFire()
{
bFiringBullet = true;
GetWorldTimerManager().SetTimer(CrosshairShootTimer, this,
&AShooterCharacter::FinishCrosshairBulletFire, ShootTimeDuration);
}
void AShooterCharacter::FinishCrosshairBulletFire()
{
bFiringBullet = false;
}
void AShooterCharacter::CalculateCrosshairSpread(float DeltaTime)
{
FVector2D WalkSpeedRange{ 0.f, 600.f };
FVector2D VelocityMultiplierRange{ 0.f, 1.f };
FVector Velocity{ GetVelocity() };
Velocity.Z = 0.f;
// Calculate crosshair velocity factor
CrosshairVelocityFactor = FMath::GetMappedRangeValueClamped(WalkSpeedRange,
VelocityMultiplierRange, Velocity.Size());
// Calculate crosshair in air factor
if (GetCharacterMovement()->IsFalling())
{
// Spread the crosshairs quickly while in air
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 2.25f,
DeltaTime, 20.f);
}
else // Character in on the ground
{
// Spread the crosshairs slowly when on the ground
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 0,
DeltaTime, 2.25f);
}
// Calculate crosshair aim factor
if (bAiming)
{
// Group the crosshairs when aiming
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0.6,
DeltaTime, 30.f);
}
else
{
// Spread the crosshairs when not aiming
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0,
DeltaTime, 30.f);
}
// True 0.05s after firing
if (bFiringBullet)
{
CrosshairShootingFactor = FMath::FInterpTo(CrosshairShootingFactor, 0.3f,
DeltaTime, 60.f);
}
else
{
CrosshairShootingFactor = FMath::FInterpTo(CrosshairShootingFactor, 0.f,
DeltaTime, 60.f);
}
CrosshairSpreadMultiplier = 0.5f
+ CrosshairVelocityFactor
+ CrosshairInAirFactor
+ CrosshairShootingFactor
- CrosshairAimFactor;
}
void AShooterCharacter::TraceForItems()
{
if (bShouldTraceForItems)
{
FHitResult ItemTraceResult;
FVector HitLocation;
TraceUnderCrosshairs(ItemTraceResult, HitLocation);
if (ItemTraceResult.bBlockingHit)
{
// If an item was being looked at from last frame, we should hide its widget
SetItemPickupWidgetVisibility(TraceHitItem, false);
// Show the new item's widget
TraceHitItem = Cast<AItem>(ItemTraceResult.GetActor());
SetItemPickupWidgetVisibility(TraceHitItem, true);
}
else if (TraceHitItem) // Hide widget for the item from last frame
{
SetItemPickupWidgetVisibility(TraceHitItem, false);
TraceHitItem = nullptr;
}
}
else if (TraceHitItem) // Hide widget for the item from last frame
{
SetItemPickupWidgetVisibility(TraceHitItem, false);
TraceHitItem = nullptr;
}
}
void AShooterCharacter::SetItemPickupWidgetVisibility(AItem* Item, bool visibility)
{
if (Item && Item->GetPickupWidget())
{
Item->GetPickupWidget()->SetVisibility(visibility);
visibility ?
Item->EnableCustomDepth() :
Item->DisableCustomDepth();
}
}
AWeapon* AShooterCharacter::SpawnDefaultWeapon()
{
// Check the TSubclassOf variable
if (DefaultWeaponClass)
{
// Spawn the weapon
return GetWorld()->SpawnActor<AWeapon>(DefaultWeaponClass);
}
return nullptr;
}
void AShooterCharacter::EquipWeapon(AWeapon* WeaponToEquip)
{
// Get the hand socket
const USkeletalMeshSocket* HandSocket = GetMesh()->GetSocketByName(FName("RightHandSocket"));
if (HandSocket && WeaponToEquip)
{
// Attach the weapon to the hand socket RightHandSocket
HandSocket->AttachActor(WeaponToEquip, GetMesh());
EquippedWeapon = WeaponToEquip;
EquippedWeapon->SetItemState(EItemState::EIS_Equipped);
EquippedWeapon->DisableGlowMaterial();
EquippedWeapon->DisableCustomDepth();
}
}
void AShooterCharacter::DropWeapon()
{
if (EquippedWeapon)
{
FDetachmentTransformRules DetachmentTransformRules(EDetachmentRule::KeepWorld, true);
EquippedWeapon->GetItemMesh()->DetachFromComponent(DetachmentTransformRules);
EquippedWeapon->SetItemState(EItemState::EIS_Falling);
EquippedWeapon->ThrowWeapon();
}
}
void AShooterCharacter::SelectButtonPressed()
{
if (TraceHitItem)
{
TraceHitItem->StartItemCurve(this);
}
}
void AShooterCharacter::SelectButtonReleased()
{
}
void AShooterCharacter::SwapWeapon(AWeapon* WeaponToSwap)
{
DropWeapon();
EquipWeapon(WeaponToSwap);
}
void AShooterCharacter::InitializeAmmoMap()
{
AmmoMap.Add(EAmmoType::EAT_9mm, Starting9mmAmmo);
AmmoMap.Add(EAmmoType::EAT_AR, StartingARAmmo);
}
bool AShooterCharacter::WeaponHasAmmo()
{
if (!EquippedWeapon) return false;
return (EquippedWeapon->GetAmmo() > 0);
}
float AShooterCharacter::GetCrosshairSpreadMultiplier() const
{
return CrosshairSpreadMultiplier;
}
void AShooterCharacter::IncrementOverlappedItemCount(int8 Amount)
{
if (OverlappedItemCount + Amount <= 0)
{
OverlappedItemCount = 0;
bShouldTraceForItems = false;
}
else
{
OverlappedItemCount += Amount;
bShouldTraceForItems = true;
}
}
// No longer needed; AItem has GetInterpLocation
//FVector AShooterCharacter::GetCameraInterpLocation()
//{
// const FVector CameraWorldLocation{ FollowCamera->GetComponentLocation() };
// const FVector CameraForward{ FollowCamera->GetForwardVector() };
//
// // Desired = CameraWorldLocation + Forward * A + Up * B
// return CameraWorldLocation + CameraForward * CameraInterpDistance
// + FVector(0.f, 0.f, CameraInterpElevation);
//}
void AShooterCharacter::GetPickupItem(AItem* Item)
{
Item->PlayEquipSound();
auto Weapon = Cast<AWeapon>(Item);
if (Weapon)
{
SwapWeapon(Weapon);
}
auto Ammo = Cast<AAmmo>(Item);
if (Ammo)
{
PickupAmmo(Ammo);
}
}
FInterpLocation AShooterCharacter::GetInterpLocation(int32 Index)
{
if (Index <= InterpLocations.Num())
{
return InterpLocations[Index];
}
return FInterpLocation();
}
void AShooterCharacter::ReloadButtonPressed()
{
ReloadWeapon();
}
void AShooterCharacter::ReloadWeapon()
{
if (CombatState != ECombatState::ECS_Unoccupied) return;
if (!EquippedWeapon) return;
// Do we have ammo for the correct type
if (CarryingAmmo() && !EquippedWeapon->ClipIsFull())
{
if (bAiming) StopAiming();
CombatState = ECombatState::ECS_Reloading;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && ReloadMontage)
{
AnimInstance->Montage_Play(ReloadMontage);
AnimInstance->Montage_JumpToSection(EquippedWeapon->GetReloadMontageSection());
}
}
}
void AShooterCharacter::FinishReloading()
{
CombatState = ECombatState::ECS_Unoccupied;
if (bAimingButtonPressed)
{
Aim();
}
if (!EquippedWeapon) return;
if (const auto AmmoType { EquippedWeapon->GetAmmoType() }; AmmoMap.Contains(AmmoType))
{
int32 CarriedAmmo = AmmoMap[AmmoType];
if (const int32 MagEmptySpace = EquippedWeapon->GetMagazineCapacity() - EquippedWeapon->GetAmmo();
MagEmptySpace > CarriedAmmo)
{
// Reload the mag with all the ammo we're carrying
EquippedWeapon->ReloadAmmo(CarriedAmmo);
CarriedAmmo = 0;
AmmoMap.Add(AmmoType, CarriedAmmo);
}
else
{
// Fill the mag
EquippedWeapon->ReloadAmmo(MagEmptySpace);
CarriedAmmo -= MagEmptySpace;
AmmoMap.Add(AmmoType, CarriedAmmo);
}
}
}
bool AShooterCharacter::CarryingAmmo()
{
if (!EquippedWeapon) return false;
if (const auto AmmoType = EquippedWeapon->GetAmmoType(); AmmoMap.Contains(AmmoType))
{
return AmmoMap[AmmoType] > 0;
}
return false;
}
void AShooterCharacter::GrabClip()
{
if (!EquippedWeapon) return;
if (!HandSceneComponent) return;
// Index for the clip bone on the Equipped weapon
int32 ClipBoneIndex{ EquippedWeapon->GetItemMesh()->GetBoneIndex(EquippedWeapon->GetClipBoneName()) };
// Store the transform of the clip
ClipTransform = EquippedWeapon->GetItemMesh()->GetBoneTransform(ClipBoneIndex);
HandSceneComponent->SetWorldTransform(ClipTransform);
// Attach Scene component to hand bone
FAttachmentTransformRules AttachmentRules(EAttachmentRule::KeepRelative, true);
HandSceneComponent->AttachToComponent(GetMesh(), AttachmentRules, FName(TEXT("Hand_L")));
EquippedWeapon->SetMovingClip(true);
}
void AShooterCharacter::ReleaseClip()
{
EquippedWeapon->SetMovingClip(false);
}
void AShooterCharacter::CrouchButtonPressed()
{
if (!GetCharacterMovement()->IsFalling())
{
bCrouching = !bCrouching;
}
if (bCrouching)
{
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
GetCharacterMovement()->GroundFriction = CrouchingGroundFriction;
}
else
{
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
GetCharacterMovement()->GroundFriction = BaseGroundFriction;
}
}
void AShooterCharacter::Jump()
{
if (bCrouching)
{
bCrouching = false;
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
GetCharacterMovement()->GroundFriction = BaseGroundFriction;
}
else
{
Super::Jump();
}
}
void AShooterCharacter::InterpCapsuleHalfHeight(float DeltaTime)
{
float TargetCapsuleHalfHeight{};
if (bCrouching)
{
TargetCapsuleHalfHeight = CrouchingCapsuleHalfHeight;
}
else
{
TargetCapsuleHalfHeight = StandingCapsuleHalfHeight;
}
const float InterpHalfHeight{ FMath::FInterpTo(
GetCapsuleComponent()->GetScaledCapsuleHalfHeight(),
TargetCapsuleHalfHeight, DeltaTime, 20.f)};
// Negative value if crouching, positive if standing
const float DeltaCapsuleHalfHeight{ InterpHalfHeight - GetCapsuleComponent()->GetScaledCapsuleHalfHeight() };
const FVector MeshOffset{0.f, 0.f, -DeltaCapsuleHalfHeight};
GetMesh()->AddLocalOffset(MeshOffset);
GetCapsuleComponent()->SetCapsuleHalfHeight(InterpHalfHeight);
}