blaster/Source/Blaster/Weapon/Weapon.cpp

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->GetEquippedWeapon() && OwnerCharacter->GetEquippedWeapon() == 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());
}