Files
BallisticsDocs/Source/EasyBallistics/Private/EBGun.cpp
T
2025-07-10 02:10:37 -07:00

983 lines
22 KiB
C++
Raw 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 2016 Mookie. All Rights Reserved.
#include "EBGun.h"
#include "EBMagazine.h"
#include "EBWeaponData.h"
#include "EBBullet.h"
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
#include "TimerManager.h"
AEBGun::AEBGun()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
SetReplicateMovement(true);
// Create a scene component as root to hold both mesh components
USceneComponent* RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
RootComponent = RootScene;
// Create both mesh components and attach to root
SkeletalGunMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalGunMesh"));
StaticGunMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticGunMesh"));
SkeletalGunMesh->SetupAttachment(RootComponent);
StaticGunMesh->SetupAttachment(RootComponent);
// Set skeletal mesh as default active component
StaticGunMesh->SetVisibility(false);
StaticGunMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Create the barrel component for backward compatibility
Barrel = CreateDefaultSubobject<UEBBarrel>(TEXT("Barrel"));
Barrel->SetupAttachment(SkeletalGunMesh, TEXT("MuzzleSocket"));
Barrel->SetParentGun(this);
// Create the magazine component
Magazine = CreateDefaultSubobject<UEBMagazine>(TEXT("Magazine"));
// Initialize state
CurrentGunState = EGunState::GS_Ready;
PreviousGunState = EGunState::GS_Ready;
bSafetyOn = false;
bChamberedRound = false;
bHammerCocked = false;
bTriggerPressed = false;
bCanProcessNextShot = true;
BurstShotsFired = 0;
ChamberedBulletType = nullptr;
// Initialize firing state
bShooting = false;
bSpooling = false;
GatlingRPS = 0.0f;
Cooldown = 0.0f;
BurstRemaining = 0;
// Initialize gun configuration
bUseSkeletalMesh = true;
bUseSkeletal = true;
MuzzleSocketName = TEXT("MuzzleSocket");
MuzzleSocket = TEXT("MuzzleSocket");
MuzzleTransform = FTransform::Identity;
MuzzleOffset = FTransform::Identity;
SkeletalMeshAsset = nullptr;
StaticMeshAsset = nullptr;
// Initialize random stream
RandomStream.Initialize(FMath::Rand());
}
void AEBGun::BeginPlay()
{
Super::BeginPlay();
// Synchronize the actor settings with component settings
bUseSkeletalMesh = bUseSkeletal;
MuzzleSocketName = MuzzleSocket;
MuzzleTransform = MuzzleOffset;
// Apply mesh assets from actor settings
if (bUseSkeletal && SkeletalMeshAsset)
{
SkeletalGunMesh->SetSkeletalMesh(SkeletalMeshAsset);
}
else if (!bUseSkeletal && StaticMeshAsset)
{
StaticGunMesh->SetStaticMesh(StaticMeshAsset);
}
// Apply the mesh type setting to ensure proper visibility
SetMeshType(bUseSkeletal);
// Apply weapon configuration if available
if (WeaponData)
{
ApplyWeaponData(WeaponData);
}
// Initialize magazine
if (Magazine)
{
Magazine->OnMagazineEmpty.AddDynamic(this, &AEBGun::OnMagazineEmptyHandler);
Magazine->OnBulletFed.AddDynamic(this, &AEBGun::OnBulletFedHandler);
}
// Initialize barrel
if (Barrel)
{
Barrel->ShotFired.AddDynamic(this, &AEBGun::OnBarrelShotFiredHandler);
}
}
void AEBGun::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Process firing state
if (CurrentGunState == EGunState::GS_Firing)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] PullTrigger() SUCCESS - AEBGun::Tick::FIRE()"));
ProcessFiring();
}
}
#if WITH_EDITOR
void AEBGun::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropertyName = PropertyChangedEvent.Property->GetFName();
// Handle mesh setup changes
if (PropertyName == GET_MEMBER_NAME_CHECKED(AEBGun, bUseSkeletal))
{
// Synchronize with internal setting
bUseSkeletalMesh = bUseSkeletal;
SetMeshType(bUseSkeletalMesh);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AEBGun, SkeletalMeshAsset))
{
if (SkeletalMeshAsset && SkeletalGunMesh)
{
SkeletalGunMesh->SetSkeletalMesh(SkeletalMeshAsset);
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AEBGun, StaticMeshAsset))
{
if (StaticMeshAsset && StaticGunMesh)
{
StaticGunMesh->SetStaticMesh(StaticMeshAsset);
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AEBGun, MuzzleSocket))
{
MuzzleSocketName = MuzzleSocket;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AEBGun, MuzzleOffset))
{
MuzzleTransform = MuzzleOffset;
}
}
}
#endif
void AEBGun::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AEBGun, CurrentGunState);
DOREPLIFETIME(AEBGun, bSafetyOn);
DOREPLIFETIME(AEBGun, bChamberedRound);
DOREPLIFETIME(AEBGun, bHammerCocked);
DOREPLIFETIME(AEBGun, ChamberedBulletType);
DOREPLIFETIME(AEBGun, FireMode);
DOREPLIFETIME(AEBGun, bShooting);
DOREPLIFETIME(AEBGun, bSpooling);
}
void AEBGun::PullTrigger()
{
if (HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] PullTrigger() SUCCESS - HasAuthority()"));
ServerPullTrigger();
}
else
{
ServerPullTrigger();
}
}
void AEBGun::ServerPullTrigger_Implementation()
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] PullTrigger() SUCCESS - ServerPullTrigger_Implementation()"));
if (!CanFire())
{
return;
}
bTriggerPressed = true;
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] PullTrigger() SUCCESS - bTriggerPressed"));
if (CurrentGunState == EGunState::GS_Ready)
{
SetGunState(EGunState::GS_Firing);
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] PullTrigger() SUCCESS - GS_Firing"));
}
}
void AEBGun::ReleaseTrigger()
{
if (HasAuthority())
{
ServerReleaseTrigger();
}
else
{
ServerReleaseTrigger();
}
}
void AEBGun::ServerReleaseTrigger_Implementation()
{
bTriggerPressed = false;
// Handle fire mode specific behavior
if (FireMode == EFireMode::FM_Semiauto || FireMode == EFireMode::FM_Manual)
{
if (CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_Ready);
}
}
}
void AEBGun::SetSafety(bool bNewSafetyState)
{
if (HasAuthority())
{
ServerSetSafety(bNewSafetyState);
}
else
{
ServerSetSafety(bNewSafetyState);
}
}
void AEBGun::ServerSetSafety_Implementation(bool bNewSafetyState)
{
if (!WeaponData || !WeaponData->bHasSafety)
{
return;
}
bool bOldSafetyState = bSafetyOn;
bSafetyOn = bNewSafetyState;
if (bSafetyOn && CurrentGunState == EGunState::GS_Firing)
{
SetGunState(EGunState::GS_SafetyOn);
}
else if (!bSafetyOn && CurrentGunState == EGunState::GS_SafetyOn)
{
SetGunState(EGunState::GS_Ready);
}
if (bOldSafetyState != bSafetyOn)
{
OnSafetyChanged.Broadcast(bSafetyOn);
MulticastOnSafetyChanged(bSafetyOn);
}
}
void AEBGun::CockHammer()
{
if (HasAuthority())
{
bHammerCocked = true;
}
}
void AEBGun::ReleaseHammer()
{
if (HasAuthority())
{
bHammerCocked = false;
}
}
void AEBGun::LoadMagazine(TArray<UEBBulletPropertiesAsset*> BulletTypes)
{
if (Magazine)
{
Magazine->LoadBullets(BulletTypes);
}
}
void AEBGun::EjectMagazine()
{
if (HasAuthority())
{
ServerEjectMagazine();
}
else
{
ServerEjectMagazine();
}
}
void AEBGun::ServerEjectMagazine_Implementation()
{
if (Magazine)
{
Magazine->UnloadAllBullets();
}
OnMagazineChanged.Broadcast(nullptr);
MulticastOnMagazineChanged(nullptr);
}
void AEBGun::InsertMagazine(UEBMagazine* NewMagazine)
{
if (HasAuthority())
{
ServerInsertMagazine(NewMagazine);
}
else
{
ServerInsertMagazine(NewMagazine);
}
}
void AEBGun::ServerInsertMagazine_Implementation(UEBMagazine* NewMagazine)
{
if (!NewMagazine)
{
return;
}
// Replace magazine component (in a real implementation, this would be more complex)
Magazine = NewMagazine;
OnMagazineChanged.Broadcast(NewMagazine);
MulticastOnMagazineChanged(NewMagazine);
}
void AEBGun::ChargingHandle()
{
if (HasAuthority())
{
ServerChargingHandle();
}
else
{
ServerChargingHandle();
}
}
void AEBGun::ServerChargingHandle_Implementation()
{
// Eject chambered round if present
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
// Feed new round from magazine
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NewBullet = Magazine->FeedNextBullet();
if (NewBullet)
{
ChamberedBulletType = NewBullet;
bChamberedRound = true;
}
}
// Update gun state
if (CurrentGunState == EGunState::GS_Empty && bChamberedRound)
{
SetGunState(EGunState::GS_Ready);
}
else if (!bChamberedRound && (!Magazine || Magazine->bIsEmpty))
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::EjectChamberedRound()
{
if (HasAuthority())
{
if (bChamberedRound)
{
ChamberedBulletType = nullptr;
bChamberedRound = false;
}
}
}
bool AEBGun::CanFire() const
{
if (bSafetyOn)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] CanFire() - Safety is ON"));
return false;
}
if (CurrentGunState == EGunState::GS_Jammed ||
CurrentGunState == EGunState::GS_Empty ||
CurrentGunState == EGunState::GS_SafetyOn ||
CurrentGunState == EGunState::GS_Malfunction)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] CanFire() - Gun is not in a ready state: %s"), *UEnum::GetValueAsString(CurrentGunState));
return false;
}
if (!bChamberedRound || !ChamberedBulletType)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] CanFire() - No chambered round or bullet type"));
return false;
}
return true;
}
bool AEBGun::IsLoaded() const
{
return bChamberedRound && ChamberedBulletType != nullptr;
}
int32 AEBGun::GetRoundsInMagazine() const
{
if (Magazine)
{
return Magazine->GetCurrentRoundCount();
}
return 0;
}
int32 AEBGun::GetTotalRounds() const
{
int32 TotalRounds = 0;
if (Magazine)
{
TotalRounds += Magazine->GetCurrentRoundCount();
}
if (bChamberedRound)
{
TotalRounds += 1;
}
return TotalRounds;
}
UEBBulletPropertiesAsset* AEBGun::GetChamberedBulletType() const
{
return ChamberedBulletType;
}
void AEBGun::ApplyWeaponData(UEBWeaponData* NewData)
{
if (!NewData) return;
WeaponData = NewData;
// Apply basic properties
// Barrel & Ballistics (defaults for unmapped)
BarrelLength = 16.0f; // Default, no direct map
RiflingTwist = 7.0f; // Default
BoreRadius = 0.112f; // Default
MuzzleVelocityMin = NewData->MuzzleVelocity;
MuzzleVelocityMax = NewData->MuzzleVelocity;
Spread = 1.0f - NewData->Accuracy;
SpreadBias = 0.0f; // Default
MuzzleVelocityMultiplierMin = 1.0f;
MuzzleVelocityMultiplierMax = 1.0f;
// Fire Control
FireMode = NewData->DefaultFireMode;
FireRateMin = NewData->FireRate / 60.0f;
FireRateMax = NewData->FireRate / 60.0f;
BurstCount = NewData->BurstCount;
BurstCooldown = 0.0f; // Default
// Gatling (defaults)
bGatlingAutoSpool = true;
GatlingSpoolUpTime = 1.0f;
GatlingSpoolDownTime = 1.0f;
// Magazine
if (Magazine) {
Magazine->MagazineConfig.MaxCapacity = NewData->MagazineSize;
// Add other magazine configs if needed
}
// Safety
if (NewData->bHasSafety) {
// bSafetyOn is managed by the gun state, not set from data by default.
// Default is 'false' (off). Use SetSafety() to change.
} else {
bSafetyOn = false;
}
// Apply to barrel for compatibility
if (Barrel) {
// Map relevant fields to barrel
}
UE_LOG(LogTemp, Log, TEXT("Gun '%s' applied data configuration"), *NewData->WeaponName.ToString());
}
void AEBGun::SetFireMode(EFireMode NewFireMode)
{
if (HasAuthority())
{
ServerSetFireMode(NewFireMode);
}
else
{
ServerSetFireMode(NewFireMode);
}
}
void AEBGun::ServerSetFireMode_Implementation(EFireMode NewFireMode)
{
if (!WeaponData || !WeaponData->IsFireModeSupported(NewFireMode))
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] SetFireMode() FAILED - Invalid fire mode: %s"), *UEnum::GetValueAsString(NewFireMode));
//Is weapon config null?
if (!WeaponData)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Weapon config is null"));
}
//Is the fire mode available?
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Fire mode available: %s"), WeaponData->IsFireModeSupported(NewFireMode) ? TEXT("True") : TEXT("False"));
return;
}
FireMode = NewFireMode;
// Update barrel for backward compatibility
// Barrel no longer keeps FireMode state.
}
EFireMode AEBGun::GetFireMode() const
{
return FireMode;
}
void AEBGun::SetGunState(EGunState NewState)
{
if (!HasAuthority())
{
return;
}
if (CurrentGunState == NewState)
{
return;
}
PreviousGunState = CurrentGunState;
CurrentGunState = NewState;
OnGunStateChanged.Broadcast(NewState);
OnGunStateChangedInternal(PreviousGunState, NewState);
}
void AEBGun::OnRep_GunState()
{
OnGunStateChanged.Broadcast(CurrentGunState);
OnGunStateChangedInternal(PreviousGunState, CurrentGunState);
}
void AEBGun::ProcessFiring()
{
if (!HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ProcessFiring() FAILED - No Authority"));
return;
}
if (!CanFire() || !bCanProcessNextShot)
{
//Print CanFire and bCanProcessNextShot state
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - CanFire: %s, bCanProcessNextShot: %s"),
CanFire() ? TEXT("True") : TEXT("False"),
bCanProcessNextShot ? TEXT("True") : TEXT("False"));
return;
}
// Check if we should continue firing based on fire mode
switch (FireMode)
{
case EFireMode::FM_Semiauto:
case EFireMode::FM_Manual:
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ProcessFiring() - FM_Semiauto or FM_Manual"));
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Auto:
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ProcessFiring() - FM_Auto"));
if (!bTriggerPressed)
{
SetGunState(EGunState::GS_Ready);
return;
}
break;
case EFireMode::FM_Burst:
case EFireMode::FM_InterBurst:
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ProcessFiring() - FM_Burst or FM_InterBurst"));
if (BurstShotsFired >= BurstCount)
{
BurstShotsFired = 0;
SetGunState(EGunState::GS_Ready);
return;
}
break;
}
// Fire the weapon
FireShot();
}
void AEBGun::FireShot()
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] FireShot() called - Firing shot from AEBGun"));
if (!HasAuthority() || !CanFire())
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] FireShot() FAILED - CanFire() returned false or no authority"));
return;
}
// Fire through barrel
if (Barrel && ChamberedBulletType)
{
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] FireShot() - Firing shot from barrel"));
// Ensure we have a valid bullet class to spawn
if (!ChamberedBulletType->BulletClass)
{
UE_LOG(LogTemp, Error, TEXT("[WEAPON DEBUG] FireShot() FAILED: Bullet Properties Asset '%s' does not have a BulletClass assigned!"), *ChamberedBulletType->GetName());
return; // Stop if we don't have a bullet to spawn.
}
// Fire a single round directly through the barrel without relying on the legacy state machine
Barrel->FireBullet(ChamberedBulletType->BulletClass);
// Clear chambered round in the gun
ChamberedBulletType = nullptr;
bChamberedRound = false;
// Update burst count
BurstShotsFired++;
// Set fire-rate cooldown. Ensure the selected rate is positive so the timer actually triggers.
float ActualFireRate = RandomStream.FRandRange(FireRateMin, FireRateMax);
if (ActualFireRate <= KINDA_SMALL_NUMBER)
{
ActualFireRate = 1.0f; // fallback to 1 RPS (60 RPM)
}
float CooldownTime = 1.0f / ActualFireRate; // FireRate is already in RPS
bCanProcessNextShot = false;
GetWorld()->GetTimerManager().SetTimer(FireRateTimer, [this]()
{
bCanProcessNextShot = true;
}, CooldownTime, false);
// Try to feed next round
if (!FeedNextRound())
{
SetGunState(EGunState::GS_Empty);
}
}
}
bool AEBGun::FeedNextRound()
{
if (!HasAuthority())
{
return false;
}
if (Magazine && Magazine->CanFeed())
{
UEBBulletPropertiesAsset* NextBullet = Magazine->FeedNextBullet();
if (NextBullet)
{
ChamberedBulletType = NextBullet;
bChamberedRound = true;
return true;
}
}
return false;
}
void AEBGun::OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine)
{
OnMagazineEmpty.Broadcast();
if (!bChamberedRound)
{
SetGunState(EGunState::GS_Empty);
}
}
void AEBGun::OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType)
{
// Handle bullet fed from magazine
}
void AEBGun::OnBarrelShotFiredHandler()
{
OnGunFired.Broadcast(ChamberedBulletType);
MulticastOnGunFired(ChamberedBulletType);
}
void AEBGun::MulticastOnGunFired_Implementation(UEBBulletPropertiesAsset* BulletType)
{
if (!HasAuthority())
{
OnGunFired.Broadcast(BulletType);
}
}
void AEBGun::MulticastOnMagazineChanged_Implementation(UEBMagazine* NewMagazine)
{
if (!HasAuthority())
{
OnMagazineChanged.Broadcast(NewMagazine);
}
}
void AEBGun::MulticastOnSafetyChanged_Implementation(bool bNewSafetyState)
{
if (!HasAuthority())
{
OnSafetyChanged.Broadcast(bNewSafetyState);
}
}
// Gun Properties Functions (moved from barrel)
float AEBGun::GetBarrelLength() const
{
return BarrelLength;
}
float AEBGun::GetEffectiveMuzzleVelocity() const
{
if (ChamberedBulletType)
{
// Calculate effective muzzle velocity based on gun properties
// This is a simplified calculation - in a real implementation you'd want
// to consider bullet properties, barrel length, powder charge, etc.
float BaseVelocity = FMath::RandRange(MuzzleVelocityMin, MuzzleVelocityMax);
float VelocityMultiplier = FMath::RandRange(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax);
return BaseVelocity * VelocityMultiplier;
}
return 0.0f;
}
float AEBGun::GetBarrelAccuracy() const
{
// Calculate accuracy based on gun properties
// This is a simplified calculation
float AccuracyFactor = 1.0f - Spread;
return FMath::Clamp(AccuracyFactor, 0.0f, 1.0f);
}
FVector AEBGun::CalculateRecoilImpulse() const
{
if (ChamberedBulletType)
{
// Calculate recoil based on gun properties and bullet
// This is a simplified calculation - in reality you'd consider:
// - Bullet mass and velocity
// - Powder charge
// - Gun weight
// - Barrel length
float RecoilMagnitude = GetEffectiveMuzzleVelocity() * 0.001f; // Simple scaling
FVector RecoilDirection = -GetActorForwardVector();
return RecoilDirection * RecoilMagnitude;
}
return FVector::ZeroVector;
}
bool AEBGun::IsBarrelConnected() const
{
return Barrel != nullptr && Barrel->IsConnectedToGun();
}
// Gun Mesh Management Functions
void AEBGun::SetMeshType(bool bUseSkeletalMeshParam)
{
bUseSkeletalMesh = bUseSkeletalMeshParam;
if (bUseSkeletalMesh)
{
// Switch to skeletal mesh
SkeletalGunMesh->SetVisibility(true);
SkeletalGunMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
StaticGunMesh->SetVisibility(false);
StaticGunMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Reattach barrel to skeletal mesh
if (Barrel)
{
Barrel->SetupAttachment(SkeletalGunMesh, *MuzzleSocketName);
}
}
else
{
// Switch to static mesh
StaticGunMesh->SetVisibility(true);
StaticGunMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
SkeletalGunMesh->SetVisibility(false);
SkeletalGunMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Reattach barrel to static mesh
if (Barrel)
{
Barrel->SetupAttachment(StaticGunMesh);
}
}
}
UPrimitiveComponent* AEBGun::GetActiveMeshComponent() const
{
return bUseSkeletalMesh ? Cast<UPrimitiveComponent>(SkeletalGunMesh) : Cast<UPrimitiveComponent>(StaticGunMesh);
}
FTransform AEBGun::GetMuzzleTransform() const
{
if (bUseSkeletalMesh && SkeletalGunMesh)
{
if (SkeletalGunMesh->DoesSocketExist(*MuzzleSocketName))
{
return SkeletalGunMesh->GetSocketTransform(*MuzzleSocketName);
}
}
else if (!bUseSkeletalMesh && StaticGunMesh)
{
// For static mesh, use the relative transform
return StaticGunMesh->GetComponentTransform() * MuzzleTransform;
}
// Fallback to component transform
UPrimitiveComponent* ActiveMesh = GetActiveMeshComponent();
return ActiveMesh ? ActiveMesh->GetComponentTransform() : GetActorTransform();
}
void AEBGun::SetMuzzleSocketName(const FString& NewSocketName)
{
MuzzleSocketName = NewSocketName;
// Update barrel attachment if using skeletal mesh
if (bUseSkeletalMesh && Barrel)
{
Barrel->SetupAttachment(SkeletalGunMesh, *MuzzleSocketName);
}
}
// Firing System Functions (moved from barrel)
void AEBGun::Shoot(bool bTrigger)
{
if (HasAuthority())
{
ServerShoot(bTrigger);
}
else
{
ServerShoot(bTrigger);
}
}
void AEBGun::ServerShoot_Implementation(bool bTrigger)
{
if (bTrigger)
{
PullTrigger();
}
else
{
ReleaseTrigger();
}
}
bool AEBGun::ServerShoot_Validate(bool bTrigger)
{
return true;
}
void AEBGun::GatlingSpool(bool bSpool)
{
if (HasAuthority())
{
ServerGatlingSpool(bSpool);
}
else
{
ServerGatlingSpool(bSpool);
}
}
void AEBGun::ServerGatlingSpool_Implementation(bool bSpool)
{
bSpooling = bSpool;
}
bool AEBGun::ServerGatlingSpool_Validate(bool bSpool)
{
return true;
}
void AEBGun::SwitchFireMode(EFireMode NewFireMode)
{
if (HasAuthority())
{
ServerSwitchFireMode(NewFireMode);
}
else
{
ServerSwitchFireMode(NewFireMode);
}
}
void AEBGun::ServerSwitchFireMode_Implementation(EFireMode NewFireMode)
{
if (WeaponData && WeaponData->IsFireModeSupported(NewFireMode)) {
FireMode = NewFireMode;
}
}
bool AEBGun::ServerSwitchFireMode_Validate(EFireMode NewFireMode)
{
return true;
}
// Legacy SpawnBullet function removed firing handled by FireShot / FireBullet.
void AEBGun::PredictHit(bool& bHit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime,
AActor*& HitActor, TArray<FVector>& Trajectory,
TSubclassOf<class AEBBullet> BulletClass,
const TArray<AActor*>& IgnoredActors,
float MaxTime, float Step) const
{
// For now, delegate to barrel for backward compatibility
if (Barrel)
{
Barrel->PredictHit(bHit, TraceResult, HitLocation, HitTime, HitActor, Trajectory, BulletClass, IgnoredActors, MaxTime, Step);
}
}
void AEBGun::CalculateAimDirection(TSubclassOf<class AEBBullet> BulletClass, FVector TargetLocation, FVector TargetVelocity,
FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation,
float& PredictedFlightTime, float& Error, float MaxTime, float Step, int32 NumIterations) const
{
// For now, delegate to barrel for backward compatibility
if (Barrel)
{
Barrel->CalculateAimDirection(BulletClass, TargetLocation, TargetVelocity, AimDirection, PredictedTargetLocation,
PredictedIntersectionLocation, PredictedFlightTime, Error, MaxTime, Step, NumIterations);
}
}
void AEBGun::MulticastShotFired_Implementation()
{
// Handle client-side shot fired effects
// This could include muzzle flash, sound effects, etc.
}