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

480 lines
13 KiB
C++

// Copyright 2018 Mookie. All Rights Reserved.
#include "EBBarrel.h"
#include "EBBullet.h"
#include "EngineUtils.h"
#include "EBGun.h"
#include "EBMagazine.h"
#include "EBWeaponConfiguration.h"
#include "EBBulletProperties.h"
UEBBarrel::UEBBarrel() {
PrimaryComponentTick.bCanEverTick = true;
bHiddenInGame = true;
bAutoActivate = true;
SetIsReplicatedByDefault(ReplicateVariables);
RandomStream.GenerateNewSeed();
GatlingRPS = FireRateMin;
}
void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (ClientSideAim){
if (GetOwner()->GetRemoteRole()==ROLE_Authority){
TimeSinceAimUpdate += DeltaTime;
if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) {
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
Location = GetComponentTransform().GetLocation();
ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim);
TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency);
};
}else{
if (!RemoteAimReceived) {
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
Location = GetComponentTransform().GetLocation();
}
else {
FVector LocOffset = (Location - GetComponentLocation());
if (LocOffset.Size() > ClientAimDistanceLimit) {
//lag or cheater???
Location = GetComponentLocation() + LocOffset.GetSafeNormal()*ClientAimDistanceLimit;
}
}
}
}
else {
Aim = GetComponentTransform().GetUnitAxis(EAxis::X);
Location = GetComponentTransform().GetLocation();
}
//Only server can tick
if (GetOwner()->GetLocalRole() == ROLE_Authority){
float RemainingDelta;
if (FireMode == EFireMode::FM_Gatling) {
if (Spooling || (GatlingAutoSpool && Shooting)) {
GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMax, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
}
else {
GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMin, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f));
}
GatlingPhase += GatlingRPS*DeltaTime;
for (int i = 1; i <= GatlingPhase; i++) {
if (Cooldown <= 0.0f && LoadNext) {
NextBullet();
}
if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
SpawnBullet(GetOwner(), Location, Aim);
}
}
GatlingPhase = FMath::Fmod(GatlingPhase, 1.0f);
}
else {
RemainingDelta = DeltaTime;
do {
float step = FMath::Min(Cooldown, RemainingDelta);
Cooldown -= step;
RemainingDelta -= step;
if (Cooldown <= 0.0f && LoadNext) {
NextBullet();
}
//shoot when ready
if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) {
if (BurstRemaining > 0 || (FireMode != EFireMode::FM_Burst && FireMode != EFireMode::FM_InterBurst)) {
SpawnBullet(GetOwner(), Location, Aim);
}
else {
Shooting = false;
}
}
} while (RemainingDelta > 0 && Cooldown > 0);
}
}
}
void UEBBarrel::NextBullet() {
if (ChamberedBullet == nullptr) {
if (Ammo.Num() > 0 && (CycleAmmoCount > 0 || CycleAmmoUnlimited || (!CycleAmmo))) {
//cycle ammo
if (CycleAmmo) {
if (CycleAmmoPos >= Ammo.Num()) { CycleAmmoPos = 0; }
ChamberedBullet = Ammo[CycleAmmoPos];
CycleAmmoPos++;
if (!CycleAmmoUnlimited) {
CycleAmmoCount--;
}
}
else {
ChamberedBullet = Ammo[0];
Ammo.RemoveAt(0, 1, EAllowShrinking::Yes);
}
ReadyToShoot.Broadcast();
}
else {
AmmoDepleted.Broadcast();
}
}
}
void UEBBarrel::SpawnBullet(AActor* Owner, FVector InLocation, FVector InAim) {
TSubclassOf<class AEBBullet> BulletClass = ChamberedBullet;
if (BulletClass != nullptr) {
FVector OutLocation;
FVector OutAim;
InitialBulletTransform(InLocation, InAim, OutLocation, OutAim);
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
float BulletSpread = Default->Spread;
if (Default->SpreadBias > 0.0f) {
float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias);
BulletSpread *= SpreadMult;
}
float BarrelSpread = Spread;
if (SpreadBias > 0.0f) {
float SpreadMult = FMath::Pow(FMath::FRand(), SpreadBias);
BarrelSpread *= SpreadMult;
}
float TotalSpread = BulletSpread+BarrelSpread;
OutAim = RandomStream.VRandCone(OutAim,TotalSpread);
float BulletVelocity = FMath::Lerp(MuzzleVelocityMultiplierMin* Default->MuzzleVelocityMin, MuzzleVelocityMultiplierMax*Default->MuzzleVelocityMax, RandomStream.FRand());
FVector Velocity = OutAim*BulletVelocity;
//get parent physics body
UPrimitiveComponent* parent = Cast<UPrimitiveComponent>(GetAttachParent());
Velocity += AdditionalVelocity;
if (parent != nullptr) {
if (parent->IsSimulatingPhysics()) {
Velocity += parent->GetPhysicsLinearVelocityAtPoint(OutLocation)*InheritVelocity;
}
if (Default->Shotgun) {
ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier*Default->ShotCount);
}
else{
ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier);
}
}
BeforeShotFired.Broadcast();
AEBBullet::SpawnWithExactVelocityFromBarrel(BulletClass, Owner, Owner->GetInstigator(), OutLocation, Velocity, this);
//spend ammo
ChamberedBullet = nullptr;
if (FireMode != EFireMode::FM_Gatling) {
Cooldown = 1.0f / FMath::Lerp(FireRateMin, FireRateMax, RandomStream.FRand());
}
//fire modes
switch (FireMode) {
case EFireMode::FM_Auto:
LoadNext = true;
break;
case EFireMode::FM_Burst:
LoadNext = true;
break;
case EFireMode::FM_InterBurst:
LoadNext = true;
break;
case EFireMode::FM_Semiauto:
Shooting = false;
LoadNext = true;
break;
case EFireMode::FM_Manual:
Shooting = false;
LoadNext = false;
break;
case EFireMode::FM_Slamfire:
LoadNext = false;
break;
case EFireMode::FM_Gatling:
LoadNext = true;
break;
};
if (BurstRemaining > 0) {
BurstRemaining--;
}
else {
if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) {
Cooldown = FMath::Max(Cooldown, BurstCooldown);
}
}
if (ReplicateShotFiredEvents) {
ShotFiredMulticast();
}
else {
ShotFired.Broadcast();
}
}
}
void UEBBarrel::InitialBulletTransform_Implementation(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection) {
OutLocation = InLocation;
OutDirection = InDirection;
}
void UEBBarrel::ApplyRecoil_Implementation(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse){
if (Component->IsSimulatingPhysics()) {
Component->AddImpulseAtLocation(Impulse, InLocation);
}
}
// Enhanced Debug Control Functions
void UEBBarrel::SetBulletDebugCategory(const FString& CategoryName, bool bEnabled)
{
// Find all bullets fired from this barrel and update their debug settings
UWorld* World = GetWorld();
if (!World)
{
return;
}
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
{
AEBBullet* Bullet = *ActorItr;
if (Bullet && Bullet->GetFiringBarrel() == this)
{
Bullet->ToggleDebugCategory(CategoryName, bEnabled);
}
}
}
void UEBBarrel::SetAllBulletDebugCategories(bool bEnabled)
{
// Update all debug categories for bullets from this barrel
SetBulletDebugCategory(TEXT("Trajectory"), bEnabled);
SetBulletDebugCategory(TEXT("Impact"), bEnabled);
SetBulletDebugCategory(TEXT("Physics"), bEnabled);
SetBulletDebugCategory(TEXT("Performance"), bEnabled);
SetBulletDebugCategory(TEXT("Ballistics"), bEnabled);
SetBulletDebugCategory(TEXT("Spalling"), bEnabled);
SetBulletDebugCategory(TEXT("Pooling"), bEnabled);
}
void UEBBarrel::ClearAllBulletDebugDisplay()
{
// Clear debug display for all bullets from this barrel
UWorld* World = GetWorld();
if (!World)
{
return;
}
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
{
AEBBullet* Bullet = *ActorItr;
if (Bullet && Bullet->GetFiringBarrel() == this)
{
Bullet->ClearDebugDisplay();
}
}
}
FString UEBBarrel::GetBulletDebugInfo() const
{
// Collect debug information from all bullets fired from this barrel
UWorld* World = GetWorld();
if (!World)
{
return TEXT("No world available");
}
int32 BulletCount = 0;
int32 DebugEnabledCount = 0;
FString DebugInfo = TEXT("Barrel Debug Info:\n");
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
{
AEBBullet* Bullet = *ActorItr;
if (Bullet && Bullet->GetFiringBarrel() == this)
{
BulletCount++;
if (Bullet->DebugEnabled)
{
DebugEnabledCount++;
}
}
}
DebugInfo += FString::Printf(TEXT("Total Bullets: %d\n"), BulletCount);
DebugInfo += FString::Printf(TEXT("Debug Enabled: %d\n"), DebugEnabledCount);
DebugInfo += FString::Printf(TEXT("Impact Debug: %s\n"), DebugImpactInfo ? TEXT("ON") : TEXT("OFF"));
return DebugInfo;
}
void UEBBarrel::SetParentGun(AEBGun* Gun)
{
ParentGun = Gun;
}
void UEBBarrel::ApplyBarrelConfiguration(const FBarrelConfiguration& BarrelConfig)
{
// Apply barrel physical properties
BarrelLength = BarrelConfig.BarrelLength;
RiflingTwist = BarrelConfig.RiflingTwist;
BoreRadius = BarrelConfig.BoreRadius;
MuzzleVelocityMin = BarrelConfig.MuzzleVelocityMin;
MuzzleVelocityMax = BarrelConfig.MuzzleVelocityMax;
// Apply ballistic properties
Spread = BarrelConfig.SpreadMax;
SpreadBias = BarrelConfig.SpreadBias;
MuzzleVelocityMultiplierMin = 1.0f - BarrelConfig.MuzzleVelocityVariation;
MuzzleVelocityMultiplierMax = 1.0f + BarrelConfig.MuzzleVelocityVariation;
// Apply recoil properties
RecoilMultiplier = BarrelConfig.RecoilMultiplier;
UE_LOG(LogTemp, Log, TEXT("Barrel Configuration Applied - Length: %.1f, Twist: 1:%.1f, Velocity: %.0f-%.0f cm/s"),
BarrelLength, RiflingTwist, MuzzleVelocityMin, MuzzleVelocityMax);
}
UEBMagazine* UEBBarrel::GetConnectedMagazine() const
{
if (ParentGun)
{
return ParentGun->Magazine;
}
return nullptr;
}
UEBBulletPropertiesAsset* UEBBarrel::GetChamberedBulletType() const
{
if (ParentGun)
{
return ParentGun->GetChamberedBulletType();
}
return nullptr;
}
float UEBBarrel::CalculateEffectiveMuzzleVelocity(UEBBulletPropertiesAsset* BulletType) const
{
if (!BulletType)
{
return MuzzleVelocityMin;
}
// Base velocity from barrel configuration
float BaseVelocity = FMath::RandRange(MuzzleVelocityMin, MuzzleVelocityMax);
// Apply velocity multiplier variation
float VelocityMultiplier = FMath::RandRange(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax);
BaseVelocity *= VelocityMultiplier;
// Barrel length affects velocity (longer barrels = higher velocity, up to a point)
float LengthFactor = FMath::Clamp((BarrelLength - 10.0f) / 20.0f, 0.8f, 1.2f);
BaseVelocity *= LengthFactor;
// Bullet weight affects velocity (heavier bullets = lower velocity)
float BulletMass = BulletType->BulletProperties.GetMassKg();
float MassFactor = FMath::Clamp(0.004f / BulletMass, 0.7f, 1.3f); // Normalized around 4g bullet
BaseVelocity *= MassFactor;
return BaseVelocity;
}
float UEBBarrel::CalculateBarrelAccuracy() const
{
// Base accuracy from spread
float BaseAccuracy = Spread;
// Barrel length improves accuracy
float LengthFactor = FMath::Clamp(BarrelLength / 20.0f, 0.5f, 1.5f);
BaseAccuracy /= LengthFactor;
// Rifling twist affects accuracy (optimal twist varies by bullet)
// For now, assume 1:7 to 1:9 is optimal for 5.56mm
float TwistOptimal = 8.0f;
float TwistFactor = 1.0f - FMath::Abs(RiflingTwist - TwistOptimal) * 0.01f;
TwistFactor = FMath::Clamp(TwistFactor, 0.8f, 1.2f);
BaseAccuracy /= TwistFactor;
return BaseAccuracy;
}
FVector UEBBarrel::CalculateRecoilImpulse(UEBBulletPropertiesAsset* BulletType) const
{
if (!BulletType)
{
return FVector::ZeroVector;
}
// Calculate recoil based on bullet momentum
float BulletMass = BulletType->BulletProperties.GetMassKg();
float MuzzleVelocity = CalculateEffectiveMuzzleVelocity(BulletType);
float Momentum = BulletMass * (MuzzleVelocity / 100.0f); // Convert cm/s to m/s
// Base recoil impulse
float RecoilForce = Momentum * RecoilMultiplier;
// Barrel length affects recoil (longer barrels spread recoil over more time, but same total impulse)
float LengthFactor = FMath::Clamp(20.0f / BarrelLength, 0.8f, 1.5f);
RecoilForce *= LengthFactor;
// Direction from weapon config (if parent gun has config)
FVector RecoilDirection = FVector(0, 0, 1); // Default upward
if (ParentGun && ParentGun->WeaponConfig)
{
RecoilDirection = ParentGun->WeaponConfig->BarrelConfig.RecoilDirection;
}
return RecoilDirection * RecoilForce;
}
int UEBBarrel::GetMagazineRoundCount() const
{
UEBMagazine* Magazine = GetConnectedMagazine();
if (Magazine)
{
return Magazine->GetCurrentRoundCount();
}
return 0;
}
bool UEBBarrel::HasChamberedRound() const
{
if (ParentGun)
{
return ParentGun->IsLoaded();
}
return false;
}
bool UEBBarrel::CanFireFromGun() const
{
if (ParentGun)
{
return ParentGun->CanFire();
}
return false;
}