Files
2025-07-10 02:10:37 -07:00

383 lines
11 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
}
void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// The barrel's tick function is now primarily for debug displays or other non-firing logic.
// All firing state, cooldowns, and ammo management has been moved to AEBGun.
}
bool UEBBarrel::ClientAim_Validate(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
return true;
}
void UEBBarrel::ClientAim_Implementation(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
Location = NewLocation;
Aim = NewAim;
TimeSinceAimUpdate = 0.0f;
RemoteAimReceived = true;
}
bool UEBBarrel::ShootRep_Validate(bool Trigger) {
return true;
}
void UEBBarrel::ShootRep_Implementation(bool Trigger) {
// Legacy function, logic moved to AEBGun.
// This can be left empty or used for simple replication effects.
}
bool UEBBarrel::ShootRepCSA_Validate(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
return true;
}
void UEBBarrel::ShootRepCSA_Implementation(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) {
ClientAim_Implementation(NewLocation, NewAim);
ShootRep_Implementation(Trigger);
}
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;
// Get recoil direction from the parent gun
FVector RecoilDirection = FVector(0, 0, 1); // Default upward
if (ParentGun)
{
// Use the gun's calculation for direction
RecoilDirection = ParentGun->CalculateRecoilImpulse().GetSafeNormal();
}
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;
}
// ===============================
// Refactored Single-Shot Helper
// ===============================
void UEBBarrel::FireBullet(TSubclassOf<class AEBBullet> BulletClass)
{
// Validate input
if (!BulletClass)
{
UE_LOG(LogTemp, Warning, TEXT("UEBBarrel::FireBullet called with invalid BulletClass"));
return;
}
AActor* OwnerActor = GetOwner();
if (!OwnerActor)
{
UE_LOG(LogTemp, Warning, TEXT("UEBBarrel::FireBullet called but barrel has no owner"));
return;
}
// Determine current muzzle location & aim direction
FVector InLocation = GetComponentTransform().GetLocation();
FVector InDirection = GetComponentTransform().GetUnitAxis(EAxis::X);
FVector OutLocation;
FVector OutDirection;
InitialBulletTransform(InLocation, InDirection, OutLocation, OutDirection);
// Calculate spread and velocity using same logic as original SpawnBullet
AEBBullet* Default = Cast<AEBBullet>(BulletClass->GetDefaultObject());
if (!Default)
{
UE_LOG(LogTemp, Warning, TEXT("UEBBarrel::FireBullet could not get default object for bullet class"));
return;
}
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;
OutDirection = RandomStream.VRandCone(OutDirection, TotalSpread);
float BulletVelocity = FMath::Lerp(MuzzleVelocityMultiplierMin * Default->MuzzleVelocityMin, MuzzleVelocityMultiplierMax * Default->MuzzleVelocityMax, RandomStream.FRand());
FVector Velocity = OutDirection * BulletVelocity;
// Inherit velocity from parent physics body if applicable
UPrimitiveComponent* ParentComp = Cast<UPrimitiveComponent>(GetAttachParent());
Velocity += AdditionalVelocity;
if (ParentComp && ParentComp->IsSimulatingPhysics())
{
Velocity += ParentComp->GetPhysicsLinearVelocityAtPoint(OutLocation) * InheritVelocity;
}
// Apply recoil impulse
if (ParentComp && ParentComp->IsSimulatingPhysics())
{
FVector Impulse = -Velocity * Default->Mass * RecoilMultiplier * (Default->Shotgun ? Default->ShotCount : 1);
ApplyRecoil(ParentComp, OutLocation, Impulse);
}
// Broadcast pre-shot event
BeforeShotFired.Broadcast();
// Actually spawn the bullet with exact velocity
AEBBullet::SpawnWithExactVelocityFromBarrel(BulletClass, OwnerActor, OwnerActor->GetInstigator(), OutLocation, Velocity, this);
// Cooldown logic is now managed by the gun/controller barrel just fires once.
// Notify listeners that shot has been fired
if (ReplicateShotFiredEvents)
{
ShotFiredMulticast();
}
else
{
ShotFired.Broadcast();
}
}