Protptype of a wave based, first person shooter game made in Unreal Engine 4
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.
 
 
 
MechDefence/Private/MWeapon.cpp

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;
}