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.
419 lines
12 KiB
419 lines
12 KiB
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "MWeapon.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "MechDefence/MechDefence.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "MAICharacterBase.h"
|
|
#include "MCharacter.h"
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
#include "MPlayerController.h"
|
|
#include "NiagaraSystem.h"
|
|
#include "NiagaraFunctionLibrary.h"
|
|
#include "NiagaraComponent.h"
|
|
#include "Components/AudioComponent.h"
|
|
#include "PhysicalMaterials/PhysicalMaterial.h"
|
|
#include "MProjectile.h"
|
|
|
|
static TAutoConsoleVariable<int32> CVAR_DebugWeaponDrawing(
|
|
TEXT("MDEF.DebugWeaponDraw"),
|
|
0,
|
|
TEXT("Draw debug lines for visualizing weapons"),
|
|
ECVF_Cheat);
|
|
|
|
AMWeapon::AMWeapon()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon Mesh"));
|
|
RootComponent = WeaponMesh;
|
|
|
|
MuzzleSocketName = "MuzzleSocket";
|
|
PlayerAttachmentSocketName = "WeaponSocket";
|
|
BaseDamage = 20.f;
|
|
VulnerableDamageMultiplier = 4.f;
|
|
RateOfFire = 100.f;
|
|
BulletSpread = 0.5f;
|
|
bFiring = false;
|
|
bReloading = false;
|
|
TimeLastFired = 0.f;
|
|
StopFireDelay = 0.1f;
|
|
MaxWeaponRange = 10000.f;
|
|
TracerParticleStartLocationName = "Target";
|
|
ClipSize = 5;
|
|
ReloadTime = 2.f;
|
|
ProjectileSpawnChance = 0.5f;
|
|
bCanZoom = true;
|
|
ZoomInFOV = 60.f;
|
|
bSecondaryFireBound = false;
|
|
}
|
|
|
|
void AMWeapon::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
ClipRemainingBullets = ClipSize;
|
|
TimeBetweenShots = 60.f / RateOfFire;
|
|
}
|
|
|
|
void AMWeapon::Fire()
|
|
{
|
|
OnFire();
|
|
|
|
TimeLastFired = GetWorld()->GetTimeSeconds();
|
|
|
|
if(ClipSize != 0)
|
|
ClipRemainingBullets--;
|
|
bFiring = true;
|
|
}
|
|
|
|
void AMWeapon::PlayFireEffects(FVector ShotDirection, UParticleSystem* ImpactEffect, FVector TraceEffectEndLocation, const FRotator ImpactParticleRotation)
|
|
{
|
|
// Muzzle Particle Effect
|
|
if(MuzzleEffect)
|
|
UGameplayStatics::SpawnEmitterAttached(MuzzleEffect, WeaponMesh, MuzzleSocketName);
|
|
|
|
// Projectile
|
|
bool ProjectileSpawned = false;
|
|
if(ProjectileClass && ProjectileSpawnChance > 0.f)
|
|
{
|
|
const float SpawnProbability = FMath::FRand();
|
|
if(SpawnProbability > 0.f && SpawnProbability <= ProjectileSpawnChance)
|
|
{
|
|
const FVector MuzzleSocketLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName);
|
|
FActorSpawnParameters SpawnParameters;
|
|
SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
AMProjectile* Projectile = GetWorld()->SpawnActor<AMProjectile>(ProjectileClass, MuzzleSocketLocation, ShotDirection.Rotation(), SpawnParameters);
|
|
Projectile->SetOwner(this);
|
|
ProjectileSpawned = true;
|
|
}
|
|
}
|
|
|
|
// Tracer Particle Effect
|
|
if(TracerEffect && !ProjectileSpawned)
|
|
{
|
|
const FVector MuzzleSocketLocation = WeaponMesh->GetSocketLocation(MuzzleSocketName);
|
|
//UParticleSystemComponent* TracerComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), TracerEffect, MuzzleSocketLocation);
|
|
UNiagaraComponent* TracerComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), TracerEffect, MuzzleSocketLocation);
|
|
if(TracerComp)
|
|
{
|
|
TracerComp->SetVectorParameter(TracerParticleStartLocationName, TraceEffectEndLocation);
|
|
}
|
|
}
|
|
|
|
// Impact Particle
|
|
if(ImpactEffect)
|
|
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactEffect, TraceEffectEndLocation, ImpactParticleRotation);
|
|
}
|
|
|
|
void AMWeapon::OnFire_Implementation()
|
|
{
|
|
AActor* WeaponOwner = GetOwner();
|
|
if(!WeaponOwner)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("No weapon owner for weapon %s. Cannot Fire!"), *GetName());
|
|
return;
|
|
}
|
|
|
|
// try and play the sound if specified
|
|
if(FireSound)
|
|
{
|
|
UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());
|
|
}
|
|
|
|
APawn* WeaponOwnerPawn = Cast<APawn>(WeaponOwner);
|
|
MakeNoise(0.75f, WeaponOwnerPawn);
|
|
|
|
UParticleSystem* ImpactEffect = DefaultImpactEffect;
|
|
|
|
FVector EyeLocation;
|
|
FRotator EyeRotation;
|
|
WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
|
|
FVector ShotDirection = EyeRotation.Vector();
|
|
|
|
// Bullet Spread if aiming from hip
|
|
bool bShouldApplyBulletSpread = true;
|
|
AMCharacter* PlayerChar = Cast<AMCharacter>(WeaponOwner);
|
|
if(PlayerChar && PlayerChar->IsZoomedIn())
|
|
{
|
|
bShouldApplyBulletSpread = false;
|
|
}
|
|
|
|
if(bShouldApplyBulletSpread)
|
|
{
|
|
float HalfAngleRadians = FMath::DegreesToRadians(BulletSpread);
|
|
ShotDirection = FMath::VRandCone(ShotDirection, HalfAngleRadians);
|
|
}
|
|
|
|
FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange);
|
|
FVector TraceEffectEndLocation = TraceEndLocation; // Init Location where the Tracer effect should end
|
|
|
|
FCollisionQueryParams QueryParams;
|
|
QueryParams.AddIgnoredActor(WeaponOwner);
|
|
QueryParams.AddIgnoredActor(this);
|
|
QueryParams.bTraceComplex = true;
|
|
QueryParams.bReturnPhysicalMaterial = true;
|
|
|
|
EPhysicalSurface HitSurfaceType = SurfaceType_Default;
|
|
FRotator ImpactRotation;
|
|
|
|
FHitResult Hit;
|
|
if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TRACE_CHANNEL_Weapon, QueryParams))
|
|
{
|
|
AActor* HitActor = Hit.GetActor();
|
|
TraceEffectEndLocation = Hit.ImpactPoint;
|
|
ImpactRotation = Hit.Normal.Rotation();
|
|
|
|
// Damage
|
|
float ActualDamage = BaseDamage;
|
|
HitSurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
|
|
if(HitSurfaceType == SurfaceType_Vulnerable)
|
|
ActualDamage *= VulnerableDamageMultiplier;
|
|
|
|
|
|
// Check tags on hit actor to determine if it was a valid target so that we can apply damage and provide feedback if the owner of this weapon is player
|
|
IGameplayTagAssetInterface* GameplayTagAsset = Cast<IGameplayTagAssetInterface>(HitActor);
|
|
if(GameplayTagAsset && GameplayTagAsset->HasAnyMatchingGameplayTags(ValidTargetTags))
|
|
{
|
|
//Damage
|
|
UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, ShotDirection, Hit, WeaponOwner->GetInstigatorController(), WeaponOwner, DamageType);
|
|
|
|
// Notify player controller so that we can show feedback for the hit on the hud
|
|
AMPlayerController* PC = WeaponOwner->GetInstigatorController<AMPlayerController>();
|
|
if(PC)
|
|
{
|
|
OnTargetHit.Broadcast(this, HitActor, WeaponOwner);
|
|
PC->HandleTargetHit(this, HitActor, WeaponOwner);
|
|
}
|
|
|
|
//Determine the impact particle to spawn. If the hit target is an enemy and has the right particle effects,
|
|
//use those. Otherwise, we'll use the defaults
|
|
AMAICharacterBase* EnemyBaseChar = Cast<AMAICharacterBase>(HitActor);
|
|
if(EnemyBaseChar)
|
|
{
|
|
if(HitSurfaceType == SurfaceType_Vulnerable && EnemyBaseChar->GetVulnerableHitParticle())
|
|
ImpactEffect = EnemyBaseChar->GetVulnerableHitParticle();
|
|
else if(EnemyBaseChar->GetDefaultHitParticle())
|
|
ImpactEffect = EnemyBaseChar->GetDefaultHitParticle();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(CVAR_DebugWeaponDrawing.GetValueOnGameThread() > 0)
|
|
DrawDebugLine(GetWorld(), EyeLocation, TraceEffectEndLocation, FColor::Red, false, 1.f, 0, 1.f);
|
|
|
|
// Fire Particle Effects
|
|
PlayFireEffects(ShotDirection, ImpactEffect, TraceEffectEndLocation, ImpactRotation);
|
|
|
|
// Camera Shake
|
|
APawn* OwnerPawn = Cast<APawn>(WeaponOwner);
|
|
if(OwnerPawn && CameraShakeClass)
|
|
{
|
|
APlayerController* PC = Cast<APlayerController>(OwnerPawn->GetController());
|
|
if(PC)
|
|
{
|
|
PC->ClientStartCameraShake(CameraShakeClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMWeapon::StopFire_Implementation()
|
|
{
|
|
if(bFiring)
|
|
{
|
|
bFiring = false;
|
|
}
|
|
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_TimeBetweenShots);
|
|
|
|
if(ClipRemainingBullets == 0 && ClipSize != 0)
|
|
Reload();
|
|
}
|
|
|
|
void AMWeapon::StartSecondaryFire_Implementation()
|
|
{
|
|
if(bCanZoom)
|
|
{
|
|
AMCharacter* PlayerChar = Cast<AMCharacter>(GetOwner());
|
|
if(PlayerChar)
|
|
{
|
|
PlayerChar->SetTargetFOV(ZoomInFOV);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMWeapon::StopSecondaryFire_Implementation()
|
|
{
|
|
if(bCanZoom)
|
|
{
|
|
AMCharacter* PlayerChar = Cast<AMCharacter>(GetOwner());
|
|
if(PlayerChar)
|
|
{
|
|
PlayerChar->ResetToDefaultFOV();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMWeapon::Reload()
|
|
{
|
|
if(bReloading)
|
|
return;
|
|
|
|
bReloading = true;
|
|
bFiring = false;
|
|
GetWorldTimerManager().SetTimer(TimerHandle_Reload, this, &AMWeapon::HandleReloadComplete, ReloadTime);
|
|
OnWeaponReloadStarted.Broadcast(this);
|
|
if(ReloadSound)
|
|
TempReloadSoundHandle = UGameplayStatics::SpawnSoundAttached(ReloadSound, RootComponent);
|
|
}
|
|
|
|
void AMWeapon::HandleWeaponEquipped(AMCharacter* Character, AMWeapon* NewWeapon, AMWeapon* PreviousWeapon)
|
|
{
|
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
|
if(NewWeapon && NewWeapon == this)
|
|
{
|
|
if(PC)
|
|
{
|
|
EnableInput(PC);
|
|
if(!bSecondaryFireBound)
|
|
{
|
|
UInputComponent* IC = this->InputComponent;
|
|
if(IC)
|
|
{
|
|
FInputActionBinding& PressedBinding = IC->BindAction("SecondaryFire", IE_Pressed, this, &AMWeapon::StartSecondaryFire);
|
|
FInputActionBinding& ReleasedBinding = IC->BindAction("SecondaryFire", IE_Released, this, &AMWeapon::StopSecondaryFire);
|
|
PressedBinding.bConsumeInput = false;
|
|
ReleasedBinding.bConsumeInput = false;
|
|
bSecondaryFireBound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(PreviousWeapon && PreviousWeapon == this)
|
|
{
|
|
if(PC)
|
|
{
|
|
DisableInput(PC);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AMWeapon::HandleReloadComplete()
|
|
{
|
|
bReloading = false;
|
|
ClipRemainingBullets = ClipSize;
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_Reload);
|
|
OnWeaponReloadCompleted.Broadcast(this);
|
|
TempReloadSoundHandle = nullptr;
|
|
}
|
|
|
|
void AMWeapon::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
}
|
|
|
|
bool AMWeapon::StartFire_Implementation()
|
|
{
|
|
if(bReloading)
|
|
return false;
|
|
|
|
if(ClipRemainingBullets == 0 && ClipSize != 0)
|
|
{
|
|
Reload();
|
|
return false;
|
|
}
|
|
|
|
float FirstFireDelay = FMath::Max(TimeLastFired + TimeBetweenShots - GetWorld()->GetTimeSeconds(), 0.f);
|
|
if(RateOfFire == 0.f)
|
|
{
|
|
Fire();
|
|
GetWorldTimerManager().SetTimer(TimerHandle_TimeBetweenShots, this, &AMWeapon::StopFire, StopFireDelay, false, 0.f);
|
|
}
|
|
else
|
|
{
|
|
GetWorldTimerManager().SetTimer(TimerHandle_TimeBetweenShots, this, &AMWeapon::Fire, TimeBetweenShots, true, FirstFireDelay);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
float AMWeapon::GetReloadProgress()
|
|
{
|
|
float Progress = 1.f;
|
|
if(bReloading)
|
|
{
|
|
float TimeElapsed = GetWorldTimerManager().GetTimerElapsed(TimerHandle_Reload);
|
|
Progress = FMath::Clamp(TimeElapsed / ReloadTime, 0.f, 1.f);
|
|
}
|
|
return Progress;
|
|
}
|
|
|
|
void AMWeapon::CancelReload()
|
|
{
|
|
if(!bReloading)
|
|
return;
|
|
|
|
GetWorldTimerManager().ClearTimer(TimerHandle_Reload);
|
|
OnWeaponReloadCancelled.Broadcast(this);
|
|
bReloading = false;
|
|
if(TempReloadSoundHandle)
|
|
TempReloadSoundHandle->Stop();
|
|
TempReloadSoundHandle = nullptr;
|
|
}
|
|
|
|
FVector AMWeapon::CalculateTargetedWorldLocation(TSubclassOf<AActor> ValidTargetClass, ECollisionChannel TraceChannel) const
|
|
{
|
|
FVector Location;
|
|
|
|
AActor* WeaponOwner = GetOwner();
|
|
if(WeaponOwner)
|
|
{
|
|
FVector EyeLocation;
|
|
FRotator EyeRotation;
|
|
WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
|
|
FVector ShotDirection = EyeRotation.Vector();
|
|
|
|
FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange);
|
|
|
|
FCollisionQueryParams QueryParams;
|
|
QueryParams.AddIgnoredActor(WeaponOwner);
|
|
QueryParams.AddIgnoredActor(this);
|
|
QueryParams.bTraceComplex = true;
|
|
QueryParams.bReturnPhysicalMaterial = true;
|
|
|
|
FHitResult Hit;
|
|
if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TraceChannel, QueryParams))
|
|
Location = Hit.Location;
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
AActor* AMWeapon::GetTargetedActor(ECollisionChannel TraceChannel) const
|
|
{
|
|
AActor* TargetedActor = nullptr;
|
|
|
|
AActor* WeaponOwner = GetOwner();
|
|
if(WeaponOwner)
|
|
{
|
|
FVector EyeLocation;
|
|
FRotator EyeRotation;
|
|
WeaponOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
|
|
FVector ShotDirection = EyeRotation.Vector();
|
|
|
|
FVector TraceEndLocation = EyeLocation + (ShotDirection * MaxWeaponRange);
|
|
|
|
FCollisionQueryParams QueryParams;
|
|
QueryParams.AddIgnoredActor(WeaponOwner);
|
|
QueryParams.AddIgnoredActor(this);
|
|
QueryParams.bTraceComplex = true;
|
|
|
|
FHitResult Hit;
|
|
if(GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEndLocation, TraceChannel, QueryParams))
|
|
TargetedActor = Hit.GetActor();
|
|
}
|
|
|
|
return TargetedActor;
|
|
}
|
|
|