1406 lines
35 KiB
C++
1406 lines
35 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/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"
|
|
#include "BulletHitInterface.h"
|
|
#include "Enemy.h"
|
|
#include "PhysicalMaterials/PhysicalMaterial.h"
|
|
#include "EnemyController.h"
|
|
#include "BehaviorTree/BlackboardComponent.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),
|
|
// Item trace variables
|
|
bShouldTraceForItems(false),
|
|
// Camera interp location variables
|
|
CameraInterpDistance(150.f),
|
|
CameraInterpElevation(55.f),
|
|
// Starting ammo amounts
|
|
Starting9mmAmmo(15),
|
|
StartingARAmmo(0),
|
|
// Combat variables
|
|
CombatState(ECombatState::ECS_Unoccupied),
|
|
bCrouching(false),
|
|
BaseMovementSpeed(650.f),
|
|
CrouchMovementSpeed(300.f),
|
|
StandingCapsuleHalfHeight(88.f),
|
|
CrouchingCapsuleHalfHeight(54.f),
|
|
CrouchingMeshZOffset(0.f),
|
|
BaseGroundFriction(2.f),
|
|
CrouchingGroundFriction(100.f),
|
|
bAimingButtonPressed(false),
|
|
bShouldPlayPickupSound(true),
|
|
bShouldPlayEquipSound(true),
|
|
PickupSoundResetTime(0.2f),
|
|
EquipSoundResetTime(0.2f),
|
|
// Icon animation property
|
|
HighlightedSlot(-1),
|
|
Health(80.f),
|
|
MaxHealth(80.f),
|
|
StunChance(.25f),
|
|
bDying(false)
|
|
{
|
|
// 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.0, 540.0, 0.0); // ... 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());
|
|
Inventory.Add(EquippedWeapon);
|
|
EquippedWeapon->SetSlotIndex(0);
|
|
EquippedWeapon->SetCharacter(this);
|
|
|
|
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);
|
|
|
|
PlayerInputComponent->BindAction("FKey", IE_Pressed, this, &AShooterCharacter::KeyFPressed);
|
|
PlayerInputComponent->BindAction("1Key", IE_Pressed, this, &AShooterCharacter::Key1Pressed);
|
|
PlayerInputComponent->BindAction("2Key", IE_Pressed, this, &AShooterCharacter::Key2Pressed);
|
|
PlayerInputComponent->BindAction("3Key", IE_Pressed, this, &AShooterCharacter::Key3Pressed);
|
|
PlayerInputComponent->BindAction("4Key", IE_Pressed, this, &AShooterCharacter::Key4Pressed);
|
|
PlayerInputComponent->BindAction("5Key", IE_Pressed, this, &AShooterCharacter::Key5Pressed);
|
|
|
|
PlayerInputComponent->BindAction("Quit", IE_Pressed, this, &AShooterCharacter::QuitPressed);
|
|
}
|
|
|
|
float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator,
|
|
AActor* DamageCauser)
|
|
{
|
|
float DamageInflicted = DamageAmount;
|
|
if (Health - DamageAmount <= 0.f || FMath::IsNearlyZero(Health, 0.1f))
|
|
{
|
|
DamageInflicted = Health;
|
|
Health = 0.f;
|
|
Die();
|
|
|
|
auto EnemyController = Cast<AEnemyController>(EventInstigator);
|
|
if (EnemyController)
|
|
{
|
|
EnemyController->GetBlackboardComponent()->SetValueAsBool(FName("CharacterDead"), true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Health -= DamageAmount;
|
|
}
|
|
return DamageInflicted;
|
|
}
|
|
|
|
void AShooterCharacter::Die()
|
|
{
|
|
if (bDying) return;
|
|
bDying = true;
|
|
|
|
APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
|
|
if (PC)
|
|
{
|
|
DisableInput(PC);
|
|
}
|
|
|
|
if (!DeathMontage) return;
|
|
|
|
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
|
|
if (!AnimInstance) return;
|
|
|
|
AnimInstance->Montage_Play(DeathMontage);
|
|
}
|
|
|
|
void AShooterCharacter::FinishDeath()
|
|
{
|
|
GetMesh()->bPauseAnims = true;
|
|
}
|
|
|
|
// 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.0, Rotation.Yaw, 0.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.0, Rotation.Yaw, 0.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 (bDying) return;
|
|
if (CombatState != ECombatState::ECS_Reloading &&
|
|
CombatState != ECombatState::ECS_Equipping &&
|
|
CombatState != ECombatState::ECS_Stunned)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void AShooterCharacter::KeyFPressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 0) return;
|
|
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 0);
|
|
}
|
|
|
|
void AShooterCharacter::Key1Pressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 1) return;
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 1);
|
|
}
|
|
|
|
void AShooterCharacter::Key2Pressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 2) return;
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 2);
|
|
}
|
|
|
|
void AShooterCharacter::Key3Pressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 3) return;
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 3);
|
|
}
|
|
|
|
void AShooterCharacter::Key4Pressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 4) return;
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 4);
|
|
}
|
|
|
|
void AShooterCharacter::Key5Pressed()
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() == 5) return;
|
|
ExchangeInventoryItems(EquippedWeapon->GetSlotIndex(), 5);
|
|
}
|
|
|
|
void AShooterCharacter::QuitPressed()
|
|
{
|
|
UKismetSystemLibrary::QuitGame(GetWorld(), Cast<APlayerController>(GetController()), EQuitPreference::Quit, false);
|
|
}
|
|
|
|
void AShooterCharacter::ExchangeInventoryItems(int32 CurrentItemIndex, int32 NewItemIndex)
|
|
{
|
|
if (bDying) return;
|
|
|
|
if (CurrentItemIndex == NewItemIndex
|
|
|| NewItemIndex >= Inventory.Num()
|
|
|| (CombatState != ECombatState::ECS_Unoccupied && CombatState != ECombatState::ECS_Equipping))
|
|
return;
|
|
|
|
if (bAiming)
|
|
{
|
|
StopAiming();
|
|
}
|
|
|
|
auto OldEquippedWeapon = EquippedWeapon;
|
|
auto NewWeapon = Cast<AWeapon>(Inventory[NewItemIndex]);
|
|
|
|
EquipWeapon(NewWeapon);
|
|
|
|
OldEquippedWeapon->SetItemState(EItemState::EIS_PickedUp);
|
|
NewWeapon->SetItemState(EItemState::EIS_Equipped);
|
|
|
|
CombatState = ECombatState::ECS_Equipping;
|
|
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
|
|
if (AnimInstance && EquipMontage)
|
|
{
|
|
AnimInstance->Montage_Play(EquipMontage, 1.0f);
|
|
AnimInstance->Montage_JumpToSection(FName("Equip"));
|
|
}
|
|
NewWeapon->PlayEquipSound(true);
|
|
}
|
|
|
|
int32 AShooterCharacter::GetEmptyInventorySlot()
|
|
{
|
|
for (int32 i = 0; i < Inventory.Num(); ++i)
|
|
{
|
|
if (Inventory[i] == nullptr)
|
|
return i;
|
|
}
|
|
|
|
if (Inventory.Num() < INVENTORY_CAPACITY)
|
|
{
|
|
return Inventory.Num();
|
|
}
|
|
|
|
return -1; // Inventory full
|
|
}
|
|
|
|
void AShooterCharacter::HighlightInventorySlot()
|
|
{
|
|
const int32 EmptySlot{ GetEmptyInventorySlot() };
|
|
if (EmptySlot != -1)
|
|
{
|
|
HighlightIconDelegate.Broadcast(EmptySlot, true);
|
|
HighlightedSlot = EmptySlot;
|
|
}
|
|
}
|
|
|
|
EPhysicalSurface AShooterCharacter::GetSurfaceType()
|
|
{
|
|
FHitResult HitResult;
|
|
const FVector Start{ GetActorLocation() };
|
|
const FVector End{ Start + FVector(0.f, 0.f, -400.f) };
|
|
|
|
FCollisionQueryParams QueryParams;
|
|
QueryParams.bReturnPhysicalMaterial = true;
|
|
|
|
GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECollisionChannel::ECC_Visibility, QueryParams);
|
|
|
|
return UPhysicalMaterial::DetermineSurfaceType(HitResult.PhysMaterial.Get());
|
|
}
|
|
|
|
void AShooterCharacter::EndStun()
|
|
{
|
|
CombatState = ECombatState::ECS_Unoccupied;
|
|
|
|
if (bAimingButtonPressed)
|
|
{
|
|
Aim();
|
|
}
|
|
|
|
if (bFireButtonPressed)
|
|
{
|
|
if (WeaponHasAmmo())
|
|
{
|
|
if (EquippedWeapon->GetAutomatic())
|
|
{
|
|
FireWeapon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReloadWeapon();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::UnhighlightInventorySlot()
|
|
{
|
|
if (HighlightedSlot != -1)
|
|
{
|
|
HighlightIconDelegate.Broadcast(HighlightedSlot, false);
|
|
HighlightedSlot = -1;
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::Stun()
|
|
{
|
|
if (bDying) return;
|
|
if (!HitReactMontage) return;
|
|
|
|
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
|
|
if (!AnimInstance) return;
|
|
|
|
CombatState = ECombatState::ECS_Stunned;
|
|
|
|
AnimInstance->Montage_Play(HitReactMontage);
|
|
}
|
|
|
|
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 (bDying) return;
|
|
if (!EquippedWeapon) return;
|
|
if (CombatState != ECombatState::ECS_Unoccupied) return;
|
|
if (!WeaponHasAmmo())
|
|
{
|
|
ReloadWeapon();
|
|
return;
|
|
}
|
|
|
|
PlayFireSound();
|
|
SendBullet();
|
|
PlayGunFireMontage();
|
|
EquippedWeapon->DecrementAmmo();
|
|
StartCrosshairBulletFire();
|
|
StartFireTimer();
|
|
|
|
if (EquippedWeapon->GetWeaponType() == EWeaponType::EWT_Pistol)
|
|
{
|
|
// Start moving slide timer
|
|
EquippedWeapon->StartSlideTimer();
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::StartFireTimer()
|
|
{
|
|
if (!EquippedWeapon) return;
|
|
|
|
CombatState = ECombatState::ECS_FireTimerInProgress;
|
|
GetWorldTimerManager().SetTimer(AutoFireTimer, this, &AShooterCharacter::AutoFireReset,
|
|
EquippedWeapon->GetAutoFireRate());
|
|
}
|
|
|
|
void AShooterCharacter::AutoFireReset()
|
|
{
|
|
if (bDying) return;
|
|
if (!EquippedWeapon) return;
|
|
if (CombatState == ECombatState::ECS_Stunned) return;
|
|
|
|
CombatState = ECombatState::ECS_Unoccupied;
|
|
|
|
if (bFireButtonPressed)
|
|
{
|
|
if (WeaponHasAmmo())
|
|
{
|
|
if (EquippedWeapon->GetAutomatic())
|
|
{
|
|
FireWeapon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReloadWeapon();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::PlayFireSound()
|
|
{
|
|
// Play fire sound
|
|
if (!EquippedWeapon) return;
|
|
if (EquippedWeapon->GetFireSound())
|
|
{
|
|
UGameplayStatics::PlaySound2D(this, EquippedWeapon->GetFireSound());
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::SendBullet()
|
|
{
|
|
// Send bullet
|
|
const USkeletalMeshSocket* BarrelSocket = EquippedWeapon->GetItemMesh()->GetSocketByName("BarrelSocket");
|
|
if (!BarrelSocket) return;
|
|
|
|
const FTransform SocketTransform = BarrelSocket->GetSocketTransform(EquippedWeapon->GetItemMesh());
|
|
|
|
if (!EquippedWeapon) return;
|
|
if (EquippedWeapon->GetMuzzleFlash())
|
|
{
|
|
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), EquippedWeapon->GetMuzzleFlash(), SocketTransform);
|
|
}
|
|
|
|
FHitResult BeamHitResult;
|
|
bool bBeamEnd = GetBeamEndLocation(SocketTransform.GetLocation(), BeamHitResult);
|
|
if (!bBeamEnd) return;
|
|
|
|
// Spawn impact particles after updating BeamEndPoint
|
|
|
|
// Does hit actor implement BulletHitInterface?
|
|
bool spawnGenericImpactParticles = false;
|
|
if (IsValid(BeamHitResult.GetActor()))
|
|
{
|
|
if (IBulletHitInterface* BulletHitInterface = Cast<IBulletHitInterface>(BeamHitResult.GetActor()))
|
|
{
|
|
BulletHitInterface->BulletHit_Implementation(BeamHitResult, this, GetController());
|
|
}
|
|
else
|
|
{
|
|
spawnGenericImpactParticles = true;
|
|
}
|
|
|
|
if (AEnemy* HitEnemy = Cast<AEnemy>(BeamHitResult.GetActor()))
|
|
{
|
|
float damageToApply;
|
|
if (BeamHitResult.BoneName.ToString() == HitEnemy->GetHeadBone())
|
|
{
|
|
damageToApply = EquippedWeapon->GetHeadshotDamage() * EquippedWeapon->GetDamageMultiplier();
|
|
HitEnemy->ShowHitNumber(damageToApply, BeamHitResult.Location, true);
|
|
}
|
|
else
|
|
{
|
|
damageToApply = EquippedWeapon->GetDamage() * EquippedWeapon->GetDamageMultiplier();
|
|
HitEnemy->ShowHitNumber(damageToApply, BeamHitResult.Location, false);
|
|
}
|
|
|
|
UGameplayStatics::ApplyDamage(HitEnemy,
|
|
damageToApply,
|
|
GetController(), this,
|
|
UDamageType::StaticClass());
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spawnGenericImpactParticles = true;
|
|
}
|
|
|
|
if (ImpactParticles && spawnGenericImpactParticles)
|
|
{
|
|
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticles,
|
|
BeamHitResult.Location);
|
|
}
|
|
|
|
// Spawn smoke trail particles
|
|
if (BeamParticles)
|
|
{
|
|
UParticleSystemComponent* Beam = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BeamParticles,
|
|
SocketTransform);
|
|
if (Beam)
|
|
{
|
|
Beam->SetVectorParameter("Target", BeamHitResult.Location);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, FHitResult& OutHitResult)
|
|
{
|
|
FVector OutBeamLocation;
|
|
|
|
// Check for crosshair trace hit
|
|
FHitResult CrosshairHitResult;
|
|
bool bCrosshairHit = TraceUnderCrosshairs(CrosshairHitResult, OutBeamLocation);
|
|
|
|
// Perform a second trace, this time from the gun barrel
|
|
const FVector WeaponTraceStart{ MuzzleSocketLocation };
|
|
const FVector StartToEnd{ OutBeamLocation - MuzzleSocketLocation };
|
|
const FVector WeaponTraceEnd{ MuzzleSocketLocation + StartToEnd * 1.25f };
|
|
|
|
GetWorld()->LineTraceSingleByChannel(OutHitResult, WeaponTraceStart, WeaponTraceEnd,
|
|
ECollisionChannel::ECC_Visibility);
|
|
|
|
if (!OutHitResult.bBlockingHit) // Object between barrel and BeamEndPoint ?
|
|
{
|
|
OutHitResult.Location = OutBeamLocation;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AShooterCharacter::TraceUnderCrosshairs(FHitResult& OutHitResult, FVector& OutHitLocation)
|
|
{
|
|
// Get Viewport Size
|
|
FVector2D ViewportSize(0, 0);
|
|
|
|
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);
|
|
|
|
TraceHitItem = Cast<AItem>(ItemTraceResult.GetActor());
|
|
|
|
if (TraceHitItem && ItemTraceResult.bBlockingHit)
|
|
{
|
|
// If an item was being looked at from last frame, we should hide its widget
|
|
if (OldTraceHitItem != TraceHitItem)
|
|
{
|
|
UnhighlightInventorySlot();
|
|
SetItemPickupWidgetVisibility(OldTraceHitItem, false);
|
|
}
|
|
|
|
if (TraceHitItem && TraceHitItem->GetItemState() == EItemState::EIS_EquipInterping)
|
|
TraceHitItem = nullptr;
|
|
|
|
const auto TraceHitWeapon = Cast<AWeapon>(TraceHitItem);
|
|
if (TraceHitWeapon && HighlightedSlot == -1)
|
|
HighlightInventorySlot();
|
|
|
|
// Show the new item's widget
|
|
SetItemPickupWidgetVisibility(TraceHitItem, true);
|
|
|
|
if (Inventory.Num() >= INVENTORY_CAPACITY)
|
|
{
|
|
// Inventory is full
|
|
TraceHitItem->SetCharacterInventoryFull(true);
|
|
}
|
|
else
|
|
{
|
|
TraceHitItem->SetCharacterInventoryFull(false);
|
|
}
|
|
|
|
OldTraceHitItem = TraceHitItem;
|
|
}
|
|
else if (OldTraceHitItem) // Hide widget for the item from last frame
|
|
{
|
|
UnhighlightInventorySlot();
|
|
|
|
SetItemPickupWidgetVisibility(OldTraceHitItem, false);
|
|
OldTraceHitItem = nullptr;
|
|
}
|
|
}
|
|
else if (TraceHitItem) // Hide widget for the item from last frame
|
|
{
|
|
UnhighlightInventorySlot();
|
|
|
|
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, bool bSwapping)
|
|
{
|
|
// 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());
|
|
|
|
if (EquippedWeapon == nullptr)
|
|
{
|
|
// -1 == no EquippedWeapon yet. No need to reverse the icon animation
|
|
EquipItemDelegate.Broadcast(-1, WeaponToEquip->GetSlotIndex());
|
|
}
|
|
else if (!bSwapping)
|
|
{
|
|
EquipItemDelegate.Broadcast(EquippedWeapon->GetSlotIndex(), WeaponToEquip->GetSlotIndex());
|
|
}
|
|
|
|
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 (bDying) return;
|
|
if (CombatState != ECombatState::ECS_Unoccupied) return;
|
|
|
|
if (TraceHitItem)
|
|
{
|
|
TraceHitItem->StartItemCurve(this, true);
|
|
TraceHitItem = nullptr;
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::SelectButtonReleased()
|
|
{
|
|
}
|
|
|
|
void AShooterCharacter::SwapWeapon(AWeapon* WeaponToSwap)
|
|
{
|
|
if (EquippedWeapon->GetSlotIndex() <= Inventory.Num() - 1)
|
|
{
|
|
Inventory[EquippedWeapon->GetSlotIndex()] = WeaponToSwap;
|
|
WeaponToSwap->SetSlotIndex(EquippedWeapon->GetSlotIndex());
|
|
}
|
|
|
|
DropWeapon();
|
|
EquipWeapon(WeaponToSwap, true);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (Inventory.Num() < INVENTORY_CAPACITY)
|
|
{
|
|
Weapon->SetSlotIndex(Inventory.Num());
|
|
Inventory.Add(Weapon);
|
|
Weapon->SetItemState(EItemState::EIS_PickedUp);
|
|
}
|
|
else // Inventory is full! Swap with EquippedWeapon
|
|
{
|
|
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 (bDying) return;
|
|
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()
|
|
{
|
|
if (bDying) return;
|
|
if (!EquippedWeapon) return;
|
|
if (CombatState == ECombatState::ECS_Stunned) return;
|
|
|
|
CombatState = ECombatState::ECS_Unoccupied;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (bAimingButtonPressed)
|
|
{
|
|
Aim();
|
|
}
|
|
|
|
if (bFireButtonPressed)
|
|
{
|
|
if (WeaponHasAmmo())
|
|
{
|
|
if (EquippedWeapon->GetAutomatic())
|
|
{
|
|
FireWeapon();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::FinishEquipping()
|
|
{
|
|
if (bDying) return;
|
|
if (CombatState == ECombatState::ECS_Stunned) return;
|
|
|
|
CombatState = ECombatState::ECS_Unoccupied;
|
|
|
|
if (bAimingButtonPressed)
|
|
{
|
|
Aim();
|
|
}
|
|
|
|
if (bFireButtonPressed)
|
|
{
|
|
if (WeaponHasAmmo())
|
|
{
|
|
if (EquippedWeapon->GetAutomatic())
|
|
{
|
|
FireWeapon();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReloadWeapon();
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (bDying) return;
|
|
if (!GetCharacterMovement()->IsFalling())
|
|
{
|
|
bCrouching = !bCrouching;
|
|
}
|
|
|
|
if (bCrouching)
|
|
{
|
|
GetCharacterMovement()->MaxWalkSpeed = CrouchMovementSpeed;
|
|
GetCharacterMovement()->GroundFriction = CrouchingGroundFriction;
|
|
}
|
|
else
|
|
{
|
|
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
|
|
GetCharacterMovement()->GroundFriction = BaseGroundFriction;
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::Jump()
|
|
{
|
|
if (bDying) return;
|
|
if (bCrouching)
|
|
{
|
|
bCrouching = false;
|
|
GetCharacterMovement()->MaxWalkSpeed = BaseMovementSpeed;
|
|
GetCharacterMovement()->GroundFriction = BaseGroundFriction;
|
|
}
|
|
else
|
|
{
|
|
Super::Jump();
|
|
}
|
|
}
|
|
|
|
void AShooterCharacter::InterpCapsuleHalfHeight(float DeltaTime)
|
|
{
|
|
float TargetCapsuleHalfHeight{};
|
|
// float TargetMeshOffset{};
|
|
|
|
if (bCrouching)
|
|
{
|
|
TargetCapsuleHalfHeight = CrouchingCapsuleHalfHeight;
|
|
// TargetMeshOffset = CrouchingMeshZOffset;
|
|
}
|
|
else
|
|
{
|
|
TargetCapsuleHalfHeight = StandingCapsuleHalfHeight;
|
|
// TargetMeshOffset = 0;
|
|
}
|
|
|
|
const float InterpHalfHeight{ FMath::FInterpTo(
|
|
GetCapsuleComponent()->GetScaledCapsuleHalfHeight(),
|
|
TargetCapsuleHalfHeight, DeltaTime, 20.f)};
|
|
// const double InterpMeshOffset{ FMath::FInterpTo(
|
|
// GetMesh()->GetRelativeLocation().Z,
|
|
// TargetMeshOffset, DeltaTime, 20.f)};
|
|
|
|
// Negative value if crouching, positive if standing
|
|
const float DeltaCapsuleHalfHeight{ InterpHalfHeight - GetCapsuleComponent()->GetScaledCapsuleHalfHeight() };
|
|
// const double DeltaMeshOffset{ -DeltaCapsuleHalfHeight + (InterpMeshOffset - GetMesh()->GetRelativeLocation().Z) };
|
|
|
|
const FVector MeshOffset{0.f, 0.f, -DeltaCapsuleHalfHeight};
|
|
GetMesh()->AddLocalOffset(MeshOffset);
|
|
GetCapsuleComponent()->SetCapsuleHalfHeight(InterpHalfHeight);
|
|
|
|
} |