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

1370 lines
42 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 "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;
// Set WeaponData on the WeaponActor as well
if (WeaponActor)
{
WeaponActor->WeaponData = 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();
}
}
}