// 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& 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& 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(Owner); WeaponActor = World->SpawnActor(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(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(WeaponActor->SkeletalGunMesh) : Cast(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 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(WeaponActor); MuzzleSmokeComponent = NewObject(WeaponActor); BarrelHeatComponent = NewObject(WeaponActor); AttachmentGlowComponent = NewObject(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(); } } }