ShooterDamageType.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "Weapons/ShooterDamageType.h"
UShooterDamageType::UShooterDamageType(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
ShooterProjectile.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "Weapons/ShooterProjectile.h"
#include "Particles/ParticleSystemComponent.h"
#include "Effects/ShooterExplosionEffect.h"
AShooterProjectile::AShooterProjectile(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
CollisionComp = ObjectInitializer.CreateDefaultSubobject<USphereComponent>(this, TEXT("SphereComp"));
CollisionComp->InitSphereRadius(5.0f);
CollisionComp->AlwaysLoadOnClient = true;
CollisionComp->AlwaysLoadOnServer = true;
CollisionComp->bTraceComplexOnMove = true;
CollisionComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
CollisionComp->SetCollisionObjectType(COLLISION_PROJECTILE);
CollisionComp->SetCollisionResponseToAllChannels(ECR_Ignore);
CollisionComp->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block);
CollisionComp->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Block);
CollisionComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
RootComponent = CollisionComp;
ParticleComp = ObjectInitializer.CreateDefaultSubobject<UParticleSystemComponent>(this, TEXT("ParticleComp"));
ParticleComp->bAutoActivate = false;
ParticleComp->bAutoDestroy = false;
ParticleComp->SetupAttachment(RootComponent);
MovementComp = ObjectInitializer.CreateDefaultSubobject<UProjectileMovementComponent>(this, TEXT("ProjectileComp"));
MovementComp->UpdatedComponent = CollisionComp;
MovementComp->InitialSpeed = 2000.0f;
MovementComp->MaxSpeed = 2000.0f;
MovementComp->bRotationFollowsVelocity = true;
MovementComp->ProjectileGravityScale = 0.f;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
SetRemoteRoleForBackwardsCompat(ROLE_SimulatedProxy);
bReplicates = true;
bReplicateMovement = true;
}
void AShooterProjectile::PostInitializeComponents()
{
Super::PostInitializeComponents();
MovementComp->OnProjectileStop.AddDynamic(this, &AShooterProjectile::OnImpact);
CollisionComp->MoveIgnoreActors.Add(Instigator);
AShooterWeapon_Projectile* OwnerWeapon = Cast<AShooterWeapon_Projectile>(GetOwner());
if (OwnerWeapon)
{
OwnerWeapon->ApplyWeaponConfig(WeaponConfig);
}
SetLifeSpan( WeaponConfig.ProjectileLife );
MyController = GetInstigatorController();
}
void AShooterProjectile::InitVelocity(FVector& ShootDirection)
{
if (MovementComp)
{
MovementComp->Velocity = ShootDirection * MovementComp->InitialSpeed;
}
}
void AShooterProjectile::OnImpact(const FHitResult& HitResult)
{
if (Role == ROLE_Authority && !bExploded)
{
Explode(HitResult);
DisableAndDestroy();
}
}
void AShooterProjectile::Explode(const FHitResult& Impact)
{
if (ParticleComp)
{
ParticleComp->Deactivate();
}
// effects and damage origin shouldn't be placed inside mesh at impact point
const FVector NudgedImpactLocation = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
if (WeaponConfig.ExplosionDamage > 0 && WeaponConfig.ExplosionRadius > 0 && WeaponConfig.DamageType)
{
UGameplayStatics::ApplyRadialDamage(this, WeaponConfig.ExplosionDamage, NudgedImpactLocation, WeaponConfig.ExplosionRadius, WeaponConfig.DamageType, TArray<AActor*>(), this, MyController.Get());
}
if (ExplosionTemplate)
{
FTransform const SpawnTransform(Impact.ImpactNormal.Rotation(), NudgedImpactLocation);
AShooterExplosionEffect* const EffectActor = GetWorld()->SpawnActorDeferred<AShooterExplosionEffect>(ExplosionTemplate, SpawnTransform);
if (EffectActor)
{
EffectActor->SurfaceHit = Impact;
UGameplayStatics::FinishSpawningActor(EffectActor, SpawnTransform);
}
}
bExploded = true;
}
void AShooterProjectile::DisableAndDestroy()
{
UAudioComponent* ProjAudioComp = FindComponentByClass<UAudioComponent>();
if (ProjAudioComp && ProjAudioComp->IsPlaying())
{
ProjAudioComp->FadeOut(0.1f, 0.f);
}
MovementComp->StopMovementImmediately();
// give clients some time to show explosion
SetLifeSpan( 2.0f );
}
///CODE_SNIPPET_START: AActor::GetActorLocation AActor::GetActorRotation
void AShooterProjectile::OnRep_Exploded()
{
FVector ProjDirection = GetActorForwardVector();
const FVector StartTrace = GetActorLocation() - ProjDirection * 200;
const FVector EndTrace = GetActorLocation() + ProjDirection * 150;
FHitResult Impact;
if (!GetWorld()->LineTraceSingleByChannel(Impact, StartTrace, EndTrace, COLLISION_PROJECTILE, FCollisionQueryParams(TEXT("ProjClient"), true, Instigator)))
{
// failsafe
Impact.ImpactPoint = GetActorLocation();
Impact.ImpactNormal = -ProjDirection;
}
Explode(Impact);
}
///CODE_SNIPPET_END
void AShooterProjectile::PostNetReceiveVelocity(const FVector& NewVelocity)
{
if (MovementComp)
{
MovementComp->Velocity = NewVelocity;
}
}
void AShooterProjectile::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
Super::GetLifetimeReplicatedProps( OutLifetimeProps );
DOREPLIFETIME( AShooterProjectile, bExploded );
}
ShooterWeapon.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "Weapons/ShooterWeapon.h"
#include "Particles/ParticleSystemComponent.h"
#include "Bots/ShooterAIController.h"
#include "Online/ShooterPlayerState.h"
#include "UI/ShooterHUD.h"
AShooterWeapon::AShooterWeapon(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
Mesh1P = ObjectInitializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("WeaponMesh1P"));
Mesh1P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
Mesh1P->bReceivesDecals = false;
Mesh1P->CastShadow = false;
Mesh1P->SetCollisionObjectType(ECC_WorldDynamic);
Mesh1P->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Mesh1P->SetCollisionResponseToAllChannels(ECR_Ignore);
RootComponent = Mesh1P;
Mesh3P = ObjectInitializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("WeaponMesh3P"));
Mesh3P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
Mesh3P->bReceivesDecals = false;
Mesh3P->CastShadow = true;
Mesh3P->SetCollisionObjectType(ECC_WorldDynamic);
Mesh3P->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Mesh3P->SetCollisionResponseToAllChannels(ECR_Ignore);
Mesh3P->SetCollisionResponseToChannel(COLLISION_WEAPON, ECR_Block);
Mesh3P->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
Mesh3P->SetCollisionResponseToChannel(COLLISION_PROJECTILE, ECR_Block);
Mesh3P->SetupAttachment(Mesh1P);
bLoopedMuzzleFX = false;
bLoopedFireAnim = false;
bPlayingFireAnim = false;
bIsEquipped = false;
bWantsToFire = false;
bPendingReload = false;
bPendingEquip = false;
CurrentState = EWeaponState::Idle;
CurrentAmmo = 0;
CurrentAmmoInClip = 0;
BurstCounter = 0;
LastFireTime = 0.0f;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
SetRemoteRoleForBackwardsCompat(ROLE_SimulatedProxy);
bReplicates = true;
bNetUseOwnerRelevancy = true;
}
void AShooterWeapon::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (WeaponConfig.InitialClips > 0)
{
CurrentAmmoInClip = WeaponConfig.AmmoPerClip;
CurrentAmmo = WeaponConfig.AmmoPerClip * WeaponConfig.InitialClips;
}
DetachMeshFromPawn();
}
void AShooterWeapon::Destroyed()
{
Super::Destroyed();
StopSimulatingWeaponFire();
}
//
// Inventory
void AShooterWeapon::OnEquip(const AShooterWeapon* LastWeapon)
{
AttachMeshToPawn();
bPendingEquip = true;
DetermineWeaponState();
// Only play animation if last weapon is valid
if (LastWeapon)
{
float Duration = PlayWeaponAnimation(EquipAnim);
if (Duration <= 0.0f)
{
// failsafe
Duration = 0.5f;
}
EquipStartedTime = GetWorld()->GetTimeSeconds();
EquipDuration = Duration;
GetWorldTimerManager().SetTimer(TimerHandle_OnEquipFinished, this, &AShooterWeapon::OnEquipFinished, Duration, false);
}
else
{
OnEquipFinished();
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
PlayWeaponSound(EquipSound);
}
}
void AShooterWeapon::OnEquipFinished()
{
AttachMeshToPawn();
bIsEquipped = true;
bPendingEquip = false;
// Determine the state so that the can reload checks will work
DetermineWeaponState();
if (MyPawn)
{
// try to reload empty clip
if (MyPawn->IsLocallyControlled() &&
CurrentAmmoInClip <= 0 &&
CanReload())
{
StartReload();
}
}
}
void AShooterWeapon::OnUnEquip()
{
DetachMeshFromPawn();
bIsEquipped = false;
StopFire();
if (bPendingReload)
{
StopWeaponAnimation(ReloadAnim);
bPendingReload = false;
GetWorldTimerManager().ClearTimer(TimerHandle_StopReload);
GetWorldTimerManager().ClearTimer(TimerHandle_ReloadWeapon);
}
if (bPendingEquip)
{
StopWeaponAnimation(EquipAnim);
bPendingEquip = false;
GetWorldTimerManager().ClearTimer(TimerHandle_OnEquipFinished);
}
DetermineWeaponState();
}
void AShooterWeapon::OnEnterInventory(AShooterCharacter* NewOwner)
{
SetOwningPawn(NewOwner);
}
void AShooterWeapon::OnLeaveInventory()
{
if (Role == ROLE_Authority)
{
SetOwningPawn(NULL);
}
if (IsAttachedToPawn())
{
OnUnEquip();
}
}
void AShooterWeapon::AttachMeshToPawn()
{
if (MyPawn)
{
// Remove and hide both first and third person meshes
DetachMeshFromPawn();
// For locally controller players we attach both weapons and let the bOnlyOwnerSee, bOwnerNoSee flags deal with visibility.
FName AttachPoint = MyPawn->GetWeaponAttachPoint();
if( MyPawn->IsLocallyControlled() == true )
{
USkeletalMeshComponent* PawnMesh1p = MyPawn->GetSpecifcPawnMesh(true);
USkeletalMeshComponent* PawnMesh3p = MyPawn->GetSpecifcPawnMesh(false);
Mesh1P->SetHiddenInGame( false );
Mesh3P->SetHiddenInGame( false );
Mesh1P->AttachToComponent(PawnMesh1p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
Mesh3P->AttachToComponent(PawnMesh3p, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
}
else
{
USkeletalMeshComponent* UseWeaponMesh = GetWeaponMesh();
USkeletalMeshComponent* UsePawnMesh = MyPawn->GetPawnMesh();
UseWeaponMesh->AttachToComponent(UsePawnMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachPoint);
UseWeaponMesh->SetHiddenInGame( false );
}
}
}
void AShooterWeapon::DetachMeshFromPawn()
{
Mesh1P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
Mesh1P->SetHiddenInGame(true);
Mesh3P->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
Mesh3P->SetHiddenInGame(true);
}
//
// Input
void AShooterWeapon::StartFire()
{
if (Role < ROLE_Authority)
{
ServerStartFire();
}
if (!bWantsToFire)
{
bWantsToFire = true;
DetermineWeaponState();
}
}
void AShooterWeapon::StopFire()
{
if (Role < ROLE_Authority)
{
ServerStopFire();
}
if (bWantsToFire)
{
bWantsToFire = false;
DetermineWeaponState();
}
}
void AShooterWeapon::StartReload(bool bFromReplication)
{
if (!bFromReplication && Role < ROLE_Authority)
{
ServerStartReload();
}
if (bFromReplication || CanReload())
{
bPendingReload = true;
DetermineWeaponState();
float AnimDuration = PlayWeaponAnimation(ReloadAnim);
if (AnimDuration <= 0.0f)
{
AnimDuration = WeaponConfig.NoAnimReloadDuration;
}
GetWorldTimerManager().SetTimer(TimerHandle_StopReload, this, &AShooterWeapon::StopReload, AnimDuration, false);
if (Role == ROLE_Authority)
{
GetWorldTimerManager().SetTimer(TimerHandle_ReloadWeapon, this, &AShooterWeapon::ReloadWeapon, FMath::Max(0.1f, AnimDuration - 0.1f), false);
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
PlayWeaponSound(ReloadSound);
}
}
}
void AShooterWeapon::StopReload()
{
if (CurrentState == EWeaponState::Reloading)
{
bPendingReload = false;
DetermineWeaponState();
StopWeaponAnimation(ReloadAnim);
}
}
bool AShooterWeapon::ServerStartFire_Validate()
{
return true;
}
void AShooterWeapon::ServerStartFire_Implementation()
{
StartFire();
}
bool AShooterWeapon::ServerStopFire_Validate()
{
return true;
}
void AShooterWeapon::ServerStopFire_Implementation()
{
StopFire();
}
bool AShooterWeapon::ServerStartReload_Validate()
{
return true;
}
void AShooterWeapon::ServerStartReload_Implementation()
{
StartReload();
}
bool AShooterWeapon::ServerStopReload_Validate()
{
return true;
}
void AShooterWeapon::ServerStopReload_Implementation()
{
StopReload();
}
void AShooterWeapon::ClientStartReload_Implementation()
{
StartReload();
}
//
// Control
bool AShooterWeapon::CanFire() const
{
bool bCanFire = MyPawn && MyPawn->CanFire();
bool bStateOKToFire = ( ( CurrentState == EWeaponState::Idle ) || ( CurrentState == EWeaponState::Firing) );
return (( bCanFire == true ) && ( bStateOKToFire == true ) && ( bPendingReload == false ));
}
bool AShooterWeapon::CanReload() const
{
bool bCanReload = (!MyPawn || MyPawn->CanReload());
bool bGotAmmo = ( CurrentAmmoInClip < WeaponConfig.AmmoPerClip) && (CurrentAmmo - CurrentAmmoInClip > 0 || HasInfiniteClip());
bool bStateOKToReload = ( ( CurrentState == EWeaponState::Idle ) || ( CurrentState == EWeaponState::Firing) );
return ( ( bCanReload == true ) && ( bGotAmmo == true ) && ( bStateOKToReload == true) );
}
//
// Weapon usage
void AShooterWeapon::GiveAmmo(int AddAmount)
{
const int32 MissingAmmo = FMath::Max(0, WeaponConfig.MaxAmmo - CurrentAmmo);
AddAmount = FMath::Min(AddAmount, MissingAmmo);
CurrentAmmo += AddAmount;
AShooterAIController* BotAI = MyPawn ? Cast<AShooterAIController>(MyPawn->GetController()) : NULL;
if (BotAI)
{
BotAI->CheckAmmo(this);
}
// start reload if clip was empty
if (GetCurrentAmmoInClip() <= 0 &&
CanReload() &&
MyPawn->GetWeapon() == this)
{
ClientStartReload();
}
}
void AShooterWeapon::UseAmmo()
{
if (!HasInfiniteAmmo())
{
CurrentAmmoInClip--;
}
if (!HasInfiniteAmmo() && !HasInfiniteClip())
{
CurrentAmmo--;
}
AShooterAIController* BotAI = MyPawn ? Cast<AShooterAIController>(MyPawn->GetController()) : NULL;
AShooterPlayerController* PlayerController = MyPawn ? Cast<AShooterPlayerController>(MyPawn->GetController()) : NULL;
if (BotAI)
{
BotAI->CheckAmmo(this);
}
else if(PlayerController)
{
AShooterPlayerState* PlayerState = Cast<AShooterPlayerState>(PlayerController->PlayerState);
switch (GetAmmoType())
{
case EAmmoType::ERocket:
PlayerState->AddRocketsFired(1);
break;
case EAmmoType::EBullet:
default:
PlayerState->AddBulletsFired(1);
break;
}
}
}
void AShooterWeapon::HandleFiring()
{
if ((CurrentAmmoInClip > 0 || HasInfiniteClip() || HasInfiniteAmmo()) && CanFire())
{
if (GetNetMode() != NM_DedicatedServer)
{
SimulateWeaponFire();
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
FireWeapon();
UseAmmo();
// update firing FX on remote clients if function was called on server
BurstCounter++;
}
}
else if (CanReload())
{
StartReload();
}
else if (MyPawn && MyPawn->IsLocallyControlled())
{
if (GetCurrentAmmo() == 0 && !bRefiring)
{
PlayWeaponSound(OutOfAmmoSound);
AShooterPlayerController* MyPC = Cast<AShooterPlayerController>(MyPawn->Controller);
AShooterHUD* MyHUD = MyPC ? Cast<AShooterHUD>(MyPC->GetHUD()) : NULL;
if (MyHUD)
{
MyHUD->NotifyOutOfAmmo();
}
}
// stop weapon fire FX, but stay in Firing state
if (BurstCounter > 0)
{
OnBurstFinished();
}
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
// local client will notify server
if (Role < ROLE_Authority)
{
ServerHandleFiring();
}
// reload after firing last round
if (CurrentAmmoInClip <= 0 && CanReload())
{
StartReload();
}
// setup refire timer
bRefiring = (CurrentState == EWeaponState::Firing && WeaponConfig.TimeBetweenShots > 0.0f);
if (bRefiring)
{
GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &AShooterWeapon::HandleFiring, WeaponConfig.TimeBetweenShots, false);
}
}
LastFireTime = GetWorld()->GetTimeSeconds();
}
bool AShooterWeapon::ServerHandleFiring_Validate()
{
return true;
}
void AShooterWeapon::ServerHandleFiring_Implementation()
{
const bool bShouldUpdateAmmo = (CurrentAmmoInClip > 0 && CanFire());
HandleFiring();
if (bShouldUpdateAmmo)
{
// update ammo
UseAmmo();
// update firing FX on remote clients
BurstCounter++;
}
}
void AShooterWeapon::ReloadWeapon()
{
int32 ClipDelta = FMath::Min(WeaponConfig.AmmoPerClip - CurrentAmmoInClip, CurrentAmmo - CurrentAmmoInClip);
if (HasInfiniteClip())
{
ClipDelta = WeaponConfig.AmmoPerClip - CurrentAmmoInClip;
}
if (ClipDelta > 0)
{
CurrentAmmoInClip += ClipDelta;
}
if (HasInfiniteClip())
{
CurrentAmmo = FMath::Max(CurrentAmmoInClip, CurrentAmmo);
}
}
void AShooterWeapon::SetWeaponState(EWeaponState::Type NewState)
{
const EWeaponState::Type PrevState = CurrentState;
if (PrevState == EWeaponState::Firing && NewState != EWeaponState::Firing)
{
OnBurstFinished();
}
CurrentState = NewState;
if (PrevState != EWeaponState::Firing && NewState == EWeaponState::Firing)
{
OnBurstStarted();
}
}
void AShooterWeapon::DetermineWeaponState()
{
EWeaponState::Type NewState = EWeaponState::Idle;
if (bIsEquipped)
{
if( bPendingReload )
{
if( CanReload() == false )
{
NewState = CurrentState;
}
else
{
NewState = EWeaponState::Reloading;
}
}
else if ( (bPendingReload == false ) && ( bWantsToFire == true ) && ( CanFire() == true ))
{
NewState = EWeaponState::Firing;
}
}
else if (bPendingEquip)
{
NewState = EWeaponState::Equipping;
}
SetWeaponState(NewState);
}
void AShooterWeapon::OnBurstStarted()
{
// start firing, can be delayed to satisfy TimeBetweenShots
const float GameTime = GetWorld()->GetTimeSeconds();
if (LastFireTime > 0 && WeaponConfig.TimeBetweenShots > 0.0f &&
LastFireTime + WeaponConfig.TimeBetweenShots > GameTime)
{
GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &AShooterWeapon::HandleFiring, LastFireTime + WeaponConfig.TimeBetweenShots - GameTime, false);
}
else
{
HandleFiring();
}
}
void AShooterWeapon::OnBurstFinished()
{
// stop firing FX on remote clients
BurstCounter = 0;
// stop firing FX locally, unless it's a dedicated server
if (GetNetMode() != NM_DedicatedServer)
{
StopSimulatingWeaponFire();
}
GetWorldTimerManager().ClearTimer(TimerHandle_HandleFiring);
bRefiring = false;
}
//
// Weapon usage helpers
UAudioComponent* AShooterWeapon::PlayWeaponSound(USoundCue* Sound)
{
UAudioComponent* AC = NULL;
if (Sound && MyPawn)
{
AC = UGameplayStatics::SpawnSoundAttached(Sound, MyPawn->GetRootComponent());
}
return AC;
}
float AShooterWeapon::PlayWeaponAnimation(const FWeaponAnim& Animation)
{
float Duration = 0.0f;
if (MyPawn)
{
UAnimMontage* UseAnim = MyPawn->IsFirstPerson() ? Animation.Pawn1P : Animation.Pawn3P;
if (UseAnim)
{
Duration = MyPawn->PlayAnimMontage(UseAnim);
}
}
return Duration;
}
void AShooterWeapon::StopWeaponAnimation(const FWeaponAnim& Animation)
{
if (MyPawn)
{
UAnimMontage* UseAnim = MyPawn->IsFirstPerson() ? Animation.Pawn1P : Animation.Pawn3P;
if (UseAnim)
{
MyPawn->StopAnimMontage(UseAnim);
}
}
}
FVector AShooterWeapon::GetCameraAim() const
{
AShooterPlayerController* const PlayerController = Instigator ? Cast<AShooterPlayerController>(Instigator->Controller) : NULL;
FVector FinalAim = FVector::ZeroVector;
if (PlayerController)
{
FVector CamLoc;
FRotator CamRot;
PlayerController->GetPlayerViewPoint(CamLoc, CamRot);
FinalAim = CamRot.Vector();
}
else if (Instigator)
{
FinalAim = Instigator->GetBaseAimRotation().Vector();
}
return FinalAim;
}
FVector AShooterWeapon::GetAdjustedAim() const
{
AShooterPlayerController* const PlayerController = Instigator ? Cast<AShooterPlayerController>(Instigator->Controller) : NULL;
FVector FinalAim = FVector::ZeroVector;
// If we have a player controller use it for the aim
if (PlayerController)
{
FVector CamLoc;
FRotator CamRot;
PlayerController->GetPlayerViewPoint(CamLoc, CamRot);
FinalAim = CamRot.Vector();
}
else if (Instigator)
{
// Now see if we have an AI controller - we will want to get the aim from there if we do
AShooterAIController* AIController = MyPawn ? Cast<AShooterAIController>(MyPawn->Controller) : NULL;
if(AIController != NULL )
{
FinalAim = AIController->GetControlRotation().Vector();
}
else
{
FinalAim = Instigator->GetBaseAimRotation().Vector();
}
}
return FinalAim;
}
FVector AShooterWeapon::GetCameraDamageStartLocation(const FVector& AimDir) const
{
AShooterPlayerController* PC = MyPawn ? Cast<AShooterPlayerController>(MyPawn->Controller) : NULL;
AShooterAIController* AIPC = MyPawn ? Cast<AShooterAIController>(MyPawn->Controller) : NULL;
FVector OutStartTrace = FVector::ZeroVector;
if (PC)
{
// use player's camera
FRotator UnusedRot;
PC->GetPlayerViewPoint(OutStartTrace, UnusedRot);
// Adjust trace so there is nothing blocking the ray between the camera and the pawn, and calculate distance from adjusted start
OutStartTrace = OutStartTrace + AimDir * ((Instigator->GetActorLocation() - OutStartTrace) | AimDir);
}
else if (AIPC)
{
OutStartTrace = GetMuzzleLocation();
}
return OutStartTrace;
}
FVector AShooterWeapon::GetMuzzleLocation() const
{
USkeletalMeshComponent* UseMesh = GetWeaponMesh();
return UseMesh->GetSocketLocation(MuzzleAttachPoint);
}
FVector AShooterWeapon::GetMuzzleDirection() const
{
USkeletalMeshComponent* UseMesh = GetWeaponMesh();
return UseMesh->GetSocketRotation(MuzzleAttachPoint).Vector();
}
FHitResult AShooterWeapon::WeaponTrace(const FVector& StartTrace, const FVector& EndTrace) const
{
static FName WeaponFireTag = FName(TEXT("WeaponTrace"));
// Perform trace to retrieve hit info
FCollisionQueryParams TraceParams(WeaponFireTag, true, Instigator);
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = true;
FHitResult Hit(ForceInit);
GetWorld()->LineTraceSingleByChannel(Hit, StartTrace, EndTrace, COLLISION_WEAPON, TraceParams);
return Hit;
}
void AShooterWeapon::SetOwningPawn(AShooterCharacter* NewOwner)
{
if (MyPawn != NewOwner)
{
Instigator = NewOwner;
MyPawn = NewOwner;
// net owner for RPC calls
SetOwner(NewOwner);
}
}
//
// Replication & effects
void AShooterWeapon::OnRep_MyPawn()
{
if (MyPawn)
{
OnEnterInventory(MyPawn);
}
else
{
OnLeaveInventory();
}
}
void AShooterWeapon::OnRep_BurstCounter()
{
if (BurstCounter > 0)
{
SimulateWeaponFire();
}
else
{
StopSimulatingWeaponFire();
}
}
void AShooterWeapon::OnRep_Reload()
{
if (bPendingReload)
{
StartReload(true);
}
else
{
StopReload();
}
}
void AShooterWeapon::SimulateWeaponFire()
{
if (Role == ROLE_Authority && CurrentState != EWeaponState::Firing)
{
return;
}
if (MuzzleFX)
{
USkeletalMeshComponent* UseWeaponMesh = GetWeaponMesh();
if (!bLoopedMuzzleFX || MuzzlePSC == NULL)
{
// Split screen requires we create 2 effects. One that we see and one that the other player sees.
if( (MyPawn != NULL ) && ( MyPawn->IsLocallyControlled() == true ) )
{
AController* PlayerCon = MyPawn->GetController();
if( PlayerCon != NULL )
{
Mesh1P->GetSocketLocation(MuzzleAttachPoint);
MuzzlePSC = UGameplayStatics::SpawnEmitterAttached(MuzzleFX, Mesh1P, MuzzleAttachPoint);
MuzzlePSC->bOwnerNoSee = false;
MuzzlePSC->bOnlyOwnerSee = true;
Mesh3P->GetSocketLocation(MuzzleAttachPoint);
MuzzlePSCSecondary = UGameplayStatics::SpawnEmitterAttached(MuzzleFX, Mesh3P, MuzzleAttachPoint);
MuzzlePSCSecondary->bOwnerNoSee = true;
MuzzlePSCSecondary->bOnlyOwnerSee = false;
}
}
else
{
MuzzlePSC = UGameplayStatics::SpawnEmitterAttached(MuzzleFX, UseWeaponMesh, MuzzleAttachPoint);
}
}
}
if (!bLoopedFireAnim || !bPlayingFireAnim)
{
PlayWeaponAnimation(FireAnim);
bPlayingFireAnim = true;
}
if (bLoopedFireSound)
{
if (FireAC == NULL)
{
FireAC = PlayWeaponSound(FireLoopSound);
}
}
else
{
PlayWeaponSound(FireSound);
}
AShooterPlayerController* PC = (MyPawn != NULL) ? Cast<AShooterPlayerController>(MyPawn->Controller) : NULL;
if (PC != NULL && PC->IsLocalController())
{
if (FireCameraShake != NULL)
{
PC->ClientPlayCameraShake(FireCameraShake, 1);
}
if (FireForceFeedback != NULL)
{
PC->ClientPlayForceFeedback(FireForceFeedback, false, "Weapon");
}
}
}
void AShooterWeapon::StopSimulatingWeaponFire()
{
if (bLoopedMuzzleFX )
{
if( MuzzlePSC != NULL )
{
MuzzlePSC->DeactivateSystem();
MuzzlePSC = NULL;
}
if( MuzzlePSCSecondary != NULL )
{
MuzzlePSCSecondary->DeactivateSystem();
MuzzlePSCSecondary = NULL;
}
}
if (bLoopedFireAnim && bPlayingFireAnim)
{
StopWeaponAnimation(FireAnim);
bPlayingFireAnim = false;
}
if (FireAC)
{
FireAC->FadeOut(0.1f, 0.0f);
FireAC = NULL;
PlayWeaponSound(FireFinishSound);
}
}
void AShooterWeapon::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
Super::GetLifetimeReplicatedProps( OutLifetimeProps );
DOREPLIFETIME( AShooterWeapon, MyPawn );
DOREPLIFETIME_CONDITION( AShooterWeapon, CurrentAmmo, COND_OwnerOnly );
DOREPLIFETIME_CONDITION( AShooterWeapon, CurrentAmmoInClip, COND_OwnerOnly );
DOREPLIFETIME_CONDITION( AShooterWeapon, BurstCounter, COND_SkipOwner );
DOREPLIFETIME_CONDITION( AShooterWeapon, bPendingReload, COND_SkipOwner );
}
USkeletalMeshComponent* AShooterWeapon::GetWeaponMesh() const
{
return (MyPawn != NULL && MyPawn->IsFirstPerson()) ? Mesh1P : Mesh3P;
}
class AShooterCharacter* AShooterWeapon::GetPawnOwner() const
{
return MyPawn;
}
bool AShooterWeapon::IsEquipped() const
{
return bIsEquipped;
}
bool AShooterWeapon::IsAttachedToPawn() const
{
return bIsEquipped || bPendingEquip;
}
EWeaponState::Type AShooterWeapon::GetCurrentState() const
{
return CurrentState;
}
int32 AShooterWeapon::GetCurrentAmmo() const
{
return CurrentAmmo;
}
int32 AShooterWeapon::GetCurrentAmmoInClip() const
{
return CurrentAmmoInClip;
}
int32 AShooterWeapon::GetAmmoPerClip() const
{
return WeaponConfig.AmmoPerClip;
}
int32 AShooterWeapon::GetMaxAmmo() const
{
return WeaponConfig.MaxAmmo;
}
bool AShooterWeapon::HasInfiniteAmmo() const
{
const AShooterPlayerController* MyPC = (MyPawn != NULL) ? Cast<const AShooterPlayerController>(MyPawn->Controller) : NULL;
return WeaponConfig.bInfiniteAmmo || (MyPC && MyPC->HasInfiniteAmmo());
}
bool AShooterWeapon::HasInfiniteClip() const
{
const AShooterPlayerController* MyPC = (MyPawn != NULL) ? Cast<const AShooterPlayerController>(MyPawn->Controller) : NULL;
return WeaponConfig.bInfiniteClip || (MyPC && MyPC->HasInfiniteClip());
}
float AShooterWeapon::GetEquipStartedTime() const
{
return EquipStartedTime;
}
float AShooterWeapon::GetEquipDuration() const
{
return EquipDuration;
}
ShooterWeapon_Instant.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "Weapons/ShooterWeapon_Instant.h"
#include "Particles/ParticleSystemComponent.h"
#include "Effects/ShooterImpactEffect.h"
AShooterWeapon_Instant::AShooterWeapon_Instant(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
CurrentFiringSpread = 0.0f;
}
//////////////////////////////////////////////////////////////////////////
// Weapon usage
void AShooterWeapon_Instant::FireWeapon()
{
const int32 RandomSeed = FMath::Rand();
FRandomStream WeaponRandomStream(RandomSeed);
const float CurrentSpread = GetCurrentSpread();
const float ConeHalfAngle = FMath::DegreesToRadians(CurrentSpread * 0.5f);
const FVector AimDir = GetAdjustedAim();
const FVector StartTrace = GetCameraDamageStartLocation(AimDir);
const FVector ShootDir = WeaponRandomStream.VRandCone(AimDir, ConeHalfAngle, ConeHalfAngle);
const FVector EndTrace = StartTrace + ShootDir * InstantConfig.WeaponRange;
const FHitResult Impact = WeaponTrace(StartTrace, EndTrace);
ProcessInstantHit(Impact, StartTrace, ShootDir, RandomSeed, CurrentSpread);
CurrentFiringSpread = FMath::Min(InstantConfig.FiringSpreadMax, CurrentFiringSpread + InstantConfig.FiringSpreadIncrement);
}
bool AShooterWeapon_Instant::ServerNotifyHit_Validate(const FHitResult& Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread)
{
return true;
}
void AShooterWeapon_Instant::ServerNotifyHit_Implementation(const FHitResult& Impact, FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread)
{
const float WeaponAngleDot = FMath::Abs(FMath::Sin(ReticleSpread * PI / 180.f));
// if we have an instigator, calculate dot between the view and the shot
if (Instigator && (Impact.GetActor() || Impact.bBlockingHit))
{
const FVector Origin = GetMuzzleLocation();
const FVector ViewDir = (Impact.Location - Origin).GetSafeNormal();
// is the angle between the hit and the view within allowed limits (limit + weapon max angle)
const float ViewDotHitDir = FVector::DotProduct(Instigator->GetViewRotation().Vector(), ViewDir);
if (ViewDotHitDir > InstantConfig.AllowedViewDotHitDir - WeaponAngleDot)
{
if (CurrentState != EWeaponState::Idle)
{
if (Impact.GetActor() == NULL)
{
if (Impact.bBlockingHit)
{
ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread);
}
}
// assume it told the truth about static things because the don't move and the hit
// usually doesn't have significant gameplay implications
else if (Impact.GetActor()->IsRootComponentStatic() || Impact.GetActor()->IsRootComponentStationary())
{
ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread);
}
else
{
// Get the component bounding box
const FBox HitBox = Impact.GetActor()->GetComponentsBoundingBox();
// calculate the box extent, and increase by a leeway
FVector BoxExtent = 0.5 * (HitBox.Max - HitBox.Min);
BoxExtent *= InstantConfig.ClientSideHitLeeway;
// avoid precision errors with really thin objects
BoxExtent.X = FMath::Max(20.0f, BoxExtent.X);
BoxExtent.Y = FMath::Max(20.0f, BoxExtent.Y);
BoxExtent.Z = FMath::Max(20.0f, BoxExtent.Z);
// Get the box center
const FVector BoxCenter = (HitBox.Min + HitBox.Max) * 0.5;
// if we are within client tolerance
if (FMath::Abs(Impact.Location.Z - BoxCenter.Z) < BoxExtent.Z &&
FMath::Abs(Impact.Location.X - BoxCenter.X) < BoxExtent.X &&
FMath::Abs(Impact.Location.Y - BoxCenter.Y) < BoxExtent.Y)
{
ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread);
}
else
{
UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s (outside bounding box tolerance)"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor()));
}
}
}
}
else if (ViewDotHitDir <= InstantConfig.AllowedViewDotHitDir)
{
UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s (facing too far from the hit direction)"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor()));
}
else
{
UE_LOG(LogShooterWeapon, Log, TEXT("%s Rejected client side hit of %s"), *GetNameSafe(this), *GetNameSafe(Impact.GetActor()));
}
}
}
bool AShooterWeapon_Instant::ServerNotifyMiss_Validate(FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread)
{
return true;
}
void AShooterWeapon_Instant::ServerNotifyMiss_Implementation(FVector_NetQuantizeNormal ShootDir, int32 RandomSeed, float ReticleSpread)
{
const FVector Origin = GetMuzzleLocation();
// play FX on remote clients
HitNotify.Origin = Origin;
HitNotify.RandomSeed = RandomSeed;
HitNotify.ReticleSpread = ReticleSpread;
// play FX locally
if (GetNetMode() != NM_DedicatedServer)
{
const FVector EndTrace = Origin + ShootDir * InstantConfig.WeaponRange;
SpawnTrailEffect(EndTrace);
}
}
void AShooterWeapon_Instant::ProcessInstantHit(const FHitResult& Impact, const FVector& Origin, const FVector& ShootDir, int32 RandomSeed, float ReticleSpread)
{
if (MyPawn && MyPawn->IsLocallyControlled() && GetNetMode() == NM_Client)
{
// if we're a client and we've hit something that is being controlled by the server
if (Impact.GetActor() && Impact.GetActor()->GetRemoteRole() == ROLE_Authority)
{
// notify the server of the hit
ServerNotifyHit(Impact, ShootDir, RandomSeed, ReticleSpread);
}
else if (Impact.GetActor() == NULL)
{
if (Impact.bBlockingHit)
{
// notify the server of the hit
ServerNotifyHit(Impact, ShootDir, RandomSeed, ReticleSpread);
}
else
{
// notify server of the miss
ServerNotifyMiss(ShootDir, RandomSeed, ReticleSpread);
}
}
}
// process a confirmed hit
ProcessInstantHit_Confirmed(Impact, Origin, ShootDir, RandomSeed, ReticleSpread);
}
void AShooterWeapon_Instant::ProcessInstantHit_Confirmed(const FHitResult& Impact, const FVector& Origin, const FVector& ShootDir, int32 RandomSeed, float ReticleSpread)
{
// handle damage
if (ShouldDealDamage(Impact.GetActor()))
{
DealDamage(Impact, ShootDir);
}
// play FX on remote clients
if (Role == ROLE_Authority)
{
HitNotify.Origin = Origin;
HitNotify.RandomSeed = RandomSeed;
HitNotify.ReticleSpread = ReticleSpread;
}
// play FX locally
if (GetNetMode() != NM_DedicatedServer)
{
const FVector EndTrace = Origin + ShootDir * InstantConfig.WeaponRange;
const FVector EndPoint = Impact.GetActor() ? Impact.ImpactPoint : EndTrace;
SpawnTrailEffect(EndPoint);
SpawnImpactEffects(Impact);
}
}
bool AShooterWeapon_Instant::ShouldDealDamage(AActor* TestActor) const
{
// if we're an actor on the server, or the actor's role is authoritative, we should register damage
if (TestActor)
{
if (GetNetMode() != NM_Client ||
TestActor->Role == ROLE_Authority ||
TestActor->bTearOff)
{
return true;
}
}
return false;
}
void AShooterWeapon_Instant::DealDamage(const FHitResult& Impact, const FVector& ShootDir)
{
FPointDamageEvent PointDmg;
PointDmg.DamageTypeClass = InstantConfig.DamageType;
PointDmg.HitInfo = Impact;
PointDmg.ShotDirection = ShootDir;
PointDmg.Damage = InstantConfig.HitDamage;
Impact.GetActor()->TakeDamage(PointDmg.Damage, PointDmg, MyPawn->Controller, this);
}
void AShooterWeapon_Instant::OnBurstFinished()
{
Super::OnBurstFinished();
CurrentFiringSpread = 0.0f;
}
//////////////////////////////////////////////////////////////////////////
// Weapon usage helpers
float AShooterWeapon_Instant::GetCurrentSpread() const
{
float FinalSpread = InstantConfig.WeaponSpread + CurrentFiringSpread;
if (MyPawn && MyPawn->IsTargeting())
{
FinalSpread *= InstantConfig.TargetingSpreadMod;
}
return FinalSpread;
}
//////////////////////////////////////////////////////////////////////////
// Replication & effects
void AShooterWeapon_Instant::OnRep_HitNotify()
{
SimulateInstantHit(HitNotify.Origin, HitNotify.RandomSeed, HitNotify.ReticleSpread);
}
void AShooterWeapon_Instant::SimulateInstantHit(const FVector& ShotOrigin, int32 RandomSeed, float ReticleSpread)
{
FRandomStream WeaponRandomStream(RandomSeed);
const float ConeHalfAngle = FMath::DegreesToRadians(ReticleSpread * 0.5f);
const FVector StartTrace = ShotOrigin;
const FVector AimDir = GetAdjustedAim();
const FVector ShootDir = WeaponRandomStream.VRandCone(AimDir, ConeHalfAngle, ConeHalfAngle);
const FVector EndTrace = StartTrace + ShootDir * InstantConfig.WeaponRange;
FHitResult Impact = WeaponTrace(StartTrace, EndTrace);
if (Impact.bBlockingHit)
{
SpawnImpactEffects(Impact);
SpawnTrailEffect(Impact.ImpactPoint);
}
else
{
SpawnTrailEffect(EndTrace);
}
}
void AShooterWeapon_Instant::SpawnImpactEffects(const FHitResult& Impact)
{
if (ImpactTemplate && Impact.bBlockingHit)
{
FHitResult UseImpact = Impact;
// trace again to find component lost during replication
if (!Impact.Component.IsValid())
{
const FVector StartTrace = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
const FVector EndTrace = Impact.ImpactPoint - Impact.ImpactNormal * 10.0f;
FHitResult Hit = WeaponTrace(StartTrace, EndTrace);
UseImpact = Hit;
}
FTransform const SpawnTransform(Impact.ImpactNormal.Rotation(), Impact.ImpactPoint);
AShooterImpactEffect* EffectActor = GetWorld()->SpawnActorDeferred<AShooterImpactEffect>(ImpactTemplate, SpawnTransform);
if (EffectActor)
{
EffectActor->SurfaceHit = UseImpact;
UGameplayStatics::FinishSpawningActor(EffectActor, SpawnTransform);
}
}
}
void AShooterWeapon_Instant::SpawnTrailEffect(const FVector& EndPoint)
{
if (TrailFX)
{
const FVector Origin = GetMuzzleLocation();
UParticleSystemComponent* TrailPSC = UGameplayStatics::SpawnEmitterAtLocation(this, TrailFX, Origin);
if (TrailPSC)
{
TrailPSC->SetVectorParameter(TrailTargetParam, EndPoint);
}
}
}
void AShooterWeapon_Instant::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
Super::GetLifetimeReplicatedProps( OutLifetimeProps );
DOREPLIFETIME_CONDITION( AShooterWeapon_Instant, HitNotify, COND_SkipOwner );
}
ShooterWeapon_Projectile.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "Weapons/ShooterWeapon_Projectile.h"
#include "Weapons/ShooterProjectile.h"
AShooterWeapon_Projectile::AShooterWeapon_Projectile(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
//////////////////////////////////////////////////////////////////////////
// Weapon usage
void AShooterWeapon_Projectile::FireWeapon()
{
FVector ShootDir = GetAdjustedAim();
FVector Origin = GetMuzzleLocation();
// trace from camera to check what's under crosshair
const float ProjectileAdjustRange = 10000.0f;
const FVector StartTrace = GetCameraDamageStartLocation(ShootDir);
const FVector EndTrace = StartTrace + ShootDir * ProjectileAdjustRange;
FHitResult Impact = WeaponTrace(StartTrace, EndTrace);
// and adjust directions to hit that actor
if (Impact.bBlockingHit)
{
const FVector AdjustedDir = (Impact.ImpactPoint - Origin).GetSafeNormal();
bool bWeaponPenetration = false;
const float DirectionDot = FVector::DotProduct(AdjustedDir, ShootDir);
if (DirectionDot < 0.0f)
{
// shooting backwards = weapon is penetrating
bWeaponPenetration = true;
}
else if (DirectionDot < 0.5f)
{
// check for weapon penetration if angle difference is big enough
// raycast along weapon mesh to check if there's blocking hit
FVector MuzzleStartTrace = Origin - GetMuzzleDirection() * 150.0f;
FVector MuzzleEndTrace = Origin;
FHitResult MuzzleImpact = WeaponTrace(MuzzleStartTrace, MuzzleEndTrace);
if (MuzzleImpact.bBlockingHit)
{
bWeaponPenetration = true;
}
}
if (bWeaponPenetration)
{
// spawn at crosshair position
Origin = Impact.ImpactPoint - ShootDir * 10.0f;
}
else
{
// adjust direction to hit
ShootDir = AdjustedDir;
}
}
ServerFireProjectile(Origin, ShootDir);
}
bool AShooterWeapon_Projectile::ServerFireProjectile_Validate(FVector Origin, FVector_NetQuantizeNormal ShootDir)
{
return true;
}
void AShooterWeapon_Projectile::ServerFireProjectile_Implementation(FVector Origin, FVector_NetQuantizeNormal ShootDir)
{
FTransform SpawnTM(ShootDir.Rotation(), Origin);
AShooterProjectile* Projectile = Cast<AShooterProjectile>(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, ProjectileConfig.ProjectileClass, SpawnTM));
if (Projectile)
{
Projectile->Instigator = Instigator;
Projectile->SetOwner(this);
Projectile->InitVelocity(ShootDir);
UGameplayStatics::FinishSpawningActor(Projectile, SpawnTM);
}
}
void AShooterWeapon_Projectile::ApplyWeaponConfig(FProjectileWeaponData& Data)
{
Data = ProjectileConfig;
}