371 lines
10 KiB
C++
371 lines
10 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "Weapon.h"
|
|
|
|
#include "Casing.h"
|
|
#include "Blaster/Character/BlasterCharacter.h"
|
|
#include "Blaster/Components/CombatComponent.h"
|
|
#include "Blaster/PlayerController/BlasterPlayerController.h"
|
|
#include "Components/SphereComponent.h"
|
|
#include "Components/WidgetComponent.h"
|
|
#include "Engine/SkeletalMeshSocket.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
|
|
AWeapon::AWeapon()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = false;
|
|
bReplicates = true;
|
|
SetReplicateMovement(true);
|
|
|
|
WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMesh"));
|
|
|
|
SetRootComponent(WeaponMesh);
|
|
|
|
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
|
|
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
|
|
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
|
|
WeaponMesh->MarkRenderStateDirty();
|
|
EnableCustomDepth(true);
|
|
|
|
AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
|
|
AreaSphere->SetupAttachment(RootComponent);
|
|
AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
|
|
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
|
|
PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
|
|
PickupWidget->SetupAttachment(RootComponent);
|
|
}
|
|
|
|
void AWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(AWeapon, WeaponState);
|
|
DOREPLIFETIME_CONDITION(AWeapon, bUseServerSideRewind, COND_OwnerOnly);
|
|
}
|
|
|
|
void AWeapon::EnableCustomDepth(bool bEnabled)
|
|
{
|
|
if (WeaponMesh)
|
|
{
|
|
WeaponMesh->SetRenderCustomDepth(bEnabled);
|
|
}
|
|
}
|
|
|
|
void AWeapon::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
|
AreaSphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
|
AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap);
|
|
AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap);
|
|
|
|
if (PickupWidget)
|
|
{
|
|
PickupWidget->SetVisibility(false);
|
|
}
|
|
}
|
|
|
|
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent,
|
|
AActor* OtherActor,
|
|
UPrimitiveComponent* OtherComp,
|
|
int32 OtherBodyIndex,
|
|
bool bFromSweep,
|
|
const FHitResult& SweepResult)
|
|
{
|
|
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
|
|
if (BlasterCharacter)
|
|
{
|
|
BlasterCharacter->SetOverlappingWeapon(this);
|
|
}
|
|
}
|
|
|
|
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
|
|
{
|
|
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor);
|
|
if (BlasterCharacter)
|
|
{
|
|
BlasterCharacter->SetOverlappingWeapon(nullptr);
|
|
}
|
|
}
|
|
|
|
void AWeapon::SetHUDAmmo()
|
|
{
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : OwnerCharacter;
|
|
if (OwnerCharacter)
|
|
{
|
|
OwnerController = OwnerController == nullptr ? Cast<ABlasterPlayerController>(OwnerCharacter->Controller) : OwnerController;
|
|
if (OwnerController)
|
|
{
|
|
OwnerController->SetHUDWeaponAmmo(Ammo);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AWeapon::SpendRound()
|
|
{
|
|
Ammo = FMath::Clamp(Ammo - 1, 0, MagCapacity);
|
|
SetHUDAmmo();
|
|
if (HasAuthority())
|
|
{
|
|
ClientUpdateAmmo(Ammo);
|
|
}
|
|
else
|
|
{
|
|
++Sequence;
|
|
}
|
|
}
|
|
|
|
void AWeapon::ClientUpdateAmmo_Implementation(int32 ServerAmmo)
|
|
{
|
|
if (HasAuthority()) return;
|
|
Ammo = ServerAmmo;
|
|
--Sequence;
|
|
Ammo -= Sequence;
|
|
SetHUDAmmo();
|
|
}
|
|
|
|
void AWeapon::AddAmmo(int32 AmmoToAdd)
|
|
{
|
|
Ammo = FMath::Clamp(Ammo + AmmoToAdd, 0, MagCapacity);
|
|
SetHUDAmmo();
|
|
ClientAddAmmo(AmmoToAdd);
|
|
}
|
|
|
|
void AWeapon::ClientAddAmmo_Implementation(int32 AmmoToAdd)
|
|
{
|
|
if (HasAuthority()) return;
|
|
Ammo = FMath::Clamp(Ammo + AmmoToAdd, 0, MagCapacity);
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : OwnerCharacter;
|
|
if (OwnerCharacter && OwnerCharacter->GetCombat() && IsFull())
|
|
{
|
|
OwnerCharacter->GetCombat()->JumpToShotgunEnd();
|
|
}
|
|
SetHUDAmmo();
|
|
}
|
|
|
|
void AWeapon::OnRep_Owner()
|
|
{
|
|
Super::OnRep_Owner();
|
|
if (Owner == nullptr)
|
|
{
|
|
OwnerCharacter = nullptr;
|
|
OwnerController = nullptr;
|
|
}
|
|
else
|
|
{
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(Owner) : OwnerCharacter;
|
|
if (OwnerCharacter && OwnerCharacter->GetPrimaryWeapon() && OwnerCharacter->GetPrimaryWeapon() == this)
|
|
{
|
|
SetHUDAmmo();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AWeapon::SetWeaponState(EWeaponState State)
|
|
{
|
|
WeaponState = State;
|
|
OnWeaponStateSet();
|
|
}
|
|
|
|
void AWeapon::OnWeaponStateSet()
|
|
{
|
|
switch (WeaponState)
|
|
{
|
|
case EWeaponState::EWS_Equipped:
|
|
OnEquipped();
|
|
break;
|
|
case EWeaponState::EWS_EquippedSecondary:
|
|
OnEquippedSecondary();
|
|
break;
|
|
case EWeaponState::EWS_Dropped:
|
|
OnDropped();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AWeapon::OnPingTooHigh(bool bPingTooHigh)
|
|
{
|
|
bUseServerSideRewind = !bPingTooHigh;
|
|
}
|
|
|
|
void AWeapon::OnRep_WeaponState()
|
|
{
|
|
OnWeaponStateSet();
|
|
}
|
|
|
|
void AWeapon::OnEquipped()
|
|
{
|
|
ShowPickupWidget(false);
|
|
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
WeaponMesh->SetSimulatePhysics(false);
|
|
WeaponMesh->SetEnableGravity(false);
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
if (IsSMG())
|
|
{
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
|
WeaponMesh->SetEnableGravity(true);
|
|
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
|
|
}
|
|
EnableCustomDepth(false);
|
|
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : OwnerCharacter;
|
|
if (OwnerCharacter && bUseServerSideRewind)
|
|
{
|
|
OwnerController = OwnerController == nullptr ? Cast<ABlasterPlayerController>(OwnerCharacter->Controller) : OwnerController;
|
|
if (OwnerController && HasAuthority() && !OwnerController->HighPingDelegate.IsBound())
|
|
{
|
|
OwnerController->HighPingDelegate.AddDynamic(this, &AWeapon::OnPingTooHigh);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AWeapon::OnDropped()
|
|
{
|
|
if (HasAuthority())
|
|
{
|
|
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
|
}
|
|
WeaponMesh->SetSimulatePhysics(true);
|
|
WeaponMesh->SetEnableGravity(true);
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
|
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
|
|
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
|
|
WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
|
|
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_BLUE);
|
|
WeaponMesh->MarkRenderStateDirty();
|
|
EnableCustomDepth(true);
|
|
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : OwnerCharacter;
|
|
if (OwnerCharacter && bUseServerSideRewind)
|
|
{
|
|
OwnerController = OwnerController == nullptr ? Cast<ABlasterPlayerController>(OwnerCharacter->Controller) : OwnerController;
|
|
if (OwnerController && HasAuthority() && OwnerController->HighPingDelegate.IsBound())
|
|
{
|
|
OwnerController->HighPingDelegate.RemoveDynamic(this, &AWeapon::OnPingTooHigh);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AWeapon::OnEquippedSecondary()
|
|
{
|
|
ShowPickupWidget(false);
|
|
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
WeaponMesh->SetSimulatePhysics(false);
|
|
WeaponMesh->SetEnableGravity(false);
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
if (IsSMG())
|
|
{
|
|
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
|
WeaponMesh->SetEnableGravity(true);
|
|
WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
|
|
}
|
|
if (WeaponMesh)
|
|
{
|
|
WeaponMesh->SetCustomDepthStencilValue(CUSTOM_DEPTH_TAN);
|
|
WeaponMesh->MarkRenderStateDirty();
|
|
}
|
|
OwnerCharacter = OwnerCharacter == nullptr ? Cast<ABlasterCharacter>(GetOwner()) : OwnerCharacter;
|
|
if (OwnerCharacter && bUseServerSideRewind)
|
|
{
|
|
OwnerController = OwnerController == nullptr ? Cast<ABlasterPlayerController>(OwnerCharacter->Controller) : OwnerController;
|
|
if (OwnerController && HasAuthority() && OwnerController->HighPingDelegate.IsBound())
|
|
{
|
|
OwnerController->HighPingDelegate.RemoveDynamic(this, &AWeapon::OnPingTooHigh);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AWeapon::IsEmpty()
|
|
{
|
|
return Ammo <= 0;
|
|
}
|
|
|
|
bool AWeapon::IsFull()
|
|
{
|
|
return Ammo == MagCapacity;
|
|
}
|
|
|
|
void AWeapon::ShowPickupWidget(bool bShowWidget)
|
|
{
|
|
if (PickupWidget)
|
|
{
|
|
PickupWidget->SetVisibility(bShowWidget);
|
|
}
|
|
}
|
|
|
|
void AWeapon::Fire(const FVector& HitTarget)
|
|
{
|
|
if (FireAnimation)
|
|
{
|
|
WeaponMesh->PlayAnimation(FireAnimation, false);
|
|
}
|
|
if (CasingClass)
|
|
{
|
|
const USkeletalMeshSocket* AmmoEjectSocket = WeaponMesh->GetSocketByName(FName("AmmoEject"));
|
|
if (AmmoEjectSocket)
|
|
{
|
|
FTransform SocketTransform = AmmoEjectSocket->GetSocketTransform(WeaponMesh);
|
|
|
|
UWorld* World = GetWorld();
|
|
if (World)
|
|
{
|
|
World->SpawnActor<ACasing>(
|
|
CasingClass,
|
|
SocketTransform.GetLocation(),
|
|
SocketTransform.GetRotation().Rotator()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
SpendRound();
|
|
}
|
|
|
|
void AWeapon::Dropped()
|
|
{
|
|
if (bDestroyWeapon)
|
|
{
|
|
SetLifeSpan(30.f);
|
|
}
|
|
SetWeaponState(EWeaponState::EWS_Dropped);
|
|
const FDetachmentTransformRules DetachRules(EDetachmentRule::KeepWorld, true);
|
|
WeaponMesh->DetachFromComponent(DetachRules);
|
|
SetOwner(nullptr);
|
|
OwnerCharacter = nullptr;
|
|
OwnerController = nullptr;
|
|
}
|
|
|
|
FVector AWeapon::TraceEndWithScatter(const FVector& HitTarget)
|
|
{
|
|
const USkeletalMeshSocket* MuzzleFlashSocket = GetWeaponMesh()->GetSocketByName("MuzzleFlash");
|
|
if (MuzzleFlashSocket == nullptr) return FVector();
|
|
|
|
const FTransform SocketTransform = MuzzleFlashSocket->GetSocketTransform(GetWeaponMesh());
|
|
const FVector TraceStart = SocketTransform.GetLocation();
|
|
|
|
const FVector ToTargetNormalized = (HitTarget - TraceStart).GetSafeNormal();
|
|
const FVector SphereCenter = TraceStart + ToTargetNormalized * DistanceToSphere;
|
|
const FVector RandVec = UKismetMathLibrary::RandomUnitVector() * FMath::FRandRange(0.f, SphereRadius);
|
|
const FVector EndLoc = SphereCenter + RandVec;
|
|
const FVector ToEndLoc = EndLoc - TraceStart;
|
|
|
|
/*
|
|
DrawDebugSphere(GetWorld(), SphereCenter, SphereRadius, 12, FColor::Red, true);
|
|
DrawDebugSphere(GetWorld(), ToEndLoc, 4.f, 12, FColor::Blue, true);
|
|
DrawDebugLine(
|
|
GetWorld(),
|
|
TraceStart,
|
|
FVector(TraceStart + ToEndLoc * TRACE_LENGTH / ToEndLoc.Size()),
|
|
FColor::Cyan,
|
|
true
|
|
);
|
|
*/
|
|
return FVector(TraceStart + ToEndLoc * TRACE_LENGTH / ToEndLoc.Size());
|
|
}
|