From e7989cfe0a44b891c55845a3bbe8334ffd82444d Mon Sep 17 00:00:00 2001 From: Zack3D Date: Fri, 4 Jul 2025 03:26:03 -0700 Subject: [PATCH] Gun Centric Refactor Start --- Documentation/Gun_Centric_Architecture.md | 291 ++++++++ EasyBallistics.uplugin | 6 + Source/EasyBallistics/EasyBallistics.Build.cs | 3 +- Source/EasyBallistics/Private/EBBarrel.cpp | 151 ++++ Source/EasyBallistics/Private/EBGun.cpp | 693 ++++++++++++++++++ Source/EasyBallistics/Private/EBMagazine.cpp | 405 ++++++++++ .../Private/EBWeaponConfiguration.cpp | 82 +++ Source/EasyBallistics/Public/EBBarrel.h | 101 ++- Source/EasyBallistics/Public/EBGun.h | 250 +++++++ Source/EasyBallistics/Public/EBMagazine.h | 229 ++++++ .../Public/EBWeaponConfiguration.h | 310 ++++++++ .../EasyBallisticsEditor.Build.cs | 23 +- .../Private/EBBarrelComponentFactory.cpp | 148 +++- .../Private/EBGunActorFactory.cpp | 44 ++ .../Private/EBMagazineComponentFactory.cpp | 100 +++ .../EBMathematicalBallisticsFactory.cpp | 17 +- .../Private/EBWeaponConfigurationFactory.cpp | 136 ++++ .../Private/EasyBallisticsEditor.cpp | 114 ++- .../Public/EBBarrelComponentFactory.h | 28 +- .../Public/EBGunActorFactory.h | 21 + .../Public/EBMagazineComponentFactory.h | 21 + .../Public/EBMathematicalBallisticsFactory.h | 14 + .../Public/EBWeaponConfigurationFactory.h | 34 + .../Public/EasyBallisticsEditor.h | 16 +- docs/docs/core-concepts/overview.md | 73 +- docs/docs/getting-started/quick-start.md | 4 + docs/docs/intro.md | 5 +- docs/docs/tutorials/blueprint-integration.md | 8 + docs/docs/tutorials/gun-blueprint-guide.md | 533 ++++++++++++++ docs/sidebars.js | 19 +- 30 files changed, 3783 insertions(+), 96 deletions(-) create mode 100644 Documentation/Gun_Centric_Architecture.md create mode 100644 Source/EasyBallistics/Private/EBGun.cpp create mode 100644 Source/EasyBallistics/Private/EBMagazine.cpp create mode 100644 Source/EasyBallistics/Private/EBWeaponConfiguration.cpp create mode 100644 Source/EasyBallistics/Public/EBGun.h create mode 100644 Source/EasyBallistics/Public/EBMagazine.h create mode 100644 Source/EasyBallistics/Public/EBWeaponConfiguration.h create mode 100644 Source/EasyBallisticsEditor/Private/EBGunActorFactory.cpp create mode 100644 Source/EasyBallisticsEditor/Private/EBMagazineComponentFactory.cpp create mode 100644 Source/EasyBallisticsEditor/Private/EBWeaponConfigurationFactory.cpp create mode 100644 Source/EasyBallisticsEditor/Public/EBGunActorFactory.h create mode 100644 Source/EasyBallisticsEditor/Public/EBMagazineComponentFactory.h create mode 100644 Source/EasyBallisticsEditor/Public/EBWeaponConfigurationFactory.h create mode 100644 docs/docs/tutorials/gun-blueprint-guide.md diff --git a/Documentation/Gun_Centric_Architecture.md b/Documentation/Gun_Centric_Architecture.md new file mode 100644 index 0000000..91760d7 --- /dev/null +++ b/Documentation/Gun_Centric_Architecture.md @@ -0,0 +1,291 @@ +# Gun-Centric Architecture Guide + +The EasyBallistics plugin has been refactored to use a gun-centric approach where weapons are the primary actors with modular components. This consolidates scattered settings and provides a more intuitive workflow for creating realistic weapons. + +## Overview + +The new architecture consists of: + +1. **AEBGun** - Main weapon actor +2. **UEBMagazine** - Magazine component for ammunition storage +3. **UEBBarrel** - Refocused barrel component for ballistics +4. **UEBWeaponConfiguration** - Asset that consolidates all weapon settings +5. **UEBBulletPropertiesAsset** - Bullet characteristics (unchanged) + +## Core Components + +### Gun Actor (AEBGun) + +The main weapon actor that brings everything together: + +```cpp +// C++ Usage +AEBGun* MyGun = GetWorld()->SpawnActor(); +MyGun->ApplyWeaponConfiguration(MyM4Config); +MyGun->LoadMagazine(BulletArray); +MyGun->PullTrigger(); +``` + +**Key Features:** +- Centralized weapon state management +- Realistic gun controls (safety, charging handle, trigger) +- Automatic configuration from weapon assets +- Full multiplayer support +- Event-driven architecture + +### Magazine Component (UEBMagazine) + +Handles ammunition storage and feeding: + +**Magazine Types:** +- Standard Box Magazine +- Drum Magazine +- Tube Magazine (shotguns) +- Stripper Clip +- Belt Fed (machine guns) +- Revolver Cylinder +- Internal Magazine (bolt-actions) + +**Features:** +- Mixed ammunition support +- Malfunction simulation +- Bolt hold-open functionality +- Realistic capacity limits +- Feed rate control for belt-fed weapons + +### Barrel Component (UEBBarrel) + +Fully integrated with the gun system for comprehensive ballistics: + +**Enhanced Integration:** +- **Parent Gun Reference**: Direct connection to main gun actor +- **Configuration Driven**: All settings applied from weapon configuration +- **Realistic Calculations**: Physics-based muzzle velocity and accuracy +- **Legacy Compatibility**: Maintains old ammo system for existing projects + +**Barrel Properties Applied from Config:** +- Barrel length, rifling twist rate, bore radius +- Muzzle velocity range and variations +- Accuracy and spread characteristics +- Recoil direction and multipliers + +**Smart Calculations:** +```cpp +// Barrel automatically calculates effective properties +float MuzzleVel = Gun->GetEffectiveMuzzleVelocity(); // Considers bullet weight, barrel length +float Accuracy = Gun->GetBarrelAccuracy(); // Factors in barrel length, rifling +FVector Recoil = Gun->CalculateRecoilImpulse(); // Physics-based recoil calculation +``` + +**Features:** +- Automatic muzzle velocity calculation based on barrel length and bullet weight +- Rifling twist optimization for different bullet types +- Physics-based recoil impulse generation +- Direct magazine and gun state integration + +### Weapon Configuration Asset (UEBWeaponConfiguration) + +Consolidates all weapon settings into a single, manageable asset: + +**Configuration Sections:** +- **Barrel Configuration**: Length, rifling, muzzle velocity, accuracy +- **Fire Control**: Available fire modes, rates, burst settings +- **Reliability**: Malfunction rates, environmental factors +- **Ammunition**: Compatible bullet types and magazine settings +- **Physics**: Mathematical vs artistic ballistics settings + +## Setting Up a New Weapon + +### 1. Create Weapon Configuration Asset + +1. In Content Browser, right-click and navigate to **Ballistics → Weapon Configuration** +2. Name it based on your weapon (e.g., "M4_Carbine_Config") +3. The factory will auto-configure based on the name: + - M4/AR15 → Modern assault rifle settings + - AK → AK-type weapon settings + - Pistol/Glock/1911 → Handgun settings + - Sniper/Bolt → Bolt-action rifle settings + - Shotgun → Shotgun settings + - LMG/Machine → Light machine gun settings + +### 2. Configure Your Weapon + +Open the weapon configuration asset and customize: + +**Weapon Info:** +``` +Weapon Name: "M4 Carbine" +Manufacturer: "Colt" +Model: "M4A1" +Caliber: "5.56x45mm NATO" +``` + +**Fire Control:** +``` +Available Fire Modes: [Semi-Auto, Full Auto] +Default Fire Mode: Semi-Auto +Fire Rate Min: 700 RPM +Fire Rate Max: 950 RPM +Burst Count: 3 +``` + +**Barrel Configuration:** +``` +Barrel Length: 14.5 inches +Muzzle Velocity: 91440 cm/s (~3000 fps) +Inherent Accuracy: 0.001 radians +Spread Max: 0.001 radians +``` + +### 3. Create Gun Blueprint + +1. Create a new Blueprint based on **EBGun** +2. Set the **Weapon Config** to your configuration asset +3. Configure the gun mesh and sockets +4. Set up animations for firing, reloading, etc. + +### 4. Set Up Ammunition + +1. Create **Bullet Properties Assets** for your ammunition types +2. Add them to the weapon configuration's **Compatible Bullet Types** +3. Set the **Default Bullet Type** + +## Blueprint Usage + +### Basic Firing + +```cpp +// Event: Player Input Trigger Pressed +Gun->PullTrigger(); + +// Event: Player Input Trigger Released +Gun->ReleaseTrigger(); +``` + +### Ammunition Management + +```cpp +// Load magazine with specific bullet types +TArray Ammo; +Ammo.Add(M855_Bullet); +Ammo.Add(M193_Bullet); +Gun->LoadMagazine(Ammo); + +// Check ammunition status +int32 RoundsLeft = Gun->GetRoundsInMagazine(); +int32 TotalRounds = Gun->GetTotalRounds(); +bool CanShoot = Gun->CanFire(); +``` + +### Weapon Controls + +```cpp +// Safety controls +Gun->SetSafety(true); // Safety on +Gun->SetSafety(false); // Safety off + +// Manual cycling (bolt-actions, pump shotguns) +Gun->ChargingHandle(); // Cycle action + +// Fire mode selection +Gun->SetFireMode(EFireMode::FM_Burst); +``` + +### Events + +```cpp +// Gun Events +Gun->OnGunFired.AddDynamic(this, &AMyWeapon::OnGunFired); +Gun->OnMagazineEmpty.AddDynamic(this, &AMyWeapon::OnMagazineEmpty); +Gun->OnGunStateChanged.AddDynamic(this, &AMyWeapon::OnStateChanged); + +// Magazine Events +Magazine->OnBulletFed.AddDynamic(this, &AMyWeapon::OnBulletFed); +Magazine->OnMagazineJammed.AddDynamic(this, &AMyWeapon::OnJammed); +``` + +## Advanced Features + +### Custom Magazine Types + +```cpp +// Belt-fed machine gun +MagazineConfig.MagazineType = EMagazineType::MT_Belt; +MagazineConfig.MaxCapacity = 200; +MagazineConfig.FeedRate = 0.05f; // Fast feeding + +// Tube magazine shotgun +MagazineConfig.MagazineType = EMagazineType::MT_Tube; +MagazineConfig.MaxCapacity = 8; +MagazineConfig.bAllowMixedBulletTypes = true; // Different shell types +``` + +### Reliability Simulation + +```cpp +// Environmental effects +ReliabilityConfig.DirtSensitivity = 0.3f; +ReliabilityConfig.TemperatureSensitivity = 0.002f; +ReliabilityConfig.MinOperatingTemp = -30.0f; +ReliabilityConfig.MaxOperatingTemp = 55.0f; + +// Wear and maintenance +ReliabilityConfig.WearFactor = 0.0001f; +ReliabilityConfig.ServiceLife = 15000; // rounds +``` + +### Multi-Barrel Systems + +```cpp +// For weapons with multiple barrels (like gatling guns) +BarrelConfig.GatlingSpoolUpTime = 2.0f; +BarrelConfig.GatlingSpoolDownTime = 3.0f; +FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Gatling); +``` + +## Migration from Old System + +### Before (Scattered Settings) +```cpp +// Settings spread across multiple components +Barrel->FireRate = 650.0f; +Barrel->Spread = 0.001f; +Barrel->MuzzleVelocity = 91440.0f; +Bullet->Mass = 0.004f; +Bullet->Diameter = 0.556f; +MaterialMap->PenetrationDepth = 15.0f; +``` + +### After (Centralized Configuration) +```cpp +// All settings in one place +WeaponConfig->FireControlConfig.FireRateMax = 650.0f; +WeaponConfig->BarrelConfig.SpreadMax = 0.001f; +WeaponConfig->BarrelConfig.MuzzleVelocityMax = 91440.0f; +WeaponConfig->DefaultBulletType = MyBulletAsset; +WeaponConfig->MaterialResponseMap = MyMaterialMap; + +// Apply to gun +Gun->ApplyWeaponConfiguration(WeaponConfig); +``` + +## Benefits of New Architecture + +1. **Centralized Settings**: All weapon parameters in one place +2. **Realistic Simulation**: Proper magazine, safety, and malfunction systems +3. **Modular Design**: Easy to swap magazines, barrels, configurations +4. **Better Workflow**: Intuitive gun-first approach +5. **Reduced Complexity**: No more hunting for scattered settings +6. **Multiplayer Ready**: Built-in replication and authority handling +7. **Asset Reuse**: Share configurations between similar weapons + +## Best Practices + +1. **Use Weapon Configurations**: Always create configuration assets for your weapons +2. **Group Similar Weapons**: Share configurations between variants (M4A1, M4A3, etc.) +3. **Plan Magazine Types**: Choose appropriate magazine types for realism +4. **Test Reliability**: Adjust malfunction rates for your game's difficulty +5. **Configure Events**: Use the event system for UI updates and effects +6. **Consider Physics Mode**: Choose mathematical vs artistic based on your needs + +This new architecture provides a much more intuitive and consolidated approach to weapon creation while maintaining all the advanced ballistics features of EasyBallistics. \ No newline at end of file diff --git a/EasyBallistics.uplugin b/EasyBallistics.uplugin index 13180d1..c6afea5 100644 --- a/EasyBallistics.uplugin +++ b/EasyBallistics.uplugin @@ -35,5 +35,11 @@ "Mac" ] } + ], + "Plugins": [ + { + "Name": "EditorScriptingUtilities", + "Enabled": true + } ] } \ No newline at end of file diff --git a/Source/EasyBallistics/EasyBallistics.Build.cs b/Source/EasyBallistics/EasyBallistics.Build.cs index 59be64b..a29a333 100644 --- a/Source/EasyBallistics/EasyBallistics.Build.cs +++ b/Source/EasyBallistics/EasyBallistics.Build.cs @@ -22,7 +22,8 @@ public class EasyBallistics : ModuleRules { "CoreUObject", "Engine", - "PhysicsCore" + "PhysicsCore", + "NetCore" // ... add private dependencies that you statically link with here ... } ); diff --git a/Source/EasyBallistics/Private/EBBarrel.cpp b/Source/EasyBallistics/Private/EBBarrel.cpp index 6eccb7a..1863318 100644 --- a/Source/EasyBallistics/Private/EBBarrel.cpp +++ b/Source/EasyBallistics/Private/EBBarrel.cpp @@ -2,6 +2,10 @@ #include "EBBarrel.h" #include "EBBullet.h" #include "EngineUtils.h" +#include "EBGun.h" +#include "EBMagazine.h" +#include "EBWeaponConfiguration.h" +#include "EBBulletProperties.h" UEBBarrel::UEBBarrel() { PrimaryComponentTick.bCanEverTick = true; @@ -326,4 +330,151 @@ FString UEBBarrel::GetBulletDebugInfo() const DebugInfo += FString::Printf(TEXT("Impact Debug: %s\n"), DebugImpactInfo ? TEXT("ON") : TEXT("OFF")); return DebugInfo; +} + +void UEBBarrel::SetParentGun(AEBGun* Gun) +{ + ParentGun = Gun; +} + +void UEBBarrel::ApplyBarrelConfiguration(const FBarrelConfiguration& BarrelConfig) +{ + // Apply barrel physical properties + BarrelLength = BarrelConfig.BarrelLength; + RiflingTwist = BarrelConfig.RiflingTwist; + BoreRadius = BarrelConfig.BoreRadius; + MuzzleVelocityMin = BarrelConfig.MuzzleVelocityMin; + MuzzleVelocityMax = BarrelConfig.MuzzleVelocityMax; + + // Apply ballistic properties + Spread = BarrelConfig.SpreadMax; + SpreadBias = BarrelConfig.SpreadBias; + MuzzleVelocityMultiplierMin = 1.0f - BarrelConfig.MuzzleVelocityVariation; + MuzzleVelocityMultiplierMax = 1.0f + BarrelConfig.MuzzleVelocityVariation; + + // Apply recoil properties + RecoilMultiplier = BarrelConfig.RecoilMultiplier; + + UE_LOG(LogTemp, Log, TEXT("Barrel Configuration Applied - Length: %.1f, Twist: 1:%.1f, Velocity: %.0f-%.0f cm/s"), + BarrelLength, RiflingTwist, MuzzleVelocityMin, MuzzleVelocityMax); +} + +UEBMagazine* UEBBarrel::GetConnectedMagazine() const +{ + if (ParentGun) + { + return ParentGun->Magazine; + } + return nullptr; +} + +UEBBulletPropertiesAsset* UEBBarrel::GetChamberedBulletType() const +{ + if (ParentGun) + { + return ParentGun->GetChamberedBulletType(); + } + return nullptr; +} + +float UEBBarrel::CalculateEffectiveMuzzleVelocity(UEBBulletPropertiesAsset* BulletType) const +{ + if (!BulletType) + { + return MuzzleVelocityMin; + } + + // Base velocity from barrel configuration + float BaseVelocity = FMath::RandRange(MuzzleVelocityMin, MuzzleVelocityMax); + + // Apply velocity multiplier variation + float VelocityMultiplier = FMath::RandRange(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax); + BaseVelocity *= VelocityMultiplier; + + // Barrel length affects velocity (longer barrels = higher velocity, up to a point) + float LengthFactor = FMath::Clamp((BarrelLength - 10.0f) / 20.0f, 0.8f, 1.2f); + BaseVelocity *= LengthFactor; + + // Bullet weight affects velocity (heavier bullets = lower velocity) + float BulletMass = BulletType->BulletProperties.GetMassKg(); + float MassFactor = FMath::Clamp(0.004f / BulletMass, 0.7f, 1.3f); // Normalized around 4g bullet + BaseVelocity *= MassFactor; + + return BaseVelocity; +} + +float UEBBarrel::CalculateBarrelAccuracy() const +{ + // Base accuracy from spread + float BaseAccuracy = Spread; + + // Barrel length improves accuracy + float LengthFactor = FMath::Clamp(BarrelLength / 20.0f, 0.5f, 1.5f); + BaseAccuracy /= LengthFactor; + + // Rifling twist affects accuracy (optimal twist varies by bullet) + // For now, assume 1:7 to 1:9 is optimal for 5.56mm + float TwistOptimal = 8.0f; + float TwistFactor = 1.0f - FMath::Abs(RiflingTwist - TwistOptimal) * 0.01f; + TwistFactor = FMath::Clamp(TwistFactor, 0.8f, 1.2f); + BaseAccuracy /= TwistFactor; + + return BaseAccuracy; +} + +FVector UEBBarrel::CalculateRecoilImpulse(UEBBulletPropertiesAsset* BulletType) const +{ + if (!BulletType) + { + return FVector::ZeroVector; + } + + // Calculate recoil based on bullet momentum + float BulletMass = BulletType->BulletProperties.GetMassKg(); + float MuzzleVelocity = CalculateEffectiveMuzzleVelocity(BulletType); + float Momentum = BulletMass * (MuzzleVelocity / 100.0f); // Convert cm/s to m/s + + // Base recoil impulse + float RecoilForce = Momentum * RecoilMultiplier; + + // Barrel length affects recoil (longer barrels spread recoil over more time, but same total impulse) + float LengthFactor = FMath::Clamp(20.0f / BarrelLength, 0.8f, 1.5f); + RecoilForce *= LengthFactor; + + // Direction from weapon config (if parent gun has config) + FVector RecoilDirection = FVector(0, 0, 1); // Default upward + if (ParentGun && ParentGun->WeaponConfig) + { + RecoilDirection = ParentGun->WeaponConfig->BarrelConfig.RecoilDirection; + } + + return RecoilDirection * RecoilForce; +} + +int UEBBarrel::GetMagazineRoundCount() const +{ + UEBMagazine* Magazine = GetConnectedMagazine(); + if (Magazine) + { + return Magazine->GetCurrentRoundCount(); + } + return 0; +} + +bool UEBBarrel::HasChamberedRound() const +{ + if (ParentGun) + { + return ParentGun->IsLoaded(); + } + return false; +} + +bool UEBBarrel::CanFireFromGun() const +{ + if (ParentGun) + { + return ParentGun->CanFire(); + } + return false; } \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBGun.cpp b/Source/EasyBallistics/Private/EBGun.cpp new file mode 100644 index 0000000..05a5d80 --- /dev/null +++ b/Source/EasyBallistics/Private/EBGun.cpp @@ -0,0 +1,693 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBGun.h" +#include "EBMagazine.h" +#include "EBWeaponConfiguration.h" +#include "EBBullet.h" +#include "Net/UnrealNetwork.h" +#include "Engine/Engine.h" +#include "TimerManager.h" + +AEBGun::AEBGun() +{ + PrimaryActorTick.bCanEverTick = true; + bReplicates = true; + SetReplicateMovement(true); + + // Create the gun mesh component + GunMesh = CreateDefaultSubobject(TEXT("GunMesh")); + RootComponent = GunMesh; + + // Create the barrel component + Barrel = CreateDefaultSubobject(TEXT("Barrel")); + Barrel->SetupAttachment(GunMesh, TEXT("MuzzleSocket")); + Barrel->SetParentGun(this); + + // Create the magazine component + Magazine = CreateDefaultSubobject(TEXT("Magazine")); + + // Initialize state + CurrentGunState = EGunState::GS_Ready; + PreviousGunState = EGunState::GS_Ready; + bSafetyOn = false; + bChamberedRound = false; + bHammerCocked = false; + bTriggerPressed = false; + bCanProcessNextShot = true; + BurstShotsFired = 0; + ChamberedBulletType = nullptr; +} + +void AEBGun::BeginPlay() +{ + Super::BeginPlay(); + + // Apply weapon configuration if available + if (WeaponConfig) + { + ApplyWeaponConfiguration(WeaponConfig); + } + + // Initialize magazine + if (Magazine) + { + Magazine->OnMagazineEmpty.AddDynamic(this, &AEBGun::OnMagazineEmptyHandler); + Magazine->OnBulletFed.AddDynamic(this, &AEBGun::OnBulletFedHandler); + } + + // Initialize barrel + if (Barrel) + { + Barrel->ShotFired.AddDynamic(this, &AEBGun::OnBarrelShotFiredHandler); + } +} + +void AEBGun::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + // Process firing state + if (CurrentGunState == EGunState::GS_Firing) + { + ProcessFiring(); + } +} + +void AEBGun::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AEBGun, CurrentGunState); + DOREPLIFETIME(AEBGun, bSafetyOn); + DOREPLIFETIME(AEBGun, bChamberedRound); + DOREPLIFETIME(AEBGun, bHammerCocked); + DOREPLIFETIME(AEBGun, ChamberedBulletType); +} + +void AEBGun::PullTrigger() +{ + if (HasAuthority()) + { + ServerPullTrigger(); + } + else + { + ServerPullTrigger(); + } +} + +void AEBGun::ServerPullTrigger_Implementation() +{ + if (!CanFire()) + { + return; + } + + bTriggerPressed = true; + + if (CurrentGunState == EGunState::GS_Ready) + { + SetGunState(EGunState::GS_Firing); + } +} + +void AEBGun::ReleaseTrigger() +{ + if (HasAuthority()) + { + ServerReleaseTrigger(); + } + else + { + ServerReleaseTrigger(); + } +} + +void AEBGun::ServerReleaseTrigger_Implementation() +{ + bTriggerPressed = false; + + // Handle fire mode specific behavior + if (Barrel && WeaponConfig) + { + EFireMode CurrentFireMode = Barrel->FireMode; + if (CurrentFireMode == EFireMode::FM_Semiauto || CurrentFireMode == EFireMode::FM_Manual) + { + if (CurrentGunState == EGunState::GS_Firing) + { + SetGunState(EGunState::GS_Ready); + } + } + } +} + +void AEBGun::SetSafety(bool bNewSafetyState) +{ + if (HasAuthority()) + { + ServerSetSafety(bNewSafetyState); + } + else + { + ServerSetSafety(bNewSafetyState); + } +} + +void AEBGun::ServerSetSafety_Implementation(bool bNewSafetyState) +{ + if (!WeaponConfig || !WeaponConfig->FireControlConfig.bHasSafety) + { + return; + } + + bool bOldSafetyState = bSafetyOn; + bSafetyOn = bNewSafetyState; + + if (bSafetyOn && CurrentGunState == EGunState::GS_Firing) + { + SetGunState(EGunState::GS_SafetyOn); + } + else if (!bSafetyOn && CurrentGunState == EGunState::GS_SafetyOn) + { + SetGunState(EGunState::GS_Ready); + } + + if (bOldSafetyState != bSafetyOn) + { + OnSafetyChanged.Broadcast(bSafetyOn); + MulticastOnSafetyChanged(bSafetyOn); + } +} + +void AEBGun::CockHammer() +{ + if (HasAuthority()) + { + bHammerCocked = true; + } +} + +void AEBGun::ReleaseHammer() +{ + if (HasAuthority()) + { + bHammerCocked = false; + } +} + +void AEBGun::LoadMagazine(TArray BulletTypes) +{ + if (Magazine) + { + Magazine->LoadBullets(BulletTypes); + } +} + +void AEBGun::EjectMagazine() +{ + if (HasAuthority()) + { + ServerEjectMagazine(); + } + else + { + ServerEjectMagazine(); + } +} + +void AEBGun::ServerEjectMagazine_Implementation() +{ + if (Magazine) + { + Magazine->UnloadAllBullets(); + } + + OnMagazineChanged.Broadcast(nullptr); + MulticastOnMagazineChanged(nullptr); +} + +void AEBGun::InsertMagazine(UEBMagazine* NewMagazine) +{ + if (HasAuthority()) + { + ServerInsertMagazine(NewMagazine); + } + else + { + ServerInsertMagazine(NewMagazine); + } +} + +void AEBGun::ServerInsertMagazine_Implementation(UEBMagazine* NewMagazine) +{ + if (!NewMagazine) + { + return; + } + + // Replace magazine component (in a real implementation, this would be more complex) + Magazine = NewMagazine; + + OnMagazineChanged.Broadcast(NewMagazine); + MulticastOnMagazineChanged(NewMagazine); +} + +void AEBGun::ChargingHandle() +{ + if (HasAuthority()) + { + ServerChargingHandle(); + } + else + { + ServerChargingHandle(); + } +} + +void AEBGun::ServerChargingHandle_Implementation() +{ + // Eject chambered round if present + if (bChamberedRound) + { + ChamberedBulletType = nullptr; + bChamberedRound = false; + } + + // Feed new round from magazine + if (Magazine && Magazine->CanFeed()) + { + UEBBulletPropertiesAsset* NewBullet = Magazine->FeedNextBullet(); + if (NewBullet) + { + ChamberedBulletType = NewBullet; + bChamberedRound = true; + } + } + + // Update gun state + if (CurrentGunState == EGunState::GS_Empty && bChamberedRound) + { + SetGunState(EGunState::GS_Ready); + } + else if (!bChamberedRound && (!Magazine || Magazine->bIsEmpty)) + { + SetGunState(EGunState::GS_Empty); + } +} + +void AEBGun::EjectChamberedRound() +{ + if (HasAuthority()) + { + if (bChamberedRound) + { + ChamberedBulletType = nullptr; + bChamberedRound = false; + } + } +} + +bool AEBGun::CanFire() const +{ + if (bSafetyOn) + { + return false; + } + + if (CurrentGunState == EGunState::GS_Jammed || + CurrentGunState == EGunState::GS_Empty || + CurrentGunState == EGunState::GS_SafetyOn || + CurrentGunState == EGunState::GS_Malfunction) + { + return false; + } + + if (!bChamberedRound || !ChamberedBulletType) + { + return false; + } + + return true; +} + +bool AEBGun::IsLoaded() const +{ + return bChamberedRound && ChamberedBulletType != nullptr; +} + +int32 AEBGun::GetRoundsInMagazine() const +{ + if (Magazine) + { + return Magazine->GetCurrentRoundCount(); + } + return 0; +} + +int32 AEBGun::GetTotalRounds() const +{ + int32 TotalRounds = 0; + + if (Magazine) + { + TotalRounds += Magazine->GetCurrentRoundCount(); + } + + if (bChamberedRound) + { + TotalRounds += 1; + } + + return TotalRounds; +} + +UEBBulletPropertiesAsset* AEBGun::GetChamberedBulletType() const +{ + return ChamberedBulletType; +} + +void AEBGun::ApplyWeaponConfiguration(UEBWeaponConfiguration* NewConfig) +{ + if (!NewConfig) + { + return; + } + + WeaponConfig = NewConfig; + + // Apply configuration to barrel + if (Barrel) + { + // Set parent gun reference + Barrel->SetParentGun(this); + + // Apply barrel-specific configuration + Barrel->ApplyBarrelConfiguration(NewConfig->BarrelConfig); + + // Apply fire control configuration + Barrel->FireMode = NewConfig->FireControlConfig.DefaultFireMode; + Barrel->FireRateMin = NewConfig->FireControlConfig.FireRateMin / 60.0f; // Convert RPM to RPS + Barrel->FireRateMax = NewConfig->FireControlConfig.FireRateMax / 60.0f; + Barrel->BurstCount = NewConfig->FireControlConfig.BurstCount; + Barrel->BurstCooldown = NewConfig->FireControlConfig.BurstCooldown; + Barrel->GatlingSpoolUpTime = NewConfig->FireControlConfig.GatlingSpoolUpTime; + Barrel->GatlingSpoolDownTime = NewConfig->FireControlConfig.GatlingSpoolDownTime; + Barrel->GatlingAutoSpool = NewConfig->FireControlConfig.bGatlingAutoSpool; + + // Apply networking configuration + Barrel->ReplicateVariables = NewConfig->bReplicateVariables; + Barrel->ReplicateShotFiredEvents = NewConfig->bReplicateShotEvents; + Barrel->ClientSideAim = NewConfig->bClientSideAim; + + // Disable legacy ammo system in favor of gun's magazine system + Barrel->CycleAmmo = false; + + UE_LOG(LogTemp, Log, TEXT("Gun '%s' applied configuration to barrel"), *NewConfig->GetWeaponDisplayName()); + } + + // Apply configuration to magazine + if (Magazine) + { + Magazine->MagazineConfig.MaxCapacity = NewConfig->DefaultMagazineCapacity; + Magazine->MagazineConfig.MagazineType = NewConfig->DefaultMagazineType; + Magazine->MagazineConfig.bAllowMixedBulletTypes = NewConfig->bAllowMixedAmmo; + Magazine->MagazineConfig.MalfunctionChance = NewConfig->ReliabilityConfig.MalfunctionRate; + } + + // Set default safety state + if (NewConfig->FireControlConfig.bHasSafety) + { + bSafetyOn = NewConfig->FireControlConfig.bDefaultSafetyOn; + } + else + { + bSafetyOn = false; + } +} + +void AEBGun::SetFireMode(EFireMode NewFireMode) +{ + if (HasAuthority()) + { + ServerSetFireMode(NewFireMode); + } + else + { + ServerSetFireMode(NewFireMode); + } +} + +void AEBGun::ServerSetFireMode_Implementation(EFireMode NewFireMode) +{ + if (!WeaponConfig || !WeaponConfig->IsFireModeAvailable(NewFireMode)) + { + return; + } + + if (Barrel) + { + Barrel->FireMode = NewFireMode; + } +} + +EFireMode AEBGun::GetFireMode() const +{ + if (Barrel) + { + return Barrel->FireMode; + } + + return EFireMode::FM_Semiauto; +} + +void AEBGun::SetGunState(EGunState NewState) +{ + if (!HasAuthority()) + { + return; + } + + if (CurrentGunState == NewState) + { + return; + } + + PreviousGunState = CurrentGunState; + CurrentGunState = NewState; + + OnGunStateChanged.Broadcast(NewState); + OnGunStateChangedInternal(PreviousGunState, NewState); +} + +void AEBGun::OnRep_GunState() +{ + OnGunStateChanged.Broadcast(CurrentGunState); + OnGunStateChangedInternal(PreviousGunState, CurrentGunState); +} + +void AEBGun::ProcessFiring() +{ + if (!HasAuthority()) + { + return; + } + + if (!CanFire() || !bCanProcessNextShot) + { + return; + } + + // Check if we should continue firing based on fire mode + if (Barrel && WeaponConfig) + { + EFireMode CurrentFireMode = Barrel->FireMode; + + switch (CurrentFireMode) + { + case EFireMode::FM_Semiauto: + case EFireMode::FM_Manual: + if (!bTriggerPressed) + { + SetGunState(EGunState::GS_Ready); + return; + } + break; + + case EFireMode::FM_Auto: + if (!bTriggerPressed) + { + SetGunState(EGunState::GS_Ready); + return; + } + break; + + case EFireMode::FM_Burst: + case EFireMode::FM_InterBurst: + if (BurstShotsFired >= WeaponConfig->FireControlConfig.BurstCount) + { + BurstShotsFired = 0; + SetGunState(EGunState::GS_Ready); + return; + } + break; + } + } + + // Fire the weapon + FireShot(); +} + +void AEBGun::FireShot() +{ + if (!HasAuthority() || !CanFire()) + { + return; + } + + // Fire through barrel + if (Barrel && ChamberedBulletType) + { + // Set bullet class on barrel (this is a simplified approach) + // In a real implementation, you would need to create a bullet and configure it properly + Barrel->Ammo.Empty(); + if (ChamberedBulletType) + { + // For now, we'll use the default bullet class since we can't get the class from the asset + // This would need to be properly implemented with a bullet class reference in the asset + } + + // Fire the shot + Barrel->Shoot(true); + + // Clear chambered round + ChamberedBulletType = nullptr; + bChamberedRound = false; + + // Update burst count + BurstShotsFired++; + + // Set fire rate cooldown + float FireRate = WeaponConfig ? WeaponConfig->GetEffectiveFireRate(GetFireMode()) : 600.0f; + float CooldownTime = 60.0f / FireRate; // Convert RPM to seconds + + bCanProcessNextShot = false; + GetWorld()->GetTimerManager().SetTimer(FireRateTimer, [this]() + { + bCanProcessNextShot = true; + }, CooldownTime, false); + + // Try to feed next round + if (!FeedNextRound()) + { + SetGunState(EGunState::GS_Empty); + } + } +} + +bool AEBGun::FeedNextRound() +{ + if (!HasAuthority()) + { + return false; + } + + if (Magazine && Magazine->CanFeed()) + { + UEBBulletPropertiesAsset* NextBullet = Magazine->FeedNextBullet(); + if (NextBullet) + { + ChamberedBulletType = NextBullet; + bChamberedRound = true; + return true; + } + } + + return false; +} + +void AEBGun::OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine) +{ + OnMagazineEmpty.Broadcast(); + + if (!bChamberedRound) + { + SetGunState(EGunState::GS_Empty); + } +} + +void AEBGun::OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType) +{ + // Handle bullet fed from magazine +} + +void AEBGun::OnBarrelShotFiredHandler() +{ + OnGunFired.Broadcast(ChamberedBulletType); + MulticastOnGunFired(ChamberedBulletType); +} + +void AEBGun::MulticastOnGunFired_Implementation(UEBBulletPropertiesAsset* BulletType) +{ + if (!HasAuthority()) + { + OnGunFired.Broadcast(BulletType); + } +} + +void AEBGun::MulticastOnMagazineChanged_Implementation(UEBMagazine* NewMagazine) +{ + if (!HasAuthority()) + { + OnMagazineChanged.Broadcast(NewMagazine); + } +} + +void AEBGun::MulticastOnSafetyChanged_Implementation(bool bNewSafetyState) +{ + if (!HasAuthority()) + { + OnSafetyChanged.Broadcast(bNewSafetyState); + } +} + +// Barrel Integration Functions +float AEBGun::GetBarrelLength() const +{ + if (Barrel) + { + return Barrel->BarrelLength; + } + return 0.0f; +} + +float AEBGun::GetEffectiveMuzzleVelocity() const +{ + if (Barrel && ChamberedBulletType) + { + return Barrel->CalculateEffectiveMuzzleVelocity(ChamberedBulletType); + } + return 0.0f; +} + +float AEBGun::GetBarrelAccuracy() const +{ + if (Barrel) + { + return Barrel->CalculateBarrelAccuracy(); + } + return 0.0f; +} + +FVector AEBGun::CalculateRecoilImpulse() const +{ + if (Barrel && ChamberedBulletType) + { + return Barrel->CalculateRecoilImpulse(ChamberedBulletType); + } + return FVector::ZeroVector; +} + +bool AEBGun::IsBarrelConnected() const +{ + return Barrel != nullptr && Barrel->IsConnectedToGun(); +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBMagazine.cpp b/Source/EasyBallistics/Private/EBMagazine.cpp new file mode 100644 index 0000000..c45ebc1 --- /dev/null +++ b/Source/EasyBallistics/Private/EBMagazine.cpp @@ -0,0 +1,405 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBMagazine.h" +#include "Net/UnrealNetwork.h" +#include "Engine/Engine.h" + +UEBMagazine::UEBMagazine() +{ + PrimaryComponentTick.bCanEverTick = false; + SetIsReplicatedByDefault(true); + + // Initialize magazine state + CurrentCapacity = 0; + bIsEmpty = true; + bIsFull = false; + bIsJammed = false; + bBoltHeldOpen = false; +} + +void UEBMagazine::BeginPlay() +{ + Super::BeginPlay(); + + UpdateMagazineState(); +} + +void UEBMagazine::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UEBMagazine, StoredBullets); + DOREPLIFETIME(UEBMagazine, CurrentCapacity); + DOREPLIFETIME(UEBMagazine, bIsEmpty); + DOREPLIFETIME(UEBMagazine, bIsFull); + DOREPLIFETIME(UEBMagazine, bIsJammed); + DOREPLIFETIME(UEBMagazine, bBoltHeldOpen); +} + +void UEBMagazine::LoadBullets(TArray BulletTypes) +{ + if (GetOwner()->HasAuthority()) + { + ServerLoadBullets(BulletTypes); + } + else + { + ServerLoadBullets(BulletTypes); + } +} + +void UEBMagazine::ServerLoadBullets_Implementation(const TArray& BulletTypes) +{ + if (bIsJammed) + { + return; + } + + for (UEBBulletPropertiesAsset* BulletType : BulletTypes) + { + if (BulletType && CurrentCapacity < MagazineConfig.MaxCapacity) + { + StoredBullets.Add(BulletType); + CurrentCapacity++; + + OnBulletLoaded.Broadcast(this, BulletType); + MulticastOnBulletLoaded(BulletType); + } + } + + UpdateMagazineState(); +} + +void UEBMagazine::LoadSingleBullet(UEBBulletPropertiesAsset* BulletType) +{ + if (GetOwner()->HasAuthority()) + { + ServerLoadSingleBullet(BulletType); + } + else + { + ServerLoadSingleBullet(BulletType); + } +} + +void UEBMagazine::ServerLoadSingleBullet_Implementation(UEBBulletPropertiesAsset* BulletType) +{ + if (!BulletType || bIsJammed || bIsFull) + { + return; + } + + StoredBullets.Add(BulletType); + CurrentCapacity++; + + OnBulletLoaded.Broadcast(this, BulletType); + MulticastOnBulletLoaded(BulletType); + + UpdateMagazineState(); +} + +UEBBulletPropertiesAsset* UEBMagazine::FeedNextBullet() +{ + if (!GetOwner()->HasAuthority()) + { + return nullptr; + } + + if (!CanFeed()) + { + return nullptr; + } + + // Check for malfunction + if (CheckForMalfunction()) + { + bIsJammed = true; + OnMagazineJammed.Broadcast(this); + MulticastOnMagazineJammed(); + UpdateMagazineState(); + return nullptr; + } + + // Feed the bullet + UEBBulletPropertiesAsset* FedBullet = StoredBullets[0]; + StoredBullets.RemoveAt(0); + CurrentCapacity--; + + OnBulletFed.Broadcast(this, FedBullet); + MulticastOnBulletFed(FedBullet); + + UpdateMagazineState(); + + // Check for bolt hold open + if (bIsEmpty && MagazineConfig.bLastRoundBoltHoldOpen) + { + ProcessBoltHoldOpen(); + } + + return FedBullet; +} + +void UEBMagazine::UnloadAllBullets() +{ + if (GetOwner()->HasAuthority()) + { + ServerUnloadAllBullets(); + } + else + { + ServerUnloadAllBullets(); + } +} + +void UEBMagazine::ServerUnloadAllBullets_Implementation() +{ + StoredBullets.Empty(); + CurrentCapacity = 0; + bBoltHeldOpen = false; + UpdateMagazineState(); +} + +UEBBulletPropertiesAsset* UEBMagazine::UnloadLastBullet() +{ + if (!GetOwner()->HasAuthority()) + { + return nullptr; + } + + if (bIsEmpty) + { + return nullptr; + } + + UEBBulletPropertiesAsset* UnloadedBullet = StoredBullets.Last(); + StoredBullets.RemoveAt(StoredBullets.Num() - 1); + CurrentCapacity--; + + UpdateMagazineState(); + return UnloadedBullet; +} + +void UEBMagazine::ClearJam() +{ + if (GetOwner()->HasAuthority()) + { + ServerClearJam(); + } + else + { + ServerClearJam(); + } +} + +void UEBMagazine::ServerClearJam_Implementation() +{ + if (bIsJammed) + { + bIsJammed = false; + UpdateMagazineState(); + } +} + +void UEBMagazine::ReleaseBoltHold() +{ + if (GetOwner()->HasAuthority()) + { + ServerReleaseBoltHold(); + } + else + { + ServerReleaseBoltHold(); + } +} + +void UEBMagazine::ServerReleaseBoltHold_Implementation() +{ + if (bBoltHeldOpen) + { + bBoltHeldOpen = false; + UpdateMagazineState(); + } +} + +void UEBMagazine::ForceJam() +{ + if (GetOwner()->HasAuthority()) + { + bIsJammed = true; + OnMagazineJammed.Broadcast(this); + MulticastOnMagazineJammed(); + UpdateMagazineState(); + } +} + +int32 UEBMagazine::GetRemainingCapacity() const +{ + return MagazineConfig.MaxCapacity - CurrentCapacity; +} + +int32 UEBMagazine::GetCurrentRoundCount() const +{ + return CurrentCapacity; +} + +int32 UEBMagazine::GetMaxCapacity() const +{ + return MagazineConfig.MaxCapacity; +} + +float UEBMagazine::GetFillPercentage() const +{ + if (MagazineConfig.MaxCapacity <= 0) + { + return 0.0f; + } + return (float)CurrentCapacity / (float)MagazineConfig.MaxCapacity; +} + +UEBBulletPropertiesAsset* UEBMagazine::PeekNextBullet() const +{ + if (bIsEmpty || StoredBullets.Num() == 0) + { + return nullptr; + } + return StoredBullets[0]; +} + +bool UEBMagazine::CanFeed() const +{ + return !bIsEmpty && !bIsJammed && !bBoltHeldOpen && StoredBullets.Num() > 0; +} + +bool UEBMagazine::NeedsReload() const +{ + return bIsEmpty || CurrentCapacity < (MagazineConfig.MaxCapacity * 0.25f); // Need reload when below 25% +} + +TArray UEBMagazine::GetBulletTypesSummary() const +{ + TArray UniqueBulletTypes; + + for (UEBBulletPropertiesAsset* BulletType : StoredBullets) + { + if (BulletType && !UniqueBulletTypes.Contains(BulletType)) + { + UniqueBulletTypes.Add(BulletType); + } + } + + return UniqueBulletTypes; +} + +void UEBMagazine::UpdateMagazineState() +{ + bool bOldEmpty = bIsEmpty; + bool bOldFull = bIsFull; + + bIsEmpty = (CurrentCapacity == 0); + bIsFull = (CurrentCapacity >= MagazineConfig.MaxCapacity); + + // Fire events for state changes + if (bIsEmpty && !bOldEmpty) + { + OnMagazineEmpty.Broadcast(this); + } + else if (bIsFull && !bOldFull) + { + OnMagazineFull.Broadcast(this); + } +} + +bool UEBMagazine::CheckForMalfunction() +{ + if (MagazineConfig.MalfunctionChance <= 0.0f) + { + return false; + } + + float RandomValue = FMath::RandRange(0.0f, 1.0f); + return RandomValue <= MagazineConfig.MalfunctionChance; +} + +void UEBMagazine::ProcessBoltHoldOpen() +{ + if (!bBoltHeldOpen) + { + bBoltHeldOpen = true; + OnBoltHoldOpen.Broadcast(this); + MulticastOnBoltHoldOpen(); + } +} + +void UEBMagazine::OnRep_StoredBullets() +{ + UpdateMagazineState(); +} + +void UEBMagazine::OnRep_CurrentCapacity() +{ + UpdateMagazineState(); +} + +void UEBMagazine::OnRep_IsEmpty() +{ + if (bIsEmpty) + { + OnMagazineEmpty.Broadcast(this); + } +} + +void UEBMagazine::OnRep_IsFull() +{ + if (bIsFull) + { + OnMagazineFull.Broadcast(this); + } +} + +void UEBMagazine::OnRep_IsJammed() +{ + if (bIsJammed) + { + OnMagazineJammed.Broadcast(this); + } +} + +void UEBMagazine::OnRep_BoltHeldOpen() +{ + if (bBoltHeldOpen) + { + OnBoltHoldOpen.Broadcast(this); + } +} + +void UEBMagazine::MulticastOnBulletFed_Implementation(UEBBulletPropertiesAsset* BulletType) +{ + if (!GetOwner()->HasAuthority()) + { + OnBulletFed.Broadcast(this, BulletType); + } +} + +void UEBMagazine::MulticastOnBulletLoaded_Implementation(UEBBulletPropertiesAsset* BulletType) +{ + if (!GetOwner()->HasAuthority()) + { + OnBulletLoaded.Broadcast(this, BulletType); + } +} + +void UEBMagazine::MulticastOnMagazineJammed_Implementation() +{ + if (!GetOwner()->HasAuthority()) + { + OnMagazineJammed.Broadcast(this); + } +} + +void UEBMagazine::MulticastOnBoltHoldOpen_Implementation() +{ + if (!GetOwner()->HasAuthority()) + { + OnBoltHoldOpen.Broadcast(this); + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBWeaponConfiguration.cpp b/Source/EasyBallistics/Private/EBWeaponConfiguration.cpp new file mode 100644 index 0000000..98d39ed --- /dev/null +++ b/Source/EasyBallistics/Private/EBWeaponConfiguration.cpp @@ -0,0 +1,82 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBWeaponConfiguration.h" +#include "EBBulletProperties.h" +#include "EBGun.h" + +bool UEBWeaponConfiguration::IsFireModeAvailable(EFireMode FireMode) const +{ + return FireControlConfig.AvailableFireModes.Contains(FireMode); +} + +bool UEBWeaponConfiguration::IsBulletTypeCompatible(UEBBulletPropertiesAsset* BulletType) const +{ + if (!BulletType) + { + return false; + } + + // Check if it's the default bullet type + if (BulletType == DefaultBulletType) + { + return true; + } + + // Check if it's in the compatible list + return CompatibleBulletTypes.Contains(BulletType); +} + +float UEBWeaponConfiguration::GetEffectiveFireRate(EFireMode FireMode) const +{ + // Base fire rate + float BaseFireRate = (FireControlConfig.FireRateMin + FireControlConfig.FireRateMax) / 2.0f; + + // Adjust based on fire mode + switch (FireMode) + { + case EFireMode::FM_Semiauto: + return FMath::Min(BaseFireRate, 300.0f); // Cap semi-auto at 300 RPM + case EFireMode::FM_Auto: + return BaseFireRate; + case EFireMode::FM_Burst: + case EFireMode::FM_InterBurst: + return BaseFireRate * 1.2f; // Burst fire is slightly faster + case EFireMode::FM_Gatling: + return BaseFireRate * 2.0f; // Gatling guns fire much faster + case EFireMode::FM_Manual: + return 60.0f; // Manual actions are slow + case EFireMode::FM_Slamfire: + return BaseFireRate * 0.8f; // Slamfire is slightly slower + default: + return BaseFireRate; + } +} + +FString UEBWeaponConfiguration::GetWeaponDisplayName() const +{ + if (!Model.IsEmpty()) + { + return FString::Printf(TEXT("%s %s"), *Manufacturer, *Model); + } + return WeaponName; +} + +FString UEBWeaponConfiguration::GetFullWeaponName() const +{ + FString FullName = GetWeaponDisplayName(); + + if (!Caliber.IsEmpty()) + { + FullName += FString::Printf(TEXT(" (%s)"), *Caliber); + } + + return FullName; +} + +void UEBWeaponConfiguration::ApplyConfigurationToGun(AEBGun* Gun) const +{ + if (Gun) + { + Gun->ApplyWeaponConfiguration(const_cast(this)); + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBBarrel.h b/Source/EasyBallistics/Public/EBBarrel.h index bc0b728..f099443 100644 --- a/Source/EasyBallistics/Public/EBBarrel.h +++ b/Source/EasyBallistics/Public/EBBarrel.h @@ -7,22 +7,11 @@ #include "Kismet/GameplayStatics.h" #include "EBBullet.h" +#include "EBWeaponConfiguration.h" #include "EBBarrel.generated.h" -UENUM(BlueprintType) -enum class EFireMode : uint8 -{ - FM_Auto UMETA(DisplayName = "Full Auto"), - FM_Semiauto UMETA(DisplayName = "Semiauto"), - FM_Burst UMETA(DisplayName = "Burst"), - FM_InterBurst UMETA(DisplayName = "Interruptible Burst"), - FM_Manual UMETA(DisplayName = "Manual"), - FM_Slamfire UMETA(DisplayName = "Slam Fire"), - FM_Gatling UMETA(DisplayName = "Gatling") -}; - -UCLASS(Blueprintable, ClassGroup = (Custom), hidecategories = (Object, LOD, Physics, Lighting, TextureStreaming, Collision, HLOD, Mobile, VirtualTexture, ComponentReplication), editinlinenew, meta = (BlueprintSpawnableComponent)) + UCLASS(Blueprintable, ClassGroup = (Custom), hidecategories = (Object, LOD, Physics, Lighting, TextureStreaming, Collision, HLOD, Mobile, VirtualTexture, ComponentReplication), editinlinenew, meta = (BlueprintSpawnableComponent)) class EASYBALLISTICS_API UEBBarrel : public UPrimitiveComponent { @@ -32,6 +21,26 @@ public: // Sets default values for this component's properties UEBBarrel(); + // Gun Integration + UPROPERTY(BlueprintReadOnly, Category = "Gun Integration") + class AEBGun* ParentGun; + + // Barrel Physical Properties (from weapon config) + UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties") + float BarrelLength = 16.0f; // inches + + UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties") + float RiflingTwist = 7.0f; // 1:X twist rate + + UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties") + float BoreRadius = 0.112f; // inches + + UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties") + float MuzzleVelocityMin = 91440.0f; // cm/s + + UPROPERTY(BlueprintReadOnly, Category = "Barrel Properties") + float MuzzleVelocityMax = 91440.0f; // cm/s + // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; @@ -51,6 +60,35 @@ public: UFUNCTION(BlueprintPure, Category = "EBBarrel|Debug") FString GetBulletDebugInfo() const; + // Gun Integration Functions + UFUNCTION(BlueprintCallable, Category = "Gun Integration") + void SetParentGun(class AEBGun* Gun); + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + class AEBGun* GetParentGun() const { return ParentGun; } + + UFUNCTION(BlueprintCallable, Category = "Gun Integration") + void ApplyBarrelConfiguration(const struct FBarrelConfiguration& BarrelConfig); + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + bool IsConnectedToGun() const { return ParentGun != nullptr; } + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + class UEBMagazine* GetConnectedMagazine() const; + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + class UEBBulletPropertiesAsset* GetChamberedBulletType() const; + + // Enhanced Barrel Functions + UFUNCTION(BlueprintPure, Category = "Barrel Properties") + float CalculateEffectiveMuzzleVelocity(class UEBBulletPropertiesAsset* BulletType) const; + + UFUNCTION(BlueprintPure, Category = "Barrel Properties") + float CalculateBarrelAccuracy() const; + + UFUNCTION(BlueprintPure, Category = "Barrel Properties") + FVector CalculateRecoilImpulse(class UEBBulletPropertiesAsset* BulletType) const; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Bullet inherits barrel velocity, only works with physics enabled or with additional velocity set")) float InheritVelocity = 1.0f; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Amount of recoil applied to the barrel, only works with physics enabled")) float RecoilMultiplier = 1.0f; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Additional velocity, for use with InheritVelocity")) FVector AdditionalVelocity = FVector(0,0,0); @@ -71,11 +109,17 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolDownTime = 1.0f; UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingPhase = 0.0f; - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo") bool CycleAmmo = true; - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) bool CycleAmmoUnlimited = true; - UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo") TArray> Ammo; - UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoCount; - UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoPos; + // Legacy Ammo System (kept for compatibility, use Gun's Magazine system for new weapons) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons")) + bool CycleAmmo = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons")) + bool CycleAmmoUnlimited = true; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons")) + TArray> Ammo; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons")) + int CycleAmmoCount; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Legacy Ammo", meta = (EditCondition = "CycleAmmo", ToolTip = "Legacy ammo system - use Gun's Magazine for new weapons")) + int CycleAmmoPos; UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") TSubclassOf ChamberedBullet; UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Shooting; @@ -94,12 +138,23 @@ public: FRandomStream RandomStream; + // Legacy Ammo Functions (kept for compatibility) UFUNCTION() void NextBullet(); - UFUNCTION(BlueprintPure, Category = "Ammo") int GetAmmoCount(bool CountChambered) const; - UFUNCTION(BlueprintPure, Category = "Ammo") TArray> GetAmmo(bool CountChambered) const; - UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray>& NewAmmo); - UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void Charge(); - UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void UnloadChambered(bool ManualCharge); + UFUNCTION(BlueprintPure, Category = "Legacy Ammo") int GetAmmoCount(bool CountChambered) const; + UFUNCTION(BlueprintPure, Category = "Legacy Ammo") TArray> GetAmmo(bool CountChambered) const; + UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Legacy Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray>& NewAmmo); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Legacy Ammo") void Charge(); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Legacy Ammo") void UnloadChambered(bool ManualCharge); + + // New Gun-Integrated Ammo Functions + UFUNCTION(BlueprintPure, Category = "Gun Integration") + int GetMagazineRoundCount() const; + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + bool HasChamberedRound() const; + + UFUNCTION(BlueprintPure, Category = "Gun Integration") + bool CanFireFromGun() const; UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void SwitchFireMode(EFireMode NewFireMode); UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Shooting") void GatlingSpool(bool Spool); UFUNCTION(BlueprintCallable, Category = "Shooting") void Shoot(bool Trigger); diff --git a/Source/EasyBallistics/Public/EBGun.h b/Source/EasyBallistics/Public/EBGun.h new file mode 100644 index 0000000..120a834 --- /dev/null +++ b/Source/EasyBallistics/Public/EBGun.h @@ -0,0 +1,250 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/StaticMeshComponent.h" + +#include "EBBarrel.h" +#include "EBMagazine.h" +#include "EBWeaponConfiguration.h" +#include "EBBulletProperties.h" + +#include "EBGun.generated.h" + +UENUM(BlueprintType) +enum class EGunState : uint8 +{ + GS_Ready UMETA(DisplayName = "Ready"), + GS_Firing UMETA(DisplayName = "Firing"), + GS_Reloading UMETA(DisplayName = "Reloading"), + GS_Jammed UMETA(DisplayName = "Jammed"), + GS_Empty UMETA(DisplayName = "Empty"), + GS_SafetyOn UMETA(DisplayName = "Safety On"), + GS_Malfunction UMETA(DisplayName = "Malfunction") +}; + +UCLASS(Blueprintable, BlueprintType) +class EASYBALLISTICS_API AEBGun : public AActor +{ + GENERATED_BODY() + +public: + AEBGun(); + + // Core Components + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + USkeletalMeshComponent* GunMesh; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UEBBarrel* Barrel; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UEBMagazine* Magazine; + + // Weapon Configuration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Configuration") + UEBWeaponConfiguration* WeaponConfig; + + // Gun State + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_GunState, Category = "Gun State") + EGunState CurrentGunState = EGunState::GS_Ready; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State") + bool bSafetyOn = false; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State") + bool bChamberedRound = false; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Gun State") + bool bHammerCocked = false; + + // Firing Interface + UFUNCTION(BlueprintCallable, Category = "Gun|Firing") + void PullTrigger(); + + UFUNCTION(BlueprintCallable, Category = "Gun|Firing") + void ReleaseTrigger(); + + UFUNCTION(BlueprintCallable, Category = "Gun|Controls") + void SetSafety(bool bNewSafetyState); + + UFUNCTION(BlueprintCallable, Category = "Gun|Controls") + void CockHammer(); + + UFUNCTION(BlueprintCallable, Category = "Gun|Controls") + void ReleaseHammer(); + + // Ammunition Management + UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition") + void LoadMagazine(TArray BulletTypes); + + UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition") + void EjectMagazine(); + + UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition") + void InsertMagazine(UEBMagazine* NewMagazine); + + UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition") + void ChargingHandle(); + + UFUNCTION(BlueprintCallable, Category = "Gun|Ammunition") + void EjectChamberedRound(); + + // State Queries + UFUNCTION(BlueprintPure, Category = "Gun|State") + bool CanFire() const; + + UFUNCTION(BlueprintPure, Category = "Gun|State") + bool IsLoaded() const; + + UFUNCTION(BlueprintPure, Category = "Gun|State") + int32 GetRoundsInMagazine() const; + + UFUNCTION(BlueprintPure, Category = "Gun|State") + int32 GetTotalRounds() const; + + UFUNCTION(BlueprintPure, Category = "Gun|State") + UEBBulletPropertiesAsset* GetChamberedBulletType() const; + + // Configuration + UFUNCTION(BlueprintCallable, Category = "Gun|Configuration") + void ApplyWeaponConfiguration(UEBWeaponConfiguration* NewConfig); + + UFUNCTION(BlueprintCallable, Category = "Gun|Configuration") + void SetFireMode(EFireMode NewFireMode); + + UFUNCTION(BlueprintPure, Category = "Gun|Configuration") + EFireMode GetFireMode() const; + + // Barrel Integration + UFUNCTION(BlueprintPure, Category = "Gun|Barrel") + float GetBarrelLength() const; + + UFUNCTION(BlueprintPure, Category = "Gun|Barrel") + float GetEffectiveMuzzleVelocity() const; + + UFUNCTION(BlueprintPure, Category = "Gun|Barrel") + float GetBarrelAccuracy() const; + + UFUNCTION(BlueprintCallable, Category = "Gun|Barrel") + FVector CalculateRecoilImpulse() const; + + UFUNCTION(BlueprintPure, Category = "Gun|Barrel") + bool IsBarrelConnected() const; + + // Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGunStateChanged, EGunState, NewState); + UPROPERTY(BlueprintAssignable, Category = "Gun Events") + FOnGunStateChanged OnGunStateChanged; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGunFired, UEBBulletPropertiesAsset*, BulletType); + UPROPERTY(BlueprintAssignable, Category = "Gun Events") + FOnGunFired OnGunFired; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMagazineEmpty); + UPROPERTY(BlueprintAssignable, Category = "Gun Events") + FOnMagazineEmpty OnMagazineEmpty; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineChanged, UEBMagazine*, NewMagazine); + UPROPERTY(BlueprintAssignable, Category = "Gun Events") + FOnMagazineChanged OnMagazineChanged; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSafetyChanged, bool, bNewSafetyState); + UPROPERTY(BlueprintAssignable, Category = "Gun Events") + FOnSafetyChanged OnSafetyChanged; + + // Networking + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +protected: + virtual void BeginPlay() override; + virtual void Tick(float DeltaTime) override; + + // Replication + UFUNCTION() + void OnRep_GunState(); + + // Internal Functions + UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal") + void OnGunStateChangedInternal(EGunState OldState, EGunState NewState); + + UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal") + void OnFireAnimationStart(); + + UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal") + void OnReloadAnimationStart(); + + UFUNCTION(BlueprintImplementableEvent, Category = "Gun|Internal") + void OnJamAnimation(); + + void SetGunState(EGunState NewState); + void ProcessFiring(); + void ProcessReloading(); + void CycleAction(); + bool FeedNextRound(); + + // Timers + FTimerHandle FireRateTimer; + FTimerHandle ReloadTimer; + FTimerHandle CycleTimer; + + // Firing Control + bool bTriggerPressed = false; + bool bCanProcessNextShot = true; + int32 BurstShotsFired = 0; + + // Chambered Round + UPROPERTY(Replicated) + UEBBulletPropertiesAsset* ChamberedBulletType; + +private: + EGunState PreviousGunState; + + // Server Functions + UFUNCTION(Server, Reliable) + void ServerPullTrigger(); + + UFUNCTION(Server, Reliable) + void ServerReleaseTrigger(); + + UFUNCTION(Server, Reliable) + void ServerSetSafety(bool bNewSafetyState); + + UFUNCTION(Server, Reliable) + void ServerEjectMagazine(); + + UFUNCTION(Server, Reliable) + void ServerInsertMagazine(UEBMagazine* NewMagazine); + + UFUNCTION(Server, Reliable) + void ServerChargingHandle(); + + UFUNCTION(Server, Reliable) + void ServerSetFireMode(EFireMode NewFireMode); + + // Multicast Functions + UFUNCTION(NetMulticast, Reliable) + void MulticastOnGunFired(UEBBulletPropertiesAsset* BulletType); + + UFUNCTION(NetMulticast, Reliable) + void MulticastOnMagazineChanged(UEBMagazine* NewMagazine); + + UFUNCTION(NetMulticast, Reliable) + void MulticastOnSafetyChanged(bool bNewSafetyState); + + // Event Handlers + UFUNCTION() + void OnMagazineEmptyHandler(UEBMagazine* EmptyMagazine); + + UFUNCTION() + void OnBulletFedHandler(UEBMagazine* SourceMagazine, UEBBulletPropertiesAsset* BulletType); + + UFUNCTION() + void OnBarrelShotFiredHandler(); + + // Internal firing function + void FireShot(); +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBMagazine.h b/Source/EasyBallistics/Public/EBMagazine.h new file mode 100644 index 0000000..6fd4a0e --- /dev/null +++ b/Source/EasyBallistics/Public/EBMagazine.h @@ -0,0 +1,229 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "EBBulletProperties.h" + +#include "EBMagazine.generated.h" + +UENUM(BlueprintType) +enum class EMagazineType : uint8 +{ + MT_Standard UMETA(DisplayName = "Standard Box Magazine"), + MT_Drum UMETA(DisplayName = "Drum Magazine"), + MT_Tube UMETA(DisplayName = "Tube Magazine"), + MT_Clip UMETA(DisplayName = "Stripper Clip"), + MT_Belt UMETA(DisplayName = "Belt Fed"), + MT_Cylinder UMETA(DisplayName = "Revolver Cylinder"), + MT_Internal UMETA(DisplayName = "Internal Magazine") +}; + +USTRUCT(BlueprintType) +struct FMagazineConfiguration +{ + GENERATED_USTRUCT_BODY() + + // Magazine Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + EMagazineType MagazineType = EMagazineType::MT_Standard; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + int32 MaxCapacity = 30; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + bool bAllowMixedBulletTypes = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + bool bLastRoundBoltHoldOpen = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + float FeedRate = 0.1f; // Seconds between rounds for belt-fed + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + float ReloadTime = 2.5f; // Full reload time in seconds + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine Config") + float MalfunctionChance = 0.001f; // Chance of malfunction per round + + FMagazineConfiguration() + { + MagazineType = EMagazineType::MT_Standard; + MaxCapacity = 30; + bAllowMixedBulletTypes = true; + bLastRoundBoltHoldOpen = true; + FeedRate = 0.1f; + ReloadTime = 2.5f; + MalfunctionChance = 0.001f; + } +}; + +UCLASS(Blueprintable, ClassGroup=(Ballistics), meta=(BlueprintSpawnableComponent)) +class EASYBALLISTICS_API UEBMagazine : public UActorComponent +{ + GENERATED_BODY() + +public: + UEBMagazine(); + + // Magazine Configuration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine") + FMagazineConfiguration MagazineConfig; + + // Ammunition Storage + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ammunition") + TArray StoredBullets; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ammunition") + int32 CurrentCapacity = 0; + + // Magazine State + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State") + bool bIsEmpty = true; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State") + bool bIsFull = false; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State") + bool bIsJammed = false; + + UPROPERTY(BlueprintReadOnly, Replicated, Category = "Magazine State") + bool bBoltHeldOpen = false; + + // Ammunition Management + UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition") + void LoadBullets(TArray BulletTypes); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition") + void LoadSingleBullet(UEBBulletPropertiesAsset* BulletType); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition") + UEBBulletPropertiesAsset* FeedNextBullet(); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition") + void UnloadAllBullets(); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Ammunition") + UEBBulletPropertiesAsset* UnloadLastBullet(); + + // Magazine Operations + UFUNCTION(BlueprintCallable, Category = "Magazine|Operations") + void ClearJam(); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Operations") + void ReleaseBoltHold(); + + UFUNCTION(BlueprintCallable, Category = "Magazine|Operations") + void ForceJam(); + + // Information + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + int32 GetRemainingCapacity() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + int32 GetCurrentRoundCount() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + int32 GetMaxCapacity() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + float GetFillPercentage() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + UEBBulletPropertiesAsset* PeekNextBullet() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + bool CanFeed() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + bool NeedsReload() const; + + UFUNCTION(BlueprintPure, Category = "Magazine|Info") + TArray GetBulletTypesSummary() const; + + // Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineEmpty, UEBMagazine*, Magazine); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnMagazineEmpty OnMagazineEmpty; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineFull, UEBMagazine*, Magazine); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnMagazineFull OnMagazineFull; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBulletFed, UEBMagazine*, Magazine, UEBBulletPropertiesAsset*, BulletType); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnBulletFed OnBulletFed; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnBulletLoaded, UEBMagazine*, Magazine, UEBBulletPropertiesAsset*, BulletType); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnBulletLoaded OnBulletLoaded; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMagazineJammed, UEBMagazine*, Magazine); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnMagazineJammed OnMagazineJammed; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnBoltHoldOpen, UEBMagazine*, Magazine); + UPROPERTY(BlueprintAssignable, Category = "Magazine Events") + FOnBoltHoldOpen OnBoltHoldOpen; + + // Networking + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +protected: + virtual void BeginPlay() override; + + // Internal Functions + void UpdateMagazineState(); + bool CheckForMalfunction(); + void ProcessBoltHoldOpen(); + + // Replication + UFUNCTION() + void OnRep_StoredBullets(); + + UFUNCTION() + void OnRep_CurrentCapacity(); + + UFUNCTION() + void OnRep_IsEmpty(); + + UFUNCTION() + void OnRep_IsFull(); + + UFUNCTION() + void OnRep_IsJammed(); + + UFUNCTION() + void OnRep_BoltHeldOpen(); + +private: + // Server Functions + UFUNCTION(Server, Reliable) + void ServerLoadBullets(const TArray& BulletTypes); + + UFUNCTION(Server, Reliable) + void ServerLoadSingleBullet(UEBBulletPropertiesAsset* BulletType); + + UFUNCTION(Server, Reliable) + void ServerUnloadAllBullets(); + + UFUNCTION(Server, Reliable) + void ServerClearJam(); + + UFUNCTION(Server, Reliable) + void ServerReleaseBoltHold(); + + // Multicast Functions + UFUNCTION(NetMulticast, Reliable) + void MulticastOnBulletFed(UEBBulletPropertiesAsset* BulletType); + + UFUNCTION(NetMulticast, Reliable) + void MulticastOnBulletLoaded(UEBBulletPropertiesAsset* BulletType); + + UFUNCTION(NetMulticast, Reliable) + void MulticastOnMagazineJammed(); + + UFUNCTION(NetMulticast, Reliable) + void MulticastOnBoltHoldOpen(); +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBWeaponConfiguration.h b/Source/EasyBallistics/Public/EBWeaponConfiguration.h new file mode 100644 index 0000000..f1235e4 --- /dev/null +++ b/Source/EasyBallistics/Public/EBWeaponConfiguration.h @@ -0,0 +1,310 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "EBBulletProperties.h" +#include "EBMaterialResponseMap.h" +#include "EBMagazine.h" + +#include "EBWeaponConfiguration.generated.h" + +UENUM(BlueprintType) +enum class EFireMode : uint8 +{ + FM_Auto UMETA(DisplayName = "Full Auto"), + FM_Semiauto UMETA(DisplayName = "Semiauto"), + FM_Burst UMETA(DisplayName = "Burst"), + FM_InterBurst UMETA(DisplayName = "Interruptible Burst"), + FM_Manual UMETA(DisplayName = "Manual"), + FM_Slamfire UMETA(DisplayName = "Slam Fire"), + FM_Gatling UMETA(DisplayName = "Gatling") +}; + +USTRUCT(BlueprintType) +struct FBarrelConfiguration +{ + GENERATED_USTRUCT_BODY() + + // Barrel Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel") + float BarrelLength = 16.0f; // inches + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel") + float RiflingTwist = 7.0f; // 1:X twist rate + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel") + float BoreRadius = 0.112f; // inches + + // Muzzle Velocity + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + float MuzzleVelocityMin = 91440.0f; // cm/s (~3000 fps) + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + float MuzzleVelocityMax = 91440.0f; // cm/s (~3000 fps) + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + float MuzzleVelocityVariation = 0.02f; // +/- 2% variation + + // Accuracy + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy") + float InherentAccuracy = 0.001f; // MOA in radians + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy") + float SpreadMin = 0.0f; // radians + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy") + float SpreadMax = 0.001f; // radians + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Accuracy") + float SpreadBias = 0.0f; + + // Recoil + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + float RecoilMultiplier = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + FVector RecoilDirection = FVector(0, 0, 1); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + float RecoilRecoveryTime = 0.2f; + + FBarrelConfiguration() + { + BarrelLength = 16.0f; + RiflingTwist = 7.0f; + BoreRadius = 0.112f; + MuzzleVelocityMin = 91440.0f; + MuzzleVelocityMax = 91440.0f; + MuzzleVelocityVariation = 0.02f; + InherentAccuracy = 0.001f; + SpreadMin = 0.0f; + SpreadMax = 0.001f; + SpreadBias = 0.0f; + RecoilMultiplier = 1.0f; + RecoilDirection = FVector(0, 0, 1); + RecoilRecoveryTime = 0.2f; + } +}; + +USTRUCT(BlueprintType) +struct FFireControlConfiguration +{ + GENERATED_USTRUCT_BODY() + + // Fire Modes + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control") + TArray AvailableFireModes; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control") + EFireMode DefaultFireMode = EFireMode::FM_Semiauto; + + // Fire Rate + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate") + float FireRateMin = 600.0f; // RPM + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate") + float FireRateMax = 650.0f; // RPM + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Rate") + float CyclicRateVariation = 0.05f; // +/- 5% variation + + // Burst Fire + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire") + int32 BurstCount = 3; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire") + float BurstCooldown = 0.1f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Burst Fire") + bool bInterruptibleBurst = false; + + // Gatling Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling") + float GatlingSpoolUpTime = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling") + float GatlingSpoolDownTime = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gatling") + bool bGatlingAutoSpool = true; + + // Safety + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Safety") + bool bHasSafety = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Safety") + bool bDefaultSafetyOn = false; + + // Trigger + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger") + float TriggerWeight = 5.5f; // pounds + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger") + float TriggerTravel = 0.25f; // inches + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trigger") + bool bTwoStage = false; + + FFireControlConfiguration() + { + AvailableFireModes.Add(EFireMode::FM_Semiauto); + DefaultFireMode = EFireMode::FM_Semiauto; + FireRateMin = 600.0f; + FireRateMax = 650.0f; + CyclicRateVariation = 0.05f; + BurstCount = 3; + BurstCooldown = 0.1f; + bInterruptibleBurst = false; + GatlingSpoolUpTime = 1.0f; + GatlingSpoolDownTime = 1.0f; + bGatlingAutoSpool = true; + bHasSafety = true; + bDefaultSafetyOn = false; + TriggerWeight = 5.5f; + TriggerTravel = 0.25f; + bTwoStage = false; + } +}; + +USTRUCT(BlueprintType) +struct FReliabilityConfiguration +{ + GENERATED_USTRUCT_BODY() + + // Reliability + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability") + float MalfunctionRate = 0.0001f; // Per round fired + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability") + float JamClearTime = 2.0f; // seconds + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability") + float DirtSensitivity = 0.1f; // 0-1 scale + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability") + float WearFactor = 0.0001f; // Per round fired + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability") + int32 ServiceLife = 10000; // Rounds before overhaul + + // Environmental + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental") + float TemperatureSensitivity = 0.001f; // Per degree C + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental") + float MinOperatingTemp = -40.0f; // Celsius + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental") + float MaxOperatingTemp = 60.0f; // Celsius + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environmental") + bool bWaterResistant = true; + + FReliabilityConfiguration() + { + MalfunctionRate = 0.0001f; + JamClearTime = 2.0f; + DirtSensitivity = 0.1f; + WearFactor = 0.0001f; + ServiceLife = 10000; + TemperatureSensitivity = 0.001f; + MinOperatingTemp = -40.0f; + MaxOperatingTemp = 60.0f; + bWaterResistant = true; + } +}; + +UCLASS(BlueprintType) +class EASYBALLISTICS_API UEBWeaponConfiguration : public UDataAsset +{ + GENERATED_BODY() + +public: + // Weapon Identity + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info") + FString WeaponName = "M4 Carbine"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info") + FString Manufacturer = "Colt"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info") + FString Model = "M4A1"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info") + FString Caliber = "5.56x45mm NATO"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Info") + FString Description = "Standard issue carbine"; + + // Configuration Sections + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Barrel Configuration") + FBarrelConfiguration BarrelConfig; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fire Control Configuration") + FFireControlConfiguration FireControlConfig; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Reliability Configuration") + FReliabilityConfiguration ReliabilityConfig; + + // Default Ammunition + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ammunition") + UEBBulletPropertiesAsset* DefaultBulletType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ammunition") + TArray CompatibleBulletTypes; + + // Magazine Configuration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine") + int32 DefaultMagazineCapacity = 30; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine") + EMagazineType DefaultMagazineType = EMagazineType::MT_Standard; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Magazine") + bool bAllowMixedAmmo = true; + + // Material Response + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + UEBMaterialResponseMap* MaterialResponseMap; + + // Physics Settings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + bool bUseMathematicalPhysics = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + bool bUseNewImpactSystem = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics") + bool bEnableSpalling = true; + + // Networking + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking") + bool bReplicateVariables = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking") + bool bReplicateShotEvents = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Networking") + bool bClientSideAim = false; + + // Utility Functions + UFUNCTION(BlueprintPure, Category = "Weapon Configuration") + bool IsFireModeAvailable(EFireMode FireMode) const; + + UFUNCTION(BlueprintPure, Category = "Weapon Configuration") + bool IsBulletTypeCompatible(UEBBulletPropertiesAsset* BulletType) const; + + UFUNCTION(BlueprintPure, Category = "Weapon Configuration") + float GetEffectiveFireRate(EFireMode FireMode) const; + + UFUNCTION(BlueprintPure, Category = "Weapon Configuration") + FString GetWeaponDisplayName() const; + + UFUNCTION(BlueprintPure, Category = "Weapon Configuration") + FString GetFullWeaponName() const; + + UFUNCTION(BlueprintCallable, Category = "Weapon Configuration") + void ApplyConfigurationToGun(class AEBGun* Gun) const; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs b/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs index edf2ab5..20fc97d 100644 --- a/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs +++ b/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs @@ -16,7 +16,12 @@ public class EasyBallisticsEditor : ModuleRules "AssetTools", "EditorWidgets", "EasyBallistics", - "PhysicsCore" + "PhysicsCore", + "ComponentVisualizers", + "EditorStyle", + "EditorSubsystem", + "LevelEditor", + "SceneOutliner" }); PrivateDependencyModuleNames.AddRange(new string[] { @@ -26,7 +31,21 @@ public class EasyBallisticsEditor : ModuleRules "WorkspaceMenuStructure", "DesktopPlatform", "ToolMenus", - "Json" + "Json", + "DetailCustomizations", + "ActorPickerMode", + "SceneDepthPickerMode", + "ViewportInteraction", + "MeshPaint", + "ContentBrowser", + "AssetRegistry", + "EditorScriptingUtilities", + "EditorInteractiveToolsFramework", + "InteractiveToolsFramework", + "EngineSettings", + "RenderCore", + "RHI", + "Projects" }); } } \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp b/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp index 99de127..045f8f7 100644 --- a/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp +++ b/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp @@ -2,50 +2,130 @@ #include "EBBarrelComponentFactory.h" #include "EBBarrel.h" -#include "AssetToolsModule.h" +#include "Engine/Selection.h" +#include "Components/SceneComponent.h" +#include "DrawDebugHelpers.h" #define LOCTEXT_NAMESPACE "EBBarrelComponentFactory" -UClass* FEBBarrelComponentFactory::GetSupportedClass() const +void FEBBarrelComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) { - return UEBBarrel::StaticClass(); -} - -uint32 FEBBarrelComponentFactory::GetCategories() -{ - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); -} - -FText FEBBarrelComponentFactory::GetAssetDescription(const FAssetData& AssetData) const -{ - return LOCTEXT("EBBarrelComponentDescription", "A weapon barrel component that handles firing mechanics, ammunition management, and ballistic calculations for projectile weapons."); -} - -UClass* FEBBarrelComponentAssetBroker::GetSupportedAssetClass() -{ - return UEBBarrel::StaticClass(); -} - -bool FEBBarrelComponentAssetBroker::AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) -{ - if (UEBBarrel* BarrelComponent = Cast(InComponent)) + const UEBBarrel* BarrelComponent = Cast(Component); + if (!BarrelComponent) { - // Components don't typically have assets assigned to them directly - // This would be used if we had barrel configuration assets - return false; + return; } + + // Get the component's transform + FTransform ComponentTransform = BarrelComponent->GetComponentTransform(); + FVector Location = ComponentTransform.GetLocation(); + FVector Forward = ComponentTransform.GetUnitAxis(EAxis::X); + FVector Right = ComponentTransform.GetUnitAxis(EAxis::Y); + FVector Up = ComponentTransform.GetUnitAxis(EAxis::Z); + + // Draw barrel representation + FColor BarrelColor = FColor::Blue; + if (BarrelComponent->ChamberedBullet) + { + BarrelColor = FColor::Green; // Loaded + } + else if (BarrelComponent->Shooting) + { + BarrelColor = FColor::Red; // Firing + } + + // Draw barrel cylinder + float BarrelLength = FMath::Max(BarrelComponent->BarrelLength, 10.0f); // Minimum visual length + float BarrelRadius = 2.0f; + FVector BarrelEnd = Location + (Forward * BarrelLength); + + // Draw barrel outline + DrawWireCylinder(PDI, Location, Forward, Right, Up, BarrelColor, BarrelRadius, BarrelLength, 12, SDPG_Foreground); + + // Draw muzzle + FVector MuzzleLocation = BarrelEnd; + DrawWireBox(PDI, FBox(MuzzleLocation - FVector(2, 2, 2), MuzzleLocation + FVector(2, 2, 2)), FColor::Orange, SDPG_Foreground); + + // Draw firing direction indicator + if (BarrelComponent->Shooting) + { + FVector FireDirection = BarrelEnd + (Forward * 20.0f); + PDI->DrawLine(BarrelEnd, FireDirection, FColor::Red, SDPG_Foreground, 2.0f); + } + + // Draw spread cone when aiming + if (BarrelComponent->Spread > 0.0f) + { + float SpreadAngle = BarrelComponent->Spread; + float ConeLength = 50.0f; + FVector ConeEnd = BarrelEnd + (Forward * ConeLength); + float ConeRadius = FMath::Tan(SpreadAngle) * ConeLength; + + // Draw spread cone outline (temporarily commented out due to function signature issues) + // FQuat ConeRotation = FQuat::FindBetweenVectors(FVector::ForwardVector, Forward); + // DrawWireCone(PDI, FTransform(ConeRotation, BarrelEnd), ConeLength, ConeRadius, 8, FColor::Yellow); + + // Alternative: Draw lines to represent the cone + int32 NumSides = 8; + for (int32 i = 0; i < NumSides; i++) + { + float Angle = (2.0f * PI * i) / NumSides; + FVector RadialDirection = Right * FMath::Cos(Angle) + Up * FMath::Sin(Angle); + FVector ConePoint = BarrelEnd + Forward * ConeLength + RadialDirection * ConeRadius; + PDI->DrawLine(BarrelEnd, ConePoint, FColor::Yellow, SDPG_Foreground); + } + } + + // Draw ammo count indicator + if (BarrelComponent->Ammo.Num() > 0) + { + FVector AmmoIndicatorLocation = Location + (Right * 10.0f); + for (int32 i = 0; i < FMath::Min(BarrelComponent->Ammo.Num(), 10); i++) + { + FVector AmmoLocation = AmmoIndicatorLocation + (Up * i * 3.0f); + FVector BoxExtent = FVector(0.5f, 0.5f, 1.0f); + DrawWireBox(PDI, FBox(AmmoLocation - BoxExtent, AmmoLocation + BoxExtent), FColor::Cyan, SDPG_Foreground); + } + } +} + +bool FEBBarrelComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ return false; } -UObject* FEBBarrelComponentAssetBroker::GetAssetFromComponent(UActorComponent* InComponent) +bool FEBBarrelComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const +{ + return false; +} + +bool FEBBarrelComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const +{ + return false; +} + +bool FEBBarrelComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) +{ + return false; +} + +bool FEBBarrelComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + return false; +} + +TSharedPtr FEBBarrelComponentVisualizer::GenerateContextMenu() const +{ + return TSharedPtr(); +} + +bool FEBBarrelComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const +{ + return true; +} + +void FEBBarrelComponentVisualizer::EndEditing() { - if (UEBBarrel* BarrelComponent = Cast(InComponent)) - { - // Return any associated asset if we had barrel configuration assets - return nullptr; - } - return nullptr; } #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBGunActorFactory.cpp b/Source/EasyBallisticsEditor/Private/EBGunActorFactory.cpp new file mode 100644 index 0000000..ca69642 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBGunActorFactory.cpp @@ -0,0 +1,44 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBGunActorFactory.h" +#include "EBGun.h" +#include "Engine/Selection.h" +#include "Editor.h" + +UEBGunActorFactory::UEBGunActorFactory() +{ + DisplayName = NSLOCTEXT("EasyBallistics", "GunDisplayName", "EasyBallistics Gun"); + NewActorClass = AEBGun::StaticClass(); + bUsePlacementExtent = true; +} + +bool UEBGunActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) +{ + if (AssetData.IsValid()) + { + UClass* AssetClass = AssetData.GetClass(); + if (AssetClass && AssetClass->IsChildOf(UEBWeaponConfiguration::StaticClass())) + { + return true; + } + } + + return Super::CanCreateActorFrom(AssetData, OutErrorMsg); +} + +AActor* UEBGunActorFactory::SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) +{ + AEBGun* NewGun = Cast(Super::SpawnActor(InAsset, InLevel, InTransform, InSpawnParams)); + + if (NewGun) + { + // If spawned from a weapon configuration asset, apply it + if (UEBWeaponConfiguration* WeaponConfig = Cast(InAsset)) + { + NewGun->WeaponConfig = WeaponConfig; + NewGun->ApplyWeaponConfiguration(WeaponConfig); + } + } + + return NewGun; +} \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBMagazineComponentFactory.cpp b/Source/EasyBallisticsEditor/Private/EBMagazineComponentFactory.cpp new file mode 100644 index 0000000..8d162cf --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBMagazineComponentFactory.cpp @@ -0,0 +1,100 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBMagazineComponentFactory.h" +#include "EBMagazine.h" +#include "Engine/Selection.h" +#include "Components/SceneComponent.h" +#include "DrawDebugHelpers.h" + +#define LOCTEXT_NAMESPACE "EBMagazineComponentFactory" + +void FEBMagazineComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + const UEBMagazine* MagazineComponent = Cast(Component); + if (!MagazineComponent) + { + return; + } + + // Get the component's owner actor + const AActor* Owner = MagazineComponent->GetOwner(); + if (!Owner) + { + return; + } + + // Draw a simple visualization for the magazine + FVector Location = Owner->GetActorLocation(); + FVector Offset = FVector(0, 0, -20); // Offset below the actor + FVector MagazineLocation = Location + Offset; + + // Draw magazine representation + FColor MagazineColor = FColor::Yellow; + if (MagazineComponent->bIsEmpty) + { + MagazineColor = FColor::Red; + } + else if (MagazineComponent->bIsFull) + { + MagazineColor = FColor::Green; + } + + // Draw a box to represent the magazine + FVector BoxExtent = FVector(2, 8, 15); + DrawWireBox(PDI, FBox(MagazineLocation - BoxExtent, MagazineLocation + BoxExtent), MagazineColor, SDPG_Foreground); + + // Draw capacity indicator + float FillRatio = MagazineComponent->GetFillPercentage(); + FVector FillLocation = MagazineLocation + FVector(0, 0, -BoxExtent.Z + (BoxExtent.Z * 2 * FillRatio)); + FVector FillExtent = FVector(1, 6, BoxExtent.Z * FillRatio); + DrawWireBox(PDI, FBox(FillLocation - FillExtent, FillLocation + FillExtent), FColor::Orange, SDPG_Foreground); + + // Draw text showing round count + FString RoundCountText = FString::Printf(TEXT("%d/%d"), + MagazineComponent->GetCurrentRoundCount(), + MagazineComponent->GetMaxCapacity()); + + // Note: Text drawing in visualizers requires more complex setup + // For now, we'll just draw the geometric representation +} + +bool FEBMagazineComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + return false; +} + +bool FEBMagazineComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const +{ + return false; +} + +bool FEBMagazineComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const +{ + return false; +} + +bool FEBMagazineComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) +{ + return false; +} + +bool FEBMagazineComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + return false; +} + +TSharedPtr FEBMagazineComponentVisualizer::GenerateContextMenu() const +{ + return TSharedPtr(); +} + +bool FEBMagazineComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const +{ + return true; +} + +void FEBMagazineComponentVisualizer::EndEditing() +{ +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp b/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp index 4589c7c..7ab25de 100644 --- a/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp +++ b/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp @@ -3,9 +3,23 @@ #include "EBMathematicalBallisticsFactory.h" #include "EBMathematicalBallistics.h" #include "AssetToolsModule.h" +#include "EasyBallisticsEditor.h" #define LOCTEXT_NAMESPACE "EBMathematicalBallisticsFactory" +UEBMathematicalBallisticsFactory::UEBMathematicalBallisticsFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UEBMathematicalBallistics::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UEBMathematicalBallisticsFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} + UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const { return UEBMathematicalBallistics::StaticClass(); @@ -13,8 +27,7 @@ UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const uint32 FEBMathematicalBallisticsFactory::GetCategories() { - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); + return FEasyBallisticsEditorModule::GetBallisticsAssetCategory(); } FText FEBMathematicalBallisticsFactory::GetAssetDescription(const FAssetData& AssetData) const diff --git a/Source/EasyBallisticsEditor/Private/EBWeaponConfigurationFactory.cpp b/Source/EasyBallisticsEditor/Private/EBWeaponConfigurationFactory.cpp new file mode 100644 index 0000000..0a22692 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBWeaponConfigurationFactory.cpp @@ -0,0 +1,136 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBWeaponConfigurationFactory.h" +#include "EBWeaponConfiguration.h" +#include "EBMagazine.h" +#include "AssetToolsModule.h" +#include "EasyBallisticsEditor.h" + +#define LOCTEXT_NAMESPACE "EBWeaponConfigurationFactory" + +UEBWeaponConfigurationFactory::UEBWeaponConfigurationFactory() +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = UEBWeaponConfiguration::StaticClass(); +} + +UObject* UEBWeaponConfigurationFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UEBWeaponConfiguration* WeaponConfig = NewObject(InParent, Class, Name, Flags); + + if (WeaponConfig) + { + // Set up default configuration based on name + FString AssetName = Name.ToString(); + + if (AssetName.Contains(TEXT("M4")) || AssetName.Contains(TEXT("AR15"))) + { + // Configure for M4/AR-15 type weapon + WeaponConfig->WeaponName = TEXT("M4 Carbine"); + WeaponConfig->Manufacturer = TEXT("Colt"); + WeaponConfig->Model = TEXT("M4A1"); + WeaponConfig->Caliber = TEXT("5.56x45mm NATO"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto); + WeaponConfig->FireControlConfig.FireRateMin = 700.0f; + WeaponConfig->FireControlConfig.FireRateMax = 950.0f; + WeaponConfig->BarrelConfig.BarrelLength = 14.5f; + WeaponConfig->DefaultMagazineCapacity = 30; + } + else if (AssetName.Contains(TEXT("AK")) || AssetName.Contains(TEXT("74"))) + { + // Configure for AK type weapon + WeaponConfig->WeaponName = TEXT("AK-74"); + WeaponConfig->Manufacturer = TEXT("Kalashnikov"); + WeaponConfig->Model = TEXT("AK-74M"); + WeaponConfig->Caliber = TEXT("5.45x39mm"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto); + WeaponConfig->FireControlConfig.FireRateMin = 650.0f; + WeaponConfig->FireControlConfig.FireRateMax = 750.0f; + WeaponConfig->BarrelConfig.BarrelLength = 16.3f; + WeaponConfig->DefaultMagazineCapacity = 30; + } + else if (AssetName.Contains(TEXT("Pistol")) || AssetName.Contains(TEXT("Glock")) || AssetName.Contains(TEXT("1911"))) + { + // Configure for pistol + WeaponConfig->WeaponName = TEXT("M1911"); + WeaponConfig->Manufacturer = TEXT("Colt"); + WeaponConfig->Model = TEXT("M1911A1"); + WeaponConfig->Caliber = TEXT(".45 ACP"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Semiauto); + WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Semiauto; + WeaponConfig->FireControlConfig.FireRateMin = 300.0f; + WeaponConfig->FireControlConfig.FireRateMax = 400.0f; + WeaponConfig->BarrelConfig.BarrelLength = 5.0f; + WeaponConfig->DefaultMagazineCapacity = 7; + WeaponConfig->DefaultMagazineType = EMagazineType::MT_Standard; + } + else if (AssetName.Contains(TEXT("Sniper")) || AssetName.Contains(TEXT("Bolt"))) + { + // Configure for bolt-action sniper rifle + WeaponConfig->WeaponName = TEXT("M24 SWS"); + WeaponConfig->Manufacturer = TEXT("Remington"); + WeaponConfig->Model = TEXT("M24"); + WeaponConfig->Caliber = TEXT("7.62x51mm NATO"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Manual); + WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Manual; + WeaponConfig->FireControlConfig.FireRateMin = 60.0f; + WeaponConfig->FireControlConfig.FireRateMax = 60.0f; + WeaponConfig->BarrelConfig.BarrelLength = 24.0f; + WeaponConfig->BarrelConfig.InherentAccuracy = 0.0003f; // Very accurate + WeaponConfig->DefaultMagazineCapacity = 5; + WeaponConfig->DefaultMagazineType = EMagazineType::MT_Internal; + } + else if (AssetName.Contains(TEXT("Shotgun"))) + { + // Configure for shotgun + WeaponConfig->WeaponName = TEXT("M870"); + WeaponConfig->Manufacturer = TEXT("Remington"); + WeaponConfig->Model = TEXT("870"); + WeaponConfig->Caliber = TEXT("12 Gauge"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Manual); + WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Manual; + WeaponConfig->FireControlConfig.FireRateMin = 120.0f; + WeaponConfig->FireControlConfig.FireRateMax = 120.0f; + WeaponConfig->BarrelConfig.BarrelLength = 18.5f; + WeaponConfig->DefaultMagazineCapacity = 8; + WeaponConfig->DefaultMagazineType = EMagazineType::MT_Tube; + } + else if (AssetName.Contains(TEXT("LMG")) || AssetName.Contains(TEXT("Machine"))) + { + // Configure for light machine gun + WeaponConfig->WeaponName = TEXT("M249 SAW"); + WeaponConfig->Manufacturer = TEXT("FN Herstal"); + WeaponConfig->Model = TEXT("M249"); + WeaponConfig->Caliber = TEXT("5.56x45mm NATO"); + WeaponConfig->FireControlConfig.AvailableFireModes.Add(EFireMode::FM_Auto); + WeaponConfig->FireControlConfig.DefaultFireMode = EFireMode::FM_Auto; + WeaponConfig->FireControlConfig.FireRateMin = 750.0f; + WeaponConfig->FireControlConfig.FireRateMax = 1000.0f; + WeaponConfig->BarrelConfig.BarrelLength = 18.0f; + WeaponConfig->DefaultMagazineCapacity = 200; + WeaponConfig->DefaultMagazineType = EMagazineType::MT_Belt; + } + } + + return WeaponConfig; +} + +UClass* FEBWeaponConfigurationFactory::GetSupportedClass() const +{ + return UEBWeaponConfiguration::StaticClass(); +} + +uint32 FEBWeaponConfigurationFactory::GetCategories() +{ + return FEasyBallisticsEditorModule::GetBallisticsAssetCategory(); +} + +FText FEBWeaponConfigurationFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBWeaponConfigurationDescription", "Defines weapon properties including fire modes, barrel characteristics, magazine types, and ammunition specifications for ballistic weapons."); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp b/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp index 4a1bbdb..7da3335 100644 --- a/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp +++ b/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp @@ -3,14 +3,32 @@ #include "EasyBallisticsEditor.h" #include "AssetToolsModule.h" #include "PropertyEditorModule.h" +#include "EditorModeRegistry.h" +#include "ComponentVisualizer.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" + +// Asset Factories #include "EBMaterialResponseMapFactory.h" #include "EBBulletPropertiesFactory.h" #include "EBBulletActorFactory.h" -#include "EBBarrelComponentFactory.h" #include "EBMathematicalBallisticsFactory.h" +#include "EBWeaponConfigurationFactory.h" +#include "EBGunActorFactory.h" + +// Component Visualizers +#include "EBBarrelComponentFactory.h" +#include "EBMagazineComponentFactory.h" + +// Customizations #include "EBPhysicalMaterialCustomization.h" #include "EBJsonImportExportTool.h" + +// Component Classes +#include "EBBarrel.h" +#include "EBMagazine.h" #include "PhysicalMaterials/PhysicalMaterial.h" +#include "Editor.h" #define LOCTEXT_NAMESPACE "FEasyBallisticsEditorModule" @@ -41,6 +59,90 @@ void FEasyBallisticsEditorModule::StartupModule() RegisteredAssetTypeActions.Add(BulletPropertiesActions); } + // Register Material Properties Asset factory + { + TSharedRef MaterialPropertiesActions = MakeShareable(new FEBMaterialPropertiesAssetFactory()); + AssetTools.RegisterAssetTypeActions(MaterialPropertiesActions); + RegisteredAssetTypeActions.Add(MaterialPropertiesActions); + } + + // Register Mathematical Ballistics Asset factory + { + TSharedRef MathematicalBallisticsActions = MakeShareable(new FEBMathematicalBallisticsFactory()); + AssetTools.RegisterAssetTypeActions(MathematicalBallisticsActions); + RegisteredAssetTypeActions.Add(MathematicalBallisticsActions); + } + + // Register Weapon Configuration Asset factory + { + TSharedRef WeaponConfigurationActions = MakeShareable(new FEBWeaponConfigurationFactory()); + AssetTools.RegisterAssetTypeActions(WeaponConfigurationActions); + RegisteredAssetTypeActions.Add(WeaponConfigurationActions); + } + + // Register UFactory objects (these handle the "Add" menu in Content Browser) + // These need to be manually registered to appear in the Add menu + + // Register Material Response Map UFactory + if (!GetDefault()) + { + NewObject(); + } + + // Register Bullet Properties UFactory + if (!GetDefault()) + { + NewObject(); + } + + // Register Material Properties UFactory + if (!GetDefault()) + { + NewObject(); + } + + // Register Mathematical Ballistics UFactory + if (!GetDefault()) + { + NewObject(); + } + + // Register Weapon Configuration UFactory + if (!GetDefault()) + { + NewObject(); + } + + // Register UObject-based factories for actors + if (GEditor) + { + // Register Weapon Configuration factory + UEBWeaponConfigurationFactory* WeaponConfigFactory = NewObject(); + // UFactory objects are automatically discovered by the editor + + // Register Gun Actor factory + UEBGunActorFactory* GunActorFactory = NewObject(); + GEditor->ActorFactories.Add(GunActorFactory); + + // Register Bullet Actor factory + UEBBulletActorFactory* BulletActorFactory = NewObject(); + GEditor->ActorFactories.Add(BulletActorFactory); + } + + // Register Component Visualizers + if (GUnrealEd) + { + // Register Barrel Component Visualizer + TSharedPtr BarrelVisualizer = MakeShareable(new FEBBarrelComponentVisualizer()); + GUnrealEd->RegisterComponentVisualizer(UEBBarrel::StaticClass()->GetFName(), BarrelVisualizer); + RegisteredComponentClassNames.Add(UEBBarrel::StaticClass()->GetFName()); + + // Register Magazine Component Visualizer + TSharedPtr MagazineVisualizer = MakeShareable(new FEBMagazineComponentVisualizer()); + GUnrealEd->RegisterComponentVisualizer(UEBMagazine::StaticClass()->GetFName(), MagazineVisualizer); + RegisteredComponentClassNames.Add(UEBMagazine::StaticClass()->GetFName()); + } + // Register Physical Material customization FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout( @@ -60,6 +162,16 @@ void FEasyBallisticsEditorModule::ShutdownModule() // Unregister JSON Import/Export tool FEBJsonImportExportTool::UnregisterMenus(); + // Unregister Component Visualizers + if (GUnrealEd) + { + for (const FName& ClassName : RegisteredComponentClassNames) + { + GUnrealEd->UnregisterComponentVisualizer(ClassName); + } + } + RegisteredComponentClassNames.Empty(); + // Unregister Physical Material customization if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) { diff --git a/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h b/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h index e85234e..949c148 100644 --- a/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h +++ b/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h @@ -3,25 +3,19 @@ #pragma once #include "CoreMinimal.h" -#include "AssetTypeActions_Base.h" -#include "ComponentAssetBroker.h" +#include "ComponentVisualizer.h" #include "EBBarrel.h" -class FEBBarrelComponentFactory : public FAssetTypeActions_Base +class FEBBarrelComponentVisualizer : public FComponentVisualizer { public: - virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBarrelComponent", "Barrel Component"); } - virtual FColor GetTypeColor() const override { return FColor(100, 150, 255); } - virtual UClass* GetSupportedClass() const override; - virtual uint32 GetCategories() override; - virtual FText GetAssetDescription(const FAssetData& AssetData) const override; - virtual bool HasActions(const TArray& InObjects) const override { return false; } -}; - -class FEBBarrelComponentAssetBroker : public IComponentAssetBroker -{ -public: - virtual UClass* GetSupportedAssetClass() override; - virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override; - virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override; + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override; + virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + virtual TSharedPtr GenerateContextMenu() const override; + virtual bool IsVisualizationShown(const UActorComponent* Component) const; + virtual void EndEditing() override; }; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBGunActorFactory.h b/Source/EasyBallisticsEditor/Public/EBGunActorFactory.h new file mode 100644 index 0000000..a2afade --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBGunActorFactory.h @@ -0,0 +1,21 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ActorFactories/ActorFactory.h" +#include "EBWeaponConfiguration.h" +#include "EBGunActorFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBGunActorFactory : public UActorFactory +{ + GENERATED_BODY() + +public: + UEBGunActorFactory(); + + // UActorFactory interface + virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override; + virtual AActor* SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBMagazineComponentFactory.h b/Source/EasyBallisticsEditor/Public/EBMagazineComponentFactory.h new file mode 100644 index 0000000..f12d0ad --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBMagazineComponentFactory.h @@ -0,0 +1,21 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "ComponentVisualizer.h" +#include "EBMagazine.h" + +class FEBMagazineComponentVisualizer : public FComponentVisualizer +{ +public: + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override; + virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + virtual TSharedPtr GenerateContextMenu() const override; + virtual bool IsVisualizationShown(const UActorComponent* Component) const; + virtual void EndEditing() override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h b/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h index 7f4a4da..bb15a88 100644 --- a/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h +++ b/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h @@ -4,7 +4,21 @@ #include "CoreMinimal.h" #include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" #include "EBMathematicalBallistics.h" +#include "EBMathematicalBallisticsFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBMathematicalBallisticsFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + +public: + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override { return true; } + virtual FText GetDisplayName() const override { return NSLOCTEXT("EBFactory", "MathematicalBallisticsText", "Mathematical Ballistics"); } + virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MathematicalBallisticsTooltip", "Creates a new Mathematical Ballistics asset for ballistic calculations"); } +}; class FEBMathematicalBallisticsFactory : public FAssetTypeActions_Base { diff --git a/Source/EasyBallisticsEditor/Public/EBWeaponConfigurationFactory.h b/Source/EasyBallisticsEditor/Public/EBWeaponConfigurationFactory.h new file mode 100644 index 0000000..989acbe --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBWeaponConfigurationFactory.h @@ -0,0 +1,34 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" +#include "EBWeaponConfiguration.h" +#include "EBWeaponConfigurationFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBWeaponConfigurationFactory : public UFactory +{ + GENERATED_BODY() + +public: + UEBWeaponConfigurationFactory(); + + // UFactory interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override { return true; } + virtual FText GetDisplayName() const override { return NSLOCTEXT("EBFactory", "WeaponConfigurationText", "Weapon Configuration"); } + virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "WeaponConfigurationTooltip", "Creates a new Weapon Configuration asset defining weapon properties and behavior"); } +}; + +class FEBWeaponConfigurationFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBWeaponConfiguration", "Weapon Configuration"); } + virtual FColor GetTypeColor() const override { return FColor(255, 255, 100); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h b/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h index 85dea9d..2174fa9 100644 --- a/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h +++ b/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h @@ -4,18 +4,26 @@ #include "CoreMinimal.h" #include "Modules/ModuleManager.h" -#include "AssetTypeActions_Base.h" -#include "AssetToolsModule.h" +#include "AssetTypeCategories.h" +#include "IAssetTypeActions.h" class FEasyBallisticsEditorModule : public IModuleInterface { public: + /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; - + + /** Gets the ballistics asset category */ static EAssetTypeCategories::Type GetBallisticsAssetCategory() { return BallisticsAssetCategory; } private: - TArray> RegisteredAssetTypeActions; + /** Handle to the asset category we registered */ static EAssetTypeCategories::Type BallisticsAssetCategory; + + /** Array of asset type actions we registered */ + TArray> RegisteredAssetTypeActions; + + /** Array of component visualizers we registered */ + TArray RegisteredComponentClassNames; }; \ No newline at end of file diff --git a/docs/docs/core-concepts/overview.md b/docs/docs/core-concepts/overview.md index fd68b7c..e4774ed 100644 --- a/docs/docs/core-concepts/overview.md +++ b/docs/docs/core-concepts/overview.md @@ -2,6 +2,39 @@ Understanding the fundamental concepts behind EasyBallistics will help you build more effective and realistic ballistic systems. +## Architectural Approaches + +EasyBallistics supports two main architectural approaches to suit different project needs: + +### Gun-Centric Architecture (Recommended) +The **Gun-Centric Architecture** uses the `AEBGun` class as a complete weapon system that integrates: +- **Barrel Component**: Firing mechanism and ballistic calculations +- **Magazine Component**: Ammunition storage and feeding +- **Weapon Configuration**: Centralized parameters and settings +- **State Management**: Gun states, safety, fire modes + +This approach provides: +- ✅ **Complete weapon simulation** with realistic gun mechanics +- ✅ **Magazine management** with mixed ammunition types +- ✅ **Multiple fire modes** (semi-auto, full-auto, burst, etc.) +- ✅ **Safety systems** and weapon state tracking +- ✅ **Network-ready** with built-in replication + +**Best for**: Complete weapon systems, realistic firearms, tactical shooters + +See the **[Gun Blueprint Guide](../tutorials/gun-blueprint-guide.md)** for implementation details. + +### Component-Based Architecture +The **Component-Based Architecture** uses individual `UEBBarrel` components for custom implementations: +- **Flexible attachment** to any actor +- **Custom weapon designs** and non-traditional projectile systems +- **Granular control** over each system component +- **Legacy compatibility** with existing projects + +**Best for**: Custom weapons, vehicles with weapons, non-firearm projectile systems + +See the **[Blueprint Integration Guide](../tutorials/blueprint-integration.md)** for implementation details. + ## System Architecture EasyBallistics uses a modular architecture that separates concerns for maximum flexibility: @@ -266,4 +299,42 @@ Now that you understand the core concepts: 1. [API Reference](../api/overview) - Detailed function documentation 2. [Quick Start Guide](../getting-started/quick-start) - Create your first weapon -3. [Troubleshooting](../troubleshooting) - Common issues and solutions \ No newline at end of file +3. [Troubleshooting](../troubleshooting) - Common issues and solutions + +## Architectural Approaches + +EasyBallistics supports two main architectural approaches to suit different project needs: + +### Gun-Centric Architecture (Recommended) +The **Gun-Centric Architecture** uses the `AEBGun` class as a complete weapon system that integrates: +- **Barrel Component**: Firing mechanism and ballistic calculations +- **Magazine Component**: Ammunition storage and feeding +- **Weapon Configuration**: Centralized parameters and settings +- **State Management**: Gun states, safety, fire modes + +This approach provides: +- ✅ **Complete weapon simulation** with realistic gun mechanics +- ✅ **Magazine management** with mixed ammunition types +- ✅ **Multiple fire modes** (semi-auto, full-auto, burst, etc.) +- ✅ **Safety systems** and weapon state tracking +- ✅ **Network-ready** with built-in replication + +**Best for**: Complete weapon systems, realistic firearms, tactical shooters + +See the **[Gun Blueprint Guide](../tutorials/gun-blueprint-guide.md)** for implementation details. + +### Component-Based Architecture +The **Component-Based Architecture** uses individual `UEBBarrel` components for custom implementations: +- **Flexible attachment** to any actor +- **Custom weapon designs** and non-traditional projectile systems +- **Granular control** over each system component +- **Legacy compatibility** with existing projects + +**Best for**: Custom weapons, vehicles with weapons, non-firearm projectile systems + +See the **[Blueprint Integration Guide](../tutorials/blueprint-integration.md)** for implementation details. + +## Core System Components + +### Bullet Properties Asset +// ... existing content ... \ No newline at end of file diff --git a/docs/docs/getting-started/quick-start.md b/docs/docs/getting-started/quick-start.md index 560c1f6..9f4aa22 100644 --- a/docs/docs/getting-started/quick-start.md +++ b/docs/docs/getting-started/quick-start.md @@ -2,6 +2,10 @@ Get up and running with EasyBallistics in under 10 minutes! This guide will walk you through creating your first ballistic weapon system. +> **🎯 New Gun-Centric Approach Available!** +> +> This guide shows the **component-based approach** using individual barrel components. For a complete weapon system with magazines, fire modes, and realistic gun mechanics, see the **[Gun Blueprint Creation Guide](../tutorials/gun-blueprint-guide.md)**. + ## Overview In this quick start, you'll: diff --git a/docs/docs/intro.md b/docs/docs/intro.md index debc5b6..101fc77 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -66,8 +66,9 @@ graph TD ## Getting Started 1. **[Installation Guide](getting-started/installation)** - Install and configure the plugin -2. **[Quick Start](getting-started/quick-start)** - Create your first weapon in 10 minutes -3. **[Core Concepts](core-concepts/overview)** - Understand the system architecture +2. **[Gun Blueprint Guide](tutorials/gun-blueprint-guide)** - Create complete weapons with the new Gun-Centric Architecture +3. **[Quick Start](getting-started/quick-start)** - Alternative component-based approach +4. **[Core Concepts](core-concepts/overview)** - Understand the system architecture ## Core Systems diff --git a/docs/docs/tutorials/blueprint-integration.md b/docs/docs/tutorials/blueprint-integration.md index 018d156..1dd486a 100644 --- a/docs/docs/tutorials/blueprint-integration.md +++ b/docs/docs/tutorials/blueprint-integration.md @@ -2,6 +2,14 @@ EasyBallistics provides extensive Blueprint support, allowing developers to create sophisticated ballistic systems without writing C++ code. This guide covers everything from basic setup to advanced Blueprint patterns. +> **🎯 New Gun-Centric Architecture Available!** +> +> EasyBallistics now features a comprehensive **Gun-Centric Architecture** with the new `AEBGun` class that provides integrated weapon systems with magazine management, multiple fire modes, and realistic gun mechanics. +> +> **For creating complete weapon systems**, see the **[Gun Blueprint Creation Guide](gun-blueprint-guide.md)** which covers the new architecture. +> +> **This guide** focuses on the component-based approach using individual `UEBBarrel` components, which is still fully supported and useful for custom implementations. + ## Prerequisites - Basic knowledge of Unreal Engine Blueprints diff --git a/docs/docs/tutorials/gun-blueprint-guide.md b/docs/docs/tutorials/gun-blueprint-guide.md new file mode 100644 index 0000000..212bac9 --- /dev/null +++ b/docs/docs/tutorials/gun-blueprint-guide.md @@ -0,0 +1,533 @@ +# Gun Blueprint Creation Guide + +This guide covers creating fully functional gun blueprints using EasyBallistics' new **Gun-Centric Architecture**. The `AEBGun` class provides a complete weapon system with integrated barrel, magazine, and ammunition management. + +## Prerequisites + +- Basic knowledge of Unreal Engine Blueprints +- Understanding of Data Assets and Actor Components +- Familiarity with Unreal's event system +- EasyBallistics plugin installed and enabled + +## Architecture Overview + +The Gun-Centric Architecture consists of: + +- **`AEBGun`**: Main weapon actor with integrated systems +- **`UEBBarrel`**: Firing mechanism and ballistic calculations +- **`UEBMagazine`**: Ammunition storage and feeding +- **`UEBWeaponConfiguration`**: Centralized weapon parameters +- **`UEBBulletPropertiesAsset`**: Ammunition specifications + +## Step-by-Step Gun Creation + +### 1. Create Required Data Assets + +#### A. Create Bullet Properties Asset + +1. **Right-click** in Content Browser → **Ballistics** → **Bullet Properties** +2. **Name**: `BP_556NATO_BulletProperties` +3. **Configure properties**: + ``` + Basic Properties: + - Grain Weight: 55.0 + - Diameter Inches: 0.224 + - Length Inches: 0.825 + - Bullet Type: Full Metal Jacket + + Ballistics: + - Ballistic Coefficient G1: 0.151 + - Ballistic Coefficient G7: 0.076 + - Use G7 Model: false + + Penetration: + - Bullet Hardness: 15.0 + - Penetration Energy Threshold: 58.0 + ``` + +#### B. Create Weapon Configuration Asset + +1. **Right-click** in Content Browser → **Ballistics** → **Weapon Configuration** +2. **Name**: `BP_M4_WeaponConfig` +3. **Configure sections**: + + **Weapon Info:** + ``` + - Weapon Name: "M4 Carbine" + - Manufacturer: "Colt" + - Model: "M4A1" + - Caliber: "5.56x45mm NATO" + - Description: "Standard issue carbine" + ``` + + **Barrel Configuration:** + ``` + - Barrel Length: 14.5 inches + - Rifling Twist: 7.0 (1:7 twist) + - Bore Radius: 0.112 inches + - Muzzle Velocity Min: 91440 cm/s (~3000 fps) + - Muzzle Velocity Max: 91440 cm/s + - Inherent Accuracy: 0.001 MOA (radians) + - Spread Max: 0.001 radians + - Recoil Multiplier: 1.0 + ``` + + **Fire Control Configuration:** + ``` + - Available Fire Modes: [Semi-Auto, Full Auto] + - Default Fire Mode: Semi-Auto + - Fire Rate Min: 600 RPM + - Fire Rate Max: 750 RPM + - Burst Count: 3 + - Has Safety: true + - Default Safety On: false + ``` + + **Ammunition:** + ``` + - Default Bullet Type: BP_556NATO_BulletProperties + - Default Magazine Capacity: 30 + - Allow Mixed Ammo: true + ``` + +### 2. Create the Gun Blueprint + +#### A. Create Blueprint Class + +1. **Right-click** in Content Browser → **Blueprint Class** +2. **Search for**: `EBGun` +3. **Select**: `EBGun` class +4. **Name**: `BP_M4_Gun` + +#### B. Configure Gun Components + +1. **Open** `BP_M4_Gun` blueprint +2. **Components Panel**: + - **GunMesh**: Set your weapon's skeletal mesh + - **Barrel**: Automatically positioned at "MuzzleSocket" + - **Magazine**: Automatically created and configured + +**Important**: Your weapon mesh must have a **"MuzzleSocket"** bone/socket for proper bullet spawning. + +#### C. Set Weapon Configuration + +1. **Select** the root `BP_M4_Gun(self)` +2. **Details Panel**: + - **Weapon Config**: Set to `BP_M4_WeaponConfig` + +### 3. Blueprint Event Graph Setup + +#### A. Basic Initialization + +```blueprint +Event BeginPlay +├── Apply Weapon Configuration +│ └── Weapon Config: BP_M4_WeaponConfig +├── Load Magazine +│ └── Bullet Types: [BP_556NATO_BulletProperties x30] +├── Charging Handle +│ └── (Chambers first round) +└── Bind Events + ├── Bind Event to OnGunFired + ├── Bind Event to OnMagazineEmpty + ├── Bind Event to OnGunStateChanged + └── Bind Event to OnSafetyChanged +``` + +#### B. Input Actions + +```blueprint +InputAction Fire (Pressed) +├── Branch (Can Fire) +│ └── True: Pull Trigger +└── False: Print String "Cannot Fire" + +InputAction Fire (Released) +└── Release Trigger + +InputAction Reload (Pressed) +├── Branch (Rounds in Magazine < Max Capacity) +│ └── True: Reload Sequence +└── False: Print String "Magazine Full" + +InputAction ToggleSafety (Pressed) +├── Get Safety State +├── NOT (Safety State) +└── Set Safety (Result) + +InputAction ToggleFireMode (Pressed) +├── Get Fire Mode +├── Switch on Enum (Fire Mode) +│ ├── Semi-Auto: Set Fire Mode (Full Auto) +│ ├── Full Auto: Set Fire Mode (Semi-Auto) +│ └── Default: Set Fire Mode (Semi-Auto) +└── Print String ("Fire Mode: " + New Mode) +``` + +#### C. Reload System + +```blueprint +Custom Event: Reload Sequence +├── Branch (Has Ammo in Inventory) +│ ├── True: Perform Reload +│ │ ├── Eject Magazine +│ │ ├── Delay (Reload Animation Time) +│ │ ├── Insert Magazine +│ │ │ └── New Magazine: Create New Magazine +│ │ ├── Charging Handle +│ │ └── Play Reload Sound +│ └── False: Play Empty Sound +└── Update Ammo UI +``` + +### 4. Event Response System + +#### A. Gun State Management + +```blueprint +OnGunStateChanged (NewState) +├── Switch on Enum (NewState) +│ ├── Ready: +│ │ ├── Set UI Color (Green) +│ │ ├── Hide Loading Indicator +│ │ └── Enable Fire Input +│ ├── Firing: +│ │ ├── Set UI Color (Red) +│ │ ├── Show Firing Indicator +│ │ └── Play Firing Animation +│ ├── Empty: +│ │ ├── Set UI Color (Gray) +│ │ ├── Show Reload Prompt +│ │ └── Disable Fire Input +│ ├── Jammed: +│ │ ├── Set UI Color (Yellow) +│ │ ├── Show Jam Indicator +│ │ └── Disable Fire Input +│ └── Safety On: +│ ├── Set UI Color (Orange) +│ ├── Show Safety Indicator +│ └── Disable Fire Input +└── Update Status Text +``` + +#### B. Firing Effects + +```blueprint +OnGunFired (BulletType) +├── Spawn Emitter Attached +│ ├── Emitter Template: MuzzleFlash +│ ├── Attach to Component: GunMesh +│ └── Socket Name: MuzzleSocket +├── Play Sound 2D (GunFireSound) +├── Apply Camera Shake +│ ├── Shake Class: GunFireShake +│ └── Scale: 1.0 +├── Add Impulse to Player +│ ├── Impulse: Calculate Recoil Impulse +│ └── Velocity Change: false +├── Spawn Actor (ShellCasing) +│ ├── Class: BP_ShellCasing +│ ├── Transform: EjectionPort Socket +│ └── Spawn Even if Colliding: true +└── Update Ammo Counter +``` + +#### C. Magazine Management + +```blueprint +OnMagazineEmpty +├── Play Sound 2D (EmptyClickSound) +├── Show Reload Prompt UI +├── Set Can Fire (false) +└── Print String "Magazine Empty - Press R to Reload" + +OnMagazineChanged (NewMagazine) +├── Branch (Is Valid: NewMagazine) +│ ├── True: +│ │ ├── Hide Reload Prompt +│ │ ├── Set Can Fire (true) +│ │ └── Play Reload Complete Sound +│ └── False: +│ ├── Show No Magazine Warning +│ └── Set Can Fire (false) +└── Update Magazine UI +``` + +### 5. Advanced Features + +#### A. Multiple Ammunition Types + +```blueprint +Function: Setup Mixed Ammo +├── Create Array (BulletTypes) +│ ├── Add Item: BP_556_FMJ x20 +│ ├── Add Item: BP_556_AP x5 +│ ├── Add Item: BP_556_HP x3 +│ └── Add Item: BP_556_Tracer x2 +├── Shuffle Array (BulletTypes) +└── Load Magazine (BulletTypes) + +Function: Get Current Ammo Info +├── Get Chambered Bullet Type +├── Branch (Is Valid: Bullet Type) +│ ├── True: Get Bullet Info +│ │ ├── Bullet Name +│ │ ├── Damage Type +│ │ └── Special Properties +│ └── False: Return "No Round Chambered" +└── Update Ammo Type UI +``` + +#### B. Weapon Customization + +```blueprint +Function: Apply Weapon Attachments +├── Input: Attachment Array +├── For Each Loop (Attachment) +│ ├── Switch on Attachment Type +│ │ ├── Scope: +│ │ │ ├── Modify Accuracy +│ │ │ └── Add Scope Mesh +│ │ ├── Suppressor: +│ │ │ ├── Reduce Noise +│ │ │ └── Modify Muzzle Flash +│ │ ├── Foregrip: +│ │ │ ├── Reduce Recoil +│ │ │ └── Improve Handling +│ │ └── Extended Magazine: +│ │ ├── Increase Capacity +│ │ └── Modify Reload Time +│ └── Update Weapon Stats +└── Refresh Weapon Model +``` + +#### C. Smart Targeting System + +```blueprint +Function: Calculate Lead Target +├── Input: Target Actor +├── Get Target Velocity +├── Calculate Aim Direction +│ ├── Bullet Class: Current Bullet +│ ├── Target Location: Target Position +│ ├── Target Velocity: Movement Vector +│ └── Max Time: 10.0 seconds +├── Output: Predicted Aim Direction +└── Update Crosshair Position + +Function: Predict Trajectory +├── Input: Aim Direction +├── Predict Hit +│ ├── Bullet Class: Current Bullet +│ ├── Max Time: 5.0 seconds +│ └── Step Size: 0.1 seconds +├── Output: Trajectory Points Array +└── Draw Trajectory Line +``` + +### 6. UI Integration + +#### A. Weapon Status HUD + +```blueprint +Widget: Weapon Status HUD +├── Ammo Counter +│ ├── Current Rounds: Get Rounds in Magazine +│ ├── Chambered Round: Get Chambered Bullet Type +│ └── Total Rounds: Get Total Rounds +├── Fire Mode Indicator +│ ├── Get Fire Mode +│ └── Display Mode Icon +├── Safety Indicator +│ ├── Get Safety State +│ └── Show/Hide Safety Icon +├── Weapon Status +│ ├── Get Gun State +│ └── Display Status Text +└── Crosshair + ├── Dynamic Size: Based on Spread + └── Hit Indicator: Based on Trajectory +``` + +#### B. Weapon Selection Menu + +```blueprint +Widget: Weapon Selection +├── For Each Available Weapon +│ ├── Create Weapon Button +│ ├── Set Weapon Icon +│ ├── Set Weapon Name +│ └── Bind Selection Event +├── On Weapon Selected +│ ├── Destroy Current Weapon +│ ├── Spawn New Weapon +│ └── Equip Weapon +└── Update Selection UI +``` + +### 7. Multiplayer Support + +#### A. Network Setup + +```blueprint +Event BeginPlay +├── Branch (Has Authority) +│ ├── True: Server Setup +│ │ ├── Initialize Weapon Systems +│ │ ├── Set Replication Properties +│ │ └── Enable Authority Functions +│ └── False: Client Setup +│ ├── Bind to Replicated Events +│ ├── Setup Client Prediction +│ └── Enable Input Handling +└── Configure Network Settings + ├── Replicate Variables: true + ├── Replicate Shot Events: true + └── Client Side Aim: true +``` + +#### B. Client Prediction + +```blueprint +Function: Client Fire Prediction +├── Branch (Has Authority) +│ ├── True: Fire Immediately +│ └── False: Predicted Fire +│ ├── Play Effects Immediately +│ ├── Send Fire Request to Server +│ └── Wait for Server Confirmation +└── Handle Prediction Correction +``` + +### 8. Performance Optimization + +#### A. LOD System + +```blueprint +Event Tick +├── Branch (Should Update LOD) +│ └── True: Update LOD +│ ├── Get Distance to Player +│ ├── Calculate LOD Level +│ ├── Switch on LOD Level +│ │ ├── High: Full Detail +│ │ ├── Medium: Reduced Effects +│ │ └── Low: Minimal Detail +│ └── Apply LOD Settings +└── Set Should Update LOD (false) +``` + +#### B. Object Pooling + +```blueprint +Manager: Weapon Pool +├── Initialize Pool +│ ├── Create Weapon Array +│ └── Pre-spawn Weapons +├── Get Weapon from Pool +│ ├── Find Available Weapon +│ ├── Configure Weapon +│ └── Return to Requester +└── Return Weapon to Pool + ├── Reset Weapon State + └── Mark as Available +``` + +### 9. Debugging and Testing + +#### A. Debug Visualization + +```blueprint +Input Action: Toggle Debug +├── Branch (Debug Enabled) +│ ├── True: Disable Debug +│ │ ├── Clear Debug Display +│ │ └── Set Debug Enabled (false) +│ └── False: Enable Debug +│ ├── Show Trajectory +│ ├── Show Impact Points +│ ├── Show Weapon Stats +│ └── Set Debug Enabled (true) +└── Update Debug UI +``` + +#### B. Test Scenarios + +```blueprint +Function: Run Weapon Tests +├── Test Basic Firing +│ ├── Load Magazine +│ ├── Fire Single Shot +│ └── Verify Bullet Spawned +├── Test Fire Modes +│ ├── Test Semi-Auto +│ ├── Test Full Auto +│ └── Test Burst Fire +├── Test Reload System +│ ├── Empty Magazine +│ ├── Reload Weapon +│ └── Verify New Ammo +└── Test Safety System + ├── Enable Safety + ├── Attempt Fire + └── Verify Block +``` + +### 10. Common Issues and Solutions + +#### A. Troubleshooting Guide + +**Gun Won't Fire:** +- Check if magazine is loaded: `Get Rounds in Magazine > 0` +- Verify round is chambered: `Get Chambered Bullet Type != None` +- Check safety state: `Get Safety State == false` +- Verify gun state: `Get Gun State == Ready` + +**Bullets Don't Spawn:** +- Check MuzzleSocket exists on mesh +- Verify barrel component is attached +- Check bullet class is valid +- Verify barrel has parent gun reference + +**Automatic Fire Won't Stop:** +- Ensure `Release Trigger` is called +- Check fire mode is set correctly +- Verify trigger input is properly bound +- Check for infinite loop in firing logic + +**Networking Issues:** +- Verify replication settings are enabled +- Check server authority for critical functions +- Ensure client prediction is configured +- Test with multiple clients + +### 11. Best Practices + +#### A. Code Organization + +1. **Separate Concerns**: Keep weapon logic, UI, and effects separate +2. **Use Events**: Leverage the gun's event system for responsiveness +3. **Validate Inputs**: Always check for valid references +4. **Handle Edge Cases**: Account for empty magazines, jams, etc. + +#### B. Performance Considerations + +1. **Limit Tick Usage**: Use events instead of tick when possible +2. **Pool Objects**: Reuse bullets, effects, and UI elements +3. **LOD Management**: Reduce detail for distant weapons +4. **Network Optimization**: Minimize replicated data + +#### C. User Experience + +1. **Responsive Feedback**: Provide immediate visual/audio feedback +2. **Clear State**: Always show current weapon state +3. **Intuitive Controls**: Use standard FPS control schemes +4. **Accessibility**: Support different input methods + +## Conclusion + +The Gun-Centric Architecture provides a comprehensive, realistic weapon system that handles all aspects of firearm behavior. By following this guide, you'll create fully functional weapons with proper ammunition management, multiple fire modes, and networking support. + +The system is designed to be extensible, allowing you to add custom features while maintaining the core ballistic simulation accuracy that EasyBallistics is known for. + +For additional examples and advanced techniques, refer to the other tutorials in this documentation set. \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index bc16ea6..49d4a18 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -38,15 +38,16 @@ const sidebars = { 'core-concepts/unit-conversions', ], }, - { - type: 'category', - label: 'Tutorials', - items: [ - 'tutorials/blueprint-integration', - 'tutorials/material-setup-guide', - 'tutorials/advanced-weapon-systems', - ], - }, + { + type: 'category', + label: 'Tutorials', + items: [ + 'tutorials/gun-blueprint-guide', + 'tutorials/blueprint-integration', + 'tutorials/material-setup-guide', + 'tutorials/advanced-weapon-systems', + ], + }, { type: 'category', label: 'Advanced Guides',