382 lines
11 KiB
C++
382 lines
11 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();
|
||
}
|
||
|
||
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;
|
||
|
||
// 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;
|
||
}
|
||
|
||
// ===============================
|
||
// 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();
|
||
}
|
||
} |