Files
BallisticsDocs/Source/EasyBallistics/Private/EBGun.cpp
T
2025-07-04 03:26:03 -07:00

693 lines
13 KiB
C++

// Copyright 2016 Mookie. All Rights Reserved.
#include "EBGun.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.h"
#include "EBBullet.h"
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
#include "TimerManager.h"
AEBGun::AEBGun()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
SetReplicateMovement(true);
// Create the gun mesh component
GunMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("GunMesh"));
RootComponent = GunMesh;
// Create the barrel component
Barrel = CreateDefaultSubobject<UEBBarrel>(TEXT("Barrel"));
Barrel->SetupAttachment(GunMesh, TEXT("MuzzleSocket"));
Barrel->SetParentGun(this);
// Create the magazine component
Magazine = CreateDefaultSubobject<UEBMagazine>(TEXT("Magazine"));
// Initialize state
CurrentGunState = EGunState::GS_Ready;
PreviousGunState = EGunState::GS_Ready;
bSafetyOn = false;
bChamberedRound = false;
bHammerCocked = false;
bTriggerPressed = false;
bCanProcessNextShot = true;
BurstShotsFired = 0;
ChamberedBulletType = nullptr;
}
void AEBGun::BeginPlay()
{
Super::BeginPlay();
// Apply weapon configuration if available
if (WeaponConfig)
{
ApplyWeaponConfiguration(WeaponConfig);
}
// Initialize magazine
if (Magazine)
{
Magazine->OnMagazineEmpty.AddDynamic(this, &AEBGun::OnMagazineEmptyHandler);
Magazine->OnBulletFed.AddDynamic(this, &AEBGun::OnBulletFedHandler);
}
// Initialize barrel
if (Barrel)
{
Barrel->ShotFired.AddDynamic(this, &AEBGun::OnBarrelShotFiredHandler);
}
}
void AEBGun::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Process firing state
if (CurrentGunState == EGunState::GS_Firing)
{
ProcessFiring();
}
}
void AEBGun::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AEBGun, CurrentGunState);
DOREPLIFETIME(AEBGun, bSafetyOn);
DOREPLIFETIME(AEBGun, bChamberedRound);
DOREPLIFETIME(AEBGun, bHammerCocked);
DOREPLIFETIME(AEBGun, ChamberedBulletType);
}
void AEBGun::PullTrigger()
{
if (HasAuthority())
{
ServerPullTrigger();
}
else
{
ServerPullTrigger();
}
}
void AEBGun::ServerPullTrigger_Implementation()
{
if (!CanFire())
{
return;
}
bTriggerPressed = true;
if (CurrentGunState == EGunState::GS_Ready)
{
SetGunState(EGunState::GS_Firing);
}
}
void AEBGun::ReleaseTrigger()
{
if (HasAuthority())
{
ServerReleaseTrigger();
}
else
{
ServerReleaseTrigger();
}
}
void AEBGun::ServerReleaseTrigger_Implementation()
{
bTriggerPressed = false;
// Handle fire mode specific behavior
if (Barrel && WeaponConfig)
{
EFireMode CurrentFireMode = Barrel->FireMode;
if (CurrentFireMode == EFireMode::FM_Semiauto || CurrentFireMode == EFireMode::FM_Manual)
{
if (CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_Ready);
}
}
}
}
void AEBGun::SetSafety(bool bNewSafetyState)
{
if (HasAuthority())
{
ServerSetSafety(bNewSafetyState);
}
else
{
ServerSetSafety(bNewSafetyState);
}
}
void AEBGun::ServerSetSafety_Implementation(bool bNewSafetyState)
{
if (!WeaponConfig || !WeaponConfig->FireControlConfig.bHasSafety)
{
return;
}
bool bOldSafetyState = bSafetyOn;
bSafetyOn = bNewSafetyState;
if (bSafetyOn && CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_SafetyOn);
}
else if (!bSafetyOn && CurrentGunState == EGunState::GS_SafetyOn)
{
SetGunState(EGunState::GS_Ready);
}
if (bOldSafetyState != bSafetyOn)
{
OnSafetyChanged.Broadcast(bSafetyOn);
MulticastOnSafetyChanged(bSafetyOn);
}
}
void AEBGun::CockHammer()
{
if (HasAuthority())
{
bHammerCocked = true;
}
}
void AEBGun::ReleaseHammer()
{
if (HasAuthority())
{
bHammerCocked = false;
}
}
void AEBGun::LoadMagazine(TArray<UEBBulletPropertiesAsset*> BulletTypes)
{
if (Magazine)
{
Magazine->LoadBullets(BulletTypes);
}
}
void AEBGun::EjectMagazine()
{
if (HasAuthority())
{
ServerEjectMagazine();
}
else
{
ServerEjectMagazine();
}
}
void AEBGun::ServerEjectMagazine_Implementation()
{
if (Magazine)
{
Magazine->UnloadAllBullets();
}
OnMagazineChanged.Broadcast(nullptr);
MulticastOnMagazineChanged(nullptr);
}
void AEBGun::InsertMagazine(UEBMagazine* NewMagazine)
{
if (HasAuthority())
{
ServerInsertMagazine(NewMagazine);
}
else
{
ServerInsertMagazine(NewMagazine);
}
}
void AEBGun::ServerInsertMagazine_Implementation(UEBMagazine* NewMagazine)
{
if (!NewMagazine)
{
return;
}
// Replace magazine component (in a real implementation, this would be more complex)
Magazine = NewMagazine;
OnMagazineChanged.Broadcast(NewMagazine);
MulticastOnMagazineChanged(NewMagazine);
}
void AEBGun::ChargingHandle()
{
if (HasAuthority())
{
ServerChargingHandle();
}
else
{
ServerChargingHandle();
}
}
void AEBGun::ServerChargingHandle_Implementation()
{
// Eject chambered round if present
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
// Feed new round from magazine
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NewBullet = Magazine->FeedNextBullet();
if (NewBullet)
{
ChamberedBulletType = NewBullet;
bChamberedRound = true;
}
}
// Update gun state
if (CurrentGunState == EGunState::GS_Empty && bChamberedRound)
{
SetGunState(EGunState::GS_Ready);
}
else if (!bChamberedRound && (!Magazine || Magazine->bIsEmpty))
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::EjectChamberedRound()
{
if (HasAuthority())
{
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
}
}
bool AEBGun::CanFire() const
{
if (bSafetyOn)
{
return false;
}
if (CurrentGunState == EGunState::GS_Jammed ||
CurrentGunState == EGunState::GS_Empty ||
CurrentGunState == EGunState::GS_SafetyOn ||
CurrentGunState == EGunState::GS_Malfunction)
{
return false;
}
if (!bChamberedRound || !ChamberedBulletType)
{
return false;
}
return true;
}
bool AEBGun::IsLoaded() const
{
return bChamberedRound && ChamberedBulletType != nullptr;
}
int32 AEBGun::GetRoundsInMagazine() const
{
if (Magazine)
{
return Magazine->GetCurrentRoundCount();
}
return 0;
}
int32 AEBGun::GetTotalRounds() const
{
int32 TotalRounds = 0;
if (Magazine)
{
TotalRounds += Magazine->GetCurrentRoundCount();
}
if (bChamberedRound)
{
TotalRounds += 1;
}
return TotalRounds;
}
UEBBulletPropertiesAsset* AEBGun::GetChamberedBulletType() const
{
return ChamberedBulletType;
}
void AEBGun::ApplyWeaponConfiguration(UEBWeaponConfiguration* NewConfig)
{
if (!NewConfig)
{
return;
}
WeaponConfig = NewConfig;
// Apply configuration to barrel
if (Barrel)
{
// Set parent gun reference
Barrel->SetParentGun(this);
// Apply barrel-specific configuration
Barrel->ApplyBarrelConfiguration(NewConfig->BarrelConfig);
// Apply fire control configuration
Barrel->FireMode = NewConfig->FireControlConfig.DefaultFireMode;
Barrel->FireRateMin = NewConfig->FireControlConfig.FireRateMin / 60.0f; // Convert RPM to RPS
Barrel->FireRateMax = NewConfig->FireControlConfig.FireRateMax / 60.0f;
Barrel->BurstCount = NewConfig->FireControlConfig.BurstCount;
Barrel->BurstCooldown = NewConfig->FireControlConfig.BurstCooldown;
Barrel->GatlingSpoolUpTime = NewConfig->FireControlConfig.GatlingSpoolUpTime;
Barrel->GatlingSpoolDownTime = NewConfig->FireControlConfig.GatlingSpoolDownTime;
Barrel->GatlingAutoSpool = NewConfig->FireControlConfig.bGatlingAutoSpool;
// Apply networking configuration
Barrel->ReplicateVariables = NewConfig->bReplicateVariables;
Barrel->ReplicateShotFiredEvents = NewConfig->bReplicateShotEvents;
Barrel->ClientSideAim = NewConfig->bClientSideAim;
// Disable legacy ammo system in favor of gun's magazine system
Barrel->CycleAmmo = false;
UE_LOG(LogTemp, Log, TEXT("Gun '%s' applied configuration to barrel"), *NewConfig->GetWeaponDisplayName());
}
// Apply configuration to magazine
if (Magazine)
{
Magazine->MagazineConfig.MaxCapacity = NewConfig->DefaultMagazineCapacity;
Magazine->MagazineConfig.MagazineType = NewConfig->DefaultMagazineType;
Magazine->MagazineConfig.bAllowMixedBulletTypes = NewConfig->bAllowMixedAmmo;
Magazine->MagazineConfig.MalfunctionChance = NewConfig->ReliabilityConfig.MalfunctionRate;
}
// Set default safety state
if (NewConfig->FireControlConfig.bHasSafety)
{
bSafetyOn = NewConfig->FireControlConfig.bDefaultSafetyOn;
}
else
{
bSafetyOn = false;
}
}
void AEBGun::SetFireMode(EFireMode NewFireMode)
{
if (HasAuthority())
{
ServerSetFireMode(NewFireMode);
}
else
{
ServerSetFireMode(NewFireMode);
}
}
void AEBGun::ServerSetFireMode_Implementation(EFireMode NewFireMode)
{
if (!WeaponConfig || !WeaponConfig->IsFireModeAvailable(NewFireMode))
{
return;
}
if (Barrel)
{
Barrel->FireMode = NewFireMode;
}
}
EFireMode AEBGun::GetFireMode() const
{
if (Barrel)
{
return Barrel->FireMode;
}
return EFireMode::FM_Semiauto;
}
void AEBGun::SetGunState(EGunState NewState)
{
if (!HasAuthority())
{
return;
}
if (CurrentGunState == NewState)
{
return;
}
PreviousGunState = CurrentGunState;
CurrentGunState = NewState;
OnGunStateChanged.Broadcast(NewState);
OnGunStateChangedInternal(PreviousGunState, NewState);
}
void AEBGun::OnRep_GunState()
{
OnGunStateChanged.Broadcast(CurrentGunState);
OnGunStateChangedInternal(PreviousGunState, CurrentGunState);
}
void AEBGun::ProcessFiring()
{
if (!HasAuthority())
{
return;
}
if (!CanFire() || !bCanProcessNextShot)
{
return;
}
// Check if we should continue firing based on fire mode
if (Barrel && WeaponConfig)
{
EFireMode CurrentFireMode = Barrel->FireMode;
switch (CurrentFireMode)
{
case EFireMode::FM_Semiauto:
case EFireMode::FM_Manual:
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Auto:
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Burst:
case EFireMode::FM_InterBurst:
if (BurstShotsFired >= WeaponConfig->FireControlConfig.BurstCount)
{
BurstShotsFired = 0;
SetGunState(EGunState::GS_Ready);
return;
}
break;
}
}
// Fire the weapon
FireShot();
}
void AEBGun::FireShot()
{
if (!HasAuthority() || !CanFire())
{
return;
}
// Fire through barrel
if (Barrel && ChamberedBulletType)
{
// Set bullet class on barrel (this is a simplified approach)
// In a real implementation, you would need to create a bullet and configure it properly
Barrel->Ammo.Empty();
if (ChamberedBulletType)
{
// For now, we'll use the default bullet class since we can't get the class from the asset
// This would need to be properly implemented with a bullet class reference in the asset
}
// Fire the shot
Barrel->Shoot(true);
// Clear chambered round
ChamberedBulletType = nullptr;
bChamberedRound = false;
// Update burst count
BurstShotsFired++;
// Set fire rate cooldown
float FireRate = WeaponConfig ? WeaponConfig->GetEffectiveFireRate(GetFireMode()) : 600.0f;
float CooldownTime = 60.0f / FireRate; // Convert RPM to seconds
bCanProcessNextShot = false;
GetWorld()->GetTimerManager().SetTimer(FireRateTimer, [this]()
{
bCanProcessNextShot = true;
}, CooldownTime, false);
// Try to feed next round
if (!FeedNextRound())
{
SetGunState(EGunState::GS_Empty);
}
}
}
bool AEBGun::FeedNextRound()
{
if (!HasAuthority())
{
return false;
}
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NextBullet = Magazine->FeedNextBullet();
if (NextBullet)
{
ChamberedBulletType = NextBullet;
bChamberedRound = true;
return true;
}
}
return false;
}
void AEBGun::OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine)
{
OnMagazineEmpty.Broadcast();
if (!bChamberedRound)
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType)
{
// Handle bullet fed from magazine
}
void AEBGun::OnBarrelShotFiredHandler()
{
OnGunFired.Broadcast(ChamberedBulletType);
MulticastOnGunFired(ChamberedBulletType);
}
void AEBGun::MulticastOnGunFired_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!HasAuthority())
{
OnGunFired.Broadcast(BulletType);
}
}
void AEBGun::MulticastOnMagazineChanged_Implementation(UEBMagazine* NewMagazine)
{
if (!HasAuthority())
{
OnMagazineChanged.Broadcast(NewMagazine);
}
}
void AEBGun::MulticastOnSafetyChanged_Implementation(bool bNewSafetyState)
{
if (!HasAuthority())
{
OnSafetyChanged.Broadcast(bNewSafetyState);
}
}
// Barrel Integration Functions
float AEBGun::GetBarrelLength() const
{
if (Barrel)
{
return Barrel->BarrelLength;
}
return 0.0f;
}
float AEBGun::GetEffectiveMuzzleVelocity() const
{
if (Barrel && ChamberedBulletType)
{
return Barrel->CalculateEffectiveMuzzleVelocity(ChamberedBulletType);
}
return 0.0f;
}
float AEBGun::GetBarrelAccuracy() const
{
if (Barrel)
{
return Barrel->CalculateBarrelAccuracy();
}
return 0.0f;
}
FVector AEBGun::CalculateRecoilImpulse() const
{
if (Barrel && ChamberedBulletType)
{
return Barrel->CalculateRecoilImpulse(ChamberedBulletType);
}
return FVector::ZeroVector;
}
bool AEBGun::IsBarrelConnected() const
{
return Barrel != nullptr && Barrel->IsConnectedToGun();
}