Gun Centric Refactor Start
This commit is contained in:
@@ -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<AEBGun>();
|
||||
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<UEBBulletPropertiesAsset*> 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.
|
||||
@@ -35,5 +35,11 @@
|
||||
"Mac"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "EditorScriptingUtilities",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,7 +22,8 @@ public class EasyBallistics : ModuleRules
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"PhysicsCore"
|
||||
"PhysicsCore",
|
||||
"NetCore"
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -327,3 +331,150 @@ FString UEBBarrel::GetBulletDebugInfo() const
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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<USkeletalMeshComponent>(TEXT("GunMesh"));
|
||||
RootComponent = GunMesh;
|
||||
|
||||
// Create the barrel component
|
||||
Barrel = CreateDefaultSubobject<UEBBarrel>(TEXT("Barrel"));
|
||||
Barrel->SetupAttachment(GunMesh, TEXT("MuzzleSocket"));
|
||||
Barrel->SetParentGun(this);
|
||||
|
||||
// Create the magazine component
|
||||
Magazine = CreateDefaultSubobject<UEBMagazine>(TEXT("Magazine"));
|
||||
|
||||
// Initialize state
|
||||
CurrentGunState = EGunState::GS_Ready;
|
||||
PreviousGunState = EGunState::GS_Ready;
|
||||
bSafetyOn = false;
|
||||
bChamberedRound = false;
|
||||
bHammerCocked = false;
|
||||
bTriggerPressed = false;
|
||||
bCanProcessNextShot = true;
|
||||
BurstShotsFired = 0;
|
||||
ChamberedBulletType = nullptr;
|
||||
}
|
||||
|
||||
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<FLifetimeProperty>& 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<UEBBulletPropertiesAsset*> BulletTypes)
|
||||
{
|
||||
if (Magazine)
|
||||
{
|
||||
Magazine->LoadBullets(BulletTypes);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBGun::EjectMagazine()
|
||||
{
|
||||
if (HasAuthority())
|
||||
{
|
||||
ServerEjectMagazine();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerEjectMagazine();
|
||||
}
|
||||
}
|
||||
|
||||
void AEBGun::ServerEjectMagazine_Implementation()
|
||||
{
|
||||
if (Magazine)
|
||||
{
|
||||
Magazine->UnloadAllBullets();
|
||||
}
|
||||
|
||||
OnMagazineChanged.Broadcast(nullptr);
|
||||
MulticastOnMagazineChanged(nullptr);
|
||||
}
|
||||
|
||||
void AEBGun::InsertMagazine(UEBMagazine* NewMagazine)
|
||||
{
|
||||
if (HasAuthority())
|
||||
{
|
||||
ServerInsertMagazine(NewMagazine);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInsertMagazine(NewMagazine);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBGun::ServerInsertMagazine_Implementation(UEBMagazine* NewMagazine)
|
||||
{
|
||||
if (!NewMagazine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace magazine component (in a real implementation, this would be more complex)
|
||||
Magazine = NewMagazine;
|
||||
|
||||
OnMagazineChanged.Broadcast(NewMagazine);
|
||||
MulticastOnMagazineChanged(NewMagazine);
|
||||
}
|
||||
|
||||
void AEBGun::ChargingHandle()
|
||||
{
|
||||
if (HasAuthority())
|
||||
{
|
||||
ServerChargingHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerChargingHandle();
|
||||
}
|
||||
}
|
||||
|
||||
void AEBGun::ServerChargingHandle_Implementation()
|
||||
{
|
||||
// Eject chambered round if present
|
||||
if (bChamberedRound)
|
||||
{
|
||||
ChamberedBulletType = nullptr;
|
||||
bChamberedRound = false;
|
||||
}
|
||||
|
||||
// Feed new round from magazine
|
||||
if (Magazine && Magazine->CanFeed())
|
||||
{
|
||||
UEBBulletPropertiesAsset* NewBullet = Magazine->FeedNextBullet();
|
||||
if (NewBullet)
|
||||
{
|
||||
ChamberedBulletType = NewBullet;
|
||||
bChamberedRound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update gun state
|
||||
if (CurrentGunState == EGunState::GS_Empty && bChamberedRound)
|
||||
{
|
||||
SetGunState(EGunState::GS_Ready);
|
||||
}
|
||||
else if (!bChamberedRound && (!Magazine || Magazine->bIsEmpty))
|
||||
{
|
||||
SetGunState(EGunState::GS_Empty);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBGun::EjectChamberedRound()
|
||||
{
|
||||
if (HasAuthority())
|
||||
{
|
||||
if (bChamberedRound)
|
||||
{
|
||||
ChamberedBulletType = nullptr;
|
||||
bChamberedRound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AEBGun::CanFire() const
|
||||
{
|
||||
if (bSafetyOn)
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -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<FLifetimeProperty>& 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<UEBBulletPropertiesAsset*> BulletTypes)
|
||||
{
|
||||
if (GetOwner()->HasAuthority())
|
||||
{
|
||||
ServerLoadBullets(BulletTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerLoadBullets(BulletTypes);
|
||||
}
|
||||
}
|
||||
|
||||
void UEBMagazine::ServerLoadBullets_Implementation(const TArray<UEBBulletPropertiesAsset*>& 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<UEBBulletPropertiesAsset*> UEBMagazine::GetBulletTypesSummary() const
|
||||
{
|
||||
TArray<UEBBulletPropertiesAsset*> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<UEBWeaponConfiguration*>(this));
|
||||
}
|
||||
}
|
||||
@@ -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<TSubclassOf<class AEBBullet>> 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<TSubclassOf<class AEBBullet>> 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<class AEBBullet> 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<TSubclassOf<class AEBBullet>> GetAmmo(bool CountChambered) const;
|
||||
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& 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<TSubclassOf<class AEBBullet>> GetAmmo(bool CountChambered) const;
|
||||
UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Legacy Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray<TSubclassOf<class AEBBullet>>& 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);
|
||||
|
||||
@@ -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<UEBBulletPropertiesAsset*> 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<FLifetimeProperty>& 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();
|
||||
};
|
||||
@@ -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<UEBBulletPropertiesAsset*> 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<UEBBulletPropertiesAsset*> 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<UEBBulletPropertiesAsset*> 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<FLifetimeProperty>& 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<UEBBulletPropertiesAsset*>& 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();
|
||||
};
|
||||
@@ -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<EFireMode> 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<UEBBulletPropertiesAsset*> 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;
|
||||
};
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<FAssetToolsModule>("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<UEBBarrel>(InComponent))
|
||||
const UEBBarrel* BarrelComponent = Cast<const UEBBarrel>(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<SWidget> FEBBarrelComponentVisualizer::GenerateContextMenu() const
|
||||
{
|
||||
return TSharedPtr<SWidget>();
|
||||
}
|
||||
|
||||
bool FEBBarrelComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FEBBarrelComponentVisualizer::EndEditing()
|
||||
{
|
||||
if (UEBBarrel* BarrelComponent = Cast<UEBBarrel>(InComponent))
|
||||
{
|
||||
// Return any associated asset if we had barrel configuration assets
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -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<AEBGun>(Super::SpawnActor(InAsset, InLevel, InTransform, InSpawnParams));
|
||||
|
||||
if (NewGun)
|
||||
{
|
||||
// If spawned from a weapon configuration asset, apply it
|
||||
if (UEBWeaponConfiguration* WeaponConfig = Cast<UEBWeaponConfiguration>(InAsset))
|
||||
{
|
||||
NewGun->WeaponConfig = WeaponConfig;
|
||||
NewGun->ApplyWeaponConfiguration(WeaponConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return NewGun;
|
||||
}
|
||||
@@ -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<const UEBMagazine>(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<SWidget> FEBMagazineComponentVisualizer::GenerateContextMenu() const
|
||||
{
|
||||
return TSharedPtr<SWidget>();
|
||||
}
|
||||
|
||||
bool FEBMagazineComponentVisualizer::IsVisualizationShown(const UActorComponent* Component) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FEBMagazineComponentVisualizer::EndEditing()
|
||||
{
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -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<UEBMathematicalBallistics>(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<FAssetToolsModule>("AssetTools").Get();
|
||||
return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics"));
|
||||
return FEasyBallisticsEditorModule::GetBallisticsAssetCategory();
|
||||
}
|
||||
|
||||
FText FEBMathematicalBallisticsFactory::GetAssetDescription(const FAssetData& AssetData) const
|
||||
|
||||
@@ -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<UEBWeaponConfiguration>(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
|
||||
@@ -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<IAssetTypeActions> MaterialPropertiesActions = MakeShareable(new FEBMaterialPropertiesAssetFactory());
|
||||
AssetTools.RegisterAssetTypeActions(MaterialPropertiesActions);
|
||||
RegisteredAssetTypeActions.Add(MaterialPropertiesActions);
|
||||
}
|
||||
|
||||
// Register Mathematical Ballistics Asset factory
|
||||
{
|
||||
TSharedRef<IAssetTypeActions> MathematicalBallisticsActions = MakeShareable(new FEBMathematicalBallisticsFactory());
|
||||
AssetTools.RegisterAssetTypeActions(MathematicalBallisticsActions);
|
||||
RegisteredAssetTypeActions.Add(MathematicalBallisticsActions);
|
||||
}
|
||||
|
||||
// Register Weapon Configuration Asset factory
|
||||
{
|
||||
TSharedRef<IAssetTypeActions> 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<UEBMaterialResponseMapFactory>())
|
||||
{
|
||||
NewObject<UEBMaterialResponseMapFactory>();
|
||||
}
|
||||
|
||||
// Register Bullet Properties UFactory
|
||||
if (!GetDefault<UEBBulletPropertiesAssetFactory>())
|
||||
{
|
||||
NewObject<UEBBulletPropertiesAssetFactory>();
|
||||
}
|
||||
|
||||
// Register Material Properties UFactory
|
||||
if (!GetDefault<UEBMaterialPropertiesAssetFactory>())
|
||||
{
|
||||
NewObject<UEBMaterialPropertiesAssetFactory>();
|
||||
}
|
||||
|
||||
// Register Mathematical Ballistics UFactory
|
||||
if (!GetDefault<UEBMathematicalBallisticsFactory>())
|
||||
{
|
||||
NewObject<UEBMathematicalBallisticsFactory>();
|
||||
}
|
||||
|
||||
// Register Weapon Configuration UFactory
|
||||
if (!GetDefault<UEBWeaponConfigurationFactory>())
|
||||
{
|
||||
NewObject<UEBWeaponConfigurationFactory>();
|
||||
}
|
||||
|
||||
// Register UObject-based factories for actors
|
||||
if (GEditor)
|
||||
{
|
||||
// Register Weapon Configuration factory
|
||||
UEBWeaponConfigurationFactory* WeaponConfigFactory = NewObject<UEBWeaponConfigurationFactory>();
|
||||
// UFactory objects are automatically discovered by the editor
|
||||
|
||||
// Register Gun Actor factory
|
||||
UEBGunActorFactory* GunActorFactory = NewObject<UEBGunActorFactory>();
|
||||
GEditor->ActorFactories.Add(GunActorFactory);
|
||||
|
||||
// Register Bullet Actor factory
|
||||
UEBBulletActorFactory* BulletActorFactory = NewObject<UEBBulletActorFactory>();
|
||||
GEditor->ActorFactories.Add(BulletActorFactory);
|
||||
}
|
||||
|
||||
// Register Component Visualizers
|
||||
if (GUnrealEd)
|
||||
{
|
||||
// Register Barrel Component Visualizer
|
||||
TSharedPtr<FEBBarrelComponentVisualizer> BarrelVisualizer = MakeShareable(new FEBBarrelComponentVisualizer());
|
||||
GUnrealEd->RegisterComponentVisualizer(UEBBarrel::StaticClass()->GetFName(), BarrelVisualizer);
|
||||
RegisteredComponentClassNames.Add(UEBBarrel::StaticClass()->GetFName());
|
||||
|
||||
// Register Magazine Component Visualizer
|
||||
TSharedPtr<FEBMagazineComponentVisualizer> MagazineVisualizer = MakeShareable(new FEBMagazineComponentVisualizer());
|
||||
GUnrealEd->RegisterComponentVisualizer(UEBMagazine::StaticClass()->GetFName(), MagazineVisualizer);
|
||||
RegisteredComponentClassNames.Add(UEBMagazine::StaticClass()->GetFName());
|
||||
}
|
||||
|
||||
// Register Physical Material customization
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("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"))
|
||||
{
|
||||
|
||||
@@ -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<UObject*>& 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<SWidget> GenerateContextMenu() const override;
|
||||
virtual bool IsVisualizationShown(const UActorComponent* Component) const;
|
||||
virtual void EndEditing() override;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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<SWidget> GenerateContextMenu() const override;
|
||||
virtual bool IsVisualizationShown(const UActorComponent* Component) const;
|
||||
virtual void EndEditing() override;
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
|
||||
/** Handle to the asset category we registered */
|
||||
static EAssetTypeCategories::Type BallisticsAssetCategory;
|
||||
|
||||
/** Array of asset type actions we registered */
|
||||
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
|
||||
|
||||
/** Array of component visualizers we registered */
|
||||
TArray<FName> RegisteredComponentClassNames;
|
||||
};
|
||||
@@ -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:
|
||||
@@ -267,3 +300,41 @@ 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
|
||||
|
||||
## 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 ...
|
||||
@@ -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:
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
+10
-9
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user