// 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 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 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 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 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(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(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(); } }