// 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 CVAR_DebugWeaponDrawing( TEXT("MDEF.DebugWeaponDraw"), 0, TEXT("Draw debug lines for visualizing weapons"), ECVF_Cheat); AMWeapon::AMWeapon() { PrimaryActorTick.bCanEverTick = true; WeaponMesh = CreateDefaultSubobject(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(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(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(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(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(); 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(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(WeaponOwner); if(OwnerPawn && CameraShakeClass) { APlayerController* PC = Cast(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(GetOwner()); if(PlayerChar) { PlayerChar->SetTargetFOV(ZoomInFOV); } } } void AMWeapon::StopSecondaryFire_Implementation() { if(bCanZoom) { AMCharacter* PlayerChar = Cast(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 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; }