// 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(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(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(TEXT("HandSceneComp")); // Create interpolation components WeaponInterpComp = CreateDefaultSubobject(TEXT("Weapon Interpolation Component")); WeaponInterpComp->SetupAttachment(GetFollowCamera()); InterpComp1 = CreateDefaultSubobject(TEXT("Interpolation Component 1")); InterpComp1->SetupAttachment(GetFollowCamera()); InterpComp2 = CreateDefaultSubobject(TEXT("Interpolation Component 2")); InterpComp2->SetupAttachment(GetFollowCamera()); InterpComp3 = CreateDefaultSubobject(TEXT("Interpolation Component 3")); InterpComp3->SetupAttachment(GetFollowCamera()); InterpComp4 = CreateDefaultSubobject(TEXT("Interpolation Component 4")); InterpComp4->SetupAttachment(GetFollowCamera()); InterpComp5 = CreateDefaultSubobject(TEXT("Interpolation Component 5")); InterpComp5->SetupAttachment(GetFollowCamera()); InterpComp6 = CreateDefaultSubobject(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(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(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(Item); if (Weapon) { SwapWeapon(Weapon); } auto Ammo = Cast(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); }