480 lines
13 KiB
C++
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;
|
|
} |