ShooterGame-weapon

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值