1365 lines
42 KiB
C++
1365 lines
42 KiB
C++
// Copyright 2016 Mookie. All Rights Reserved.
|
||
|
||
#include "EBPlayerWeaponController.h"
|
||
#include "EBGun.h"
|
||
#include "EBWeaponData.h"
|
||
#include "EBMagazine.h"
|
||
#include "EBBullet.h"
|
||
#include "Net/UnrealNetwork.h"
|
||
#include "Engine/Engine.h"
|
||
#include "Engine/World.h"
|
||
#include "GameFramework/Actor.h"
|
||
#include "Components/SceneComponent.h"
|
||
#include "Components/SkeletalMeshComponent.h"
|
||
#include "Components/StaticMeshComponent.h"
|
||
|
||
UEBPlayerWeaponController::UEBPlayerWeaponController()
|
||
{
|
||
PrimaryComponentTick.bCanEverTick = false;
|
||
SetIsReplicatedByDefault(true);
|
||
|
||
// Initialize defaults
|
||
WeaponActor = nullptr;
|
||
CurrentWeaponData = nullptr;
|
||
CurrentWeaponIndex = 0;
|
||
DefaultWeapon = nullptr;
|
||
bAutoEquipOnBeginPlay = true;
|
||
|
||
// Ammo
|
||
CurrentAmmo = 0;
|
||
ReserveAmmo = 0;
|
||
bInfiniteAmmo = false;
|
||
|
||
// Weapon spawn settings
|
||
WeaponSpawnOffset = FTransform::Identity;
|
||
|
||
// Niagara VFX
|
||
MuzzleFlashComponent = nullptr;
|
||
MuzzleSmokeComponent = nullptr;
|
||
BarrelHeatComponent = nullptr;
|
||
AttachmentGlowComponent = nullptr;
|
||
CurrentHeatLevel = 0.0f;
|
||
bEnableAdvancedVFX = true;
|
||
}
|
||
|
||
void UEBPlayerWeaponController::BeginPlay()
|
||
{
|
||
Super::BeginPlay();
|
||
|
||
if (HasAuthority())
|
||
{
|
||
// Create the single persistent weapon actor
|
||
CreateWeaponActor();
|
||
|
||
// Create Niagara components for advanced VFX
|
||
if (bEnableAdvancedVFX)
|
||
{
|
||
CreateNiagaraComponents();
|
||
}
|
||
|
||
// Set up default inventory if empty
|
||
if (WeaponInventory.Num() == 0 && DefaultWeapon)
|
||
{
|
||
WeaponInventory.Add(DefaultWeapon);
|
||
}
|
||
|
||
// Auto-equip first weapon
|
||
if (bAutoEquipOnBeginPlay && WeaponInventory.Num() > 0)
|
||
{
|
||
SwitchToWeapon(0);
|
||
}
|
||
else if (bAutoEquipOnBeginPlay && DefaultWeapon && WeaponInventory.Num() == 0)
|
||
{
|
||
// If no inventory but we have a default weapon, add it and equip it
|
||
WeaponInventory.Add(DefaultWeapon);
|
||
SwitchToWeapon(0);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||
{
|
||
UnbindWeaponEvents();
|
||
|
||
// Clean up weapon actor
|
||
if (WeaponActor)
|
||
{
|
||
WeaponActor->Destroy();
|
||
WeaponActor = nullptr;
|
||
}
|
||
|
||
Super::EndPlay(EndPlayReason);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||
{
|
||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||
|
||
DOREPLIFETIME(UEBPlayerWeaponController, WeaponActor);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, CurrentWeaponData);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, WeaponInventory);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, CurrentWeaponIndex);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, CurrentAmmo);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, ReserveAmmo);
|
||
DOREPLIFETIME(UEBPlayerWeaponController, CurrentHeatLevel);
|
||
}
|
||
|
||
#if WITH_EDITOR
|
||
void UEBPlayerWeaponController::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||
{
|
||
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||
|
||
if (PropertyChangedEvent.Property)
|
||
{
|
||
FName PropertyName = PropertyChangedEvent.Property->GetFName();
|
||
|
||
// Handle weapon inventory changes
|
||
if (PropertyName == GET_MEMBER_NAME_CHECKED(UEBPlayerWeaponController, WeaponInventory) ||
|
||
PropertyName == GET_MEMBER_NAME_CHECKED(UEBPlayerWeaponController, DefaultWeapon) ||
|
||
PropertyName == GET_MEMBER_NAME_CHECKED(UEBPlayerWeaponController, CurrentWeaponIndex))
|
||
{
|
||
// Create weapon actor if it doesn't exist
|
||
if (!WeaponActor)
|
||
{
|
||
CreateWeaponActor();
|
||
}
|
||
|
||
// Apply weapon data for immediate preview
|
||
UEBWeaponData* PreviewWeaponData = nullptr;
|
||
|
||
// Try to get weapon from inventory first
|
||
if (WeaponInventory.IsValidIndex(CurrentWeaponIndex))
|
||
{
|
||
PreviewWeaponData = WeaponInventory[CurrentWeaponIndex];
|
||
}
|
||
// Fall back to default weapon
|
||
else if (DefaultWeapon)
|
||
{
|
||
PreviewWeaponData = DefaultWeapon;
|
||
}
|
||
// Or use first weapon in inventory
|
||
else if (WeaponInventory.Num() > 0)
|
||
{
|
||
PreviewWeaponData = WeaponInventory[0];
|
||
}
|
||
|
||
if (PreviewWeaponData)
|
||
{
|
||
CurrentWeaponData = PreviewWeaponData;
|
||
InternalApplyWeaponData(PreviewWeaponData);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
// ========================================
|
||
// INPUT ACTIONS
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::Fire()
|
||
{
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Fire() called - Starting fire sequence"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Weapon: %s"), CurrentWeaponData->WeaponName.ToString().IsEmpty() ? TEXT("Unnamed") : *CurrentWeaponData->WeaponName.ToString());
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Current Ammo: %d"), CurrentAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Reserve Ammo: %d"), ReserveAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Infinite Ammo: %s"), bInfiniteAmmo ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WeaponActor Valid: %s"), WeaponActor ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - CurrentWeaponData Valid: %s"), CurrentWeaponData ? TEXT("True") : TEXT("False"));
|
||
}
|
||
|
||
if (!CanFire())
|
||
{
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Fire() FAILED - CanFire() returned false"));
|
||
|
||
// Detailed failure analysis
|
||
if (!WeaponActor)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - FAILURE REASON: WeaponActor is null"));
|
||
}
|
||
else if (!CurrentWeaponData)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - FAILURE REASON: CurrentWeaponData is null"));
|
||
}
|
||
else if (CurrentAmmo <= 0)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - FAILURE REASON: No ammo (CurrentAmmo: %d)"), CurrentAmmo);
|
||
}
|
||
else if (!WeaponActor->CanFire())
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - FAILURE REASON: WeaponActor->CanFire() returned false"));
|
||
|
||
// Additional weapon actor state info
|
||
if (WeaponActor->bSafetyOn)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WEAPON STATE: Safety is ON"));
|
||
}
|
||
|
||
EFireMode CurrentFireMode = WeaponActor->GetFireMode();
|
||
FString FireModeString = TEXT("Unknown");
|
||
switch (CurrentFireMode)
|
||
{
|
||
case EFireMode::FM_Auto: FireModeString = TEXT("Auto"); break;
|
||
case EFireMode::FM_Semiauto: FireModeString = TEXT("Semi"); break;
|
||
case EFireMode::FM_Burst: FireModeString = TEXT("Burst"); break;
|
||
case EFireMode::FM_Manual: FireModeString = TEXT("Manual"); break;
|
||
case EFireMode::FM_InterBurst: FireModeString = TEXT("InterBurst"); break;
|
||
case EFireMode::FM_Slamfire: FireModeString = TEXT("Slamfire"); break;
|
||
case EFireMode::FM_Gatling: FireModeString = TEXT("Gatling"); break;
|
||
}
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WEAPON STATE: Fire Mode: %s"), *FireModeString);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!WeaponActor)
|
||
{
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Fire() FAILED - WeaponActor is null"));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] Fire() SUCCESS - Calling WeaponActor->PullTrigger()"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Weapon Fire Rate: %.2f RPM"), CurrentWeaponData->FireRate);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Weapon Damage: %.2f"), CurrentWeaponData->Damage);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Muzzle Velocity: %.2f cm/s"), CurrentWeaponData->MuzzleVelocity);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Heat Level: %.2f%%"), CurrentHeatLevel * 100.0f);
|
||
}
|
||
|
||
WeaponActor->PullTrigger();
|
||
}
|
||
|
||
void UEBPlayerWeaponController::StopFire()
|
||
{
|
||
if (WeaponActor)
|
||
{
|
||
WeaponActor->ReleaseTrigger();
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::Reload()
|
||
{
|
||
if (WeaponActor && CurrentWeaponData)
|
||
{
|
||
// Check if we need ammo and have reserve ammo
|
||
if (CurrentAmmo < CurrentWeaponData->MagazineSize && (ReserveAmmo > 0 || bInfiniteAmmo))
|
||
{
|
||
// Calculate ammo to reload
|
||
int32 AmmoNeeded = CurrentWeaponData->MagazineSize - CurrentAmmo;
|
||
int32 AmmoToReload = bInfiniteAmmo ? AmmoNeeded : FMath::Min(AmmoNeeded, ReserveAmmo);
|
||
|
||
if (HasAuthority())
|
||
{
|
||
// Update ammo
|
||
CurrentAmmo += AmmoToReload;
|
||
if (!bInfiniteAmmo)
|
||
{
|
||
ReserveAmmo -= AmmoToReload;
|
||
}
|
||
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
OnWeaponReloaded.Broadcast(CurrentWeaponData);
|
||
}
|
||
|
||
// Trigger reload animation/sound on weapon
|
||
WeaponActor->ChargingHandle();
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::NextWeapon()
|
||
{
|
||
if (WeaponInventory.Num() > 1)
|
||
{
|
||
int32 NextIndex = (CurrentWeaponIndex + 1) % WeaponInventory.Num();
|
||
SwitchToWeapon(NextIndex);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::PreviousWeapon()
|
||
{
|
||
if (WeaponInventory.Num() > 1)
|
||
{
|
||
int32 PrevIndex = (CurrentWeaponIndex - 1 + WeaponInventory.Num()) % WeaponInventory.Num();
|
||
SwitchToWeapon(PrevIndex);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SwitchToWeapon(int32 WeaponIndex)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
if (WeaponInventory.IsValidIndex(WeaponIndex) && WeaponIndex != CurrentWeaponIndex)
|
||
{
|
||
CurrentWeaponIndex = WeaponIndex;
|
||
UEBWeaponData* NewWeaponData = WeaponInventory[WeaponIndex];
|
||
|
||
if (NewWeaponData != CurrentWeaponData)
|
||
{
|
||
ApplyWeaponData(NewWeaponData);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerSwitchToWeapon(WeaponIndex);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SwitchToWeaponData(UEBWeaponData* WeaponData)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
int32 WeaponIndex = FindWeaponIndex(WeaponData);
|
||
if (WeaponIndex != INDEX_NONE)
|
||
{
|
||
SwitchToWeapon(WeaponIndex);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerSwitchToWeaponData(WeaponData);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ToggleFireMode()
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
if (WeaponActor && CurrentWeaponData)
|
||
{
|
||
EFireMode CurrentMode = WeaponActor->GetFireMode();
|
||
|
||
// Find next available fire mode
|
||
int32 CurrentModeIndex = CurrentWeaponData->AvailableFireModes.Find(CurrentMode);
|
||
if (CurrentModeIndex != INDEX_NONE)
|
||
{
|
||
int32 NextModeIndex = (CurrentModeIndex + 1) % CurrentWeaponData->AvailableFireModes.Num();
|
||
EFireMode NextMode = CurrentWeaponData->AvailableFireModes[NextModeIndex];
|
||
WeaponActor->SetFireMode(NextMode);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerToggleFireMode();
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ToggleSafety()
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
if (WeaponActor && CurrentWeaponData && CurrentWeaponData->bHasSafety)
|
||
{
|
||
WeaponActor->SetSafety(!WeaponActor->bSafetyOn);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerToggleSafety();
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// WEAPON MANAGEMENT
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::AddWeapon(UEBWeaponData* WeaponData)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
if (WeaponData && !WeaponInventory.Contains(WeaponData))
|
||
{
|
||
WeaponInventory.Add(WeaponData);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerAddWeapon(WeaponData);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::RemoveWeapon(UEBWeaponData* WeaponData)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
int32 WeaponIndex = WeaponInventory.Find(WeaponData);
|
||
if (WeaponIndex != INDEX_NONE)
|
||
{
|
||
WeaponInventory.RemoveAt(WeaponIndex);
|
||
|
||
// Adjust current index if needed
|
||
if (CurrentWeaponIndex >= WeaponInventory.Num())
|
||
{
|
||
CurrentWeaponIndex = FMath::Max(0, WeaponInventory.Num() - 1);
|
||
}
|
||
|
||
// Switch to new weapon if current was removed
|
||
if (WeaponInventory.Num() > 0 && WeaponInventory.IsValidIndex(CurrentWeaponIndex))
|
||
{
|
||
ApplyWeaponData(WeaponInventory[CurrentWeaponIndex]);
|
||
}
|
||
else
|
||
{
|
||
ApplyWeaponData(nullptr);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ServerRemoveWeapon(WeaponData);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ClearWeapons()
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
WeaponInventory.Empty();
|
||
CurrentWeaponIndex = 0;
|
||
ApplyWeaponData(nullptr);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetWeaponInventory(const TArray<UEBWeaponData*>& NewInventory)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
WeaponInventory = NewInventory;
|
||
CurrentWeaponIndex = 0;
|
||
|
||
if (WeaponInventory.Num() > 0)
|
||
{
|
||
ApplyWeaponData(WeaponInventory[0]);
|
||
}
|
||
else
|
||
{
|
||
ApplyWeaponData(nullptr);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ApplyWeaponData(UEBWeaponData* WeaponData)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
UEBWeaponData* PreviousWeapon = CurrentWeaponData;
|
||
CurrentWeaponData = WeaponData;
|
||
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ApplyWeaponData() called"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Previous Weapon: %s"), PreviousWeapon ? *PreviousWeapon->WeaponName.ToString() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - New Weapon: %s"), WeaponData ? *WeaponData->WeaponName.ToString() : TEXT("None"));
|
||
}
|
||
|
||
// Apply to weapon actor
|
||
InternalApplyWeaponData(WeaponData);
|
||
|
||
// Update ammo
|
||
UpdateAmmoFromWeaponData();
|
||
|
||
// Broadcast event
|
||
OnWeaponChanged.Broadcast(WeaponData);
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] ApplyWeaponData() completed - weapon data applied and events broadcast"));
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// AMMO MANAGEMENT
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::AddAmmo(int32 Amount)
|
||
{
|
||
if (HasAuthority() && CurrentWeaponData)
|
||
{
|
||
ReserveAmmo = FMath::Min(ReserveAmmo + Amount, CurrentWeaponData->MaxReserveAmmo);
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetCurrentAmmo(int32 Amount)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
CurrentAmmo = FMath::Max(0, Amount);
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
}
|
||
else
|
||
{
|
||
ServerSetCurrentAmmo(Amount);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetReserveAmmo(int32 Amount)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
ReserveAmmo = FMath::Max(0, Amount);
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
}
|
||
else
|
||
{
|
||
ServerSetReserveAmmo(Amount);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::RefillAmmo()
|
||
{
|
||
if (HasAuthority() && CurrentWeaponData)
|
||
{
|
||
CurrentAmmo = CurrentWeaponData->MagazineSize;
|
||
ReserveAmmo = CurrentWeaponData->MaxReserveAmmo;
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// STATUS QUERIES
|
||
// ========================================
|
||
|
||
bool UEBPlayerWeaponController::CanFire() const
|
||
{
|
||
bool bCanFire = WeaponActor && CurrentWeaponData && CurrentAmmo > 0 && WeaponActor->CanFire();
|
||
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] CanFire() check - Result: %s"), bCanFire ? TEXT("TRUE") : TEXT("FALSE"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WeaponActor valid: %s"), WeaponActor ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - CurrentWeaponData valid: %s"), CurrentWeaponData ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - CurrentAmmo > 0: %s (%d)"), CurrentAmmo > 0 ? TEXT("True") : TEXT("False"), CurrentAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WeaponActor->CanFire(): %s"), WeaponActor ? (WeaponActor->CanFire() ? TEXT("True") : TEXT("False")) : TEXT("N/A"));
|
||
}
|
||
|
||
return bCanFire;
|
||
}
|
||
|
||
bool UEBPlayerWeaponController::HasWeapon() const
|
||
{
|
||
return CurrentWeaponData != nullptr;
|
||
}
|
||
|
||
UEBWeaponData* UEBPlayerWeaponController::GetWeaponAtIndex(int32 Index) const
|
||
{
|
||
return WeaponInventory.IsValidIndex(Index) ? WeaponInventory[Index] : nullptr;
|
||
}
|
||
|
||
int32 UEBPlayerWeaponController::FindWeaponIndex(UEBWeaponData* WeaponData) const
|
||
{
|
||
return WeaponInventory.Find(WeaponData);
|
||
}
|
||
|
||
bool UEBPlayerWeaponController::HasWeaponInInventory(UEBWeaponData* WeaponData) const
|
||
{
|
||
return WeaponInventory.Contains(WeaponData);
|
||
}
|
||
|
||
EFireMode UEBPlayerWeaponController::GetFireMode() const
|
||
{
|
||
return WeaponActor ? WeaponActor->GetFireMode() : EFireMode::FM_Auto;
|
||
}
|
||
|
||
bool UEBPlayerWeaponController::IsSafetyOn() const
|
||
{
|
||
return WeaponActor ? WeaponActor->bSafetyOn : false;
|
||
}
|
||
|
||
FTransform UEBPlayerWeaponController::GetMuzzleTransform() const
|
||
{
|
||
return WeaponActor ? WeaponActor->GetMuzzleTransform() : FTransform::Identity;
|
||
}
|
||
|
||
// ========================================
|
||
// INTERNAL FUNCTIONS
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::CreateWeaponActor()
|
||
{
|
||
if (!HasAuthority())
|
||
return;
|
||
|
||
UWorld* World = GetWorld();
|
||
AActor* Owner = GetOwner();
|
||
if (!World || !Owner)
|
||
return;
|
||
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] CreateWeaponActor() called"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Owner: %s"), Owner ? *Owner->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - World: %s"), World ? *World->GetName() : TEXT("None"));
|
||
}
|
||
|
||
// Calculate spawn transform
|
||
FTransform SpawnTransform = GetComponentTransform();
|
||
SpawnTransform = SpawnTransform * WeaponSpawnOffset;
|
||
|
||
// Spawn the single persistent weapon actor
|
||
FActorSpawnParameters SpawnParams;
|
||
SpawnParams.Owner = Owner;
|
||
SpawnParams.Instigator = Cast<APawn>(Owner);
|
||
|
||
WeaponActor = World->SpawnActor<AEBGun>(AEBGun::StaticClass(), SpawnTransform, SpawnParams);
|
||
|
||
if (WeaponActor)
|
||
{
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] WeaponActor spawned successfully: %s"), *WeaponActor->GetName());
|
||
}
|
||
|
||
// Attach weapon directly to this scene component
|
||
WeaponActor->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetIncludingScale);
|
||
WeaponActor->SetActorRelativeTransform(WeaponSpawnOffset);
|
||
|
||
// Configure collision to prevent collision with player
|
||
WeaponActor->SetActorEnableCollision(false);
|
||
|
||
// Alternative: If you need collision enabled but want to ignore the owner
|
||
// WeaponActor->SetOwner(Owner);
|
||
// if (UPrimitiveComponent* WeaponRootComp = Cast<UPrimitiveComponent>(WeaponActor->GetRootComponent()))
|
||
// {
|
||
// WeaponRootComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
|
||
// WeaponRootComp->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Ignore);
|
||
// }
|
||
|
||
// Bind events
|
||
BindWeaponEvents();
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] WeaponActor setup completed - attached and events bound"));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Debug logging for failed spawn
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Error, TEXT("[WEAPON DEBUG] FAILED to spawn WeaponActor!"));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
void UEBPlayerWeaponController::InternalApplyWeaponData(UEBWeaponData* WeaponData)
|
||
{
|
||
if (!WeaponActor)
|
||
{
|
||
if (WeaponData && WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Error, TEXT("[WEAPON DEBUG] InternalApplyWeaponData() FAILED - WeaponActor is null"));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (WeaponData)
|
||
{
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] InternalApplyWeaponData() called for weapon: %s"), *WeaponData->WeaponName.ToString());
|
||
}
|
||
|
||
// Determine which mesh type to use and apply it
|
||
bool bUsingSkeletalMesh = false;
|
||
|
||
if (WeaponData->FirstPersonMesh)
|
||
{
|
||
// Prefer first-person skeletal mesh when available
|
||
WeaponActor->SetMeshType(true /*bUseSkeletal*/);
|
||
WeaponActor->SkeletalGunMesh->SetSkeletalMesh(WeaponData->FirstPersonMesh);
|
||
bUsingSkeletalMesh = true;
|
||
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Using FirstPersonMesh: %s"), *WeaponData->FirstPersonMesh->GetName());
|
||
}
|
||
}
|
||
else if (WeaponData->ThirdPersonMesh)
|
||
{
|
||
// Fallback to third-person skeletal mesh
|
||
WeaponActor->SetMeshType(true);
|
||
WeaponActor->SkeletalGunMesh->SetSkeletalMesh(WeaponData->ThirdPersonMesh);
|
||
bUsingSkeletalMesh = true;
|
||
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Using ThirdPersonMesh: %s"), *WeaponData->ThirdPersonMesh->GetName());
|
||
}
|
||
}
|
||
else if (WeaponData->StaticMesh)
|
||
{
|
||
// No skeletal mesh provided – use static mesh
|
||
WeaponActor->StaticGunMesh->SetStaticMesh(WeaponData->StaticMesh);
|
||
WeaponActor->SetMeshType(false /*use static*/);
|
||
bUsingSkeletalMesh = false;
|
||
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Using StaticMesh: %s"), *WeaponData->StaticMesh->GetName());
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - No mesh found for weapon!"));
|
||
}
|
||
}
|
||
|
||
// Apply material overrides (works for both mesh types)
|
||
UPrimitiveComponent* TargetMeshComp = bUsingSkeletalMesh
|
||
? Cast<UPrimitiveComponent>(WeaponActor->SkeletalGunMesh)
|
||
: Cast<UPrimitiveComponent>(WeaponActor->StaticGunMesh);
|
||
|
||
for (int32 i = 0; i < WeaponData->MaterialOverrides.Num(); ++i)
|
||
{
|
||
if (WeaponData->MaterialOverrides[i] && TargetMeshComp)
|
||
{
|
||
TargetMeshComp->SetMaterial(i, WeaponData->MaterialOverrides[i]);
|
||
}
|
||
}
|
||
|
||
// Apply ballistics settings
|
||
WeaponActor->MuzzleVelocityMin = WeaponData->MuzzleVelocity;
|
||
WeaponActor->MuzzleVelocityMax = WeaponData->MuzzleVelocity;
|
||
WeaponActor->FireRateMin = WeaponData->GetFireRatePerSecond();
|
||
WeaponActor->FireRateMax = WeaponData->GetFireRatePerSecond();
|
||
WeaponActor->BurstCount = WeaponData->BurstCount;
|
||
|
||
// Set available fire modes
|
||
if (WeaponData->AvailableFireModes.Num() > 0)
|
||
{
|
||
WeaponActor->SetFireMode(WeaponData->DefaultFireMode);
|
||
}
|
||
|
||
// Apply socket name or transform depending on mesh type
|
||
WeaponActor->SetMuzzleSocketName(WeaponData->MuzzleSocketName.ToString());
|
||
|
||
// ===== Initialize magazine with bullets and chamber first round =====
|
||
if (HasAuthority() && WeaponActor->Magazine && WeaponData->BulletProperties)
|
||
{
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Initializing magazine with %d bullets"), WeaponData->MagazineSize);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Bullet Properties: %s"), *WeaponData->BulletProperties->GetName());
|
||
}
|
||
|
||
TArray<UEBBulletPropertiesAsset*> Bullets;
|
||
Bullets.Reserve(WeaponData->MagazineSize);
|
||
for (int32 i = 0; i < WeaponData->MagazineSize; ++i)
|
||
{
|
||
Bullets.Add(WeaponData->BulletProperties);
|
||
}
|
||
|
||
// Load bullets into the magazine
|
||
WeaponActor->Magazine->LoadBullets(Bullets);
|
||
|
||
// Chamber the first round so the weapon is ready to fire
|
||
WeaponActor->ChargingHandle();
|
||
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Magazine loaded and first round chambered"));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Magazine initialization skipped:"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - HasAuthority: %s"), HasAuthority() ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WeaponActor->Magazine: %s"), WeaponActor->Magazine ? TEXT("Valid") : TEXT("Null"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WeaponData->BulletProperties: %s"), WeaponData->BulletProperties ? TEXT("Valid") : TEXT("Null"));
|
||
}
|
||
}
|
||
|
||
// Ensure weapon is visible
|
||
WeaponActor->SetActorHiddenInGame(false);
|
||
|
||
if (WeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Weapon made visible"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] InternalApplyWeaponData() completed successfully"));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Hide weapon when no data is present
|
||
WeaponActor->SetActorHiddenInGame(true);
|
||
|
||
// Debug logging for null weapon data
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] InternalApplyWeaponData() called with null WeaponData - weapon hidden"));
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::UpdateAmmoFromWeaponData()
|
||
{
|
||
if (CurrentWeaponData)
|
||
{
|
||
// Set ammo if this is a new weapon or first time
|
||
if (CurrentAmmo == 0 && ReserveAmmo == 0)
|
||
{
|
||
CurrentAmmo = CurrentWeaponData->MagazineSize;
|
||
ReserveAmmo = CurrentWeaponData->MaxReserveAmmo;
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::HandleWeaponFired(UEBBulletPropertiesAsset* BulletType)
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
// Debug logging if enabled
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] HandleWeaponFired() called - Shot fired successfully!"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Bullet Type: %s"), BulletType ? *BulletType->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Pre-fire Ammo: %d"), CurrentAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Infinite Ammo: %s"), bInfiniteAmmo ? TEXT("True") : TEXT("False"));
|
||
}
|
||
|
||
// Consume ammo
|
||
if (!bInfiniteAmmo && CurrentAmmo > 0)
|
||
{
|
||
CurrentAmmo--;
|
||
OnAmmoChanged.Broadcast(CurrentAmmo, ReserveAmmo);
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Ammo consumed. New ammo count: %d"), CurrentAmmo);
|
||
}
|
||
}
|
||
|
||
// Add heat from firing
|
||
if (CurrentWeaponData)
|
||
{
|
||
float PreviousHeat = CurrentWeaponData->GetHeatLevel();
|
||
CurrentWeaponData->AddHeat();
|
||
CurrentHeatLevel = CurrentWeaponData->GetHeatLevel();
|
||
|
||
if (CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Heat updated: %.2f%% -> %.2f%%"), PreviousHeat * 100.0f, CurrentHeatLevel * 100.0f);
|
||
if (CurrentWeaponData->IsOverheated())
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - WARNING: Weapon is now OVERHEATED!"));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Trigger VFX
|
||
if (bEnableAdvancedVFX)
|
||
{
|
||
TriggerMuzzleFlash();
|
||
TriggerShellEjection();
|
||
UpdateHeatEffects();
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - VFX triggered (muzzle flash, shell ejection, heat effects)"));
|
||
}
|
||
}
|
||
|
||
// Check for empty
|
||
if (CurrentAmmo <= 0)
|
||
{
|
||
OnWeaponEmpty.Broadcast(CurrentWeaponData);
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Weapon is now EMPTY! OnWeaponEmpty broadcast"));
|
||
}
|
||
}
|
||
|
||
// Broadcast fired event
|
||
OnWeaponFired.Broadcast(CurrentWeaponData);
|
||
|
||
if (CurrentWeaponData && CurrentWeaponData->bEnableFireDebug)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - OnWeaponFired broadcast completed"));
|
||
UE_LOG(LogTemp, Warning, TEXT("[WEAPON DEBUG] - Final state: Ammo=%d, Heat=%.2f%%, Overheated=%s"),
|
||
CurrentAmmo, CurrentHeatLevel * 100.0f, CurrentWeaponData->IsOverheated() ? TEXT("Yes") : TEXT("No"));
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::HandleMagazineEmpty()
|
||
{
|
||
if (HasAuthority())
|
||
{
|
||
OnWeaponEmpty.Broadcast(CurrentWeaponData);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::BindWeaponEvents()
|
||
{
|
||
if (WeaponActor)
|
||
{
|
||
WeaponActor->OnGunFired.AddDynamic(this, &UEBPlayerWeaponController::HandleWeaponFired);
|
||
WeaponActor->OnMagazineEmpty.AddDynamic(this, &UEBPlayerWeaponController::HandleMagazineEmpty);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::UnbindWeaponEvents()
|
||
{
|
||
if (WeaponActor)
|
||
{
|
||
WeaponActor->OnGunFired.RemoveDynamic(this, &UEBPlayerWeaponController::HandleWeaponFired);
|
||
WeaponActor->OnMagazineEmpty.RemoveDynamic(this, &UEBPlayerWeaponController::HandleMagazineEmpty);
|
||
}
|
||
}
|
||
|
||
bool UEBPlayerWeaponController::HasAuthority() const
|
||
{
|
||
AActor* Owner = GetOwner();
|
||
return Owner ? Owner->HasAuthority() : false;
|
||
}
|
||
|
||
// ========================================
|
||
// SERVER RPCS
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::ServerSwitchToWeapon_Implementation(int32 WeaponIndex)
|
||
{
|
||
SwitchToWeapon(WeaponIndex);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerSwitchToWeaponData_Implementation(UEBWeaponData* WeaponData)
|
||
{
|
||
SwitchToWeaponData(WeaponData);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerAddWeapon_Implementation(UEBWeaponData* WeaponData)
|
||
{
|
||
AddWeapon(WeaponData);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerRemoveWeapon_Implementation(UEBWeaponData* WeaponData)
|
||
{
|
||
RemoveWeapon(WeaponData);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerSetCurrentAmmo_Implementation(int32 Amount)
|
||
{
|
||
SetCurrentAmmo(Amount);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerSetReserveAmmo_Implementation(int32 Amount)
|
||
{
|
||
SetReserveAmmo(Amount);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerToggleFireMode_Implementation()
|
||
{
|
||
ToggleFireMode();
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ServerToggleSafety_Implementation()
|
||
{
|
||
ToggleSafety();
|
||
}
|
||
|
||
// ========================================
|
||
// REPLICATION
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::OnRep_CurrentWeaponData()
|
||
{
|
||
InternalApplyWeaponData(CurrentWeaponData);
|
||
OnWeaponChanged.Broadcast(CurrentWeaponData);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::OnRep_WeaponInventory()
|
||
{
|
||
// Inventory changed, might need to update UI
|
||
}
|
||
|
||
void UEBPlayerWeaponController::OnRep_CurrentWeaponIndex()
|
||
{
|
||
// Weapon index changed, might need to update UI
|
||
}
|
||
|
||
// ========================================
|
||
// REPLICATION NOTIFICATIONS
|
||
// ========================================
|
||
|
||
void UEBPlayerWeaponController::OnRep_WeaponActor()
|
||
{
|
||
if (WeaponActor)
|
||
{
|
||
// Ensure the weapon is attached correctly on the owning client
|
||
WeaponActor->AttachToComponent(this, FAttachmentTransformRules::SnapToTargetIncludingScale);
|
||
WeaponActor->SetActorRelativeTransform(WeaponSpawnOffset);
|
||
|
||
// Configure collision to prevent collision with player
|
||
WeaponActor->SetActorEnableCollision(false);
|
||
|
||
// Re-bind events (replication does not copy dynamic binds)
|
||
BindWeaponEvents();
|
||
|
||
// Apply the current weapon data if we already have one
|
||
if (CurrentWeaponData)
|
||
{
|
||
InternalApplyWeaponData(CurrentWeaponData);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// NIAGARA VFX IMPLEMENTATIONS
|
||
// ========================================
|
||
|
||
bool UEBPlayerWeaponController::IsOverheated() const
|
||
{
|
||
return CurrentWeaponData ? CurrentWeaponData->IsOverheated() : false;
|
||
}
|
||
|
||
void UEBPlayerWeaponController::PrintWeaponDebugInfo() const
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("=== WEAPON DEBUG INFO ==="));
|
||
UE_LOG(LogTemp, Warning, TEXT("Controller State:"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Owner: %s"), GetOwner() ? *GetOwner()->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- HasAuthority: %s"), HasAuthority() ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- WeaponActor: %s"), WeaponActor ? *WeaponActor->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- CurrentWeaponData: %s"), CurrentWeaponData ? *CurrentWeaponData->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- CurrentWeaponIndex: %d"), CurrentWeaponIndex);
|
||
UE_LOG(LogTemp, Warning, TEXT("- WeaponInventory Count: %d"), WeaponInventory.Num());
|
||
|
||
UE_LOG(LogTemp, Warning, TEXT("Ammo State:"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- CurrentAmmo: %d"), CurrentAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("- ReserveAmmo: %d"), ReserveAmmo);
|
||
UE_LOG(LogTemp, Warning, TEXT("- InfiniteAmmo: %s"), bInfiniteAmmo ? TEXT("True") : TEXT("False"));
|
||
|
||
if (CurrentWeaponData)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("Current Weapon Data:"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Name: %s"), *CurrentWeaponData->WeaponName.ToString());
|
||
UE_LOG(LogTemp, Warning, TEXT("- Fire Rate: %.2f RPM"), CurrentWeaponData->FireRate);
|
||
UE_LOG(LogTemp, Warning, TEXT("- Damage: %.2f"), CurrentWeaponData->Damage);
|
||
UE_LOG(LogTemp, Warning, TEXT("- Magazine Size: %d"), CurrentWeaponData->MagazineSize);
|
||
UE_LOG(LogTemp, Warning, TEXT("- Muzzle Velocity: %.2f cm/s"), CurrentWeaponData->MuzzleVelocity);
|
||
UE_LOG(LogTemp, Warning, TEXT("- Heat Level: %.2f%%"), CurrentWeaponData->GetHeatLevel() * 100.0f);
|
||
UE_LOG(LogTemp, Warning, TEXT("- Overheated: %s"), CurrentWeaponData->IsOverheated() ? TEXT("Yes") : TEXT("No"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Debug Enabled: %s"), CurrentWeaponData->bEnableFireDebug ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- FirstPersonMesh: %s"), CurrentWeaponData->FirstPersonMesh ? *CurrentWeaponData->FirstPersonMesh->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- ThirdPersonMesh: %s"), CurrentWeaponData->ThirdPersonMesh ? *CurrentWeaponData->ThirdPersonMesh->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- StaticMesh: %s"), CurrentWeaponData->StaticMesh ? *CurrentWeaponData->StaticMesh->GetName() : TEXT("None"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- BulletProperties: %s"), CurrentWeaponData->BulletProperties ? *CurrentWeaponData->BulletProperties->GetName() : TEXT("None"));
|
||
}
|
||
|
||
if (WeaponActor)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("Weapon Actor State:"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- CanFire: %s"), WeaponActor->CanFire() ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Safety On: %s"), WeaponActor->bSafetyOn ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Hidden: %s"), WeaponActor->IsHidden() ? TEXT("True") : TEXT("False"));
|
||
UE_LOG(LogTemp, Warning, TEXT("- Magazine: %s"), WeaponActor->Magazine ? TEXT("Valid") : TEXT("None"));
|
||
|
||
EFireMode CurrentFireMode = WeaponActor->GetFireMode();
|
||
FString FireModeString = TEXT("Unknown");
|
||
switch (CurrentFireMode)
|
||
{
|
||
case EFireMode::FM_Auto: FireModeString = TEXT("Auto"); break;
|
||
case EFireMode::FM_Semiauto: FireModeString = TEXT("Semi"); break;
|
||
case EFireMode::FM_Burst: FireModeString = TEXT("Burst"); break;
|
||
case EFireMode::FM_Manual: FireModeString = TEXT("Manual"); break;
|
||
case EFireMode::FM_InterBurst: FireModeString = TEXT("InterBurst"); break;
|
||
case EFireMode::FM_Slamfire: FireModeString = TEXT("Slamfire"); break;
|
||
case EFireMode::FM_Gatling: FireModeString = TEXT("Gatling"); break;
|
||
}
|
||
UE_LOG(LogTemp, Warning, TEXT("- Fire Mode: %s"), *FireModeString);
|
||
}
|
||
|
||
UE_LOG(LogTemp, Warning, TEXT("CanFire() Result: %s"), CanFire() ? TEXT("TRUE") : TEXT("FALSE"));
|
||
UE_LOG(LogTemp, Warning, TEXT("=== END WEAPON DEBUG INFO ==="));
|
||
}
|
||
|
||
void UEBPlayerWeaponController::TriggerMuzzleFlash()
|
||
{
|
||
if (MuzzleFlashComponent && CurrentWeaponData && CurrentWeaponData->VFXConfig.MuzzleFlash)
|
||
{
|
||
// Use different effect if suppressed
|
||
UNiagaraSystem* EffectToUse = CurrentWeaponData->VFXConfig.MuzzleFlash;
|
||
if (CurrentWeaponData->VFXConfig.SuppressedMuzzleFlash)
|
||
{
|
||
// Check if weapon has suppressor (could add suppressor detection logic)
|
||
// EffectToUse = CurrentWeaponData->VFXConfig.SuppressedMuzzleFlash;
|
||
}
|
||
|
||
MuzzleFlashComponent->SetAsset(EffectToUse);
|
||
ApplyNiagaraParameters(MuzzleFlashComponent, CurrentWeaponData->VFXConfig.MuzzleFlashParams);
|
||
MuzzleFlashComponent->Activate(true);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::TriggerShellEjection()
|
||
{
|
||
if (WeaponActor && CurrentWeaponData && CurrentWeaponData->VFXConfig.EjectedShell)
|
||
{
|
||
FVector EjectionLocation = WeaponActor->GetMuzzleTransform().GetLocation();
|
||
FVector EjectionVelocity = CurrentWeaponData->VFXConfig.ShellEjectionVelocity;
|
||
|
||
UNiagaraFunctionLibrary::SpawnSystemAtLocation(
|
||
GetWorld(),
|
||
CurrentWeaponData->VFXConfig.EjectedShell,
|
||
EjectionLocation,
|
||
FRotator::ZeroRotator,
|
||
FVector::OneVector,
|
||
true,
|
||
true,
|
||
ENCPoolMethod::AutoRelease
|
||
);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SpawnTracerEffect(const FVector& StartLocation, const FVector& EndLocation)
|
||
{
|
||
if (CurrentWeaponData && CurrentWeaponData->VFXConfig.TracerEffect)
|
||
{
|
||
UNiagaraComponent* TracerComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
|
||
GetWorld(),
|
||
CurrentWeaponData->VFXConfig.TracerEffect,
|
||
StartLocation,
|
||
FRotator::ZeroRotator,
|
||
FVector::OneVector,
|
||
true,
|
||
true,
|
||
ENCPoolMethod::AutoRelease
|
||
);
|
||
|
||
if (TracerComponent)
|
||
{
|
||
// Set tracer end location
|
||
TracerComponent->SetVectorParameter(TEXT("EndLocation"), EndLocation);
|
||
|
||
// Apply tracer parameters
|
||
ApplyNiagaraParameters(TracerComponent, CurrentWeaponData->VFXConfig.TracerParams);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SpawnImpactEffect(const FVector& ImpactLocation, const FVector& ImpactNormal, UPhysicalMaterial* SurfaceMaterial)
|
||
{
|
||
if (!CurrentWeaponData)
|
||
return;
|
||
|
||
UNiagaraSystem* ImpactEffect = CurrentWeaponData->VFXConfig.HitImpact;
|
||
|
||
// Check for material-specific impact effect
|
||
if (SurfaceMaterial && CurrentWeaponData->VFXConfig.MaterialImpactEffects.Contains(SurfaceMaterial))
|
||
{
|
||
ImpactEffect = CurrentWeaponData->VFXConfig.MaterialImpactEffects[SurfaceMaterial];
|
||
}
|
||
|
||
if (ImpactEffect)
|
||
{
|
||
FRotator ImpactRotation = FRotationMatrix::MakeFromZ(ImpactNormal).Rotator();
|
||
|
||
UNiagaraComponent* ImpactComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
|
||
GetWorld(),
|
||
ImpactEffect,
|
||
ImpactLocation,
|
||
ImpactRotation,
|
||
FVector::OneVector,
|
||
true,
|
||
true,
|
||
ENCPoolMethod::AutoRelease
|
||
);
|
||
|
||
if (ImpactComponent)
|
||
{
|
||
// Set impact normal
|
||
ImpactComponent->SetVectorParameter(TEXT("ImpactNormal"), ImpactNormal);
|
||
|
||
// Apply impact parameters
|
||
ApplyNiagaraParameters(ImpactComponent, CurrentWeaponData->VFXConfig.HitImpactParams);
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::UpdateHeatEffects()
|
||
{
|
||
if (BarrelHeatComponent && CurrentWeaponData && CurrentWeaponData->VFXConfig.BarrelHeatEffect)
|
||
{
|
||
float HeatLevel = CurrentWeaponData->GetHeatLevel();
|
||
|
||
// Update heat effect intensity based on heat level
|
||
BarrelHeatComponent->SetFloatParameter(TEXT("HeatLevel"), HeatLevel);
|
||
BarrelHeatComponent->SetFloatParameter(TEXT("Intensity"), HeatLevel * 2.0f);
|
||
|
||
// Change effect visibility based on heat
|
||
if (HeatLevel > 0.3f && !BarrelHeatComponent->IsActive())
|
||
{
|
||
BarrelHeatComponent->Activate(true);
|
||
}
|
||
else if (HeatLevel <= 0.1f && BarrelHeatComponent->IsActive())
|
||
{
|
||
BarrelHeatComponent->Deactivate();
|
||
}
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetNiagaraFloatParameter(const FString& ParameterName, float Value)
|
||
{
|
||
if (MuzzleFlashComponent) MuzzleFlashComponent->SetFloatParameter(*ParameterName, Value);
|
||
if (MuzzleSmokeComponent) MuzzleSmokeComponent->SetFloatParameter(*ParameterName, Value);
|
||
if (BarrelHeatComponent) BarrelHeatComponent->SetFloatParameter(*ParameterName, Value);
|
||
if (AttachmentGlowComponent) AttachmentGlowComponent->SetFloatParameter(*ParameterName, Value);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetNiagaraVectorParameter(const FString& ParameterName, const FVector& Value)
|
||
{
|
||
if (MuzzleFlashComponent) MuzzleFlashComponent->SetVectorParameter(*ParameterName, Value);
|
||
if (MuzzleSmokeComponent) MuzzleSmokeComponent->SetVectorParameter(*ParameterName, Value);
|
||
if (BarrelHeatComponent) BarrelHeatComponent->SetVectorParameter(*ParameterName, Value);
|
||
if (AttachmentGlowComponent) AttachmentGlowComponent->SetVectorParameter(*ParameterName, Value);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::SetNiagaraColorParameter(const FString& ParameterName, const FLinearColor& Value)
|
||
{
|
||
if (MuzzleFlashComponent) MuzzleFlashComponent->SetColorParameter(*ParameterName, Value);
|
||
if (MuzzleSmokeComponent) MuzzleSmokeComponent->SetColorParameter(*ParameterName, Value);
|
||
if (BarrelHeatComponent) BarrelHeatComponent->SetColorParameter(*ParameterName, Value);
|
||
if (AttachmentGlowComponent) AttachmentGlowComponent->SetColorParameter(*ParameterName, Value);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::CreateNiagaraComponents()
|
||
{
|
||
if (!WeaponActor)
|
||
return;
|
||
|
||
// Create persistent Niagara components attached to weapon
|
||
MuzzleFlashComponent = NewObject<UNiagaraComponent>(WeaponActor);
|
||
MuzzleSmokeComponent = NewObject<UNiagaraComponent>(WeaponActor);
|
||
BarrelHeatComponent = NewObject<UNiagaraComponent>(WeaponActor);
|
||
AttachmentGlowComponent = NewObject<UNiagaraComponent>(WeaponActor);
|
||
|
||
// Attach to muzzle socket
|
||
if (MuzzleFlashComponent)
|
||
{
|
||
MuzzleFlashComponent->SetupAttachment(WeaponActor->SkeletalGunMesh, TEXT("MuzzleSocket"));
|
||
MuzzleFlashComponent->SetAutoActivate(false);
|
||
}
|
||
|
||
if (MuzzleSmokeComponent)
|
||
{
|
||
MuzzleSmokeComponent->SetupAttachment(WeaponActor->SkeletalGunMesh, TEXT("MuzzleSocket"));
|
||
MuzzleSmokeComponent->SetAutoActivate(false);
|
||
}
|
||
|
||
// Attach heat effect to barrel
|
||
if (BarrelHeatComponent)
|
||
{
|
||
BarrelHeatComponent->SetupAttachment(WeaponActor->SkeletalGunMesh, TEXT("BarrelSocket"));
|
||
BarrelHeatComponent->SetAutoActivate(false);
|
||
}
|
||
|
||
// Attach glow effect to weapon body
|
||
if (AttachmentGlowComponent)
|
||
{
|
||
AttachmentGlowComponent->SetupAttachment(WeaponActor->SkeletalGunMesh);
|
||
AttachmentGlowComponent->SetAutoActivate(false);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::UpdateNiagaraComponents()
|
||
{
|
||
if (!CurrentWeaponData || !bEnableAdvancedVFX)
|
||
return;
|
||
|
||
// Update muzzle flash
|
||
if (MuzzleFlashComponent && CurrentWeaponData->VFXConfig.MuzzleFlash)
|
||
{
|
||
MuzzleFlashComponent->SetAsset(CurrentWeaponData->VFXConfig.MuzzleFlash);
|
||
ApplyNiagaraParameters(MuzzleFlashComponent, CurrentWeaponData->VFXConfig.MuzzleFlashParams);
|
||
}
|
||
|
||
// Update muzzle smoke
|
||
if (MuzzleSmokeComponent && CurrentWeaponData->VFXConfig.MuzzleSmoke)
|
||
{
|
||
MuzzleSmokeComponent->SetAsset(CurrentWeaponData->VFXConfig.MuzzleSmoke);
|
||
ApplyNiagaraParameters(MuzzleSmokeComponent, CurrentWeaponData->VFXConfig.MuzzleSmokeParams);
|
||
}
|
||
|
||
// Update barrel heat
|
||
if (BarrelHeatComponent && CurrentWeaponData->VFXConfig.BarrelHeatEffect)
|
||
{
|
||
BarrelHeatComponent->SetAsset(CurrentWeaponData->VFXConfig.BarrelHeatEffect);
|
||
}
|
||
|
||
// Update attachment glow
|
||
if (AttachmentGlowComponent && CurrentWeaponData->VFXConfig.AttachmentGlowEffect)
|
||
{
|
||
AttachmentGlowComponent->SetAsset(CurrentWeaponData->VFXConfig.AttachmentGlowEffect);
|
||
}
|
||
|
||
// Apply global parameters
|
||
ApplyNiagaraParameters(MuzzleFlashComponent, CurrentWeaponData->GlobalEffectParams);
|
||
ApplyNiagaraParameters(MuzzleSmokeComponent, CurrentWeaponData->GlobalEffectParams);
|
||
ApplyNiagaraParameters(BarrelHeatComponent, CurrentWeaponData->GlobalEffectParams);
|
||
ApplyNiagaraParameters(AttachmentGlowComponent, CurrentWeaponData->GlobalEffectParams);
|
||
}
|
||
|
||
void UEBPlayerWeaponController::ApplyNiagaraParameters(UNiagaraComponent* Component, const FNiagaraEffectParams& Params)
|
||
{
|
||
if (!Component)
|
||
return;
|
||
|
||
// Apply basic parameters
|
||
Component->SetFloatParameter(TEXT("Intensity"), Params.Intensity);
|
||
Component->SetFloatParameter(TEXT("Scale"), Params.Scale);
|
||
Component->SetColorParameter(TEXT("ColorTint"), Params.ColorTint);
|
||
|
||
// Apply custom parameters
|
||
for (const auto& FloatParam : Params.FloatParameters)
|
||
{
|
||
Component->SetFloatParameter(*FloatParam.Key, FloatParam.Value);
|
||
}
|
||
|
||
for (const auto& VectorParam : Params.VectorParameters)
|
||
{
|
||
Component->SetVectorParameter(*VectorParam.Key, VectorParam.Value);
|
||
}
|
||
|
||
for (const auto& BoolParam : Params.BoolParameters)
|
||
{
|
||
Component->SetBoolParameter(*BoolParam.Key, BoolParam.Value);
|
||
}
|
||
}
|
||
|
||
void UEBPlayerWeaponController::UpdateWeaponHeat(float DeltaTime)
|
||
{
|
||
if (CurrentWeaponData && HasAuthority())
|
||
{
|
||
CurrentWeaponData->UpdateHeat(DeltaTime);
|
||
CurrentHeatLevel = CurrentWeaponData->GetHeatLevel();
|
||
|
||
// Update heat effects if enabled
|
||
if (bEnableAdvancedVFX)
|
||
{
|
||
UpdateHeatEffects();
|
||
}
|
||
}
|
||
} |