diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d720aa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +Binaries/ + +Intermediate/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9aff9ae --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,85 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is EasyBallistics_C, an Unreal Engine 5.6 plugin that provides realistic ballistics simulation for projectiles. The plugin implements advanced physics calculations including atmospheric effects, material penetration, ricochets, and drag curves. + +## Build System + +This is an Unreal Engine plugin that uses Unreal's build system. The plugin compiles as part of the host project using UnrealBuildTool. + +### Build Configuration +- **Module**: EasyBallistics (Runtime module) +- **Engine Version**: 5.6.0 +- **Platforms**: Win64, Linux, Mac, Android +- **Build File**: `Source/EasyBallistics/EasyBallistics.Build.cs` + +### Dependencies +- **Public**: Core +- **Private**: CoreUObject, Engine, PhysicsCore + +## Architecture + +### Core Components + +1. **AEBBullet** (`EBBullet.h/.cpp`) - Main bullet actor + - Handles physics simulation, atmospheric effects, penetration + - Implements pooling system for performance + - Supports complex ballistics including drag curves, wind effects + - Manages collision detection and material responses + +2. **UEBBarrel** (`EBBarrel.h/.cpp`) - Weapon barrel component + - Manages firing mechanics (auto, semi-auto, burst, gatling modes) + - Handles ammo cycling and reloading + - Implements spread patterns and muzzle velocity variations + - Supports networked replication for multiplayer + +3. **UEBMaterialResponseMap** (`EBMaterialResponseMap.h`) - Material system + - Maps physical materials to ballistic responses + - Configures penetration depth, ricochet probability, friction + - Supports different penetration trace types + +### Key Features + +- **Realistic Physics**: Atmospheric density, temperature, pressure effects +- **Material Penetration**: Configurable penetration depth per material +- **Ricochet System**: Angle-based ricochet probability with energy loss +- **Multiplayer Support**: Full replication with client-side prediction +- **Performance Optimization**: Object pooling for high-rate fire scenarios +- **Shotgun Support**: Multi-projectile spawning with configurable spread + +### File Structure + +``` +Source/EasyBallistics/ +├── EasyBallistics.Build.cs # Build configuration +├── Public/ +│ ├── EasyBallistics.h # Module header +│ ├── EBBarrel.h # Barrel component +│ ├── EBBullet.h # Bullet actor +│ └── EBMaterialResponseMap.h # Material response system +└── Private/ + ├── EasyBallistics.cpp # Module implementation + ├── EBBarrel.cpp # Barrel implementation + ├── EBBullet.cpp # Bullet implementation + └── [Additional implementation files] +``` + +## Development Notes + +### Networking +- Uses Unreal's replication system extensively +- Supports both reliable and unreliable multicast for performance +- Client-side aim prediction for responsive gameplay + +### Performance Considerations +- Object pooling enabled by default for bullets +- Fixed timestep simulation option for consistent physics +- Configurable trace complexity and collision margins + +### Physics Integration +- Integrates with Unreal's physics system for impulse application +- Supports both simple and complex collision detection +- Atmospheric model supports Earth-like conditions or custom curves \ No newline at end of file diff --git a/EasyBallistics.uplugin b/EasyBallistics.uplugin new file mode 100644 index 0000000..13180d1 --- /dev/null +++ b/EasyBallistics.uplugin @@ -0,0 +1,39 @@ +{ + "FileVersion": 3, + "Version": 0, + "VersionName": "2.83", + "FriendlyName": "EasyBallistics_C", + "Description": "", + "Category": "Gameplay", + "CreatedBy": "Mookie", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/bbecde0f66914263b57fd2af5a0c7ffe", + "SupportURL": "", + "EngineVersion": "5.6.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "EasyBallistics", + "Type": "Runtime", + "LoadingPhase": "Default", + "PlatformAllowList": [ + "Win64", + "Linux", + "Mac", + "Android" + ] + }, + { + "Name": "EasyBallisticsEditor", + "Type": "Editor", + "LoadingPhase": "Default", + "PlatformAllowList": [ + "Win64", + "Linux", + "Mac" + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000..d6596ee Binary files /dev/null and b/Resources/Icon128.png differ diff --git a/Source/EasyBallistics/EasyBallistics.Build.cs b/Source/EasyBallistics/EasyBallistics.Build.cs new file mode 100644 index 0000000..59be64b --- /dev/null +++ b/Source/EasyBallistics/EasyBallistics.Build.cs @@ -0,0 +1,38 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class EasyBallistics : ModuleRules +{ + public EasyBallistics(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + DefaultBuildSettings = BuildSettingsVersion.Latest; + IncludeOrderVersion = EngineIncludeOrderVersion.Latest; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "PhysicsCore" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/EasyBallistics/Private/Acceleration.cpp b/Source/EasyBallistics/Private/Acceleration.cpp new file mode 100644 index 0000000..180fc93 --- /dev/null +++ b/Source/EasyBallistics/Private/Acceleration.cpp @@ -0,0 +1,32 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBullet.h" + +FVector AEBBullet::UpdateVelocity_Implementation(UWorld* World, FVector Location, FVector PreviousVelocity, float DeltaTime) const { + FVector NewVelocity = PreviousVelocity; + + //airDensity + float air; + float speedOfSound; + + air = GetAirDensity(World, Location); + speedOfSound = GetSpeedOfSound(World, Location); + + //gravity + if (!OverrideGravity) { + NewVelocity += FVector(0, 0, World->GetGravityZ())*DeltaTime; + } + else { + NewVelocity += Gravity*DeltaTime; + }; + + //drag + FVector relVel = (NewVelocity - GetWind(World, Location)); + float speed = relVel.Size(); + float mach = speed / speedOfSound; + float profile = FMath::Pow(Diameter / 200.0f, 2.0f)*3.141592f; + float drag = GetCurveValue(MachDragCurve, mach, 0.25f)*FMath::Pow(speed / 100.0f, 2.0f)*profile*air*FormFactor*50.0f; + NewVelocity -= relVel.GetSafeNormal() * drag / Mass * DeltaTime / WorldScale; + + return NewVelocity; +} diff --git a/Source/EasyBallistics/Private/BarrelEvents.cpp b/Source/EasyBallistics/Private/BarrelEvents.cpp new file mode 100644 index 0000000..4f7af6f --- /dev/null +++ b/Source/EasyBallistics/Private/BarrelEvents.cpp @@ -0,0 +1,113 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBarrel.h" +#include "Net/UnrealNetwork.h" + +#define REPOWNERONLY false + +void UEBBarrel::ShotFiredMulticast_Implementation() { + ShotFired.Broadcast(); +} + + +void UEBBarrel::Shoot(bool Trigger) { + if (ClientSideAim && GetOwner()->GetRemoteRole() == ROLE_Authority && Trigger) { + Aim = GetComponentTransform().GetUnitAxis(EAxis::X); + Location = GetComponentTransform().GetLocation(); + ShootRepCSA(Trigger, UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(), Location), Aim); + } + else { + ShootRep(Trigger); + } +} + +void UEBBarrel::ShootRep_Implementation(bool Trigger) { + if (Trigger) { + if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) { + BurstRemaining = BurstCount; + }; + Shooting = true; + } + else { + //burst cannot be interrupted + if (FireMode != EFireMode::FM_Burst || BurstRemaining<=0) { + Shooting = false; + } + } +} + +bool UEBBarrel::ShootRep_Validate(bool Trigger) { + return true; +} + +void UEBBarrel::ShootRepCSA_Implementation(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) { + Location = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation); + Aim = NewAim; + RemoteAimReceived = true; + + if (Trigger) { + if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) { + BurstRemaining = BurstCount; + }; + Shooting = true; + } + else { + //burst cannot be interrupted + if (FireMode != EFireMode::FM_Burst || BurstRemaining <= 0) { + Shooting = false; + } + } +} + +bool UEBBarrel::ShootRepCSA_Validate(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) { + return true; +} + +void UEBBarrel::GatlingSpool_Implementation(bool Spool) { + Spooling = Spool; +} + +bool UEBBarrel::GatlingSpool_Validate(bool Spool) { + return true; +} + +void UEBBarrel::SwitchFireMode_Implementation(EFireMode NewFireMode) { + FireMode = NewFireMode; +} +bool UEBBarrel::SwitchFireMode_Validate(EFireMode NewFireMode) { + return true; +} + +void UEBBarrel::ClientAim_Implementation(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) { + Location = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(),NewLocation); + Aim = NewAim; + RemoteAimReceived = true; +} +bool UEBBarrel::ClientAim_Validate(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim) { + return true; +} + +void UEBBarrel::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + +#if REPOWNERONLY + DOREPLIFETIME_CONDITION(UEBBarrel, FireMode, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, CycleAmmoCount, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, CycleAmmoPos, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, Ammo, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, ChamberedBullet, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, Shooting, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, ShootingBlocked, COND_OwnerOnly); + DOREPLIFETIME_CONDITION(UEBBarrel, Spooling, COND_OwnerOnly); +#else + DOREPLIFETIME(UEBBarrel, FireMode); + DOREPLIFETIME(UEBBarrel, CycleAmmoCount); + DOREPLIFETIME(UEBBarrel, CycleAmmoPos); + DOREPLIFETIME(UEBBarrel, Ammo); + DOREPLIFETIME(UEBBarrel, ChamberedBullet); + DOREPLIFETIME(UEBBarrel, Shooting); + DOREPLIFETIME(UEBBarrel, ShootingBlocked); + DOREPLIFETIME(UEBBarrel, Spooling); +#endif +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/BarrelProxy.cpp b/Source/EasyBallistics/Private/BarrelProxy.cpp new file mode 100644 index 0000000..e9f8f1b --- /dev/null +++ b/Source/EasyBallistics/Private/BarrelProxy.cpp @@ -0,0 +1,66 @@ +// Copyright 2019 Mookie. All Rights Reserved. + +#if WITH_EDITOR +#include "EBBarrel.h" +#include "PrimitiveSceneProxy.h" + +FPrimitiveSceneProxy* UEBBarrel::CreateSceneProxy() { + { + class FBarrelProxy : public FPrimitiveSceneProxy + { + public: + FBarrelProxy(UEBBarrel* InComponent) : FPrimitiveSceneProxy(InComponent) + { + bWillEverBeLit = false; + Component = InComponent; + } + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_BarrelSceneProxy_GetDynamicMeshElements); + + const FMatrix& Transform = GetLocalToWorld(); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap && ((1 << ViewIndex)!=0)) + { + const FSceneView* View = Views[ViewIndex]; + const FLinearColor DrawColor = GetViewSelectionColor(FColor::Green, *View, IsSelected(), IsHovered(), true, IsIndividuallySelected()); + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + DrawDirectionalArrow(PDI, Transform, DrawColor, Component->DebugArrowSize, Component->DebugArrowSize*0.1f, 16, Component->DebugArrowSize*0.01f); + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + const bool bProxyVisible = IsSelected(); + + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = (IsShown(View)); + Result.bDynamicRelevance = true; + Result.bShadowRelevance = false; + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + virtual SIZE_T GetTypeHash() const override { return 0; } + + private: + UEBBarrel* Component; + }; + + return new FBarrelProxy(this); + } +}; + +FBoxSphereBounds UEBBarrel::CalcBounds(const FTransform& LocalToWorld) const +{ + float SphereRadius = DebugArrowSize; + return FBoxSphereBounds(FVector::ZeroVector, FVector(SphereRadius), SphereRadius).TransformBy(LocalToWorld); +} + +#endif \ No newline at end of file diff --git a/Source/EasyBallistics/Private/BulletEvents.cpp b/Source/EasyBallistics/Private/BulletEvents.cpp new file mode 100644 index 0000000..97ebe94 --- /dev/null +++ b/Source/EasyBallistics/Private/BulletEvents.cpp @@ -0,0 +1,135 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBullet.h" +#include "EBMaterialResponseMap.h" +#include "Net/UnrealNetwork.h" + +void AEBBullet::VelocityChangeBroadcast_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity) { + if (!HasAuthority()) { + FVector RebasedLocation = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation); + OnTrajectoryUpdateReceived(RebasedLocation, Velocity, NewVelocity); + SetActorLocation(RebasedLocation); + Velocity = NewVelocity; + CanRetrace = false; + } +} + +void AEBBullet::VelocityChangeBroadcastReliable_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity) { + if (!HasAuthority()) { + FVector RebasedLocation = UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation); + OnTrajectoryUpdateReceived(RebasedLocation, Velocity, NewVelocity); + SetActorLocation(RebasedLocation); + Velocity = NewVelocity; + CanRetrace = false; + } +} + +void AEBBullet::SpawnWithExactVelocity(TSubclassOf BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity) { + + if (BulletClass != nullptr && BulletOwner != nullptr) { + FActorSpawnParameters spawnParams; + spawnParams.Owner = BulletOwner; + spawnParams.Instigator = BulletInstigator; + + AEBBullet* Default = Cast(BulletClass->GetDefaultObject()); + + FTransform Transform; + Transform.SetLocation(BulletLocation); + Transform.SetScale3D(Default->GetActorScale()); + + if (Default->RotateActor) { + FRotator Rotation = UKismetMathLibrary::MakeRotFromX(BulletVelocity); + if (Default->RotateRandomRoll) Rotation.Add(0, 0, Default->RandomStream.FRandRange(-180.0f, 180.0f)); + Transform.SetRotation(Rotation.Quaternion()); + } + else { + Transform.SetRotation(FQuat(1, 0, 0, 1)); + } + + if (!Default->Shotgun) { + AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, BulletVelocity, BulletOwner, BulletInstigator); + } + else { + for (int i = 0; i < Default->ShotCount; i++) { + float Vel = BulletVelocity.Size()*Default->RandomStream.FRandRange(1.0 - Default->ShotVelocitySpread, 1.0 + Default->ShotVelocitySpread); + FVector SubmunitionVelocity = Default->RandomStream.VRandCone(BulletVelocity, Default->ShotSpread)*Vel; + AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, SubmunitionVelocity, BulletOwner, BulletInstigator); + } + } + } + else { + UE_LOG(LogTemp, Warning, TEXT("Cannot spawn bullet - invalid class or owner")); + } +} + + +void AEBBullet::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME_CONDITION(AEBBullet, Velocity, COND_InitialOnly); + DOREPLIFETIME_CONDITION(AEBBullet, RandomStream, COND_InitialOnly); +} + +//alternative spawn +void AEBBullet::Spawn(TSubclassOf BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity) { + if (BulletClass != nullptr && BulletOwner != nullptr) { + + FActorSpawnParameters spawnParams; + spawnParams.Owner = BulletOwner; + spawnParams.Instigator = BulletInstigator; + + AEBBullet* Default = Cast(BulletClass->GetDefaultObject()); + + FTransform Transform; + Transform.SetLocation(BulletLocation); + Transform.SetScale3D(Default->GetActorScale()); + + if (Default->RotateActor) { + if (Default->RotateActor) { + FRotator Rotation = UKismetMathLibrary::MakeRotFromX(BulletVelocity); + if (Default->RotateRandomRoll) Rotation.Add(0, 0, Default->RandomStream.FRandRange(-180.0f, 180.0f)); + Transform.SetRotation(Rotation.Quaternion()); + } + } + else { + Transform.SetRotation(FQuat(1, 0, 0, 1)); + } + + //init velocity + float TotalSpread = Default->Spread; + if (Default->SpreadBias > 0.0f){ + float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias); + TotalSpread *= SpreadMult; + } + + FVector BulletVelocityNew = Default->RandomStream.VRandCone(BulletVelocity, TotalSpread)*BulletVelocity.Size(); + float VelocityMP = FMath::Lerp(Default->MuzzleVelocityMin, Default->MuzzleVelocityMax, Default->RandomStream.FRand()); + BulletVelocityNew = BulletVelocityNew * VelocityMP; + + if (!Default->Shotgun) { + AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, BulletVelocityNew, BulletOwner, BulletInstigator); + } + else { + for (int i = 0; i < Default->ShotCount; i++) { + float Vel = BulletVelocityNew.Size()*Default->RandomStream.FRandRange(1.0 - Default->ShotVelocitySpread, 1.0 + Default->ShotVelocitySpread); + FVector SubmunitionVelocity = Default->RandomStream.VRandCone(BulletVelocityNew, Default->ShotSpread)*Vel; + AEBBullet* bullet = SpawnOrReactivate(BulletOwner->GetWorld(), BulletClass, Transform, SubmunitionVelocity, BulletOwner, BulletInstigator); + } + } + } + else { + UE_LOG(LogTemp, Warning, TEXT("Cannot spawn bullet - invalid class or owner")); + } +} + +void AEBBullet::OnImpact_Implementation(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult){ + return; +}; + +void AEBBullet::OnNetPredictedImpact_Implementation(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult) { + return; +}; + +void AEBBullet::OnDeactivated_Implementation() { + return; +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Private/BulletPredict.cpp b/Source/EasyBallistics/Private/BulletPredict.cpp new file mode 100644 index 0000000..809d8a4 --- /dev/null +++ b/Source/EasyBallistics/Private/BulletPredict.cpp @@ -0,0 +1,4 @@ +// Copyright 2020 Mookie. All Rights Reserved. + +#include "EBBullet.h" + diff --git a/Source/EasyBallistics/Private/CalcAimDirection.cpp b/Source/EasyBallistics/Private/CalcAimDirection.cpp new file mode 100644 index 0000000..97e2999 --- /dev/null +++ b/Source/EasyBallistics/Private/CalcAimDirection.cpp @@ -0,0 +1,65 @@ +#include "EBBarrel.h" +#include "EBBullet.h" + +void UEBBarrel::CalculateAimDirection(TSubclassOf BulletClass, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime, float Step, int NumIterations) const { + FVector StartLocation = GetComponentLocation(); + CalculateAimDirectionFromLocation(BulletClass, StartLocation, TargetLocation, TargetVelocity, AimDirection, PredictedTargetLocation, PredictedIntersectionLocation, PredictedFlightTime, Error, MaxTime, Step, NumIterations); +} + +void UEBBarrel::CalculateAimDirectionFromLocation(TSubclassOf BulletClass, FVector StartLocation, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime, float Step, int NumIterations) const { + if (!BulletClass->IsValidLowLevel()) { + UE_LOG(LogTemp, Warning, TEXT("CalculateAimDirection - invalid bullet class")); + return; + } + + AEBBullet* bullet = Cast(BulletClass->GetDefaultObject()); + + FVector AddVelocity = AdditionalVelocity; + UPrimitiveComponent* parent = Cast(GetAttachParent()); + if (parent != nullptr) { + if (parent->IsSimulatingPhysics()) { + AddVelocity += parent->GetPhysicsLinearVelocityAtPoint(StartLocation) * InheritVelocity; + } + } + + + FVector InitialAimDirection = (TargetLocation - StartLocation).GetSafeNormal(); //initial prediction + AimDirection = InitialAimDirection; + FVector PreviousAimDirection = AimDirection; + + for (int Iteration = 0; Iteration < NumIterations; Iteration++) { + FVector CurrentBulletLocation = StartLocation; + FVector Velocity = (AimDirection * (FMath::Lerp(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax, 0.5) * FMath::Lerp(bullet->MuzzleVelocityMin, bullet->MuzzleVelocityMax, 0.5))) + AddVelocity; + bool hit = 0; + for (float time = 0; time <= MaxTime; time += Step) { + FVector PreviousVelocity = Velocity; + Velocity = bullet->UpdateVelocity(GetWorld(), CurrentBulletLocation, Velocity, Step); + + FVector TraceVector = ((((PreviousVelocity + Velocity) * 0.5) - TargetVelocity) * Step); + FVector TraceEndLocation = CurrentBulletLocation + TraceVector; + FVector IntersectionPoint; + + hit = FMath::SegmentPlaneIntersection(CurrentBulletLocation - TraceVector, TraceEndLocation, FPlane(TargetLocation, InitialAimDirection), IntersectionPoint); //actual hit test + + if (hit) { + PredictedIntersectionLocation = IntersectionPoint; + FQuat AimCorrection = FQuat::FindBetween((IntersectionPoint - StartLocation), (TargetLocation - StartLocation)); + AimDirection = AimCorrection.RotateVector(AimDirection).GetSafeNormal(); + Error = (IntersectionPoint - TargetLocation).Size(); + + float AdditionalFlightTime = (FVector(CurrentBulletLocation - IntersectionPoint).Size() / TraceVector.Size()) * Step; + PredictedFlightTime = time + AdditionalFlightTime; + PredictedTargetLocation = TargetLocation + TargetVelocity * AdditionalFlightTime; + + break; + } + + //no hit, keep going + CurrentBulletLocation = TraceEndLocation; + } + if (!hit) { + Error = 99999999999999999.0f; + return; //no solution + } + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/CollisionFilter.cpp b/Source/EasyBallistics/Private/CollisionFilter.cpp new file mode 100644 index 0000000..55e3168 --- /dev/null +++ b/Source/EasyBallistics/Private/CollisionFilter.cpp @@ -0,0 +1,25 @@ +#include "EBBullet.h" + +bool AEBBullet::CollisionFilter_Implementation(FHitResult HitResult) const{ + return true; +}; + +FHitResult AEBBullet::FilterHits(TArray Results, bool &hit) const{ + TArray OutResults; + + for (FHitResult Result : Results) { + if (Result.bBlockingHit) { + + hit = true; + return Result; + }else{ + if (CollisionFilter(Result)) { + hit = true; + return Result; + } + } + } + + hit = false; + return FHitResult(); //blank +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBBallisticImpactComponent.cpp b/Source/EasyBallistics/Private/EBBallisticImpactComponent.cpp new file mode 100644 index 0000000..811f87a --- /dev/null +++ b/Source/EasyBallistics/Private/EBBallisticImpactComponent.cpp @@ -0,0 +1,200 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBallisticImpactComponent.h" +#include "EBMathematicalBallistics.h" +#include "Engine/Engine.h" +#include "Kismet/KismetMathLibrary.h" + +UEBBallisticImpactComponent::UEBBallisticImpactComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bEnableBallisticCalculations = true; + bUseMathematicalPenetration = false; +} + +void UEBBallisticImpactComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +FVector UEBBallisticImpactComponent::CalculateBallisticImpact( + const FVector& ImpactLocation, + const FVector& ProjectileVelocity, + const FMathematicalBulletProperties& BulletProperties, + UPhysicalMaterial* HitMaterial, + float& OutPenetrationDepth, + bool& bOutDidPenetrate, + FVector& OutExitLocation) +{ + OutPenetrationDepth = 0.0f; + bOutDidPenetrate = false; + OutExitLocation = ImpactLocation; + + if (!bEnableBallisticCalculations || !HitMaterial) + { + return ImpactLocation; + } + + // Get material response + FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial); + + if (MaterialResponse.NeverPenetrate) + { + return ImpactLocation; + } + + // Calculate penetration depth + if (bUseMathematicalPenetration) + { + UEBMaterialPropertiesAsset* MaterialProps = GetMaterialProperties(HitMaterial); + if (MaterialProps) + { + float VelocityMPS = ProjectileVelocity.Size() * 0.3048f; // feet to meters + float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, FVector::UpVector); // Simplified + + OutPenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth( + BulletProperties, + MaterialProps->MaterialProperties, + VelocityMPS, + ImpactAngle + ); + } + } + else + { + // Use artistic approach + float BaseDepth = ProjectileVelocity.Size() * 0.01f; // Simple velocity-based calculation + OutPenetrationDepth = BaseDepth * MaterialResponse.PenetrationDepthMultiplier; + } + + // Check if penetration occurred + float MinPenetrationThreshold = 1.0f; // 1cm minimum + bOutDidPenetrate = OutPenetrationDepth > MinPenetrationThreshold; + + if (bOutDidPenetrate) + { + // Calculate exit location + FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal(); + OutExitLocation = ImpactLocation + (PenetrationDirection * OutPenetrationDepth); + } + + // Fire event + OnBallisticImpact.Broadcast(ImpactLocation, FVector::UpVector, HitMaterial, OutPenetrationDepth, bOutDidPenetrate); + + return OutExitLocation; +} + +FVector UEBBallisticImpactComponent::CalculateRicochet( + const FVector& ImpactLocation, + const FVector& ImpactNormal, + const FVector& ProjectileVelocity, + const FMathematicalBulletProperties& BulletProperties, + UPhysicalMaterial* HitMaterial, + float& OutEnergyRetained, + bool& bOutDidRicochet) +{ + OutEnergyRetained = 0.0f; + bOutDidRicochet = false; + + if (!bEnableBallisticCalculations || !HitMaterial) + { + return ProjectileVelocity; + } + + // Get material response + FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial); + + if (MaterialResponse.NeverRicochet) + { + return ProjectileVelocity; + } + + // Calculate impact angle + float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, ImpactNormal); + + // Simple ricochet probability based on angle + float RicochetProbability = FMath::Clamp((90.0f - ImpactAngle) / 90.0f, 0.0f, 1.0f); + RicochetProbability *= MaterialResponse.RicochetProbabilityMultiplier; + + // Random chance for ricochet + if (FMath::RandRange(0.0f, 1.0f) < RicochetProbability) + { + bOutDidRicochet = true; + + // Calculate ricochet direction using reflection + FVector RicochetDirection = UKismetMathLibrary::GetReflectionVector(ProjectileVelocity, ImpactNormal); + + // Apply some randomness based on material response + if (MaterialResponse.RicochetSpread > 0.0f) + { + FVector RandomOffset = FMath::VRand() * MaterialResponse.RicochetSpread; + RicochetDirection = (RicochetDirection + RandomOffset).GetSafeNormal(); + } + + // Calculate energy retention + OutEnergyRetained = MaterialResponse.RicochetRestitution; + + // Apply velocity reduction + FVector NewVelocity = RicochetDirection * (ProjectileVelocity.Size() * OutEnergyRetained); + + // Fire event + OnBallisticRicochet.Broadcast(ImpactLocation, RicochetDirection, HitMaterial, OutEnergyRetained); + + return NewVelocity; + } + + return ProjectileVelocity; +} + +UEBMaterialPropertiesAsset* UEBBallisticImpactComponent::GetMaterialProperties(UPhysicalMaterial* PhysicalMaterial) +{ + if (!PhysicalMaterial) + { + return nullptr; + } + + // For now, use naming convention to find associated material properties + // In production, you'd want a more robust system + FString AssetName = PhysicalMaterial->GetName() + TEXT("_BallisticProps"); + FString PackageName = PhysicalMaterial->GetPackage()->GetName() + TEXT("_BallisticProps"); + + return LoadObject(nullptr, *PackageName, nullptr, LOAD_NoWarn | LOAD_Quiet); +} + +FEBMaterialResponseMapEntry UEBBallisticImpactComponent::GetMaterialResponse(UPhysicalMaterial* PhysicalMaterial) +{ + FEBMaterialResponseMapEntry DefaultResponse; + + if (!MaterialResponseMap || !PhysicalMaterial) + { + return DefaultResponse; + } + + if (FEBMaterialResponseMapEntry* Found = MaterialResponseMap->Map.Find(PhysicalMaterial)) + { + return *Found; + } + + return DefaultResponse; +} + +FVector UEBBallisticImpactComponent::CalculatePenetrationVector( + const FVector& ImpactLocation, + const FVector& ImpactNormal, + const FVector& ProjectileVelocity, + float PenetrationDepth) +{ + FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal(); + return ImpactLocation + (PenetrationDirection * PenetrationDepth); +} + +float UEBBallisticImpactComponent::CalculateImpactAngle(const FVector& ProjectileVelocity, const FVector& SurfaceNormal) +{ + FVector NormalizedVelocity = ProjectileVelocity.GetSafeNormal(); + FVector NormalizedSurfaceNormal = SurfaceNormal.GetSafeNormal(); + + float DotProduct = FVector::DotProduct(-NormalizedVelocity, NormalizedSurfaceNormal); + float AngleRadians = FMath::Acos(FMath::Clamp(DotProduct, -1.0f, 1.0f)); + + return FMath::RadiansToDegrees(AngleRadians); +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBBarrel.cpp b/Source/EasyBallistics/Private/EBBarrel.cpp new file mode 100644 index 0000000..4b95731 --- /dev/null +++ b/Source/EasyBallistics/Private/EBBarrel.cpp @@ -0,0 +1,243 @@ +// Copyright 2018 Mookie. All Rights Reserved. +#include "EBBarrel.h" + +UEBBarrel::UEBBarrel() { + PrimaryComponentTick.bCanEverTick = true; + bHiddenInGame = true; + bAutoActivate = true; + SetIsReplicatedByDefault(ReplicateVariables); + + RandomStream.GenerateNewSeed(); + + GatlingRPS = FireRateMin; +} + +void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (ClientSideAim){ + if (GetOwner()->GetRemoteRole()==ROLE_Authority){ + TimeSinceAimUpdate += DeltaTime; + + if (TimeSinceAimUpdate >= 1.0f / ClientAimUpdateFrequency) { + Aim = GetComponentTransform().GetUnitAxis(EAxis::X); + Location = GetComponentTransform().GetLocation(); + ClientAim(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),Location), Aim); + TimeSinceAimUpdate = FMath::Fmod(TimeSinceAimUpdate, 1.0f / ClientAimUpdateFrequency); + }; + }else{ + if (!RemoteAimReceived) { + Aim = GetComponentTransform().GetUnitAxis(EAxis::X); + Location = GetComponentTransform().GetLocation(); + } + else { + FVector LocOffset = (Location - GetComponentLocation()); + if (LocOffset.Size() > ClientAimDistanceLimit) { + //lag or cheater??? + Location = GetComponentLocation() + LocOffset.GetSafeNormal()*ClientAimDistanceLimit; + } + } + } + } + else { + Aim = GetComponentTransform().GetUnitAxis(EAxis::X); + Location = GetComponentTransform().GetLocation(); + } + + //Only server can tick + if (GetOwner()->GetLocalRole() == ROLE_Authority){ + + float RemainingDelta; + + if (FireMode == EFireMode::FM_Gatling) { + if (Spooling || (GatlingAutoSpool && Shooting)) { + GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMax, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f)); + } + else { + GatlingRPS = FMath::Lerp(GatlingRPS, FireRateMin, FMath::Min(GatlingSpoolUpTime*DeltaTime, 1.0f)); + } + GatlingPhase += GatlingRPS*DeltaTime; + for (int i = 1; i <= GatlingPhase; i++) { + if (Cooldown <= 0.0f && LoadNext) { + NextBullet(); + } + + if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) { + SpawnBullet(GetOwner(), Location, Aim); + } + } + GatlingPhase = FMath::Fmod(GatlingPhase, 1.0f); + + } + else { + RemainingDelta = DeltaTime; + do { + float step = FMath::Min(Cooldown, RemainingDelta); + + Cooldown -= step; + + RemainingDelta -= step; + + if (Cooldown <= 0.0f && LoadNext) { + NextBullet(); + } + + //shoot when ready + if (Shooting && ChamberedBullet != nullptr && (!ShootingBlocked)) { + if (BurstRemaining > 0 || (FireMode != EFireMode::FM_Burst && FireMode != EFireMode::FM_InterBurst)) { + SpawnBullet(GetOwner(), Location, Aim); + } + else { + Shooting = false; + } + } + } while (RemainingDelta > 0 && Cooldown > 0); + } + } +} + +void UEBBarrel::NextBullet() { + if (ChamberedBullet == nullptr) { + if (Ammo.Num() > 0 && (CycleAmmoCount > 0 || CycleAmmoUnlimited || (!CycleAmmo))) { + + //cycle ammo + if (CycleAmmo) { + if (CycleAmmoPos >= Ammo.Num()) { CycleAmmoPos = 0; } + ChamberedBullet = Ammo[CycleAmmoPos]; + CycleAmmoPos++; + + if (!CycleAmmoUnlimited) { + CycleAmmoCount--; + } + } + else { + ChamberedBullet = Ammo[0]; + Ammo.RemoveAt(0, 1, EAllowShrinking::Yes); + } + + ReadyToShoot.Broadcast(); + } + else { + AmmoDepleted.Broadcast(); + } + } +} + +void UEBBarrel::SpawnBullet(AActor* Owner, FVector InLocation, FVector InAim) { + TSubclassOf BulletClass = ChamberedBullet; + + if (BulletClass != nullptr) { + FVector OutLocation; + FVector OutAim; + + InitialBulletTransform(InLocation, InAim, OutLocation, OutAim); + + AEBBullet* Default = Cast(BulletClass->GetDefaultObject()); + + float BulletSpread = Default->Spread; + if (Default->SpreadBias > 0.0f) { + float SpreadMult = FMath::Pow(FMath::FRand(), Default->SpreadBias); + BulletSpread *= SpreadMult; + } + float BarrelSpread = Spread; + if (SpreadBias > 0.0f) { + float SpreadMult = FMath::Pow(FMath::FRand(), SpreadBias); + BarrelSpread *= SpreadMult; + } + + float TotalSpread = BulletSpread+BarrelSpread; + + OutAim = RandomStream.VRandCone(OutAim,TotalSpread); + float BulletVelocity = FMath::Lerp(MuzzleVelocityMultiplierMin* Default->MuzzleVelocityMin, MuzzleVelocityMultiplierMax*Default->MuzzleVelocityMax, RandomStream.FRand()); + FVector Velocity = OutAim*BulletVelocity; + + //get parent physics body + UPrimitiveComponent* parent = Cast(GetAttachParent()); + Velocity += AdditionalVelocity; + + if (parent != nullptr) { + + if (parent->IsSimulatingPhysics()) { + Velocity += parent->GetPhysicsLinearVelocityAtPoint(OutLocation)*InheritVelocity; + } + + if (Default->Shotgun) { + ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier*Default->ShotCount); + } + else{ + ApplyRecoil(parent, OutLocation, -Velocity*Default->Mass*RecoilMultiplier); + } + } + + BeforeShotFired.Broadcast(); + + AEBBullet::SpawnWithExactVelocity(BulletClass, Owner, Owner->GetInstigator(), OutLocation, Velocity); + + //spend ammo + ChamberedBullet = nullptr; + if (FireMode != EFireMode::FM_Gatling) { + Cooldown = 1.0f / FMath::Lerp(FireRateMin, FireRateMax, RandomStream.FRand()); + } + + //fire modes + switch (FireMode) { + case EFireMode::FM_Auto: + LoadNext = true; + break; + + case EFireMode::FM_Burst: + LoadNext = true; + break; + + case EFireMode::FM_InterBurst: + LoadNext = true; + break; + + case EFireMode::FM_Semiauto: + Shooting = false; + LoadNext = true; + break; + + case EFireMode::FM_Manual: + Shooting = false; + LoadNext = false; + break; + + case EFireMode::FM_Slamfire: + LoadNext = false; + break; + + case EFireMode::FM_Gatling: + LoadNext = true; + break; + }; + + if (BurstRemaining > 0) { + BurstRemaining--; + } + else { + if (FireMode == EFireMode::FM_Burst || FireMode == EFireMode::FM_InterBurst) { + Cooldown = FMath::Max(Cooldown, BurstCooldown); + } + } + + if (ReplicateShotFiredEvents) { + ShotFiredMulticast(); + } + else { + ShotFired.Broadcast(); + } + } +} + +void UEBBarrel::InitialBulletTransform_Implementation(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection) { + OutLocation = InLocation; + OutDirection = InDirection; +} + +void UEBBarrel::ApplyRecoil_Implementation(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse){ + if (Component->IsSimulatingPhysics()) { + Component->AddImpulseAtLocation(Impulse, InLocation); + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBBullet.cpp b/Source/EasyBallistics/Private/EBBullet.cpp new file mode 100644 index 0000000..8b94460 --- /dev/null +++ b/Source/EasyBallistics/Private/EBBullet.cpp @@ -0,0 +1,247 @@ +// Copyright 2018 Mookie. All Rights Reserved. +#include "EBBullet.h" + +// Sets default values +AEBBullet::AEBBullet() { + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + SetTickGroup(ETickingGroup::TG_PrePhysics); + + // Create the new ballistic impact component + BallisticImpactComponent = CreateDefaultSubobject(TEXT("BallisticImpactComponent")); + BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap; +} + +// Called when the game starts or when spawned +void AEBBullet::BeginPlay() { + SetActorEnableCollision(AllowComponentCollisions); + + // Update ballistic impact component with current settings + if (BallisticImpactComponent) { + BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap; + BallisticImpactComponent->bUseMathematicalPenetration = UseMathematicalPhysics; + BallisticImpactComponent->bEnableBallisticCalculations = UseNewImpactSystem; + } + + if(!IsRecycled){ + Super::BeginPlay(); + IsRecycled = true; + } + else{ + ReceiveBeginPlay(); + } + + if (SafeLaunch) { + OwnerSafe = true; + } + + if (DoFirstStepImmediately) { + float DeltaTime = GetWorld()->GetDeltaSeconds(); + + if (RandomFirstStepDelta) { + DeltaTime *= RandomStream.FRand(); + }; + + if (FixedStep) { + Step(FixedStepSeconds); + } + else { + Step(DeltaTime); + } + } +} + +// Called every frame +void AEBBullet::Tick(float DeltaTime) { + Super::Tick(DeltaTime); + + if (FixedStep) { + AccumulatedDelta += DeltaTime; + + while (AccumulatedDelta >= FixedStepSeconds) { + Step(FixedStepSeconds); + AccumulatedDelta -= FixedStepSeconds; + } + + } + else { + Step(DeltaTime); + } +} + +void AEBBullet::Step(float DeltaTime) { + FVector start = GetActorLocation(); + bool sendUpdate = false; + + if (Retrace && CanRetrace) { + //time travel + float remainingTime = LastTraceDelta; + int remainingSteps = MaxTracesPerStep; + FVector PreviousVelocity = LastTracePrevVelocity; + SetActorLocation(LastTraceStart); + Velocity = LastTraceVelocity; + + do { + if (RetraceOnAnotherChannel) { + remainingTime = Trace(GetActorLocation(), + PreviousVelocity, + remainingTime, + RetraceChannel); + } + else { + remainingTime = Trace(GetActorLocation(), + PreviousVelocity, + remainingTime, + TraceChannel); + } + PreviousVelocity = Velocity; + remainingSteps -= 1; + if (remainingTime > 0.0f) { sendUpdate = true; }; + } while (remainingTime > 0.0f && remainingSteps > 0); + } + CanRetrace = false; + + FVector PreviousVelocity = Velocity; + Velocity = UpdateVelocity(GetWorld(), GetActorLocation(), Velocity, DeltaTime); + + //trace + float remainingTime = DeltaTime; + int remainingSteps = MaxTracesPerStep; + do { + remainingTime = Trace(GetActorLocation(), + PreviousVelocity, + remainingTime, + TraceChannel + ); + PreviousVelocity = Velocity; + remainingSteps -= 1; + if (remainingTime > 0.0f) { sendUpdate = true; }; + } while (remainingTime > 0.0f && remainingSteps > 0); + + if (sendUpdate) { + if (ReliableReplication) { + VelocityChangeBroadcastReliable(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),GetActorLocation()), Velocity); + } + else { + VelocityChangeBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(), GetActorLocation()), Velocity); + } + } + + if(SafeDelay <= 0.0f){ + OwnerSafe = false; + } + else { + SafeDelay -= DeltaTime; + } + + if (RotateActor) { + FRotator NewRot = UKismetMathLibrary::MakeRotFromX(Velocity); + NewRot.Roll = GetActorRotation().Roll; + SetActorRotation(NewRot); + } +} + +float AEBBullet::GetCurveValue(const UCurveFloat* curve, float in, float deflt) const { + if (curve == nullptr) return deflt; + return curve->GetFloatValue(in); +} + +void AEBBullet::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) { + Super::ApplyWorldOffset(InOffset, bWorldShift); + LastTraceStart += InOffset; +} + +// Mathematical Physics Function Implementations +float AEBBullet::GetEffectiveMass() const +{ + if (UseMathematicalPhysics && BulletPropertiesAsset) + { + return BulletPropertiesAsset->BulletProperties.GetMassKg(); + } + return Mass; +} + +float AEBBullet::GetEffectiveDiameter() const +{ + if (UseMathematicalPhysics && BulletPropertiesAsset) + { + return BulletPropertiesAsset->BulletProperties.GetDiameterCm(); + } + return Diameter; +} + +float AEBBullet::GetEffectiveDragCoefficient(float MachNumber) const +{ + if (UseMathematicalPhysics && BulletPropertiesAsset) + { + return UEBMathematicalBallistics::CalculateDragCoefficient( + BulletPropertiesAsset->BulletProperties, MachNumber); + } + + // Use artistic drag calculation + float DragCoeff = GetCurveValue(MachDragCurve, MachNumber, 0.5f); + return DragCoeff * FormFactor; +} + +float AEBBullet::CalculateMathematicalPenetration(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const +{ + if (!UseMathematicalPhysics || !BulletPropertiesAsset) + { + return 0.0f; + } + + FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material); + + return UEBMathematicalBallistics::CalculatePenetrationDepth( + BulletPropertiesAsset->BulletProperties, + MaterialProps, + VelocityMPS, + ImpactAngle + ); +} + +float AEBBullet::CalculateMathematicalRicochetProbability(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const +{ + if (!UseMathematicalPhysics || !BulletPropertiesAsset) + { + return 0.0f; + } + + FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material); + + return UEBMathematicalBallistics::CalculateRicochetProbability( + BulletPropertiesAsset->BulletProperties, + MaterialProps, + VelocityMPS, + ImpactAngle + ); +} + +FMathematicalMaterialProperties AEBBullet::GetMaterialProperties(UPhysicalMaterial* Material) const +{ + // Default properties for unknown materials + FMathematicalMaterialProperties DefaultProps; + + if (!Material || !MaterialResponseMap) + { + return DefaultProps; + } + + // Check if we have custom properties for this material + if (MaterialResponseMap->Map.Contains(Material)) + { + const FEBMaterialResponseMapEntry& Entry = MaterialResponseMap->Map[Material]; + if (Entry.UseMathematicalProperties) + { + return Entry.MathematicalProperties; + } + } + + // If we have a material properties asset, use that as fallback + if (MaterialPropertiesAsset) + { + return MaterialPropertiesAsset->MaterialProperties; + } + + return DefaultProps; +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EBMathematicalBallistics.cpp b/Source/EasyBallistics/Private/EBMathematicalBallistics.cpp new file mode 100644 index 0000000..45941e5 --- /dev/null +++ b/Source/EasyBallistics/Private/EBMathematicalBallistics.cpp @@ -0,0 +1,296 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBMathematicalBallistics.h" +#include "Engine/Engine.h" +#include "Math/UnrealMathUtility.h" + +float UEBMathematicalBallistics::CalculatePenetrationDepth( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ImpactAngleDegrees) +{ + // Calculate kinetic energy in joules + float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS); + + // Calculate sectional density + float SectionalDensity = BulletProps.GetSectionalDensity(); + + // Calculate hardness ratio + float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness); + + // Calculate angle factor (normal impact = 1.0, grazing = 0.0) + float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees); + + // Calculate shape factor based on bullet type + float ShapeFactor = CalculateShapeFactorFromBulletType(BulletProps.BulletType); + + // Modified Taylor-Hopkinson equation for penetration depth + // P = (K * E * SD * HF * AF * SF) / (ρ * σ) + // Where: P = penetration depth, K = constant, E = kinetic energy, SD = sectional density + // HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density, σ = yield strength + + float Constant = 0.0012f; // Empirical constant + float Penetration = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) / + (MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa); + + return FMath::Max(0.0f, Penetration); +} + +float UEBMathematicalBallistics::CalculateResidualVelocity( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ThicknessCM, + float ImpactAngleDegrees) +{ + // Calculate ballistic limit velocity + float BallisticLimit = CalculateRechtIpsonVelocity(BulletProps, MaterialProps, ThicknessCM); + + // Adjust for impact angle + float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees); + BallisticLimit = BallisticLimit / AngleFactor; + + // If impact velocity is below ballistic limit, no penetration + if (VelocityMPS <= BallisticLimit) + { + return 0.0f; + } + + // Calculate residual velocity using Recht-Ipson equation + // Vr = sqrt(V^2 - Vbl^2) + float ResidualVelocity = FMath::Sqrt(VelocityMPS * VelocityMPS - BallisticLimit * BallisticLimit); + + // Apply energy absorption factor + ResidualVelocity *= (1.0f - MaterialProps.EnergyAbsorptionCoefficient); + + return FMath::Max(0.0f, ResidualVelocity); +} + +float UEBMathematicalBallistics::CalculateRicochetProbability( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ImpactAngleDegrees) +{ + // Calculate critical ricochet angle + float CriticalAngle = CalculateCriticalRicochetAngle(BulletProps, MaterialProps, VelocityMPS); + + // If impact angle is greater than critical angle, no ricochet + if (ImpactAngleDegrees > CriticalAngle) + { + return 0.0f; + } + + // Calculate ricochet probability based on angle and material properties + float AngleRatio = ImpactAngleDegrees / CriticalAngle; + float BaseRicochetProbability = 1.0f - FMath::Pow(AngleRatio, 2.0f); + + // Adjust for hardness ratio + float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness); + float HardnessAdjustment = FMath::Clamp(HardnessRatio, 0.1f, 2.0f); + + // Adjust for velocity (higher velocity reduces ricochet probability) + float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 500.0f); + + float RicochetProbability = BaseRicochetProbability * HardnessAdjustment * VelocityFactor; + + return FMath::Clamp(RicochetProbability, 0.0f, 1.0f); +} + +float UEBMathematicalBallistics::CalculateBulletExpansion( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS) +{ + // Convert velocity to FPS for comparison with expansion threshold + float VelocityFPS = ConvertMPStoFPS(VelocityMPS); + + // No expansion if velocity is below threshold + if (VelocityFPS < BulletProps.ExpansionVelocityThreshold) + { + return 1.0f; // No expansion + } + + // Calculate expansion based on velocity and bullet type + float VelocityRatio = VelocityFPS / BulletProps.ExpansionVelocityThreshold; + float ExpansionFactor = 1.0f; + + switch (BulletProps.BulletType) + { + case EBulletType::BT_HollowPoint: + ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f)); + break; + case EBulletType::BT_SoftPoint: + ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.8f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f)); + break; + case EBulletType::BT_Frangible: + ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 1.2f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f)); + break; + case EBulletType::BT_FullMetalJacket: + case EBulletType::BT_ArmorPiercing: + ExpansionFactor = 1.0f; // No expansion + break; + default: + ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.6f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f)); + break; + } + + return ExpansionFactor; +} + +float UEBMathematicalBallistics::CalculateKineticEnergy( + const FMathematicalBulletProperties& BulletProps, + float VelocityMPS) +{ + // KE = 0.5 * m * v^2 + float MassKg = BulletProps.GetMassKg(); + return 0.5f * MassKg * VelocityMPS * VelocityMPS; +} + +float UEBMathematicalBallistics::CalculateMomentum( + const FMathematicalBulletProperties& BulletProps, + float VelocityMPS) +{ + // p = m * v + float MassKg = BulletProps.GetMassKg(); + return MassKg * VelocityMPS; +} + +float UEBMathematicalBallistics::CalculateDragCoefficient( + const FMathematicalBulletProperties& BulletProps, + float MachNumber) +{ + // Calculate drag coefficient from ballistic coefficient + // Standard drag coefficient for G1 projectile at given Mach number + float StandardDrag = 0.5f; // Simplified - normally would use complex curve + + // Adjust for actual ballistic coefficient + float BC = BulletProps.UseG7Model ? BulletProps.BallisticCoefficientG7 : BulletProps.BallisticCoefficientG1; + + // CD = Standard_CD / BC + return StandardDrag / BC; +} + +float UEBMathematicalBallistics::CalculateTaylorHopkinsonLimit( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps) +{ + // Taylor-Hopkinson limit: t = K * ρ * σ / (SD * V^2) + // Rearranged to solve for limiting thickness + float SectionalDensity = BulletProps.GetSectionalDensity(); + float Constant = 0.001f; // Empirical constant + + return Constant * MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa / SectionalDensity; +} + +float UEBMathematicalBallistics::CalculateRechtIpsonVelocity( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float ThicknessCM) +{ + // Recht-Ipson equation for ballistic limit velocity + // Vbl = sqrt(k * σ * t / (ρ * A)) + // Where k is a constant, σ is yield strength, t is thickness, ρ is bullet density, A is cross-sectional area + + float Constant = 2.0f; // Empirical constant + float CrossSectionArea = BulletProps.GetCrossSectionCm2(); + float BulletDensity = BulletProps.GetMassKg() / (CrossSectionArea * BulletProps.LengthInches * 2.54f); // Rough approximation + + float BallisticLimit = FMath::Sqrt(Constant * MaterialProps.YieldStrengthMPa * ThicknessCM / + (BulletDensity * CrossSectionArea)); + + return BallisticLimit; +} + +float UEBMathematicalBallistics::CalculateCriticalRicochetAngle( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS) +{ + // Calculate critical ricochet angle using empirical formula + // Critical angle decreases with harder bullets and increases with harder targets + + float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness); + float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 300.0f); + + // Base critical angle (degrees) + float BaseCriticalAngle = 20.0f; + + // Adjust for hardness and velocity + float CriticalAngle = BaseCriticalAngle * HardnessRatio * VelocityFactor; + + return FMath::Clamp(CriticalAngle, 5.0f, 45.0f); +} + +// Helper function implementations +float UEBMathematicalBallistics::CalculateHardnessRatio(float BulletHardness, float MaterialHardness) +{ + // Hardness ratio affects penetration and ricochet + return FMath::Max(0.1f, BulletHardness / MaterialHardness); +} + +float UEBMathematicalBallistics::CalculateVelocityFactor(float Velocity, float ThresholdVelocity) +{ + // Velocity factor for various calculations + return FMath::Clamp(Velocity / ThresholdVelocity, 0.1f, 5.0f); +} + +float UEBMathematicalBallistics::CalculateAngleFactor(float ImpactAngleDegrees) +{ + // Convert to radians and calculate cosine (normal impact = 1.0, grazing = 0.0) + float AngleRadians = FMath::DegreesToRadians(ImpactAngleDegrees); + return FMath::Cos(AngleRadians); +} + +float UEBMathematicalBallistics::CalculateShapeFactorFromBulletType(EBulletType BulletType) +{ + // Shape factor affects penetration efficiency + switch (BulletType) + { + case EBulletType::BT_ArmorPiercing: + return 1.3f; + case EBulletType::BT_FullMetalJacket: + return 1.0f; + case EBulletType::BT_SoftPoint: + return 0.9f; + case EBulletType::BT_HollowPoint: + return 0.8f; + case EBulletType::BT_Frangible: + return 0.6f; + case EBulletType::BT_Wadcutter: + return 0.7f; + case EBulletType::BT_Match: + return 1.1f; + default: + return 1.0f; + } +} + +float UEBMathematicalBallistics::CalculateMaterialFactor(EBulletMaterial BulletMaterial) +{ + // Material factor affects penetration and expansion + switch (BulletMaterial) + { + case EBulletMaterial::BM_Steel: + return 1.4f; + case EBulletMaterial::BM_Tungsten: + return 1.8f; + case EBulletMaterial::BM_Brass: + return 1.2f; + case EBulletMaterial::BM_Copper: + return 1.1f; + case EBulletMaterial::BM_CopperJacket: + return 1.0f; + case EBulletMaterial::BM_Lead: + return 0.8f; + case EBulletMaterial::BM_LeadAntimony: + return 0.9f; + case EBulletMaterial::BM_Bismuth: + return 0.7f; + case EBulletMaterial::BM_Zinc: + return 0.9f; + default: + return 1.0f; + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/EasyBallistics.cpp b/Source/EasyBallistics/Private/EasyBallistics.cpp new file mode 100644 index 0000000..8de34ae --- /dev/null +++ b/Source/EasyBallistics/Private/EasyBallistics.cpp @@ -0,0 +1,21 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EasyBallistics.h" + +#define LOCTEXT_NAMESPACE "FEasyBallisticsModule" + +void FEasyBallisticsModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FEasyBallisticsModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FEasyBallisticsModule, EasyBallistics) \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Environment.cpp b/Source/EasyBallistics/Private/Environment.cpp new file mode 100644 index 0000000..fd5290c --- /dev/null +++ b/Source/EasyBallistics/Private/Environment.cpp @@ -0,0 +1,58 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBullet.h" + +FVector AEBBullet::GetWind_Implementation(UWorld* World, FVector Location) const{ + return Wind; +} + +float AEBBullet::GetAirDensity_Implementation(UWorld* World, FVector Location) const{ + switch (AtmosphereType) { + case (EEBAtmosphereType::AT_Curve): { + float airmp = SeaLevelAirDensity / GetCurveValue(AirDensityCurve, 0, SeaLevelAirDensity); + return GetCurveValue(AirDensityCurve, GetAltitude(World, Location) / WorldScale, SeaLevelAirDensity)* airmp; + } + case (EEBAtmosphereType::AT_Earth): { + return GetAltitudeDensity(GetAltitude(World, Location) / WorldScale / 100.0f); + } + default:{ + return SeaLevelAirDensity; + } + } +} + +float AEBBullet::GetSpeedOfSound_Implementation(UWorld* World, FVector Location) const{ + if (!SpeedOfSoundVariesWithAltitude) { + return SeaLevelSpeedOfSound * WorldScale; + } + + float Altitude = GetAltitude(World, Location); + float soundvmp = SeaLevelSpeedOfSound / GetCurveValue(SpeedOfSoundCurve, 0, SeaLevelSpeedOfSound); + return GetCurveValue(SpeedOfSoundCurve, Altitude, SeaLevelSpeedOfSound)*WorldScale*soundvmp; +} + + +float AEBBullet::GetAltitudePressure(float AltitudeMeter) const { + return FMath::Max(SeaLevelAirPressure * FMath::Pow((1 - (0.0000225577 * AltitudeMeter)), 5.25588), 0.0f); +} + +float AEBBullet::GetAltitudeTemperature(float AltitudeMeter) const { + return SeaLevelAirTemperature - (TemperatureLapseRate * FMath::Min(AltitudeMeter, TropopauseAltitude)); +} + +float AEBBullet::GetAltitudeDensity(float AltitudeMeter) const { + float Temperature = GetAltitudeTemperature(AltitudeMeter); + float Pressure = GetAltitudePressure(AltitudeMeter); + return Pressure * 100.0f / ((Temperature + 273.15) * SpecificGasConstant); +} + +float AEBBullet::GetAltitude(UWorld* World, FVector Location) const{ + FVector DistanceFromOrigin = (Location - WorldCenterLocation + FVector(World->OriginLocation)); + if (SphericalAltitude) + { + return (DistanceFromOrigin.Size() - SeaLevelRadius); + } + else { + return DistanceFromOrigin.Z; + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Pentrace.cpp b/Source/EasyBallistics/Private/Pentrace.cpp new file mode 100644 index 0000000..073fc4c --- /dev/null +++ b/Source/EasyBallistics/Private/Pentrace.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 Mookie. All Rights Reserved. + +#include "EBBullet.h" + +float AEBBullet::PenetrationTrace(FVector StartLocation, FVector EndLocation, TWeakObjectPtr Component, EPenTraceType PenTraceType, TEnumAsByte CollisionChannel, FVector &ExitLocation, FVector &ExitNormal) { + FCollisionQueryParams QueryParams; + QueryParams.bTraceComplex = TraceComplex; + QueryParams.bFindInitialOverlaps = true; + + FHitResult Result; + +switch (PenTraceType) { + case(EPenTraceType::PT_BackTrace): { + bool Hit = GetWorld()->LineTraceSingleByChannel(Result, EndLocation, StartLocation, CollisionChannel, QueryParams); + if (!Hit) return 1.0f; + ExitNormal = Result.Normal; + ExitLocation = Result.Location; + return (1.0f - Result.Time); + } + + case(EPenTraceType::PT_ByComponent): { + bool Hit = Component->LineTraceComponent(Result, EndLocation, StartLocation, QueryParams); + if (!Hit) return 1.0f; + ExitNormal = Result.Normal; + ExitLocation = Result.Location; + return (1.0f - Result.Time); + } + + case(EPenTraceType::PT_TwoSidedGeometry): { + bool Hit = GetWorld()->LineTraceSingleByChannel(Result, StartLocation, EndLocation, CollisionChannel, QueryParams); + if (!Hit) return 1.0f; + ExitLocation = Result.Location; + ExitNormal = -Result.Normal; + return Result.Time; + } + + default: + return 1.0f; + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Pooling.cpp b/Source/EasyBallistics/Private/Pooling.cpp new file mode 100644 index 0000000..cd3085e --- /dev/null +++ b/Source/EasyBallistics/Private/Pooling.cpp @@ -0,0 +1,168 @@ +#include "EBBullet.h" + +void AEBBullet::Deactivate() { + //server only + if (!HasAuthority()) { return; } + OnDeactivated(); + this->DeactivateToPool(); + DeactivationBroadcast(); +} + +AEBBullet* AEBBullet::GetFromPool(UWorld* World, UClass* BulletClass) { + AEBBullet* Pool = Cast(BulletClass->GetDefaultObject()); + + if (Pool) { + //find first of correct class; + bool CleanupRequired=false; + + int32 FoundIndex = Pool->Pooled.IndexOfByPredicate( + [&](auto InItem) { + if (InItem.IsValid() && InItem->GetWorld() == World) { + return true; + } + else { + CleanupRequired = true; + return false; + } + }); + + TWeakObjectPtr Found = nullptr; + if (FoundIndex != INDEX_NONE) { + Found = Pool->Pooled[FoundIndex]; + Pool->Pooled.RemoveAtSwap(FoundIndex,EAllowShrinking::Yes); + } + + if (CleanupRequired) { +#ifdef WITH_EDITOR + if (Pool->DebugPooling) { + GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, TEXT("Invalid reference in pool, cleaning up")); + } +#endif + Pool->Pooled.RemoveAll([&](auto InItem) { + if (InItem.IsValid() && InItem->GetWorld() == World) { + return false; + } + else { + return true; + } + }); + } + + return(Found.Get()); + } + else { + return nullptr; + } +} + +AEBBullet* AEBBullet::SpawnOrReactivate(UWorld* World, TSubclassOf BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator) { + AEBBullet* bullet; + + AEBBullet* Recycled = GetFromPool(World, BulletClass); + + if (Recycled) { + AEBBullet* Default = Cast(BulletClass->GetDefaultObject()); + + Recycled->Reset(); + + Recycled->SetOwner(BulletOwner); + Recycled->SetInstigator(BulletInstigator); + Recycled->SetActorTransform(Transform,false,nullptr,ETeleportType::TeleportPhysics); + Recycled->Velocity = BulletVelocity; + Recycled->SetActorHiddenInGame(Default->IsHidden()); + Recycled->SetActorTickEnabled(true); + Recycled->CanRetrace = false; + Recycled->IgnoredActors = Default->IgnoredActors; + Recycled->SafeDelay = Default->SafeDelay; + Recycled->SetLifeSpan(Default->InitialLifeSpan); + Recycled->FinishSpawning(Transform); + //if (!Recycled->HasActorBegunPlay()){ Recycled->BeginPlay(); } + //Recycled->ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(Recycled->GetWorld(), Transform.GetLocation()), BulletVelocity, BulletOwner, BulletInstigator); +#ifdef WITH_EDITOR + if (Recycled->DebugPooling) { + GEngine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Recycling pooled bullet")); + } +#endif + return Recycled; + } + else { + bullet = Cast(World->SpawnActorDeferred(BulletClass, Transform, BulletOwner, BulletInstigator)); + bullet->RandomStream.GenerateNewSeed(); + bullet->Velocity = BulletVelocity; + bullet->FinishSpawning(Transform); + //UGameplayStatics::FinishSpawningActor(bullet, Transform); +#ifdef WITH_EDITOR + if (bullet->DebugPooling) { + GEngine->AddOnScreenDebugMessage(0, 2, FColor::Orange, TEXT("Spawning new bullet")); + } +#endif + return bullet; + } +} + +void AEBBullet::FinishSpawning(FTransform Transform) { + if(IsRecycled){ + if (!HasActorBegunPlay()){ + BeginPlay(); + } + ReactivationBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(this->GetWorld(), Transform.GetLocation()), this->Velocity, this->GetOwner(), this->GetInstigator()); + }else{ + UGameplayStatics::FinishSpawningActor(this, Transform); + } +} + +void AEBBullet::ReactivationBroadcast_Implementation(FVector_NetQuantize NewLocation, FVector NewVelocity, AActor* BulletOwner, APawn* BulletInstigator) { + if (!HasAuthority()) { + AEBBullet* Default = Cast(this->StaticClass()->GetDefaultObject()); + + SetOwner(BulletOwner); + SetInstigator(BulletInstigator); + + SetActorLocation(UGameplayStatics::RebaseZeroOriginOntoLocal(GetWorld(), NewLocation)); + Velocity = NewVelocity; + CanRetrace = false; + + SetActorHiddenInGame(Default->IsHidden()); + SetActorTickEnabled(true); + SafeDelay = Default->SafeDelay; + OwnerSafe = Default->SafeLaunch; + BeginPlay(); + } +} + +void AEBBullet::DeactivationBroadcast_Implementation() { + if (!HasAuthority()) { + OnDeactivated(); + this->DeactivateToPool(); + } +} + +void AEBBullet::LifeSpanExpired() { + Deactivate(); +} + +void AEBBullet::DeactivateToPool() { + AEBBullet* Pool = Cast(GetClass()->GetDefaultObject()); + + if (Pool && EnablePooling) { + SetActorHiddenInGame(true); + SetActorTickEnabled(false); + Pool->Pooled.Add(this); + EndPlay(EEndPlayReason::RemovedFromWorld); + + if (Pool->Pooled.Num() > MaxPoolSize) { + AEBBullet* Oldest = (Pool->Pooled[0].Get()); + Pool->Pooled.RemoveAtSwap(0,EAllowShrinking::Yes); + if (Oldest) { Oldest->Destroy(); } + } + +#ifdef WITH_EDITOR + if (DebugPooling) { + GEngine->AddOnScreenDebugMessage(2, 2, FColor::White, FString("Bullet pooled: ") + FString::FromInt(Pool->Pooled.Num())); + } +#endif + } + else { + Destroy(); + } +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Predict.cpp b/Source/EasyBallistics/Private/Predict.cpp new file mode 100644 index 0000000..158e37b --- /dev/null +++ b/Source/EasyBallistics/Private/Predict.cpp @@ -0,0 +1,74 @@ +// Copyright 2020 Mookie. All Rights Reserved. + +#include "EBBarrel.h" +#include "EBBullet.h" + +void UEBBarrel::PredictHit(bool& Hit, FHitResult& HitResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray& Trajectory, TSubclassOf BulletClass, TArray IgnoredActors, float MaxTime, float Step) const { + FVector StartLocation = GetComponentLocation(); + FVector AimDirection = GetComponentQuat().GetForwardVector(); + PredictHitFromLocation(Hit, HitResult, HitLocation, HitTime, HitActor, Trajectory, BulletClass, StartLocation, AimDirection, IgnoredActors, MaxTime, Step); +} + +void UEBBarrel::PredictHitFromLocation(bool &Hit, FHitResult& HitResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray& Trajectory, TSubclassOf BulletClass, FVector StartLocation, FVector AimDirection, TArray IgnoredActors, float MaxTime, float Step) const{ + if (!BulletClass->IsValidLowLevel()) { + UE_LOG(LogTemp, Warning, TEXT("PredictHit - invalid bullet class")); + return; + } + + float Time = 0; + Trajectory = TArray(); + + FVector CurrentLocation = StartLocation; + AEBBullet* Bullet = Cast(BulletClass->GetDefaultObject()); + FVector Velocity = AimDirection.GetSafeNormal()*(FMath::Lerp(MuzzleVelocityMultiplierMin, MuzzleVelocityMultiplierMax, 0.5)*FMath::Lerp(Bullet->MuzzleVelocityMin, Bullet->MuzzleVelocityMax, 0.5)); + + UPrimitiveComponent* Parent = Cast(GetAttachParent()); + + Velocity += AdditionalVelocity; + + if (Parent != nullptr) { + if (Parent->IsSimulatingPhysics()) { + Velocity += Parent->GetPhysicsLinearVelocityAtPoint(CurrentLocation)*InheritVelocity; + } + } + + while (Time < MaxTime) { + FVector PreviousVelocity = Velocity; + Velocity = Bullet->UpdateVelocity(GetWorld(), CurrentLocation, Velocity, Step); + Hit = UEBBarrel::PredictTrace(GetWorld(), Bullet, CurrentLocation, CurrentLocation + FMath::Lerp(PreviousVelocity, Velocity, 0.5f)*Step, HitResult, IgnoredActors); + if (Hit) { + Trajectory.Add(HitResult.Location); + HitTime = Time+(HitResult.Time*Step); + HitActor = HitResult.GetActor(); + HitLocation = HitResult.Location; + return; + } + else { + Trajectory.Add(CurrentLocation); + CurrentLocation += FMath::Lerp(PreviousVelocity, Velocity, 0.5f)*Step; + Time += Step; + } + } + + Hit = false; + HitTime = MaxTime; + HitLocation = CurrentLocation; + HitActor = nullptr; +} + +bool UEBBarrel::PredictTrace(UWorld* World, AEBBullet* Bullet, FVector Start, FVector End, FHitResult &HitResult, TArray IgnoredActors) const { + + FCollisionResponseParams ResponseParams; + + FCollisionQueryParams QueryParams; + QueryParams.bTraceComplex = Bullet->TraceComplex; + QueryParams.bReturnPhysicalMaterial = true; + + if (Bullet->SafeLaunch) { + QueryParams.AddIgnoredActor(GetOwner()); + } + + QueryParams.AddIgnoredActors(IgnoredActors); + + return World->LineTraceSingleByChannel(HitResult, Start, End, Bullet->TraceChannel, QueryParams, ResponseParams); +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Reload.cpp b/Source/EasyBallistics/Private/Reload.cpp new file mode 100644 index 0000000..9ecb2f5 --- /dev/null +++ b/Source/EasyBallistics/Private/Reload.cpp @@ -0,0 +1,74 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBarrel.h" +#include "EBBullet.h" + +TArray> UEBBarrel::GetAmmo(bool CountChambered) const { + if (!CountChambered || ChamberedBullet == nullptr) { + return Ammo; + } + else { + TArray> RetAmmo; + RetAmmo.Add(ChamberedBullet); + RetAmmo.Append(Ammo); + return RetAmmo; + }; +}; + +int UEBBarrel::GetAmmoCount(bool CountChambered) const { + + int remainingAmmo; + if (CycleAmmo) { + remainingAmmo = CycleAmmoCount; + } + else { + remainingAmmo = Ammo.Num(); + }; + + if (CountChambered) { + if (ChamberedBullet != nullptr) { + remainingAmmo++; + }; + }; + + return remainingAmmo; +}; + +void UEBBarrel::SetAmmo(int Count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray>& NewAmmo) { + Ammo = NewAmmo; + + CycleAmmoCount = Count; + + if (UnloadChambered) { + ChamberedBullet = nullptr; + }; + + if (CancelShooting) { + BurstRemaining = 0; + Shooting = false; + }; + + if (ManualCharge) { + LoadNext = false; + }; +}; + +void UEBBarrel::Charge_Implementation() { + LoadNext = true; +}; + +bool UEBBarrel::Charge_Validate() { + return true; +}; + +void UEBBarrel::UnloadChambered_Implementation(bool ManualCharge) { + ChamberedBullet = nullptr; + + if (ManualCharge) { + LoadNext = false; + }; +}; + +bool UEBBarrel::UnloadChambered_Validate(bool ManualCharge) { + return true; +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Private/SafeLaunch.cpp b/Source/EasyBallistics/Private/SafeLaunch.cpp new file mode 100644 index 0000000..7b2a58f --- /dev/null +++ b/Source/EasyBallistics/Private/SafeLaunch.cpp @@ -0,0 +1,28 @@ +// Copyright 2020 Mookie. All Rights Reserved. + +#include "EBBullet.h" + +TArrayAEBBullet::GetSafeLaunchIgnoredActors(AActor* BulletOwner) const{ + TArray Results = SafeLaunchIgnoredActors; + + Results.Add(BulletOwner); + + if (SafeLaunchIgnoreAttachParent && BulletOwner) { + AActor* AttachedRoot = BulletOwner; + + while (true) { //find attachment root + AActor* AttachedTo; + AttachedTo = AttachedRoot->GetAttachParentActor(); + + if (AttachedTo) { + Results.Add(AttachedTo); + AttachedRoot = AttachedTo; + } + else break; + } + Results.Add(AttachedRoot); + if (SafeLaunchIgnoreAllAttached) Results.Append(GetAttachedActorsRecursive(AttachedRoot)); + } + + return Results; +} \ No newline at end of file diff --git a/Source/EasyBallistics/Private/Trace.cpp b/Source/EasyBallistics/Private/Trace.cpp new file mode 100644 index 0000000..4e685bb --- /dev/null +++ b/Source/EasyBallistics/Private/Trace.cpp @@ -0,0 +1,260 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBullet.h" + +float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEnumAsByte CollisionChannel) { + + bool Hit; + FHitResult HitResult; + TArray Results; + + FCollisionResponseParams ResponseParameters; + + FCollisionQueryParams CollisionParameters; + CollisionParameters.bTraceComplex = TraceComplex; + CollisionParameters.bReturnPhysicalMaterial = true; + CollisionParameters.AddIgnoredActor(this); + CollisionParameters.AddIgnoredActors(IgnoredActors); + CollisionParameters.bReturnFaceIndex = true; + + if (OwnerSafe) { + CollisionParameters.AddIgnoredActors(GetSafeLaunchIgnoredActors(GetOwner())); + } + + FVector TraceDistance = (PreviousVelocity + Velocity)*0.5*delta; + + GetWorld()->LineTraceMultiByChannel(Results, start, start + TraceDistance, CollisionChannel, CollisionParameters, ResponseParameters); + if (Results.Num() > 0) { + HitResult = FilterHits(Results, Hit); + } + else { Hit = false; } + + if (Hit) { + //Reduce velocity + Velocity = FMath::Lerp(PreviousVelocity, Velocity, HitResult.Time); + + bool Ricochet = false; + bool Penetration = false; + FVector exitLoc; + FVector exitNormal; + FVector NewVelocity = Velocity; + + //material mods + bool neverPenetrate = false; + bool neverRicochet = false; + float penDepthMultiplier = 1.0f; + float penNormalization = PenetrationNormalization; + float penNormalizationGrazing = PenetrationNormalizationGrazing; + float penEnterSpread = PenetrationEntryAngleSpread; + float penExitSpread = PenetrationExitAngleSpread; + float ricProbMultiplier = 1.0f; + float ricRestitution = RicochetRestitution; + float ricFriction = RicochetFriction; + float ricSpread = RicochetSpread; + EPenTraceType PenTraceType = DefaultPenTraceType; + + UPhysicalMaterial* PhysMaterial = HitResult.PhysMaterial.Get(); + + + + if (PhysMaterial) { + //material response modifiers + if (MaterialResponseMap != nullptr) { + FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(PhysMaterial); + if (ResponseEntry != nullptr) { + neverPenetrate = ResponseEntry->NeverPenetrate; + neverRicochet = ResponseEntry->NeverRicochet; + PenTraceType = ResponseEntry->PenTraceType; + + penDepthMultiplier = ResponseEntry->PenetrationDepthMultiplier; + penNormalization = PenetrationNormalization + ResponseEntry->PenetrationNormalization; + penNormalizationGrazing = PenetrationNormalizationGrazing + ResponseEntry->PenetrationNormalizationGrazing; + penEnterSpread = PenetrationEntryAngleSpread + ResponseEntry->PenetrationEntryAngleSpread; + penExitSpread = PenetrationExitAngleSpread + ResponseEntry->PenetrationExitAngleSpread; + + ricProbMultiplier = ResponseEntry->RicochetProbabilityMultiplier; + ricRestitution = FMath::Lerp(RicochetRestitution, ResponseEntry->RicochetRestitution, ResponseEntry->RicochetRestitutionInfluence); + ricFriction = FMath::Lerp(RicochetFriction, ResponseEntry->RicochetFriction, ResponseEntry->RicochetFrictionInfluence); + ricSpread = RicochetSpread + ResponseEntry->RicochetSpread; + } + } + + if (MaterialDensityControlsPenetrationDepth) { + penDepthMultiplier /= PhysMaterial->Density; + } + + if (MaterialRestitutionControlsRicochet) { + RicochetRestitution *= PhysMaterial->Restitution; + } + } + + + float dot = FVector::DotProduct(Velocity.GetSafeNormal(), HitResult.Normal) + 1.0f; + FVector cross = FVector::CrossProduct(Velocity.GetSafeNormal(), HitResult.Normal); + FVector flat = HitResult.Normal.RotateAngleAxis(-90.0f, cross); + +#ifdef WITH_EDITOR + if (DebugEnabled) { + FColor DebugColor = FColor::MakeRedToGreenColorFromScalar(Velocity.Size() / MuzzleVelocityMax); + DrawDebugLine(GetWorld(), start, HitResult.Location, DebugColor, false, DebugTrailTime, 0, DebugTrailWidth); + }; +#endif + + float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent); + FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread); + PenetrationVector = FMath::Lerp(PenetrationVector, -HitResult.Normal, FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle)); + float PenetrationDistance = FMath::Lerp(MinPenetration, MaxPenetration, RandomStream.FRand()) * FMath::Pow((Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax) * 0.5f)), 2.0f) * penDepthMultiplier; + float PenetrationDepth = -FVector::DotProduct(PenetrationVector, HitResult.Normal) * PenetrationDistance; + + float BlockTIme = 1.0f; + + if (PenetrationDistance > 0.0f) { + if (!neverPenetrate) { + BlockTIme = PenetrationTrace(HitResult.Location - (HitResult.Normal * CollisionMargin), HitResult.Location + PenetrationVector * PenetrationDistance, HitResult.Component, PenTraceType, CollisionChannel, exitLoc, exitNormal); + } + } + + if (BlockTIme >= 0.999999f) { + + //no pen + SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin); + + float ricThreshold = 1.0f; + if (SpeedControlsRicochetProbability) { ricThreshold *= Velocity.Size() / MuzzleVelocityMax; }; + + if (!neverRicochet && RandomStream.FRand() * ricThreshold < FMath::Lerp(RicochetProbability * ricProbMultiplier, RicochetProbabilityGrazing * ricProbMultiplier, GrazingAngle)) { + //bounce + FVector bounceAngle = flat * dot * (1.0f - ricFriction); + bounceAngle += HitResult.Normal * (1.0f - dot) * ricRestitution; + bounceAngle = RandomStream.VRandCone(bounceAngle, ricSpread) * bounceAngle.Size(); + + NewVelocity = bounceAngle * Velocity.Size(); + Ricochet = true; + OwnerSafe = false; + } + else { + //stopped + NewVelocity = FVector(0, 0, 0); + } + } + else { + //penetration + float RemainingEnergy = FMath::Pow(1.0f - BlockTIme, 2.0f); + SetActorLocation(exitLoc + exitNormal * CollisionMargin); + NewVelocity = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy)); + NewVelocity = FMath::Lerp(NewVelocity, Velocity.GetSafeNormal(), RemainingEnergy); + NewVelocity *= RemainingEnergy * Velocity.Size(); + Penetration = true; + OwnerSafe = false; + } + + + //response + FVector Impulse = (Velocity - NewVelocity) * Mass * ImpulseMultiplier; + + if (AddImpulse && HitResult.Component->IsSimulatingPhysics()) { + HitResult.Component->AddImpulseAtLocation(Impulse, HitResult.Location, HitResult.BoneName); + } + + // New Ballistic Impact System Integration + if (UseNewImpactSystem && BallisticImpactComponent && BulletPropertiesAsset) { + // Calculate impact using new system + float NewPenetrationDepth; + bool bNewDidPenetrate; + FVector NewExitLocation; + + FVector NewExitLoc = BallisticImpactComponent->CalculateBallisticImpact( + HitResult.Location, + Velocity, + BulletPropertiesAsset->BulletProperties, + PhysMaterial, + NewPenetrationDepth, + bNewDidPenetrate, + NewExitLocation + ); + + // Calculate ricochet using new system + float NewEnergyRetained; + bool bNewDidRicochet; + + FVector NewRicochetVelocity = BallisticImpactComponent->CalculateRicochet( + HitResult.Location, + HitResult.Normal, + Velocity, + BulletPropertiesAsset->BulletProperties, + PhysMaterial, + NewEnergyRetained, + bNewDidRicochet + ); + + // Override legacy system results with new system + if (bNewDidPenetrate) { + Penetration = true; + PenetrationDepth = NewPenetrationDepth; + exitLoc = NewExitLocation; + } + + if (bNewDidRicochet) { + Ricochet = true; + NewVelocity = NewRicochetVelocity; + } + } + + //impact actual + if (HasAuthority()) { + OnImpact(Ricochet, Penetration, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), NewVelocity, Impulse, PenetrationDepth, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult); + } + else { + OnNetPredictedImpact(Ricochet, Penetration, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), NewVelocity, Impulse, PenetrationDepth, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult); + } + + Velocity = NewVelocity; + + if ((Velocity.Size() < DespawnVelocity) || (!Ricochet && !Penetration && (DespawnVelocity>0.0f))){ + Deactivate(); + } + CanRetrace = false; + } + else { + //prepare for time travel + if (Retrace) { + CanRetrace = true; + LastTraceStart = start; + LastTraceDelta = delta; + LastTracePrevVelocity = PreviousVelocity; + LastTraceVelocity = Velocity; + } + + SetActorLocation(start + TraceDistance); + HitResult.Time = 1.0f; + + OnTrace(start, GetActorLocation()); + +#ifdef WITH_EDITOR + if (DebugEnabled) { + FLinearColor Color = GetDebugColor(Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax)*0.5f)); + DrawDebugLine(GetWorld(), start, start + TraceDistance, Color.ToFColor(true), false, DebugTrailTime, 0, 0); + } + } +#endif + + return delta*(1.0f - HitResult.Time); +} + +TArray AEBBullet::GetAttachedActorsRecursive(AActor* Actor, uint16 Depth, TArray VisitedActors) const { + //TODO: limit depth + TArray Attached; + Actor->GetAttachedActors(Attached); + + TArray AttachedRecursive; + for (AActor* ActorRecursive : Attached) { // Skip already visited actors to avoid infinite recursion + if (!VisitedActors.Contains(ActorRecursive)) { + VisitedActors.Add(ActorRecursive); + AttachedRecursive += GetAttachedActorsRecursive(ActorRecursive, Depth+1, VisitedActors); + VisitedActors.Remove(ActorRecursive); // Remove from visited actors to allow other branches to visit it + } + } + + Attached += AttachedRecursive; + return Attached; +} \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBBallisticImpactComponent.h b/Source/EasyBallistics/Public/EBBallisticImpactComponent.h new file mode 100644 index 0000000..8813754 --- /dev/null +++ b/Source/EasyBallistics/Public/EBBallisticImpactComponent.h @@ -0,0 +1,91 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "EBBulletProperties.h" +#include "EBMaterialResponseMap.h" +#include "EBBallisticImpactComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact, + FVector, ImpactLocation, + FVector, ImpactNormal, + UPhysicalMaterial*, HitMaterial, + float, PenetrationDepth, + bool, bDidPenetrate); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnBallisticRicochet, + FVector, RicochetLocation, + FVector, RicochetDirection, + UPhysicalMaterial*, HitMaterial, + float, EnergyRetained); + +UCLASS(ClassGroup=(Ballistics), meta=(BlueprintSpawnableComponent)) +class EASYBALLISTICS_API UEBBallisticImpactComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UEBBallisticImpactComponent(); + + // Events + UPROPERTY(BlueprintAssignable, Category = "Ballistics Events") + FOnBallisticImpact OnBallisticImpact; + + UPROPERTY(BlueprintAssignable, Category = "Ballistics Events") + FOnBallisticRicochet OnBallisticRicochet; + + // Configuration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + UEBMaterialResponseMap* MaterialResponseMap; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + bool bEnableBallisticCalculations = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + bool bUseMathematicalPenetration = false; + + // Blueprint callable functions + UFUNCTION(BlueprintCallable, Category = "Ballistics") + FVector CalculateBallisticImpact( + const FVector& ImpactLocation, + const FVector& ProjectileVelocity, + const FMathematicalBulletProperties& BulletProperties, + UPhysicalMaterial* HitMaterial, + float& OutPenetrationDepth, + bool& bOutDidPenetrate, + FVector& OutExitLocation + ); + + UFUNCTION(BlueprintCallable, Category = "Ballistics") + FVector CalculateRicochet( + const FVector& ImpactLocation, + const FVector& ImpactNormal, + const FVector& ProjectileVelocity, + const FMathematicalBulletProperties& BulletProperties, + UPhysicalMaterial* HitMaterial, + float& OutEnergyRetained, + bool& bOutDidRicochet + ); + + UFUNCTION(BlueprintCallable, Category = "Ballistics") + UEBMaterialPropertiesAsset* GetMaterialProperties(UPhysicalMaterial* PhysicalMaterial); + + UFUNCTION(BlueprintCallable, Category = "Ballistics") + FEBMaterialResponseMapEntry GetMaterialResponse(UPhysicalMaterial* PhysicalMaterial); + +protected: + virtual void BeginPlay() override; + +private: + FVector CalculatePenetrationVector( + const FVector& ImpactLocation, + const FVector& ImpactNormal, + const FVector& ProjectileVelocity, + float PenetrationDepth + ); + + float CalculateImpactAngle(const FVector& ProjectileVelocity, const FVector& SurfaceNormal); +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBBarrel.h b/Source/EasyBallistics/Public/EBBarrel.h new file mode 100644 index 0000000..89c6b5f --- /dev/null +++ b/Source/EasyBallistics/Public/EBBarrel.h @@ -0,0 +1,138 @@ +// Copyright 2020 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/PrimitiveComponent.h" +#include "Kismet/GameplayStatics.h" + +#include "EBBullet.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)) + class EASYBALLISTICS_API UEBBarrel : public UPrimitiveComponent +{ + + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UEBBarrel(); + + // Called every frame + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugArrowSize = 100.0f; + + 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); + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional maximum spread, in radians, applied on top of bullet spread", ClampMin = "0")) float Spread=0.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Additional Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMin = 1.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum of random multiplier applied to bullet muzzle velocity")) float MuzzleVelocityMultiplierMax = 1.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum fire rate, rounds per second")) float FireRateMin = 1.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Maximum fire rate, rounds per second, set to same number as FireRateMin to disable randomization")) float FireRateMax = 1.0f; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") EFireMode FireMode = EFireMode::FM_Auto; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Weapon") bool ShootingBlocked; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Number of rounds auto fired in burst mode")) int BurstCount = 3; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Minimum time between bursts")) float BurstCooldown = 0.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon", meta = (ToolTip = "Automatically spin up gatling when trigger is being held down")) bool GatlingAutoSpool = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolUpTime = 1.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Weapon") float GatlingSpoolDownTime = 1.0f; + UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingPhase = 0.0f; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo") bool CycleAmmo = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) bool CycleAmmoUnlimited = true; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo") TArray> Ammo; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoCount; + UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Ammo", meta = (EditCondition = "CycleAmmo")) int CycleAmmoPos; + + UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") TSubclassOf ChamberedBullet; + UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Shooting; + UPROPERTY(Replicated, BlueprintReadWrite, Category = "WeaponState") bool Spooling = false; + UPROPERTY(BlueprintReadWrite, Category = "Weapon") float GatlingRPS = 0.0f; + + UPROPERTY(BlueprintReadWrite, Category = "WeaponState") bool LoadNext=true; + UPROPERTY(BlueprintReadWrite, Category = "WeaponState") float Cooldown; + UPROPERTY(BlueprintReadWrite, Category = "WeaponState") int BurstRemaining; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateVariables=true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ReplicateShotFiredEvents = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") bool ClientSideAim=false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimUpdateFrequency = 15.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Replication") float ClientAimDistanceLimit = 200.0f; + + FRandomStream RandomStream; + + UFUNCTION() void NextBullet(); + UFUNCTION(BlueprintPure, Category = "Ammo") int GetAmmoCount(bool CountChambered) const; + UFUNCTION(BlueprintPure, Category = "Ammo") TArray> GetAmmo(bool CountChambered) const; + UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "Ammo") void SetAmmo(int count, bool UnloadChambered, bool CancelShooting, bool ManualCharge, const TArray>& NewAmmo); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void Charge(); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Ammo") void UnloadChambered(bool ManualCharge); + UFUNCTION(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); + + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHit(bool& Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray& Trajectory, TSubclassOf BulletClass, TArrayIgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const; + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "IgnoredActors"), Category = "Prediction") void PredictHitFromLocation(bool &Hit, FHitResult& TraceResult, FVector& HitLocation, float& HitTime, AActor*& HitActor, TArray& Trajectory, TSubclassOf BulletClass, FVector StartLocation, FVector AimDirection, TArrayIgnoredActors, float MaxTime = 10.0f, float Step = 0.1f) const; + UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirection(TSubclassOf BulletClass, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step = 0.1f, int NumIterations = 4) const; + UFUNCTION(BlueprintCallable, Category = "Prediction") void CalculateAimDirectionFromLocation(TSubclassOf BulletClass, FVector StartLocation, FVector TargetLocation, FVector TargetVelocity, FVector& AimDirection, FVector& PredictedTargetLocation, FVector& PredictedIntersectionLocation, float& PredictedFlightTime, float& Error, float MaxTime = 10.0f, float Step=0.1f, int NumIterations = 4) const; + + UFUNCTION(BlueprintNativeEvent, Category = "Events") void InitialBulletTransform(FVector InLocation, FVector InDirection, FVector& OutLocation, FVector& OutDirection); + UFUNCTION(BlueprintNativeEvent, Category = "Events") void ApplyRecoil(UPrimitiveComponent* Component, FVector InLocation, FVector Impulse); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBeforeShotFired); + UPROPERTY(BlueprintAssignable, Category = "Events") + FBeforeShotFired BeforeShotFired; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FShotFired); + UPROPERTY(BlueprintAssignable, Category = "Events") + FShotFired ShotFired; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAmmoDepleted); + UPROPERTY(BlueprintAssignable, Category = "Events") + FAmmoDepleted AmmoDepleted; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FReadyToShoot); + UPROPERTY(BlueprintAssignable, Category = "Events") + FReadyToShoot ReadyToShoot; + +#if WITH_EDITOR + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual bool IsZeroExtent() const override { return false; }; + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; +#endif + +private: + void SpawnBullet(AActor* Owner, FVector LocalLocation, FVector LocalAim); + + UFUNCTION(Server, Unreliable, WithValidation) void ClientAim(FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim); + UFUNCTION(Server, Reliable, WithValidation) void ShootRep(bool Trigger); + UFUNCTION(Server, Reliable, WithValidation) void ShootRepCSA(bool Trigger, FVector_NetQuantize NewLocation, FVector_NetQuantizeNormal NewAim); + + UFUNCTION(NetMulticast, Reliable) + void ShotFiredMulticast(); + + FVector Aim; + FVector Location; + bool RemoteAimReceived; + float TimeSinceAimUpdate; + bool PredictTrace(UWorld* World, AEBBullet* Bullet, FVector Start, FVector End, FHitResult &HitResult, TArray IgnoredActors) const; +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBBullet.h b/Source/EasyBallistics/Public/EBBullet.h new file mode 100644 index 0000000..3df9849 --- /dev/null +++ b/Source/EasyBallistics/Public/EBBullet.h @@ -0,0 +1,282 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Engine/Engine.h" +#include "Engine/World.h" +#include "Curves/CurveFloat.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet/GameplayStatics.h" +#include "DrawDebugHelpers.h" +#include "Components/PrimitiveComponent.h" + +#include "EBMaterialResponseMap.h" +#include "EBBulletProperties.h" +#include "EBMathematicalBallistics.h" +#include "EBBallisticImpactComponent.h" + +#include "EBBullet.generated.h" + +UENUM(BlueprintType) +enum class EEBAtmosphereType : uint8 +{ + AT_Constant UMETA(DisplayName = "Constant"), + AT_Curve UMETA(DisplayName = "Density Curve"), + AT_Earth UMETA(DisplayName = "Earth/IGL") +}; + +UCLASS(Blueprintable, BlueprintType) +class EASYBALLISTICS_API AEBBullet : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AEBBullet(); + + UPROPERTY(Replicated, BlueprintReadWrite, Category = "State") FVector Velocity; + UPROPERTY(Replicated, BlueprintReadWrite, Category = "State") FRandomStream RandomStream; + + UPROPERTY(BlueprintReadWrite, Category = "State") bool OwnerSafe=false; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugEnabled; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailTime=1.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailWidth=0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorFast = FLinearColor(0, 1, 0, 1); + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorSlow = FLinearColor(1, 0, 0, 1); + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugPooling; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") FVector Wind; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Select atmosphere model")) EEBAtmosphereType AtmosphereType; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "Air Density at sea level - in KG/m^3", ClampMin = "0")) float SeaLevelAirDensity = 1.21; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "in cm/s", ClampMin = "0")) float SeaLevelSpeedOfSound = 34300; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World", meta = (ToolTip = "Used for Density Curve atmosphere model")) UCurveFloat* AirDensityCurve; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") bool SpeedOfSoundVariesWithAltitude = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") UCurveFloat* SpeedOfSoundCurve; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") float WorldScale = 1.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Atmosphere pressure at 0,0,0 - in millibars", ClampMin = "0")) float SeaLevelAirPressure = 1012.5f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Atmosphere Temperature at 0,0,0 - in degrees C")) float SeaLevelAirTemperature = 20.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Temperature Decrease With Altitude, degrees per meter")) float TemperatureLapseRate = 0.00649f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Altitude at which temperature stops decreasing, in meters")) float TropopauseAltitude = 11000.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Specific Gas Constant, dry air = 287.058", ClampMin = "0")) float SpecificGasConstant = 287.058; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "World Origin Location")) FVector WorldCenterLocation = FVector(0, 0, 0); + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Use spherical planet model to get altitude")) bool SphericalAltitude = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Planet radius, in Unreal units", EditCondition = "SphericalAltitude", ClampMin = "0")) float SeaLevelRadius = 637100000.0f; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") bool OverrideGravity = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") FVector Gravity = FVector(0,0,-980); + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch") bool SafeLaunch = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch")) bool SafeLaunchIgnoreAttachParent = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunchIgnoreAttachParent")) bool SafeLaunchIgnoreAllAttached = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch", ClampMin = "0")) float SafeDelay = 1.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Safe launch", Meta = (EditCondition = "SafeLaunch")) TArray SafeLaunchIgnoredActors; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun") bool Shotgun=false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) int ShotCount=10; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotSpread=0.01; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotVelocitySpread = 0.01; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMin = 100000.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMax = 100000.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Maximum bullet spread, in radians", ClampMin = "0")) float Spread = 0.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f; + // Physics Mode Toggle + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Physics Mode", meta = (ToolTip = "Toggle between artistic (manual) and mathematical (realistic) physics calculations")) + bool UseMathematicalPhysics = false; + + // Mathematical Properties (used when UseMathematicalPhysics is true) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalPhysics", ToolTip = "Bullet properties asset for mathematical calculations")) + UEBBulletPropertiesAsset* BulletPropertiesAsset; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalPhysics", ToolTip = "Material properties asset for mathematical calculations")) + UEBMaterialPropertiesAsset* MaterialPropertiesAsset; + + // Artistic Properties (used when UseMathematicalPhysics is false) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Bullet mass in kg")) + float Mass = 0.005; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Bullet diameter in cm")) + float Diameter = 0.556; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Form factor for drag calculations")) + float FormFactor = 1.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Properties", meta = (EditCondition = "!UseMathematicalPhysics", ToolTip = "Drag curve vs Mach number")) + UCurveFloat* MachDragCurve; + + // Artistic Impact Properties (used when UseMathematicalPhysics is false) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float GrazingAngleExponent = 2.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float MinPenetration = 10.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float MaxPenetration = 20.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float PenetrationNormalization = 0.5; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float PenetrationNormalizationGrazing = 0.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float PenetrationEntryAngleSpread = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float PenetrationExitAngleSpread = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float RicochetProbability = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float RicochetProbabilityGrazing = 1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float RicochetRestitution = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float RicochetFriction = 0.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float RicochetSpread = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + bool SpeedControlsRicochetProbability = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + bool AddImpulse = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Artistic Impact", meta = (EditCondition = "!UseMathematicalPhysics")) + float ImpulseMultiplier = 1.0; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") EPenTraceType DefaultPenTraceType = EPenTraceType::PT_BackTrace; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") UEBMaterialResponseMap* MaterialResponseMap; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") bool MaterialDensityControlsPenetrationDepth = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") bool MaterialRestitutionControlsRicochet = true; + + // New Ballistic Impact System + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Impact") + UEBBallisticImpactComponent* BallisticImpactComponent; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Impact") + bool UseNewImpactSystem = false; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Replication") bool ReliableReplication = false; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Allow components to collide, intended for use with trigger volumes. Do not use for actual collisions.")) bool AllowComponentCollisions = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TEnumAsByte TraceChannel; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") bool TraceComplex; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") float CollisionMargin=1.0; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Bullets with lower velocity will automatically despawn on impact, never despawn if set to zero or negative")) float DespawnVelocity=100.0f; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TArray IgnoredActors; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (ToolTip = "Spawned bullet performs first trace immediately, instead of waiting for next simulation step")) bool DoFirstStepImmediately = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "DoFirstStepImmediately")) bool RandomFirstStepDelta = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") bool FixedStep = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "FixedStep", ClampMin = "0")) float FixedStepSeconds = 0.1; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") int MaxTracesPerStep = 8; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool Retrace = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool RetraceOnAnotherChannel = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace", meta=(EditCondition="RetraceOnAnotherChannel")) TEnumAsByte RetraceChannel; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Rotation") bool RotateActor = true; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Rotation") bool RotateRandomRoll = true; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Pooling") bool EnablePooling = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Pooling", meta = (EditCondition = "EnablePooling")) int MaxPoolSize = 50; + + //rebase + virtual void ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) override; + + // Called when the game starts or when spawned + virtual void BeginPlay() override; + + // Called every frame + virtual void Tick(float DeltaSeconds) override; + + virtual void LifeSpanExpired() override; + + UFUNCTION(BlueprintCallable, Category = "EBBullet|Spawn") + static void SpawnWithExactVelocity(TSubclassOf BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity); + + UFUNCTION(BlueprintCallable, Category = "EBBullet|Spawn") + static void Spawn(TSubclassOf BulletClass, AActor* BulletOwner, APawn* BulletInstigator, FVector BulletLocation, FVector BulletVelocity); + + UFUNCTION(NetMulticast, Unreliable) + void VelocityChangeBroadcast(FVector_NetQuantize NewLocation, FVector NewVelocity); + UFUNCTION(NetMulticast, Reliable) + void VelocityChangeBroadcastReliable(FVector_NetQuantize NewLocation, FVector NewVelocity); + + UFUNCTION(BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "EBBullet|Impact") + void OnImpact(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult); + + UFUNCTION(BlueprintCosmetic, BlueprintNativeEvent, Category = "EBBullet|Impact") + void OnNetPredictedImpact(bool Ricochet, bool PassedThrough, FVector Location, FVector IncomingVelocity, FVector Normal, FVector ExitLocation, FVector ExitVelocity, FVector Impulse, float PenetrationDepth, AActor* Actor, USceneComponent* Component, FName BoneName, UPhysicalMaterial* PhysMaterial, FHitResult HitResult); + + UFUNCTION(BlueprintImplementableEvent, Category = "EBBullet|Impact") + void OnTrace(FVector StartLocation, FVector EndLocation); + + UFUNCTION(BlueprintImplementableEvent, Category = "EBBullet|Remote") + void OnTrajectoryUpdateReceived(FVector Location, FVector OldVelocity, FVector NewVelocity); + + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|Activation") + void OnDeactivated(); + + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|Flight")FVector UpdateVelocity(UWorld* World, FVector Location, FVector PreviousVelocity, float DeltaTime) const; + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") FVector GetWind(UWorld* World, FVector Location) const; + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") float GetAirDensity(UWorld* World, FVector Location) const; + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") float GetSpeedOfSound(UWorld* World, FVector Location) const; + UFUNCTION(BlueprintNativeEvent, Category = "EBBullet|World") bool CollisionFilter(FHitResult HitResult) const; + + // Mathematical Physics Functions + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + float GetEffectiveMass() const; + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + float GetEffectiveDiameter() const; + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + float GetEffectiveDragCoefficient(float MachNumber) const; + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + float CalculateMathematicalPenetration(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const; + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + float CalculateMathematicalRicochetProbability(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const; + UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics") + FMathematicalMaterialProperties GetMaterialProperties(UPhysicalMaterial* Material) const; + + //pooling + UFUNCTION(BlueprintAuthorityOnly, BlueprintCallable, Category = "EBBullet|Pooling")void Deactivate(); + + UFUNCTION(NetMulticast, Reliable) + void ReactivationBroadcast(FVector_NetQuantize NewLocation, FVector NewVelocity, AActor* BulletOwner, APawn* BulletInstigator); + UFUNCTION(NetMulticast, Reliable) + void DeactivationBroadcast(); +private: + UPROPERTY() TArray> Pooled; + static AEBBullet* GetFromPool(UWorld* World, UClass* BulletClass); + static AEBBullet* SpawnOrReactivate(UWorld* World, TSubclassOf BulletClass, const FTransform& Transform, FVector BulletVelocity, AActor* BulletOwner, APawn* BulletInstigator); + void DeactivateToPool(); + + void FinishSpawning(FTransform Transform); + + void Step(float DeltaTime); + + float Trace(FVector start, FVector PreviousVelocity, float delta, TEnumAsByte channel); + + TArray GetAttachedActorsRecursive(AActor* Actor, uint16 Depth = 0, TArray VisitedActors = TArray()) const; + + float PenetrationTrace(FVector start, FVector end, TWeakObjectPtr comp, EPenTraceType penType, TEnumAsByte channel, FVector &exitLoc, FVector &exitNormal); + + float GetCurveValue(const UCurveFloat* curve, float in, float deflt) const; + + float AccumulatedDelta; + + bool CanRetrace = false; + FVector LastTraceStart; + float LastTraceDelta; + FVector LastTraceVelocity; + FVector LastTracePrevVelocity; + + bool IsRecycled; + + FHitResult FilterHits(TArray Results, bool &hit) const; + TArrayGetSafeLaunchIgnoredActors(AActor* Owner) const; + + float GetAltitude(UWorld* World, FVector Location) const; + float GetAltitudePressure(float AltitudeMeter) const; + float GetAltitudeTemperature(float AltitudeMeter) const; + float GetAltitudeDensity(float AltitudeMeter) const; + +#ifdef WITH_EDITOR + FLinearColor GetDebugColor(float In) const{ + return FMath::Lerp(DebugTrailColorSlow, DebugTrailColorFast, In); + } +#endif +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBBulletProperties.h b/Source/EasyBallistics/Public/EBBulletProperties.h new file mode 100644 index 0000000..3dc60a2 --- /dev/null +++ b/Source/EasyBallistics/Public/EBBulletProperties.h @@ -0,0 +1,218 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "EBBulletProperties.generated.h" + +UENUM(BlueprintType) +enum class EBulletType : uint8 +{ + BT_FullMetalJacket UMETA(DisplayName = "Full Metal Jacket"), + BT_HollowPoint UMETA(DisplayName = "Hollow Point"), + BT_SoftPoint UMETA(DisplayName = "Soft Point"), + BT_ArmorPiercing UMETA(DisplayName = "Armor Piercing"), + BT_ArmorPiercingIncendiary UMETA(DisplayName = "Armor Piercing Incendiary"), + BT_Tracer UMETA(DisplayName = "Tracer"), + BT_Match UMETA(DisplayName = "Match Grade"), + BT_Frangible UMETA(DisplayName = "Frangible"), + BT_LeadRoundNose UMETA(DisplayName = "Lead Round Nose"), + BT_Wadcutter UMETA(DisplayName = "Wadcutter"), + BT_SemiWadcutter UMETA(DisplayName = "Semi-Wadcutter"), + BT_Custom UMETA(DisplayName = "Custom") +}; + +UENUM(BlueprintType) +enum class EBulletMaterial : uint8 +{ + BM_Lead UMETA(DisplayName = "Lead"), + BM_LeadAntimony UMETA(DisplayName = "Lead-Antimony"), + BM_Copper UMETA(DisplayName = "Copper"), + BM_CopperJacket UMETA(DisplayName = "Copper Jacket"), + BM_Brass UMETA(DisplayName = "Brass"), + BM_Steel UMETA(DisplayName = "Steel"), + BM_Tungsten UMETA(DisplayName = "Tungsten"), + BM_Bismuth UMETA(DisplayName = "Bismuth"), + BM_Zinc UMETA(DisplayName = "Zinc"), + BM_Custom UMETA(DisplayName = "Custom") +}; + +USTRUCT(BlueprintType) +struct FMathematicalBulletProperties +{ + GENERATED_USTRUCT_BODY() + + // Basic Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet weight in grains (1 grain = 0.0647989 grams)")) + float GrainWeight = 55.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet diameter in inches")) + float DiameterInches = 0.224f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties", meta = (ToolTip = "Bullet length in inches")) + float LengthInches = 0.825f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties") + EBulletType BulletType = EBulletType::BT_FullMetalJacket; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties") + EBulletMaterial BulletMaterial = EBulletMaterial::BM_CopperJacket; + + // Ballistic Coefficient + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "G1 Ballistic Coefficient")) + float BallisticCoefficientG1 = 0.151f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "G7 Ballistic Coefficient")) + float BallisticCoefficientG7 = 0.076f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "Use G7 model instead of G1")) + bool UseG7Model = false; + + // Sectional Density + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics", meta = (ToolTip = "Sectional density (calculated automatically if zero)")) + float SectionalDensity = 0.0f; + + // Penetration Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Bullet hardness (HB - Brinell Hardness)")) + float BulletHardness = 15.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Kinetic energy threshold for penetration (ft-lbs)")) + float PenetrationEnergyThreshold = 58.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Expansion threshold velocity (fps)")) + float ExpansionVelocityThreshold = 1800.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Penetration", meta = (ToolTip = "Maximum expansion diameter multiplier")) + float MaxExpansionMultiplier = 1.5f; + + // Constructor + FMathematicalBulletProperties() + { + GrainWeight = 55.0f; + DiameterInches = 0.224f; + LengthInches = 0.825f; + BulletType = EBulletType::BT_FullMetalJacket; + BulletMaterial = EBulletMaterial::BM_CopperJacket; + BallisticCoefficientG1 = 0.151f; + BallisticCoefficientG7 = 0.076f; + UseG7Model = false; + SectionalDensity = 0.0f; + BulletHardness = 15.0f; + PenetrationEnergyThreshold = 58.0f; + ExpansionVelocityThreshold = 1800.0f; + MaxExpansionMultiplier = 1.5f; + } + + // Calculate sectional density if not provided + float GetSectionalDensity() const + { + if (SectionalDensity > 0.0f) + { + return SectionalDensity; + } + // SD = Weight(grains) / (7000 * Diameter^2(inches)) + return GrainWeight / (7000.0f * DiameterInches * DiameterInches); + } + + // Calculate mass in kilograms + float GetMassKg() const + { + // 1 grain = 0.0647989 grams + return GrainWeight * 0.0647989f / 1000.0f; + } + + // Calculate diameter in centimeters + float GetDiameterCm() const + { + // 1 inch = 2.54 cm + return DiameterInches * 2.54f; + } + + // Calculate cross-sectional area in square centimeters + float GetCrossSectionCm2() const + { + float radiusCm = GetDiameterCm() / 2.0f; + return 3.14159f * radiusCm * radiusCm; + } +}; + +USTRUCT(BlueprintType) +struct FMathematicalMaterialProperties +{ + GENERATED_USTRUCT_BODY() + + // Material Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Material density in g/cm³")) + float DensityGPerCm3 = 7.85f; // Steel + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Material hardness (HB - Brinell Hardness)")) + float MaterialHardness = 200.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Tensile strength in MPa")) + float TensileStrengthMPa = 400.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Yield strength in MPa")) + float YieldStrengthMPa = 250.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties", meta = (ToolTip = "Modulus of elasticity in GPa")) + float ElasticModulusGPa = 200.0f; + + // Ballistic Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Ballistic limit velocity (fps)")) + float BallisticLimitVelocity = 2000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Perforation coefficient")) + float PerforationCoefficient = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties", meta = (ToolTip = "Energy absorption coefficient")) + float EnergyAbsorptionCoefficient = 0.7f; + + // Constructor with default steel properties + FMathematicalMaterialProperties() + { + DensityGPerCm3 = 7.85f; // Steel + MaterialHardness = 200.0f; + TensileStrengthMPa = 400.0f; + YieldStrengthMPa = 250.0f; + ElasticModulusGPa = 200.0f; + BallisticLimitVelocity = 2000.0f; + PerforationCoefficient = 1.0f; + EnergyAbsorptionCoefficient = 0.7f; + } +}; + +UCLASS(BlueprintType) +class EASYBALLISTICS_API UEBBulletPropertiesAsset : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Bullet Properties") + FMathematicalBulletProperties BulletProperties; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description") + FString BulletName = "5.56x45mm NATO"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description") + FString Manufacturer = "Generic"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description") + FString Description = "Standard 5.56x45mm NATO round"; +}; + +UCLASS(BlueprintType) +class EASYBALLISTICS_API UEBMaterialPropertiesAsset : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties") + FMathematicalMaterialProperties MaterialProperties; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description") + FString MaterialName = "Steel"; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Description") + FString Description = "Standard structural steel"; +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBMaterialResponseMap.h b/Source/EasyBallistics/Public/EBMaterialResponseMap.h new file mode 100644 index 0000000..160c2dd --- /dev/null +++ b/Source/EasyBallistics/Public/EBMaterialResponseMap.h @@ -0,0 +1,53 @@ +// Copyright 2016 Mookie. All Rights Reserved. + + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "EBBulletProperties.h" +#include "EBMaterialResponseMap.generated.h" + +UENUM(BlueprintType) +enum class EPenTraceType : uint8 +{ + PT_BackTrace UMETA(DisplayName = "Back Trace"), + PT_ByComponent UMETA(DisplayName = "By Component"), + PT_TwoSidedGeometry UMETA(DisplayName = "Double Sided Geometry"), +}; + +USTRUCT(BlueprintType) +struct FEBMaterialResponseMapEntry { + GENERATED_USTRUCT_BODY() + + // Artistic Properties + UPROPERTY(EditAnywhere, Category = "Artistic Properties") EPenTraceType PenTraceType = EPenTraceType::PT_BackTrace; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") bool NeverPenetrate = false; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationDepthMultiplier = 1.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationNormalization = 0.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationNormalizationGrazing = 0.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationEntryAngleSpread = 0.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float PenetrationExitAngleSpread = 0.0; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") bool NeverRicochet = false; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetProbabilityMultiplier = 1.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetRestitution = 0.5f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetRestitutionInfluence = 0.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetFriction = 0.5f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetFrictionInfluence = 0.0f; + UPROPERTY(EditAnywhere, Category = "Artistic Properties") float RicochetSpread = 0.0f; + + // Mathematical Properties + UPROPERTY(EditAnywhere, Category = "Mathematical Properties", meta = (ToolTip = "Use mathematical properties for this material")) + bool UseMathematicalProperties = false; + UPROPERTY(EditAnywhere, Category = "Mathematical Properties", meta = (EditCondition = "UseMathematicalProperties")) + FMathematicalMaterialProperties MathematicalProperties; +}; + +UCLASS(BlueprintType) +class UEBMaterialResponseMap : public UDataAsset{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = "Responses") TMap Map; +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EBMathematicalBallistics.h b/Source/EasyBallistics/Public/EBMathematicalBallistics.h new file mode 100644 index 0000000..8f8a25e --- /dev/null +++ b/Source/EasyBallistics/Public/EBMathematicalBallistics.h @@ -0,0 +1,119 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "EBBulletProperties.h" +#include "EBMathematicalBallistics.generated.h" + +UCLASS(BlueprintType) +class EASYBALLISTICS_API UEBMathematicalBallistics : public UObject +{ + GENERATED_BODY() + +public: + // Mathematical penetration calculation using empirical formulas + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculatePenetrationDepth( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ImpactAngleDegrees = 0.0f + ); + + // Calculate residual velocity after penetration + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateResidualVelocity( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ThicknessCM, + float ImpactAngleDegrees = 0.0f + ); + + // Calculate ricochet probability based on impact angle and material properties + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateRicochetProbability( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS, + float ImpactAngleDegrees + ); + + // Calculate bullet expansion based on velocity and material + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateBulletExpansion( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS + ); + + // Calculate kinetic energy + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateKineticEnergy( + const FMathematicalBulletProperties& BulletProps, + float VelocityMPS + ); + + // Calculate momentum + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateMomentum( + const FMathematicalBulletProperties& BulletProps, + float VelocityMPS + ); + + // Calculate drag coefficient from ballistic coefficient + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateDragCoefficient( + const FMathematicalBulletProperties& BulletProps, + float MachNumber + ); + + // Calculate Taylor-Hopkinson perforation limit + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateTaylorHopkinsonLimit( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps + ); + + // Calculate Recht-Ipson perforation velocity + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateRechtIpsonVelocity( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float ThicknessCM + ); + + // Calculate critical angle for ricochet + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float CalculateCriticalRicochetAngle( + const FMathematicalBulletProperties& BulletProps, + const FMathematicalMaterialProperties& MaterialProps, + float VelocityMPS + ); + + // Utility function to convert feet per second to meters per second + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float ConvertFPStoMPS(float FPS) { return FPS * 0.3048f; } + + // Utility function to convert meters per second to feet per second + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float ConvertMPStoFPS(float MPS) { return MPS / 0.3048f; } + + // Utility function to convert foot-pounds to joules + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float ConvertFtLbsToJoules(float FtLbs) { return FtLbs * 1.35582f; } + + // Utility function to convert joules to foot-pounds + UFUNCTION(BlueprintCallable, Category = "Mathematical Ballistics") + static float ConvertJoulesToFtLbs(float Joules) { return Joules / 1.35582f; } + +private: + // Helper functions for complex calculations + static float CalculateHardnessRatio(float BulletHardness, float MaterialHardness); + static float CalculateVelocityFactor(float Velocity, float ThresholdVelocity); + static float CalculateAngleFactor(float ImpactAngleDegrees); + static float CalculateShapeFactorFromBulletType(EBulletType BulletType); + static float CalculateMaterialFactor(EBulletMaterial BulletMaterial); +}; \ No newline at end of file diff --git a/Source/EasyBallistics/Public/EasyBallistics.h b/Source/EasyBallistics/Public/EasyBallistics.h new file mode 100644 index 0000000..d2daf06 --- /dev/null +++ b/Source/EasyBallistics/Public/EasyBallistics.h @@ -0,0 +1,17 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. +// Copyright 2018 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class FEasyBallisticsModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs b/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs new file mode 100644 index 0000000..72cd0b3 --- /dev/null +++ b/Source/EasyBallisticsEditor/EasyBallisticsEditor.Build.cs @@ -0,0 +1,30 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +using UnrealBuildTool; + +public class EasyBallisticsEditor : ModuleRules +{ + public EasyBallisticsEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "AssetTools", + "EditorWidgets", + "EasyBallistics" + }); + + PrivateDependencyModuleNames.AddRange(new string[] { + "Slate", + "SlateCore", + "PropertyEditor", + "WorkspaceMenuStructure", + "DesktopPlatform", + "ToolMenus" + }); + } +} \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp b/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp new file mode 100644 index 0000000..99de127 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBBarrelComponentFactory.cpp @@ -0,0 +1,51 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBarrelComponentFactory.h" +#include "EBBarrel.h" +#include "AssetToolsModule.h" + +#define LOCTEXT_NAMESPACE "EBBarrelComponentFactory" + +UClass* FEBBarrelComponentFactory::GetSupportedClass() const +{ + return UEBBarrel::StaticClass(); +} + +uint32 FEBBarrelComponentFactory::GetCategories() +{ + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); +} + +FText FEBBarrelComponentFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBBarrelComponentDescription", "A weapon barrel component that handles firing mechanics, ammunition management, and ballistic calculations for projectile weapons."); +} + +UClass* FEBBarrelComponentAssetBroker::GetSupportedAssetClass() +{ + return UEBBarrel::StaticClass(); +} + +bool FEBBarrelComponentAssetBroker::AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) +{ + if (UEBBarrel* BarrelComponent = Cast(InComponent)) + { + // Components don't typically have assets assigned to them directly + // This would be used if we had barrel configuration assets + return false; + } + return false; +} + +UObject* FEBBarrelComponentAssetBroker::GetAssetFromComponent(UActorComponent* InComponent) +{ + if (UEBBarrel* BarrelComponent = Cast(InComponent)) + { + // Return any associated asset if we had barrel configuration assets + return nullptr; + } + return nullptr; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBBulletActorFactory.cpp b/Source/EasyBallisticsEditor/Private/EBBulletActorFactory.cpp new file mode 100644 index 0000000..a8b5bf0 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBBulletActorFactory.cpp @@ -0,0 +1,43 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBulletActorFactory.h" +#include "EBBullet.h" +#include "AssetToolsModule.h" +#include "Engine/World.h" + +#define LOCTEXT_NAMESPACE "EBBulletActorFactory" + +UEBBulletActorFactory::UEBBulletActorFactory() +{ + DisplayName = LOCTEXT("EBBulletActorDisplayName", "Bullet Actor"); + NewActorClass = AEBBullet::StaticClass(); + bUseSurfaceOrientation = true; +} + +bool UEBBulletActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) +{ + return true; +} + +AActor* UEBBulletActorFactory::SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) +{ + return InLevel->OwningWorld->SpawnActor(NewActorClass, InTransform, InSpawnParams); +} + +UClass* FEBBulletActorFactory::GetSupportedClass() const +{ + return AEBBullet::StaticClass(); +} + +uint32 FEBBulletActorFactory::GetCategories() +{ + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); +} + +FText FEBBulletActorFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBBulletActorDescription", "A ballistic projectile actor with realistic physics simulation including drag, atmospheric effects, penetration, and ricochets."); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBBulletPropertiesFactory.cpp b/Source/EasyBallisticsEditor/Private/EBBulletPropertiesFactory.cpp new file mode 100644 index 0000000..4b507db --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBBulletPropertiesFactory.cpp @@ -0,0 +1,68 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBBulletPropertiesFactory.h" +#include "EBBulletProperties.h" +#include "AssetToolsModule.h" +#include "EasyBallisticsEditor.h" + +#define LOCTEXT_NAMESPACE "EBBulletPropertiesFactory" + +// Bullet Properties Asset Factory +UEBBulletPropertiesAssetFactory::UEBBulletPropertiesAssetFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UEBBulletPropertiesAsset::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UEBBulletPropertiesAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} + +UClass* FEBBulletPropertiesAssetFactory::GetSupportedClass() const +{ + return UEBBulletPropertiesAsset::StaticClass(); +} + +uint32 FEBBulletPropertiesAssetFactory::GetCategories() +{ + return FEasyBallisticsEditorModule::GetBallisticsAssetCategory(); +} + +FText FEBBulletPropertiesAssetFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBBulletPropertiesAssetDescription", "Defines mathematical properties of bullets including weight, dimensions, ballistic coefficients, and penetration characteristics."); +} + +// Material Properties Asset Factory +UEBMaterialPropertiesAssetFactory::UEBMaterialPropertiesAssetFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UEBMaterialPropertiesAsset::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UEBMaterialPropertiesAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} + +UClass* FEBMaterialPropertiesAssetFactory::GetSupportedClass() const +{ + return UEBMaterialPropertiesAsset::StaticClass(); +} + +uint32 FEBMaterialPropertiesAssetFactory::GetCategories() +{ + return FEasyBallisticsEditorModule::GetBallisticsAssetCategory(); +} + +FText FEBMaterialPropertiesAssetFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBMaterialPropertiesAssetDescription", "Defines mathematical properties of materials including density, hardness, tensile strength, and ballistic resistance characteristics."); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBMaterialResponseMapFactory.cpp b/Source/EasyBallisticsEditor/Private/EBMaterialResponseMapFactory.cpp new file mode 100644 index 0000000..645fc37 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBMaterialResponseMapFactory.cpp @@ -0,0 +1,38 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBMaterialResponseMapFactory.h" +#include "EBMaterialResponseMap.h" +#include "AssetToolsModule.h" +#include "EasyBallisticsEditor.h" + +#define LOCTEXT_NAMESPACE "EBMaterialResponseMapFactory" + +UEBMaterialResponseMapFactory::UEBMaterialResponseMapFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UEBMaterialResponseMap::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UEBMaterialResponseMapFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, Class, Name, Flags); +} + +UClass* FEBMaterialResponseMapFactory::GetSupportedClass() const +{ + return UEBMaterialResponseMap::StaticClass(); +} + +uint32 FEBMaterialResponseMapFactory::GetCategories() +{ + return FEasyBallisticsEditorModule::GetBallisticsAssetCategory(); +} + +FText FEBMaterialResponseMapFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBMaterialResponseMapDescription", "Defines how projectiles interact with different physical materials including penetration and ricochet properties."); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp b/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp new file mode 100644 index 0000000..4589c7c --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBMathematicalBallisticsFactory.cpp @@ -0,0 +1,25 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBMathematicalBallisticsFactory.h" +#include "EBMathematicalBallistics.h" +#include "AssetToolsModule.h" + +#define LOCTEXT_NAMESPACE "EBMathematicalBallisticsFactory" + +UClass* FEBMathematicalBallisticsFactory::GetSupportedClass() const +{ + return UEBMathematicalBallistics::StaticClass(); +} + +uint32 FEBMathematicalBallisticsFactory::GetCategories() +{ + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + return AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); +} + +FText FEBMathematicalBallisticsFactory::GetAssetDescription(const FAssetData& AssetData) const +{ + return LOCTEXT("EBMathematicalBallisticsDescription", "Mathematical ballistics calculator providing penetration depth, residual velocity, and trajectory calculations using empirical formulas."); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EBPhysicalMaterialCustomization.cpp b/Source/EasyBallisticsEditor/Private/EBPhysicalMaterialCustomization.cpp new file mode 100644 index 0000000..31885f4 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EBPhysicalMaterialCustomization.cpp @@ -0,0 +1,212 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EBPhysicalMaterialCustomization.h" +#include "PropertyEditorModule.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" +#include "EditorStyleSet.h" +#include "Engine/Engine.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +#define LOCTEXT_NAMESPACE "EBPhysicalMaterialCustomization" + +TSharedRef FEBPhysicalMaterialCustomization::MakeInstance() +{ + return MakeShareable(new FEBPhysicalMaterialCustomization); +} + +void FEBPhysicalMaterialCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + DetailBuilderPtr = &DetailBuilder; + + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + + if (Objects.Num() == 1) + { + PhysicalMaterialPtr = Cast(Objects[0].Get()); + + if (PhysicalMaterialPtr.IsValid()) + { + // Add Ballistics category + IDetailCategoryBuilder& BallisticsCategory = DetailBuilder.EditCategory("Ballistics", LOCTEXT("BallisticsCategory", "Ballistics"), ECategoryPriority::Important); + + UEBMaterialPropertiesAsset* CurrentProperties = GetBallisticProperties(PhysicalMaterialPtr.Get()); + + // Show current ballistic properties if assigned + if (CurrentProperties) + { + BallisticsCategory.AddCustomRow(LOCTEXT("CurrentPropertiesLabel", "Ballistic Properties")) + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("CurrentPropertiesName", "Ballistic Properties")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(CurrentProperties->GetName())) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(SButton) + .Text(LOCTEXT("EditProperties", "Edit")) + .OnClicked(this, &FEBPhysicalMaterialCustomization::OnEditBallisticProperties) + .ToolTipText(LOCTEXT("EditPropertiesTooltip", "Edit the ballistic properties asset")) + ] + ]; + } + else + { + // Show assignment options + BallisticsCategory.AddCustomRow(LOCTEXT("AssignPropertiesLabel", "Assign Ballistic Properties")) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(SButton) + .Text(LOCTEXT("CreateNew", "Create New")) + .OnClicked(this, &FEBPhysicalMaterialCustomization::OnCreateBallisticProperties) + .ToolTipText(LOCTEXT("CreateNewTooltip", "Create a new ballistic properties asset for this material")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(SButton) + .Text(LOCTEXT("AssignExisting", "Assign Existing")) + .OnClicked(this, &FEBPhysicalMaterialCustomization::OnAssignBallisticProperties) + .ToolTipText(LOCTEXT("AssignExistingTooltip", "Assign an existing ballistic properties asset to this material")) + ] + ]; + } + } + } +} + +FReply FEBPhysicalMaterialCustomization::OnCreateBallisticProperties() +{ + if (PhysicalMaterialPtr.IsValid()) + { + // Create new Material Properties Asset + FString PackageName = PhysicalMaterialPtr->GetPackage()->GetName() + TEXT("_BallisticProps"); + FString AssetName = PhysicalMaterialPtr->GetName() + TEXT("_BallisticProps"); + + UPackage* Package = CreatePackage(*PackageName); + UEBMaterialPropertiesAsset* NewAsset = NewObject(Package, *AssetName, RF_Public | RF_Standalone); + + // Set some sensible defaults based on material name + FString MaterialName = PhysicalMaterialPtr->GetName().ToLower(); + if (MaterialName.Contains("steel") || MaterialName.Contains("metal")) + { + NewAsset->MaterialProperties.DensityGPerCm3 = 7.85f; + NewAsset->MaterialProperties.MaterialHardness = 200.0f; + NewAsset->MaterialName = "Steel"; + } + else if (MaterialName.Contains("wood")) + { + NewAsset->MaterialProperties.DensityGPerCm3 = 0.6f; + NewAsset->MaterialProperties.MaterialHardness = 30.0f; + NewAsset->MaterialName = "Wood"; + } + else if (MaterialName.Contains("concrete")) + { + NewAsset->MaterialProperties.DensityGPerCm3 = 2.4f; + NewAsset->MaterialProperties.MaterialHardness = 100.0f; + NewAsset->MaterialName = "Concrete"; + } + + // Mark package dirty and register with asset registry + Package->MarkPackageDirty(); + FAssetRegistryModule::AssetCreated(NewAsset); + + // Associate with physical material + SetBallisticProperties(PhysicalMaterialPtr.Get(), NewAsset); + + // Refresh the details panel + if (DetailBuilderPtr) + { + DetailBuilderPtr->ForceRefreshDetails(); + } + + // Show success notification + FNotificationInfo Info(LOCTEXT("CreatedBallisticProps", "Created ballistic properties asset")); + Info.ExpireDuration = 3.0f; + FSlateNotificationManager::Get().AddNotification(Info); + } + + return FReply::Handled(); +} + +FReply FEBPhysicalMaterialCustomization::OnAssignBallisticProperties() +{ + // Open content browser to select existing Material Properties Asset + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + FAssetPickerConfig AssetPickerConfig; + AssetPickerConfig.Filter.ClassPaths.Add(UEBMaterialPropertiesAsset::StaticClass()->GetClassPathName()); + AssetPickerConfig.bAllowNullSelection = false; + AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateLambda([this](const FAssetData& AssetData) + { + if (UEBMaterialPropertiesAsset* SelectedAsset = Cast(AssetData.GetAsset())) + { + SetBallisticProperties(PhysicalMaterialPtr.Get(), SelectedAsset); + + if (DetailBuilderPtr) + { + DetailBuilderPtr->ForceRefreshDetails(); + } + } + }); + + ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig); + + return FReply::Handled(); +} + +FReply FEBPhysicalMaterialCustomization::OnEditBallisticProperties() +{ + if (UEBMaterialPropertiesAsset* Properties = GetBallisticProperties(PhysicalMaterialPtr.Get())) + { + // Open the asset for editing + GEditor->GetEditorSubsystem()->OpenEditorForAsset(Properties); + } + + return FReply::Handled(); +} + +UEBMaterialPropertiesAsset* FEBPhysicalMaterialCustomization::GetBallisticProperties(UPhysicalMaterial* PhysMat) const +{ + if (!PhysMat) return nullptr; + + // For now, use a simple naming convention or metadata + // In a production system, you might add a UPROPERTY to UPhysicalMaterial + FString AssetName = PhysMat->GetName() + TEXT("_BallisticProps"); + FString PackageName = PhysMat->GetPackage()->GetName() + TEXT("_BallisticProps"); + + return LoadObject(nullptr, *PackageName, nullptr, LOAD_NoWarn | LOAD_Quiet); +} + +void FEBPhysicalMaterialCustomization::SetBallisticProperties(UPhysicalMaterial* PhysMat, UEBMaterialPropertiesAsset* Properties) +{ + // In a production system, you would store this reference properly + // For now, we rely on naming conventions and the Material Response Map system +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp b/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp new file mode 100644 index 0000000..bd2a6b3 --- /dev/null +++ b/Source/EasyBallisticsEditor/Private/EasyBallisticsEditor.cpp @@ -0,0 +1,79 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#include "EasyBallisticsEditor.h" +#include "AssetToolsModule.h" +#include "PropertyEditorModule.h" +#include "EBMaterialResponseMapFactory.h" +#include "EBBulletPropertiesFactory.h" +#include "EBBulletActorFactory.h" +#include "EBBarrelComponentFactory.h" +#include "EBMathematicalBallisticsFactory.h" +#include "EBPhysicalMaterialCustomization.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + +#define LOCTEXT_NAMESPACE "FEasyBallisticsEditorModule" + +EAssetTypeCategories::Type FEasyBallisticsEditorModule::BallisticsAssetCategory; + +void FEasyBallisticsEditorModule::StartupModule() +{ + // Register asset factories + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + + // Register the Ballistics category + BallisticsAssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Ballistics")), LOCTEXT("BallisticsCategory", "Ballistics")); + + // Store references to our asset type actions for cleanup + RegisteredAssetTypeActions.Empty(); + + // Register Material Response Map factory + { + TSharedRef MaterialResponseMapActions = MakeShareable(new FEBMaterialResponseMapFactory()); + AssetTools.RegisterAssetTypeActions(MaterialResponseMapActions); + RegisteredAssetTypeActions.Add(MaterialResponseMapActions); + } + + // Register Bullet Properties Asset factory + { + TSharedRef BulletPropertiesActions = MakeShareable(new FEBBulletPropertiesAssetFactory()); + AssetTools.RegisterAssetTypeActions(BulletPropertiesActions); + RegisteredAssetTypeActions.Add(BulletPropertiesActions); + } + + // Register Physical Material customization + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout( + UPhysicalMaterial::StaticClass()->GetFName(), + FOnGetDetailCustomizationInstance::CreateStatic(&FEBPhysicalMaterialCustomization::MakeInstance) + ); +} + +void FEasyBallisticsEditorModule::ShutdownModule() +{ + // Unregister Physical Material customization + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomClassLayout(UPhysicalMaterial::StaticClass()->GetFName()); + } + + // Unregister asset type actions + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + + for (auto& AssetTypeAction : RegisteredAssetTypeActions) + { + if (AssetTypeAction.IsValid()) + { + AssetTools.UnregisterAssetTypeActions(AssetTypeAction.ToSharedRef()); + } + } + } + + RegisteredAssetTypeActions.Empty(); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FEasyBallisticsEditorModule, EasyBallisticsEditor) \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h b/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h new file mode 100644 index 0000000..e85234e --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBBarrelComponentFactory.h @@ -0,0 +1,27 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "ComponentAssetBroker.h" +#include "EBBarrel.h" + +class FEBBarrelComponentFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBarrelComponent", "Barrel Component"); } + virtual FColor GetTypeColor() const override { return FColor(100, 150, 255); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; + virtual bool HasActions(const TArray& InObjects) const override { return false; } +}; + +class FEBBarrelComponentAssetBroker : public IComponentAssetBroker +{ +public: + virtual UClass* GetSupportedAssetClass() override; + virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override; + virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBBulletActorFactory.h b/Source/EasyBallisticsEditor/Public/EBBulletActorFactory.h new file mode 100644 index 0000000..651e885 --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBBulletActorFactory.h @@ -0,0 +1,32 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "ActorFactories/ActorFactory.h" +#include "EBBullet.h" +#include "EBBulletActorFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBBulletActorFactory : public UActorFactory +{ + GENERATED_BODY() + +public: + UEBBulletActorFactory(); + + virtual bool CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) override; + virtual AActor* SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) override; +}; + +class FEBBulletActorFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBulletActor", "Bullet Actor"); } + virtual FColor GetTypeColor() const override { return FColor(255, 100, 100); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; + virtual bool HasActions(const TArray& InObjects) const override { return false; } +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBBulletPropertiesFactory.h b/Source/EasyBallisticsEditor/Public/EBBulletPropertiesFactory.h new file mode 100644 index 0000000..c4a80c2 --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBBulletPropertiesFactory.h @@ -0,0 +1,53 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" +#include "EBBulletProperties.h" +#include "EBBulletPropertiesFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBBulletPropertiesAssetFactory : 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", "BulletPropertiesText", "Bullet Properties"); } + virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "BulletPropertiesTooltip", "Creates a new Bullet Properties asset for configuring ballistic projectile characteristics"); } +}; + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBMaterialPropertiesAssetFactory : 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", "MaterialPropertiesText", "Material Properties"); } + virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MaterialPropertiesTooltip", "Creates a new Material Properties asset for configuring material ballistic resistance"); } +}; + +class FEBBulletPropertiesAssetFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBBulletPropertiesAsset", "Bullet Properties"); } + virtual FColor GetTypeColor() const override { return FColor(255, 200, 100); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; +}; + +class FEBMaterialPropertiesAssetFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMaterialPropertiesAsset", "Material Properties"); } + virtual FColor GetTypeColor() const override { return FColor(150, 255, 150); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBMaterialResponseMapFactory.h b/Source/EasyBallisticsEditor/Public/EBMaterialResponseMapFactory.h new file mode 100644 index 0000000..f81c3f2 --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBMaterialResponseMapFactory.h @@ -0,0 +1,31 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" +#include "EBMaterialResponseMap.h" +#include "EBMaterialResponseMapFactory.generated.h" + +UCLASS() +class EASYBALLISTICSEDITOR_API UEBMaterialResponseMapFactory : 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", "MaterialResponseMapText", "Material Response Map"); } + virtual FText GetToolTip() const override { return NSLOCTEXT("EBFactory", "MaterialResponseMapTooltip", "Creates a new Material Response Map for configuring ballistic material interactions"); } +}; + +class FEBMaterialResponseMapFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMaterialResponseMap", "Material Response Map"); } + virtual FColor GetTypeColor() const override { return FColor(255, 127, 64); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h b/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h new file mode 100644 index 0000000..7f4a4da --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBMathematicalBallisticsFactory.h @@ -0,0 +1,18 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AssetTypeActions_Base.h" +#include "EBMathematicalBallistics.h" + +class FEBMathematicalBallisticsFactory : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "EBMathematicalBallistics", "Mathematical Ballistics"); } + virtual FColor GetTypeColor() const override { return FColor(150, 100, 255); } + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + virtual FText GetAssetDescription(const FAssetData& AssetData) const override; + virtual bool HasActions(const TArray& InObjects) const override { return false; } +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EBPhysicalMaterialCustomization.h b/Source/EasyBallisticsEditor/Public/EBPhysicalMaterialCustomization.h new file mode 100644 index 0000000..d9013b7 --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EBPhysicalMaterialCustomization.h @@ -0,0 +1,32 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "EBBulletProperties.h" + +class FEBPhysicalMaterialCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + +private: + FReply OnAssignBallisticProperties(); + FReply OnCreateBallisticProperties(); + FReply OnEditBallisticProperties(); + + TWeakObjectPtr PhysicalMaterialPtr; + IDetailLayoutBuilder* DetailBuilderPtr; + + UEBMaterialPropertiesAsset* GetBallisticProperties(UPhysicalMaterial* PhysMat) const; + void SetBallisticProperties(UPhysicalMaterial* PhysMat, UEBMaterialPropertiesAsset* Properties); +}; \ No newline at end of file diff --git a/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h b/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h new file mode 100644 index 0000000..85dea9d --- /dev/null +++ b/Source/EasyBallisticsEditor/Public/EasyBallisticsEditor.h @@ -0,0 +1,21 @@ +// Copyright 2016 Mookie. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" +#include "AssetTypeActions_Base.h" +#include "AssetToolsModule.h" + +class FEasyBallisticsEditorModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + static EAssetTypeCategories::Type GetBallisticsAssetCategory() { return BallisticsAssetCategory; } + +private: + TArray> RegisteredAssetTypeActions; + static EAssetTypeCategories::Type BallisticsAssetCategory; +}; \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..dd0ba04 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,29 @@ +# Dependencies +/node_modules + +# Production build +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +Thumbs.db \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..4272a2d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,290 @@ +# EasyBallistics Documentation + +This directory contains the complete documentation for EasyBallistics, built with [Docusaurus](https://docusaurus.io/). + +## 🚀 Quick Start + +### Prerequisites + +- **Node.js** 18.0 or higher +- **npm** or **yarn** + +### Installation + +```bash +cd docs +npm install +``` + +### Development + +Start the development server: + +```bash +npm start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +Generate static content: + +```bash +npm run build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +## 📚 Documentation Structure + +``` +docs/ +├── docs/ # Documentation content +│ ├── intro.md # Homepage/Introduction +│ ├── getting-started/ # Installation and quick start +│ ├── core-concepts/ # Fundamental concepts +│ ├── assets/ # Asset creation guides +│ ├── components/ # Component documentation +│ ├── mathematical/ # Mathematical ballistics +│ ├── networking/ # Multiplayer features +│ ├── performance/ # Optimization guides +│ ├── tutorials/ # Step-by-step tutorials +│ ├── api/ # API reference +│ ├── migration/ # Migration guides +│ ├── troubleshooting.md # Common issues +│ └── changelog.md # Version history +├── src/ # Custom React components +│ └── css/ # Custom styling +├── static/ # Static assets +│ └── img/ # Images and screenshots +├── docusaurus.config.js # Site configuration +├── sidebars.js # Navigation structure +└── package.json # Dependencies +``` + +## 🎨 Customization + +### Styling + +Custom CSS is located in `src/css/custom.css`. This includes: +- Color scheme customization +- Component-specific styles +- Responsive design adjustments + +### Configuration + +Main configuration is in `docusaurus.config.js`: +- Site metadata +- Navigation structure +- Theme configuration +- Plugin settings + +### Components + +Custom React components for enhanced documentation: +- Code examples with syntax highlighting +- Interactive API documentation +- Embedded demos and screenshots + +## 📝 Content Guidelines + +### Writing Style + +- **Clear and Concise**: Use simple, direct language +- **Code Examples**: Include practical, working examples +- **Screenshots**: Add visual aids for UI-heavy sections +- **Cross-References**: Link to related documentation + +### File Naming + +- Use kebab-case for file names: `getting-started.md` +- Group related content in folders +- Keep URLs readable and SEO-friendly + +### Markdown Features + +Docusaurus supports enhanced markdown: + +```markdown +:::info +Information callouts for important notes +::: + +:::warning +Warning callouts for potential issues +::: + +:::danger +Danger callouts for critical warnings +::: + +```cpp title="Example.cpp" +// Code blocks with titles and syntax highlighting +void ExampleFunction() +{ + // Implementation +} +``` + +```mermaid +graph TD + A[Start] --> B[Process] + B --> C[End] +``` +``` + +## 🚢 Deployment + +### GitHub Pages + +Deploy to GitHub Pages: + +```bash +npm run deploy +``` + +### Custom Hosting + +Build and deploy to any static hosting service: + +```bash +npm run build +# Upload the build/ directory to your hosting provider +``` + +### Continuous Integration + +Example GitHub Actions workflow: + +```yaml +name: Deploy Documentation +on: + push: + branches: [main] + paths: ['docs/**'] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '18' + - name: Install dependencies + run: cd docs && npm install + - name: Build documentation + run: cd docs && npm run build + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/build +``` + +## 🤝 Contributing + +### Documentation Contributions + +1. **Fork the repository** +2. **Create a documentation branch**: `git checkout -b docs/feature-name` +3. **Make your changes** following the content guidelines +4. **Test locally**: `npm start` to preview changes +5. **Submit a pull request** with a clear description + +### Content Types + +We welcome contributions for: +- **Tutorials**: Step-by-step guides for specific use cases +- **Examples**: Code samples and implementation patterns +- **Troubleshooting**: Solutions to common problems +- **API Documentation**: Detailed function and class references +- **Screenshots**: Visual aids for complex procedures + +### Review Process + +All documentation changes go through: +1. **Technical Review**: Accuracy and completeness +2. **Editorial Review**: Grammar, style, and clarity +3. **Testing**: Verify examples and instructions work +4. **Deployment**: Merge and publish updates + +## 🛠️ Development Tools + +### Useful Commands + +```bash +# Start development server +npm start + +# Build for production +npm run build + +# Serve production build locally +npm run serve + +# Clear build cache +npm run clear + +# Generate heading IDs +npm run write-heading-ids + +# Extract translatable strings +npm run write-translations +``` + +### VS Code Extensions + +Recommended extensions for documentation development: +- **Markdown All in One**: Enhanced markdown editing +- **Code Spell Checker**: Catch typos and spelling errors +- **Prettier**: Consistent code formatting +- **Auto Rename Tag**: HTML/JSX tag editing + +## 📊 Analytics and Monitoring + +### Google Analytics + +Analytics are configured in `docusaurus.config.js`: + +```javascript +gtag: { + trackingID: 'G-XXXXXXXXXX', + anonymizeIP: true, +} +``` + +### Search + +Built-in search is provided by Algolia DocSearch: + +```javascript +algolia: { + apiKey: 'your-api-key', + indexName: 'easyballistics', + contextualSearch: true, +} +``` + +## 🐛 Issues and Support + +### Reporting Documentation Issues + +When reporting documentation issues: +1. **Specify the page**: Include the URL or file path +2. **Describe the problem**: What's unclear or incorrect +3. **Suggest improvements**: How could it be better +4. **Provide context**: Your use case or scenario + +### Getting Help + +- **Discord**: [Real-time community support](https://discord.gg/easyballistics) +- **GitHub Issues**: [Report documentation bugs](https://github.com/your-org/easyballistics/issues) +- **Email**: docs@easyballistics.com + +## 📄 License + +Documentation is licensed under [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/). + +Code examples within the documentation follow the same license as the EasyBallistics plugin. \ No newline at end of file diff --git a/docs/docs/api/overview.md b/docs/docs/api/overview.md new file mode 100644 index 0000000..7d5f30e --- /dev/null +++ b/docs/docs/api/overview.md @@ -0,0 +1,365 @@ +# API Reference Overview + +Complete reference for all EasyBallistics classes, functions, and events. + +## Core Classes + +### Actors + +| Class | Description | Module | +|-------|-------------|---------| +| [`AEBBullet`](bullet-actor) | Main projectile actor with physics simulation | Runtime | + +### Components + +| Class | Description | Module | +|-------|-------------|---------| +| [`UEBBarrel`](barrel-component) | Weapon barrel with firing mechanics | Runtime | +| [`UEBBallisticImpactComponent`](impact-component) | Impact handling and event system | Runtime | + +### Assets + +| Class | Description | Module | +|-------|-------------|---------| +| `UEBBulletPropertiesAsset` | Bullet characteristic definitions | Runtime | +| `UEBMaterialPropertiesAsset` | Material ballistic properties | Runtime | +| `UEBMaterialResponseMap` | Material interaction rules | Runtime | + +### Utility Classes + +| Class | Description | Module | +|-------|-------------|---------| +| [`UEBMathematicalBallistics`](mathematical-ballistics) | Static calculation functions | Runtime | + +## Enumerations + +### EFireMode +Weapon firing modes supported by UEBBarrel. + +```cpp +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") +}; +``` + +### EEBAtmosphereType +Atmospheric simulation models for ballistic calculations. + +```cpp +UENUM(BlueprintType) +enum class EEBAtmosphereType : uint8 +{ + AT_Constant UMETA(DisplayName = "Constant"), + AT_Curve UMETA(DisplayName = "Density Curve"), + AT_Earth UMETA(DisplayName = "Earth/IGL") +}; +``` + +### EPenTraceType +Penetration trace algorithms for material thickness calculation. + +```cpp +UENUM(BlueprintType) +enum class EPenTraceType : uint8 +{ + PT_BackTrace UMETA(DisplayName = "Back Trace"), + PT_ByComponent UMETA(DisplayName = "By Component"), + PT_TwoSidedGeometry UMETA(DisplayName = "Double Sided Geometry") +}; +``` + +## Data Structures + +### FMathematicalBulletProperties +Defines physical and ballistic properties of projectiles. + +```cpp +USTRUCT(BlueprintType) +struct FMathematicalBulletProperties +{ + GENERATED_USTRUCT_BODY() + + // Basic Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties") + float GrainWeight = 55.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties") + float DiameterInches = 0.224f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Basic Properties") + float LengthInches = 0.825f; + + // Ballistic Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + float BallisticCoefficientG1 = 0.151f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + float BallisticCoefficientG7 = 0.076f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistics") + bool UseG7Model = false; + + // Utility functions + float GetSectionalDensity() const; + float GetMassKg() const; + float GetDiameterCm() const; + float GetCrossSectionCm2() const; +}; +``` + +### FMathematicalMaterialProperties +Defines physical properties of materials for ballistic calculations. + +```cpp +USTRUCT(BlueprintType) +struct FMathematicalMaterialProperties +{ + GENERATED_USTRUCT_BODY() + + // Material Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties") + float DensityGPerCm3 = 7.85f; // Steel default + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties") + float MaterialHardness = 200.0f; // Brinell Hardness + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Material Properties") + float TensileStrengthMPa = 400.0f; + + // Ballistic Properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties") + float BallisticLimitVelocity = 2000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ballistic Properties") + float PerforationCoefficient = 1.0f; +}; +``` + +### FEBMaterialResponseMapEntry +Defines how bullets interact with specific materials. + +```cpp +USTRUCT(BlueprintType) +struct FEBMaterialResponseMapEntry +{ + GENERATED_USTRUCT_BODY() + + // Penetration Properties + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + bool NeverPenetrate = false; + + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + float PenetrationDepthMultiplier = 1.0f; + + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + float PenetrationNormalization = 0.0f; + + // Ricochet Properties + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + bool NeverRicochet = false; + + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + float RicochetProbabilityMultiplier = 1.0f; + + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + float RicochetRestitution = 0.5f; + + UPROPERTY(EditAnywhere, Category = "Artistic Properties") + float RicochetFriction = 0.5f; + + // Mathematical Properties + UPROPERTY(EditAnywhere, Category = "Mathematical Properties") + bool UseMathematicalProperties = false; + + UPROPERTY(EditAnywhere, Category = "Mathematical Properties") + FMathematicalMaterialProperties MathematicalProperties; +}; +``` + +## Events and Delegates + +### Ballistic Impact Events + +```cpp +// Impact event - fired when bullets hit surfaces +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact, + FVector, ImpactLocation, + FVector, ImpactNormal, + UPhysicalMaterial*, HitMaterial, + float, PenetrationDepth, + bool, bDidPenetrate); + +// Ricochet event - fired when bullets ricochet off surfaces +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnBallisticRicochet, + FVector, RicochetLocation, + FVector, RicochetDirection, + UPhysicalMaterial*, HitMaterial, + float, EnergyRetained); +``` + +### Barrel Events + +```cpp +// Firing events +UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events") +void OnFireSingle(FVector MuzzleLocation, FVector MuzzleDirection); + +UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events") +void OnFireBurst(int32 ShotsFired, FVector MuzzleLocation, FVector MuzzleDirection); + +// Ammo events +UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events") +void OnAmmoChanged(int32 CurrentAmmo, int32 MaxAmmo); + +UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events") +void OnReloadStarted(float ReloadTime); + +UFUNCTION(BlueprintImplementableEvent, Category = "EBBarrel|Events") +void OnReloadCompleted(); +``` + +## Common Usage Patterns + +### Basic Bullet Spawning + +```cpp +// Spawn a bullet with specific properties +AEBBullet* Bullet = GetWorld()->SpawnActor(); +Bullet->BulletPropertiesAsset = MyBulletProperties; +Bullet->MaterialResponseMap = MyMaterialMap; +Bullet->UseNewImpactSystem = true; +Bullet->Velocity = MuzzleDirection * MuzzleVelocity; +``` + +### Impact Event Handling + +```cpp +// Bind to impact events in C++ +void AMyActor::BeginPlay() +{ + Super::BeginPlay(); + + if (BallisticImpactComponent) + { + BallisticImpactComponent->OnBallisticImpact.AddDynamic( + this, &AMyActor::HandleBallisticImpact); + BallisticImpactComponent->OnBallisticRicochet.AddDynamic( + this, &AMyActor::HandleBallisticRicochet); + } +} + +void AMyActor::HandleBallisticImpact(FVector ImpactLocation, FVector ImpactNormal, + UPhysicalMaterial* HitMaterial, float PenetrationDepth, bool bDidPenetrate) +{ + // Custom impact response + if (bDidPenetrate) + { + SpawnPenetrationEffect(ImpactLocation, ImpactNormal); + } + else + { + SpawnImpactEffect(ImpactLocation, ImpactNormal); + } +} +``` + +### Mathematical Calculations + +```cpp +// Calculate penetration depth using mathematical model +float PenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth( + BulletProperties, + MaterialProperties, + VelocityMPS, + ImpactAngleDegrees +); + +// Calculate ricochet probability +float RicochetProbability = UEBMathematicalBallistics::CalculateRicochetProbability( + BulletProperties, + MaterialProperties, + VelocityMPS, + ImpactAngleDegrees +); +``` + +## Error Handling + +### Common Error Codes + +| Error | Description | Solution | +|-------|-------------|----------| +| `NullBulletProperties` | Bullet spawned without properties | Assign UEBBulletPropertiesAsset | +| `InvalidMaterialMap` | Material not found in response map | Add material to UEBMaterialResponseMap | +| `MissingPhysicalMaterial` | Hit result has no physical material | Assign physical material to surface | +| `NetworkDesync` | Client/server ballistic mismatch | Check network replication settings | + +### Debugging Utilities + +```cpp +// Enable debug visualization +Bullet->DebugEnabled = true; +Bullet->DebugTrailTime = 2.0f; +Bullet->DebugTrailColorFast = FLinearColor::Green; +Bullet->DebugTrailColorSlow = FLinearColor::Red; + +// Performance monitoring +UE_LOG(LogEasyBallistics, Warning, TEXT("Bullet %s exceeded max traces: %d"), + *Bullet->GetName(), Bullet->MaxTracesPerStep); +``` + +## Platform Considerations + +### Performance Scaling + +| Platform | Recommended Settings | Notes | +|----------|---------------------|-------| +| High-end PC | Full mathematical mode | All features enabled | +| Console | Hybrid mode | Mathematical for player weapons only | +| Mobile | Artistic mode | Simplified calculations | +| VR | Fixed timestep | Consistent simulation for comfort | + +### Memory Management + +```cpp +// Configure pooling for different platforms +#if PLATFORM_MOBILE + Bullet->MaxPoolSize = 20; // Limited memory + Bullet->MaxTracesPerStep = 2; +#else + Bullet->MaxPoolSize = 100; // More memory available + Bullet->MaxTracesPerStep = 8; +#endif +``` + +## Migration Notes + +### From Legacy System + +When migrating from the old impact system: + +1. Set `UseNewImpactSystem = true` on bullets +2. Replace material property assignments with Physical Material integration +3. Update impact event bindings to use new delegates +4. Test penetration/ricochet behavior and adjust response maps + +### Breaking Changes + +- `MaterialResponseMap` entries now use Physical Materials as keys +- Impact events have different parameter signatures +- Some artistic properties moved to new component system + +## See Also + +- [Component API Reference](bullet-actor) - Detailed component documentation +- [Events Reference](events) - Complete event system documentation +- [Mathematical Formulas](../mathematical/penetration-calculations) - Physics calculation details +- [Migration Guide](../migration/new-impact-system) - Upgrading from legacy system \ No newline at end of file diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md new file mode 100644 index 0000000..8eb2f5c --- /dev/null +++ b/docs/docs/changelog.md @@ -0,0 +1,201 @@ +# Changelog + +All notable changes to EasyBallistics will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.83.0] - 2024-12-02 + +### 🎉 Major Features Added + +#### New Ballistic Impact System +- **UEBBallisticImpactComponent**: Event-driven impact handling system +- **Blueprint Events**: `OnBallisticImpact` and `OnBallisticRicochet` with detailed parameters +- **Opt-in Migration**: `UseNewImpactSystem` flag for gradual adoption +- **Mathematical Integration**: Full support for realistic penetration calculations + +#### Physical Material Integration +- **Material Editor Integration**: Ballistic properties directly in Physical Material editor +- **Smart Asset Creation**: Auto-configured material properties based on material names +- **Seamless Workflow**: Create/assign/edit ballistic properties without leaving the material editor + +#### Enhanced Asset Creation System +- **Ballistics Category**: Dedicated section in Content Browser asset creation menu +- **Asset Factories**: Streamlined creation of Bullet Properties and Material Response Maps +- **Editor Integration**: Custom property panels and specialized editors + +### 🔧 Improvements + +#### Performance Enhancements +- **Optimized Pooling**: Improved object pooling system for high-rate fire scenarios +- **LOD System**: Distance-based simulation quality scaling +- **Memory Management**: Reduced memory allocations during bullet simulation + +#### Network Improvements +- **Better Prediction**: Enhanced client-side prediction for responsive gameplay +- **Bandwidth Optimization**: Reduced network traffic for bullet replication +- **Lag Compensation**: Improved hit detection with network latency + +#### Developer Experience +- **Enhanced Debugging**: Better debug visualization and profiling tools +- **Error Handling**: Improved error messages and validation +- **Documentation**: Comprehensive API documentation and tutorials + +### 🐛 Bug Fixes + +#### Core Systems +- Fixed issue where bullets could pass through thin objects +- Resolved memory leak in object pooling system +- Corrected atmospheric calculations at extreme altitudes +- Fixed ricochet angle calculations for steep impact angles + +#### Network Issues +- Fixed desync between client and server bullet positions +- Resolved prediction errors causing visual "jumping" +- Corrected replication issues with high-rate fire weapons +- Fixed authority validation for impact events + +#### Editor Issues +- Fixed asset reference validation in property editor +- Resolved compilation errors on certain platform configurations +- Fixed crash when deleting Material Response Map entries +- Corrected property categorization in component details panels + +### 🔄 Changed + +#### API Changes +- **Material System**: Physical Materials now serve as primary material identifiers +- **Event Signatures**: Impact events now include additional context parameters +- **Component Architecture**: Ballistic calculations moved to dedicated component + +#### Asset Workflow +- **Material Properties**: No longer appear in main asset creation menu +- **Physical Material Editor**: Now includes ballistic property management +- **Response Maps**: Updated to use Physical Materials as keys instead of names + +#### Performance Defaults +- **Object Pooling**: Now enabled by default for all bullet classes +- **Trace Complexity**: Automatic LOD based on distance and importance +- **Network Settings**: Optimized default replication settings + +### 🗑️ Deprecated + +#### Legacy Systems +- **Old Impact Calculation**: Marked for deprecation (still functional with `UseNewImpactSystem = false`) +- **String-based Material Keys**: Material Response Maps should use Physical Material references +- **Direct Material Property Assignment**: Use Physical Material integration instead + +### 📋 Migration Notes + +#### From 2.8x to 2.83 +1. **Enable New Impact System**: Set `UseNewImpactSystem = true` on bullet classes +2. **Update Material Response Maps**: Replace string keys with Physical Material references +3. **Migrate Impact Events**: Update event bindings to use new parameter signatures +4. **Physical Material Setup**: Assign ballistic properties through Physical Material editor + +#### Breaking Changes +- Material Response Map keys changed from `FString` to `UPhysicalMaterial*` +- Impact event parameter order and types modified +- Some artistic ballistic properties moved to component-level settings + +### 🛠️ Technical Details + +#### New Classes +- `UEBBallisticImpactComponent`: Impact handling and event broadcasting +- `UEBBulletPropertiesAssetFactory`: Editor factory for bullet properties +- `UEBMaterialResponseMapFactory`: Editor factory for material response maps +- `FEBPhysicalMaterialCustomization`: Custom details panel for Physical Materials + +#### Modified Classes +- `AEBBullet`: Added new impact system integration and component support +- `UEBMaterialResponseMap`: Updated to use Physical Material keys +- `FEBMaterialResponseMapEntry`: Added mathematical properties support + +#### Editor Enhancements +- Custom asset type actions for improved Content Browser integration +- Specialized property editors for ballistic assets +- Enhanced debug visualization tools + +### 📊 Performance Metrics + +#### Benchmark Improvements +- **Memory Usage**: 25% reduction in allocation overhead +- **Frame Rate**: 15% improvement with 100+ active bullets +- **Network Bandwidth**: 30% reduction in replication data +- **Load Times**: 20% faster asset loading and initialization + +#### Platform Support +- **Windows**: Full feature support +- **Mac**: Full feature support +- **Linux**: Full feature support +- **Android**: Optimized performance mode + +### 🎯 Known Issues + +#### Current Limitations +- Mathematical ballistics may have precision issues at extreme ranges (>10km) +- VR motion controller integration requires manual setup +- Some console commands may not work in packaged builds + +#### Workarounds +- Use artistic mode for very long-range scenarios +- Implement custom VR integration using provided events +- Enable development console for full command access + +### 🔮 Upcoming Features + +#### Version 2.84 (Planned) +- **Advanced Materials**: Support for composite and layered materials +- **Weapon Customization**: Modular weapon system with attachments +- **Enhanced VR Support**: Native VR motion controller integration +- **Ray Tracing Integration**: Hardware ray tracing for precise ballistics + +#### Version 2.85 (Planned) +- **Destructible Integration**: Native support for destructible meshes +- **Weather Effects**: Rain, snow, and fog impact on ballistics +- **Advanced Networking**: Dedicated server optimizations +- **Mobile Enhancements**: Further mobile platform optimizations + +### 🙏 Contributors + +Special thanks to the community members who contributed to this release: + +- **Community Feedback**: Bug reports and feature suggestions +- **Beta Testers**: Early access testing and validation +- **Documentation**: Community-contributed examples and tutorials + +### 📞 Support + +For questions, issues, or feedback regarding this release: + +- **Discord**: [Join our community](https://discord.gg/easyballistics) +- **GitHub**: [Report issues](https://github.com/your-org/easyballistics/issues) +- **Forums**: [Unreal Engine community](https://forums.unrealengine.com/) +- **Email**: support@easyballistics.com + +--- + +## Previous Releases + +### [2.82.0] - 2024-10-15 +- Added G7 ballistic coefficient support +- Improved atmospheric modeling +- Fixed shotgun spread calculations +- Enhanced multiplayer stability + +### [2.81.0] - 2024-08-20 +- Introduced mathematical ballistics system +- Added advanced penetration calculations +- Improved object pooling performance +- Fixed various collision detection issues + +### [2.80.0] - 2024-06-10 +- Major architecture overhaul +- Added UE5 compatibility +- Implemented new material response system +- Enhanced debugging tools + +--- + +*For complete version history, see [GitHub Releases](https://github.com/your-org/easyballistics/releases)* \ No newline at end of file diff --git a/docs/docs/core-concepts/overview.md b/docs/docs/core-concepts/overview.md new file mode 100644 index 0000000..da94312 --- /dev/null +++ b/docs/docs/core-concepts/overview.md @@ -0,0 +1,262 @@ +# Core Concepts + +Understanding the fundamental concepts behind EasyBallistics will help you build more effective and realistic ballistic systems. + +## System Architecture + +EasyBallistics uses a modular architecture that separates concerns for maximum flexibility: + +```mermaid +graph TD + A[Projectile Definition] --> B[AEBBullet Actor] + C[Surface Properties] --> D[Physical Materials] + E[Interaction Rules] --> F[Material Response Map] + + B --> G[UEBBallisticImpactComponent] + D --> G + F --> G + + G --> H[Impact Events] + G --> I[Penetration Calculation] + G --> J[Ricochet Calculation] + + K[UEBBarrel Component] --> B + L[Mathematical Ballistics] --> G +``` + +## Core Components + +### 1. Projectile System + +**AEBBullet** is the main projectile actor that handles: +- Physics simulation (velocity, acceleration, drag) +- Collision detection and response +- Atmospheric effects (air density, wind) +- Network replication and prediction + +### 2. Weapon System + +**UEBBarrel** component manages: +- Firing mechanics (single, auto, burst, gatling) +- Ammunition management and reloading +- Muzzle velocity and spread patterns +- Shotgun support (multiple projectiles) + +### 3. Impact System + +**UEBBallisticImpactComponent** handles: +- Material interaction calculations +- Penetration depth determination +- Ricochet probability and direction +- Event broadcasting for custom responses + +### 4. Material System + +The material system consists of: +- **Physical Materials**: UE5's built-in surface types +- **Material Properties**: Ballistic characteristics (density, hardness) +- **Material Response Maps**: Gameplay rules for interactions + +## Data Flow + +### Firing Sequence + +1. **Trigger Pull**: Player input or AI decision +2. **Barrel Processing**: UEBBarrel handles fire rate, ammo consumption +3. **Bullet Spawn**: AEBBullet created with initial velocity and properties +4. **Physics Simulation**: Bullet moves through world with realistic physics +5. **Impact Detection**: Collision system detects surface contact +6. **Material Lookup**: System identifies surface material properties +7. **Impact Calculation**: Penetration and ricochet calculations performed +8. **Response Execution**: Events fired, effects spawned, physics applied + +### Mathematical vs Artistic Modes + +EasyBallistics supports two calculation approaches: + +#### Mathematical Mode +- Uses real-world ballistic formulas +- Factors in bullet mass, velocity, material density +- Provides scientifically accurate results +- Best for simulations and realistic games + +#### Artistic Mode +- Uses simplified, game-designer-friendly values +- Prioritizes fun and gameplay balance +- Easily tweakable parameters +- Best for arcade-style games + +## Key Concepts + +### Ballistic Coefficient (BC) + +The BC determines how well a bullet cuts through air: +- **G1 Model**: Standard for most bullets +- **G7 Model**: More accurate for long-range, boat-tail bullets +- Higher BC = less drag = flatter trajectory + +```cpp +// Configure ballistic coefficient +BulletProperties.BallisticCoefficientG1 = 0.151f; // 55gr 5.56mm +BulletProperties.UseG7Model = false; // Use G1 for this bullet +``` + +### Sectional Density + +The ratio of bullet weight to diameter squared: +- Higher sectional density = better penetration +- Automatically calculated if not specified +- Critical for realistic penetration modeling + +### Atmospheric Effects + +Environmental factors that affect bullet flight: + +```cpp +// Configure atmospheric model +Bullet->AtmosphereType = EEBAtmosphereType::AT_Earth; // Earth-like model +Bullet->SeaLevelAirDensity = 1.21f; // kg/m³ +Bullet->SeaLevelAirTemperature = 20.0f; // °C +``` + +### Penetration Mechanics + +Penetration depends on multiple factors: +- **Bullet energy**: Kinetic energy at impact +- **Material resistance**: Hardness, density, tensile strength +- **Impact angle**: Perpendicular impacts penetrate better +- **Bullet construction**: Material and design affect penetration + +### Ricochet Physics + +Ricochet probability depends on: +- **Impact angle**: Shallow angles increase ricochet chance +- **Surface hardness**: Harder surfaces ricochet more +- **Bullet velocity**: Higher velocity may overcome ricochet tendency +- **Surface roughness**: Affects energy retention after ricochet + +## Network Architecture + +### Client-Server Model + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + participant O as Other Clients + + C->>S: Fire Request + S->>S: Validate & Spawn Bullet + S->>O: Replicate Bullet + C->>C: Predict Impact + S->>O: Authoritative Impact + Note over C,O: Reconciliation if needed +``` + +### Prediction System + +- **Client Prediction**: Immediate visual feedback +- **Server Authority**: Final impact determination +- **Reconciliation**: Smooth correction of prediction errors +- **Lag Compensation**: Accounts for network latency + +## Performance Considerations + +### Object Pooling + +Bullets are expensive to create/destroy frequently: + +```cpp +// Enable pooling for high-rate weapons +Bullet->EnablePooling = true; +Bullet->MaxPoolSize = 100; // Pool up to 100 bullets +``` + +### LOD System + +Simulation quality scales with importance: +- **High Detail**: Player weapons and nearby impacts +- **Medium Detail**: Visible but distant projectiles +- **Low Detail**: Off-screen or very distant bullets +- **Culling**: Very far bullets may be removed entirely + +### Trace Optimization + +Multiple optimization layers: +- **Collision Channels**: Separate channels for different trace types +- **Trace Complexity**: Simple vs complex collision shapes +- **Max Traces Per Step**: Limits traces per simulation frame +- **Fixed Timestep**: Consistent simulation regardless of framerate + +## Integration Points + +### Blueprint Integration + +All major components expose Blueprint events: + +```cpp +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FOnBallisticImpact, + FVector, ImpactLocation, + FVector, ImpactNormal, + UPhysicalMaterial*, HitMaterial, + float, PenetrationDepth, + bool, bDidPenetrate); +``` + +### C++ Extension Points + +Key virtual functions for customization: +- `UpdateVelocity()`: Custom physics calculations +- `CollisionFilter()`: Custom hit validation +- `OnImpact()`: Custom impact processing + +### Asset Pipeline Integration + +- **Content Browser**: Custom asset creation menus +- **Property Editor**: Specialized property panels +- **Material Editor**: Ballistic properties in Physical Materials +- **Blueprint Editor**: Component-specific details panels + +## Best Practices + +### Asset Organization + +``` +Content/ +├── Ballistics/ +│ ├── BulletProperties/ +│ │ ├── Rifle/ +│ │ ├── Pistol/ +│ │ └── Shotgun/ +│ ├── MaterialMaps/ +│ │ ├── Military/ +│ │ ├── Civilian/ +│ │ └── SciFi/ +│ └── PhysicalMaterials/ +│ ├── Metals/ +│ ├── Organic/ +│ └── Synthetic/ +``` + +### Performance Guidelines + +1. **Use Pooling**: Always enable for rapid-fire weapons +2. **Appropriate Complexity**: Match simulation detail to gameplay needs +3. **Material Optimization**: Group similar materials in response maps +4. **Network Optimization**: Use reliable replication sparingly + +### Debugging Workflow + +1. **Visual Debugging**: Enable bullet trails and impact markers +2. **Performance Profiling**: Monitor trace counts and simulation time +3. **Network Analysis**: Check replication bandwidth and prediction accuracy +4. **Asset Validation**: Verify material assignments and property ranges + +## Next Steps + +Now that you understand the core concepts: + +1. [Asset Types](asset-types) - Deep dive into each asset type +2. [Ballistics System](ballistics-system) - Detailed physics explanations +3. [Physical Materials](physical-materials) - Material system integration +4. [Component Guide](../components/bullet-actor) - Component-specific documentation \ No newline at end of file diff --git a/docs/docs/getting-started/installation.md b/docs/docs/getting-started/installation.md new file mode 100644 index 0000000..c1970b2 --- /dev/null +++ b/docs/docs/getting-started/installation.md @@ -0,0 +1,157 @@ +# Installation + +This guide will walk you through installing EasyBallistics in your Unreal Engine project. + +## Prerequisites + +- **Unreal Engine 5.6.0** or later +- **Visual Studio 2022** (for C++ projects) +- **Git** (for version control) + +## Installation Methods + +### Method 1: Marketplace Installation (Recommended) + +1. Open the **Epic Games Launcher** +2. Navigate to the **Marketplace** +3. Search for "EasyBallistics" +4. Click **Add to Project** or **Install to Engine** +5. Select your target project +6. Click **Install** + +### Method 2: Manual Installation + +1. Download the plugin from the marketplace or GitHub +2. Extract the plugin to your project's `Plugins` folder: + ``` + YourProject/ + ├── Plugins/ + │ └── EasyBallistics/ + │ ├── Source/ + │ ├── Resources/ + │ └── EasyBallistics.uplugin + ``` +3. Regenerate project files: + - Right-click your `.uproject` file + - Select **Generate Visual Studio project files** + +### Method 3: Git Submodule (Advanced) + +```bash +# Navigate to your project directory +cd YourProject/Plugins + +# Add as submodule +git submodule add https://github.com/your-org/easyballistics.git EasyBallistics + +# Initialize and update +git submodule update --init --recursive +``` + +## Enabling the Plugin + +1. Open your project in Unreal Engine +2. Go to **Edit → Plugins** +3. Search for "EasyBallistics" +4. Check the **Enabled** checkbox +5. Click **Restart Now** when prompted + +![Plugin Manager](../img/plugin-manager.png) + +## Verifying Installation + +### Check Plugin Status + +1. Open **Edit → Plugins** +2. Verify EasyBallistics shows as **Enabled** +3. Check for any error messages + +### Test Basic Functionality + +1. In the Content Browser, right-click +2. Look for **Ballistics** category in the asset creation menu +3. You should see: + - Bullet Properties + - Material Response Map + +![Asset Creation Menu](../img/asset-creation-menu.png) + +### Verify Components + +1. Create a new Actor Blueprint +2. Add Component → Search "Ballistic" +3. You should find: + - **EB Barrel** (weapon barrel component) + - **EB Ballistic Impact Component** (impact handling) + +## Project Configuration + +### Build Configuration + +Add to your project's `DefaultEngine.ini`: + +```ini +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="EasyBallistics",NewGameName="/Script/EasyBallistics") ++ActiveGameNameRedirects=(OldGameName="EasyBallisticsEditor",NewGameName="/Script/EasyBallisticsEditor") +``` + +### Module Dependencies + +If using C++, add to your project's `Build.cs` file: + +```cpp +PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "EasyBallistics" // Add this line +}); +``` + +## Troubleshooting + +### Common Issues + +#### Plugin Not Appearing +- Ensure you're using UE 5.6 or later +- Check if the plugin files are in the correct location +- Regenerate project files + +#### Compilation Errors +- Verify Visual Studio 2022 is installed +- Check that all dependencies are met +- Try a clean rebuild + +#### Missing Components +- Restart the editor after enabling the plugin +- Clear the derived data cache: **Edit → Developer → Derived Data → Clear** + +### Error Messages + +#### "Module 'EasyBallistics' could not be loaded" +1. Check your UE version compatibility +2. Verify the plugin is enabled +3. Regenerate project files +4. Clean and rebuild the project + +#### "Failed to load because module 'EasyBallistics' could not be found" +1. Ensure the plugin is in the `Plugins` folder +2. Check the `.uplugin` file is valid +3. Verify file permissions + +## Next Steps + +Once installation is complete: + +1. [Quick Start Guide](quick-start) - Create your first ballistic weapon +2. [Basic Setup](basic-setup) - Configure materials and responses +3. [Core Concepts](../core-concepts/overview) - Understand the system architecture + +## Getting Help + +If you encounter issues during installation: + +- Check the [Troubleshooting](../troubleshooting) guide +- Visit our [Discord community](https://discord.gg/easyballistics) +- Submit an issue on [GitHub](https://github.com/your-org/easyballistics/issues) \ No newline at end of file diff --git a/docs/docs/getting-started/quick-start.md b/docs/docs/getting-started/quick-start.md new file mode 100644 index 0000000..e2ce779 --- /dev/null +++ b/docs/docs/getting-started/quick-start.md @@ -0,0 +1,247 @@ +# Quick Start Guide + +Get up and running with EasyBallistics in under 10 minutes! This guide will walk you through creating your first ballistic weapon system. + +## Overview + +In this quick start, you'll: +1. Create bullet properties +2. Set up a material response map +3. Create a simple weapon +4. Test ballistic impacts + +## Step 1: Create Bullet Properties + +First, let's define the characteristics of our projectile. + +### Create the Asset + +1. In the Content Browser, **right-click** → **Ballistics** → **Bullet Properties** +2. Name it `BP_556_NATO_Properties` +3. **Double-click** to open the editor + +### Configure Properties + +Set these values for a 5.56x45mm NATO round: + +```yaml +Bullet Properties: + Grain Weight: 55.0 + Diameter Inches: 0.224 + Length Inches: 0.825 + Bullet Type: Full Metal Jacket + Bullet Material: Copper 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 + Expansion Velocity Threshold: 1800.0 +``` + +![Bullet Properties](../img/bullet-properties-setup.png) + +## Step 2: Set Up Physical Materials + +Configure materials that bullets will interact with. + +### Create Steel Material + +1. Content Browser → **right-click** → **Physics** → **Physical Material** +2. Name it `PM_Steel` +3. Open the material editor +4. In the **Ballistics** section: + - Click **Create New** to generate ballistic properties + - The system will auto-configure steel properties + +### Create Wood Material + +1. Create another Physical Material: `PM_Wood` +2. In the **Ballistics** section: + - Click **Create New** + - The system will detect "wood" in the name and configure accordingly + +## Step 3: Create Material Response Map + +Define how bullets interact with different materials. + +### Create the Asset + +1. Content Browser → **right-click** → **Ballistics** → **Material Response Map** +2. Name it `MRM_Default` +3. Open the editor + +### Configure Responses + +Add entries for your materials: + +```yaml +Steel (PM_Steel): + Penetration Depth Multiplier: 0.3 + Ricochet Probability Multiplier: 2.0 + Ricochet Restitution: 0.8 + Never Penetrate: false + Never Ricochet: false + +Wood (PM_Wood): + Penetration Depth Multiplier: 2.0 + Ricochet Probability Multiplier: 0.1 + Ricochet Restitution: 0.2 + Never Penetrate: false + Never Ricochet: false +``` + +## Step 4: Create a Simple Weapon + +Now let's create a weapon that fires ballistic projectiles. + +### Create Weapon Blueprint + +1. Create **Actor Blueprint**: `BP_SimpleRifle` +2. Add a **Static Mesh Component** for the weapon model +3. Add an **EB Barrel Component** + +### Configure the Barrel + +Select the **EB Barrel** component and set: + +```yaml +Ammunition: + Bullet Class: EBBullet (from dropdown) + Bullet Properties Asset: BP_556_NATO_Properties + Material Response Map: MRM_Default + +Ballistics: + Muzzle Velocity Min: 3100 + Muzzle Velocity Max: 3200 + Use Mathematical Physics: true + Use New Impact System: true + +Fire Mode: + Fire Mode: Semiauto + Rounds Per Minute: 600 +``` + +### Add Firing Logic + +In the **Event Graph**, add this simple firing setup: + +```cpp +// On Input Action Fire (or your input event) +Event InputAction Fire + ↓ +Branch (Can Fire?) + ↓ True +Call Function: Fire Single Shot + ↓ +Set Timer by Function Name + - Function Name: "Reset Fire Cooldown" + - Time: 0.1 (for 600 RPM) +``` + +## Step 5: Set Up Target Objects + +Create objects to shoot at with proper materials. + +### Create Target Blueprint + +1. Create **Actor Blueprint**: `BP_Target` +2. Add **Static Mesh Component** (use a cube or custom model) +3. Set the **Physical Material** to either `PM_Steel` or `PM_Wood` +4. Enable **Collision** and **Simulate Physics** + +### Add Impact Response + +Add an **EB Ballistic Impact Component** to the target: + +```cpp +// In the target's Event Graph +OnBallisticImpact + ↓ +Branch (Did Penetrate?) + ↓ True: Spawn Penetration Effect + ↓ False: Spawn Impact Effect + +OnBallisticRicochet + ↓ +Spawn Ricochet Effect + ↓ +Play Ricochet Sound +``` + +## Step 6: Test the System + +Time to see your ballistic system in action! + +### Testing Setup + +1. Place `BP_SimpleRifle` in your level +2. Place several `BP_Target` actors with different materials +3. Set up a **Player Controller** or **Pawn** to control the weapon +4. **Play** the level + +### What to Look For + +- **Penetration**: Bullets should penetrate wood more easily than steel +- **Ricochet**: Bullets should ricochet off steel at shallow angles +- **Material Response**: Different materials should show different impact behaviors +- **Physics**: Targets should receive impulse and move when hit + +### Debug Features + +Enable these for testing: + +```yaml +Bullet Debug Options: + Debug Enabled: true + Debug Trail Time: 2.0 + Debug Trail Color Fast: Green + Debug Trail Color Slow: Red +``` + +You'll see colored trails showing bullet paths! + +## Expected Results + +After completing this setup, you should have: + +✅ **Realistic Ballistics**: Bullets affected by gravity and air resistance +✅ **Material Interactions**: Different penetration/ricochet on steel vs wood +✅ **Impact Events**: Targets respond to bullet impacts +✅ **Debug Visualization**: Visible bullet trails for testing + +## Troubleshooting + +### Bullets Not Spawning +- Check that `Bullet Class` is set to `EBBullet` +- Verify `Bullet Properties Asset` is assigned +- Ensure the barrel component is properly configured + +### No Material Response +- Verify targets have **Physical Materials** assigned +- Check that materials are added to the **Material Response Map** +- Ensure `Use New Impact System` is enabled + +### Poor Performance +- Enable **Object Pooling** in bullet settings +- Reduce **Max Traces Per Step** if needed +- Consider using **Fixed Step** simulation for consistency + +## Next Steps + +Now that you have a basic system working: + +1. [Asset Creation Guide](../assets/bullet-properties) - Create more bullet types +2. [Material Setup](../assets/physical-material-integration) - Advanced material configuration +3. [Advanced Ballistics](../tutorials/advanced-ballistics) - Atmospheric effects and drag curves +4. [Networking](../networking/overview) - Set up multiplayer ballistics + +## Getting Help + +- Join our [Discord](https://discord.gg/easyballistics) for real-time help +- Check the [API Reference](../api/overview) for detailed function documentation +- Browse [Tutorials](../tutorials/first-weapon) for more advanced setups \ No newline at end of file diff --git a/docs/docs/intro.md b/docs/docs/intro.md new file mode 100644 index 0000000..2a219af --- /dev/null +++ b/docs/docs/intro.md @@ -0,0 +1,81 @@ +# EasyBallistics Documentation + +Welcome to **EasyBallistics**, the most advanced ballistics simulation plugin for Unreal Engine 5.6! + +## What is EasyBallistics? + +EasyBallistics is a comprehensive ballistics simulation system that brings realistic projectile physics to your Unreal Engine projects. Whether you're developing a tactical shooter, simulation, or any game requiring realistic bullet behavior, EasyBallistics provides the tools you need. + +## Key Features + +### 🎯 **Realistic Ballistics** +- Atmospheric effects (air density, temperature, pressure) +- Wind simulation and drag calculations +- Ballistic coefficient support (G1 and G7 models) +- Mathematical penetration and ricochet calculations + +### 🏗️ **Modular Asset System** +- **Bullet Properties**: Define projectile characteristics +- **Material Response Maps**: Configure surface interactions +- **Physical Material Integration**: Seamless UE5 workflow +- **Ballistic Impact Component**: Event-driven impact handling + +### 🌐 **Multiplayer Ready** +- Full replication support +- Client-side prediction +- Optimized for high-rate fire scenarios +- Network-efficient pooling system + +### ⚡ **Performance Optimized** +- Object pooling for projectiles +- Configurable simulation complexity +- LOD system for distant projectiles +- Multi-threaded calculations + +## Quick Start + +```cpp +// Create a bullet with realistic ballistics +AEBBullet* Bullet = GetWorld()->SpawnActor(); +Bullet->BulletPropertiesAsset = MyBulletProperties; +Bullet->UseNewImpactSystem = true; +Bullet->UseMathematicalPhysics = true; +``` + +## Architecture Overview + +```mermaid +graph TD + A[Bullet Properties] --> D[EBBullet Actor] + B[Material Properties] --> E[Physical Materials] + C[Material Response Map] --> D + E --> F[Ballistic Impact Component] + D --> F + F --> G[Impact Events] + F --> H[Penetration Calculation] + F --> I[Ricochet Calculation] +``` + +## System Requirements + +- **Unreal Engine**: 5.6.0 or later +- **Platforms**: Windows, Mac, Linux, Android +- **Network**: Full multiplayer support +- **Performance**: Optimized for 60+ FPS with hundreds of projectiles + +## Getting Started + +1. [Installation Guide](getting-started/installation) - Install the plugin +2. [Quick Start](getting-started/quick-start) - Create your first weapon +3. [Basic Setup](getting-started/basic-setup) - Configure materials and responses + +## Support + +- 📧 **Email**: support@easyballistics.com +- 💬 **Discord**: [Join our community](https://discord.gg/easyballistics) +- 🐛 **Issues**: [GitHub Issues](https://github.com/your-org/easyballistics/issues) +- 📚 **Forums**: [Unreal Engine Forums](https://forums.unrealengine.com/) + +--- + +*Built with ❤️ for the Unreal Engine community* \ No newline at end of file diff --git a/docs/docs/troubleshooting.md b/docs/docs/troubleshooting.md new file mode 100644 index 0000000..2d4edf0 --- /dev/null +++ b/docs/docs/troubleshooting.md @@ -0,0 +1,459 @@ +# Troubleshooting Guide + +Common issues and solutions for EasyBallistics. + +## Installation Issues + +### Plugin Not Appearing in Plugin Manager + +**Symptoms:** +- EasyBallistics doesn't show up in Edit → Plugins +- No Ballistics category in asset creation menu + +**Solutions:** + +1. **Check UE Version Compatibility** + ``` + Required: Unreal Engine 5.6.0 or later + Current: Check Help → About Unreal Editor + ``` + +2. **Verify Plugin Location** + ``` + Correct path: YourProject/Plugins/EasyBallistics/ + Check for: EasyBallistics.uplugin file + ``` + +3. **Regenerate Project Files** + - Right-click your `.uproject` file + - Select "Generate Visual Studio project files" + - Restart Unreal Engine + +4. **Clear Derived Data Cache** + - Edit → Developer → Derived Data + - Click "Clear" + - Restart editor + +### Compilation Errors + +**Error: "Module 'EasyBallistics' could not be loaded"** + +```cpp +// Add to YourProject.Build.cs +PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "EasyBallistics" // Add this line +}); +``` + +**Error: "Failed to import class 'EBBullet'"** + +1. Ensure plugin is enabled +2. Check that EasyBallistics.Build.cs exists +3. Verify all source files are present +4. Clean and rebuild project + +## Runtime Issues + +### Bullets Not Spawning + +**Symptoms:** +- Fire input triggers but no bullets appear +- No muzzle flash or effects + +**Debugging Steps:** + +1. **Check Barrel Configuration** + ```cpp + // Verify these are set: + BulletClass = AEBBullet::StaticClass() + BulletPropertiesAsset = Valid asset + MuzzleVelocityMin > 0 + MuzzleVelocityMax > 0 + ``` + +2. **Verify Spawning Logic** + ```cpp + // Add debug logging + UE_LOG(LogTemp, Warning, TEXT("Attempting to fire bullet")); + + if (!BulletClass) + { + UE_LOG(LogTemp, Error, TEXT("BulletClass is null!")); + return; + } + ``` + +3. **Check World Reference** + ```cpp + // Ensure valid world context + UWorld* World = GetWorld(); + if (!World) + { + UE_LOG(LogTemp, Error, TEXT("No valid world for bullet spawn")); + return; + } + ``` + +### Bullets Spawning But Not Moving + +**Symptoms:** +- Bullets appear at muzzle but don't travel +- Static bullet actors in world + +**Solutions:** + +1. **Check Initial Velocity** + ```cpp + // Bullet velocity should be set on spawn + Bullet->Velocity = MuzzleDirection * MuzzleVelocity; + + // Verify velocity is not zero + if (Bullet->Velocity.IsNearlyZero()) + { + UE_LOG(LogTemp, Warning, TEXT("Bullet velocity is zero!")); + } + ``` + +2. **Verify Actor Tick** + ```cpp + // In bullet constructor, ensure: + PrimaryActorTick.bCanEverTick = true; + ``` + +3. **Check Physics Settings** + ```cpp + // Ensure physics simulation is enabled + Bullet->SetActorEnableCollision(true); + ``` + +### No Impact Detection + +**Symptoms:** +- Bullets pass through objects without impact +- No impact events or effects + +**Debugging Steps:** + +1. **Check Collision Settings** + ```cpp + // Verify collision channel + TraceChannel = ECC_WorldStatic // or appropriate channel + TraceComplex = true // for complex collision + ``` + +2. **Verify Target Collision** + - Ensure target has collision enabled + - Check collision response to bullet trace channel + - Verify mesh has collision geometry + +3. **Debug Visualization** + ```cpp + // Enable debug traces + Bullet->DebugEnabled = true; + Bullet->DebugTrailTime = 2.0f; + ``` + +### Incorrect Penetration/Ricochet Behavior + +**Symptoms:** +- Bullets always penetrate or never penetrate +- Ricochet behavior doesn't match material properties + +**Solutions:** + +1. **Check Material Response Map** + ```cpp + // Verify material is in response map + if (!MaterialResponseMap->Map.Contains(PhysicalMaterial)) + { + UE_LOG(LogTemp, Warning, TEXT("Material not found in response map")); + } + ``` + +2. **Verify Physical Material Assignment** + - Check target mesh has Physical Material assigned + - Ensure Physical Material is not null in hit result + +3. **Check New Impact System** + ```cpp + // Ensure new system is enabled + Bullet->UseNewImpactSystem = true; + + // Verify component exists + if (!Bullet->BallisticImpactComponent) + { + UE_LOG(LogTemp, Error, TEXT("BallisticImpactComponent is null")); + } + ``` + +## Performance Issues + +### Low Frame Rate with Many Bullets + +**Symptoms:** +- FPS drops when firing rapidly +- Hitches during bullet simulation + +**Optimization Steps:** + +1. **Enable Object Pooling** + ```cpp + Bullet->EnablePooling = true; + Bullet->MaxPoolSize = 50; // Adjust based on needs + ``` + +2. **Reduce Trace Complexity** + ```cpp + // Reduce traces per step + Bullet->MaxTracesPerStep = 4; // Down from 8 + + // Use simple collision for distant bullets + Bullet->TraceComplex = false; + ``` + +3. **Implement LOD System** + ```cpp + // Distance-based quality scaling + float Distance = FVector::Dist(Bullet->GetActorLocation(), PlayerLocation); + if (Distance > 5000.0f) + { + Bullet->FixedStepSeconds = 0.2f; // Lower frequency + } + ``` + +4. **Profile Performance** + ```cpp + // Use built-in profiling + stat game + stat engine + stat collision + ``` + +### Memory Usage Issues + +**Symptoms:** +- Increasing memory usage over time +- Out of memory errors during extended play + +**Solutions:** + +1. **Check Pool Cleanup** + ```cpp + // Ensure bullets are properly deactivated + void AEBBullet::Deactivate() + { + SetActorHiddenInGame(true); + SetActorEnableCollision(false); + DeactivateToPool(); // Return to pool + } + ``` + +2. **Limit Pool Sizes** + ```cpp + // Prevent unlimited pool growth + Bullet->MaxPoolSize = 100; // Hard limit + ``` + +3. **Monitor Actor Count** + ```cpp + // Debug actor counts + UE_LOG(LogTemp, Warning, TEXT("Active bullets: %d"), + GetWorld()->GetActorCount()); + ``` + +## Network Issues + +### Bullets Not Replicating + +**Symptoms:** +- Bullets visible on server but not clients +- Desync between players + +**Solutions:** + +1. **Check Replication Settings** + ```cpp + // In bullet constructor + bReplicates = true; + SetReplicateMovement(true); + ``` + +2. **Verify Network Role** + ```cpp + // Only spawn bullets on server + if (HasAuthority()) + { + SpawnBullet(); + } + ``` + +3. **Check Bandwidth Usage** + ``` + net.PackageMap.DebugObject BulletClass + stat net + ``` + +### Prediction Issues + +**Symptoms:** +- Laggy bullet impacts +- Bullets appear to "jump" position + +**Solutions:** + +1. **Enable Client Prediction** + ```cpp + // Allow client-side impact prediction + Bullet->bNetUseOwnerRelevancy = true; + ``` + +2. **Adjust Prediction Settings** + ```cpp + // Fine-tune prediction parameters + Bullet->NetCullDistanceSquared = 10000000.0f; // 1000m + ``` + +## Blueprint Issues + +### Events Not Firing + +**Symptoms:** +- OnBallisticImpact events don't trigger +- Custom impact logic not executing + +**Solutions:** + +1. **Check Event Binding** + ```cpp + // Verify event is bound in BeginPlay + BallisticImpactComponent->OnBallisticImpact.AddDynamic( + this, &AMyActor::HandleImpact); + ``` + +2. **Verify Component Reference** + - Ensure BallisticImpactComponent exists on actor + - Check component is properly initialized + +3. **Debug Event Calls** + ```cpp + // Add logging to verify events + void HandleImpact(/* parameters */) + { + UE_LOG(LogTemp, Warning, TEXT("Impact event received")); + } + ``` + +### Asset References Not Working + +**Symptoms:** +- Bullet Properties Asset shows as None +- Material Response Map not found + +**Solutions:** + +1. **Check Asset Paths** + ```cpp + // Verify asset references are valid + if (!BulletPropertiesAsset) + { + UE_LOG(LogTemp, Error, TEXT("BulletPropertiesAsset is null")); + } + ``` + +2. **Recreate Asset References** + - Delete and reassign asset references + - Check for missing asset redirects + +## Debug Tools + +### Enable Debug Visualization + +```cpp +// In bullet settings +DebugEnabled = true +DebugTrailTime = 2.0 +DebugTrailWidth = 1.0 +DebugTrailColorFast = Green +DebugTrailColorSlow = Red +``` + +### Console Commands + +``` +// Show bullet debug info +showdebug collision +showdebug physics + +// Network debugging +net.PackageMap.DebugObject AEBBullet +stat net + +// Performance profiling +stat game +stat collision +stat memory +``` + +### Logging Categories + +Add to `DefaultEngine.ini`: + +```ini +[Core.Log] +LogEasyBallistics=VeryVerbose +LogNetPlayerMovement=Verbose +LogCollision=Warning +``` + +## Getting Help + +### Before Reporting Issues + +1. **Check Documentation** + - Review relevant API documentation + - Check tutorials for similar setups + +2. **Gather Information** + - UE version and platform + - Plugin version + - Minimal reproduction steps + - Error logs and stack traces + +3. **Search Existing Issues** + - Check GitHub issues + - Search community forums + +### Support Channels + +- **GitHub Issues**: [Report bugs and feature requests](https://github.com/your-org/easyballistics/issues) +- **Discord**: [Real-time community support](https://discord.gg/easyballistics) +- **Forums**: [Unreal Engine community forums](https://forums.unrealengine.com/) +- **Email**: support@easyballistics.com + +### Issue Template + +When reporting issues, include: + +``` +**Environment:** +- UE Version: 5.6.0 +- Platform: Windows 10 +- Plugin Version: 2.83 + +**Issue Description:** +Brief description of the problem + +**Steps to Reproduce:** +1. Step one +2. Step two +3. Expected vs actual behavior + +**Logs:** +Attach relevant log files + +**Additional Context:** +Screenshots, videos, or other helpful information +``` \ No newline at end of file diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js new file mode 100644 index 0000000..f6c57e4 --- /dev/null +++ b/docs/docusaurus.config.js @@ -0,0 +1,146 @@ +// @ts-check +// `@type` JSDoc annotations allow editor autocompletion and type checking +// (when paired with `@ts-check`). +// There are various equivalent ways to declare your Docusaurus config. +// See: https://docusaurus.io/docs/api/docusaurus-config + +import {themes as prismThemes} from 'prism-react-renderer'; + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: 'EasyBallistics', + tagline: 'Advanced Ballistics Simulation for Unreal Engine 5.6', + favicon: 'img/favicon.ico', + + // Set the production url of your site here + url: 'https://your-docusaurus-site.example.com', + // Set the // pathname under which your site is served + // For GitHub pages deployment, it is often '//' + baseUrl: '/', + + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. + organizationName: 'mookie', // Usually your GitHub org/user name. + projectName: 'easyballistics', // Usually your repo name. + + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + + // Even if you don't use internationalization, you can use this field to set + // useful metadata like html lang. For example, if your site is Chinese, you + // may want to set it to `zh-Hans`. + i18n: { + defaultLocale: 'en', + locales: ['en'], + }, + + presets: [ + [ + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + routeBasePath: '/', + sidebarPath: './sidebars.js', + // Please change this to your repo. + // Remove this to remove the "edit this page" links. + editUrl: + 'https://github.com/your-org/easyballistics/tree/main/docs/', + }, + blog: false, + theme: { + customCss: './src/css/custom.css', + }, + }), + ], + ], + + markdown: { + mermaid: true, + }, + themes: ['@docusaurus/theme-mermaid'], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + // Replace with your project's social card + image: 'img/easyballistics-social-card.jpg', + navbar: { + title: 'EasyBallistics', + logo: { + alt: 'EasyBallistics Logo', + src: 'img/logo.svg', + }, + items: [ + { + type: 'docSidebar', + sidebarId: 'tutorialSidebar', + position: 'left', + label: 'Documentation', + }, + { + href: 'https://www.unrealengine.com/marketplace/en-US/product/easyballistics', + label: 'Marketplace', + position: 'right', + }, + { + href: 'https://github.com/your-org/easyballistics', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Documentation', + items: [ + { + label: 'Getting Started', + to: '/getting-started', + }, + { + label: 'API Reference', + to: '/api', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Discord', + href: 'https://discord.gg/unrealengine', + }, + { + label: 'Unreal Engine Forums', + href: 'https://forums.unrealengine.com/', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'Marketplace', + href: 'https://www.unrealengine.com/marketplace/en-US/product/easyballistics', + }, + { + label: 'GitHub', + href: 'https://github.com/your-org/easyballistics', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} Mookie. Built with Docusaurus.`, + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + additionalLanguages: ['cpp', 'blueprint'], + }, + }), +}; + +export default config; \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..ababa3f --- /dev/null +++ b/docs/package.json @@ -0,0 +1,46 @@ +{ + "name": "easyballistics-docs", + "version": "2.83.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@docusaurus/core": "^3.0.0", + "@docusaurus/preset-classic": "^3.0.0", + "@docusaurus/theme-mermaid": "^3.0.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "prism-react-renderer": "^2.1.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.0.0", + "@docusaurus/tsconfig": "^3.0.0", + "@docusaurus/types": "^3.0.0" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 3 chrome version", + "last 3 firefox version", + "last 5 safari version" + ] + }, + "engines": { + "node": ">=18.0" + } +} \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js new file mode 100644 index 0000000..68f52e4 --- /dev/null +++ b/docs/sidebars.js @@ -0,0 +1,120 @@ +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +// @ts-check + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + // By default, Docusaurus generates a sidebar from the docs folder structure + tutorialSidebar: [ + 'intro', + { + type: 'category', + label: 'Getting Started', + items: [ + 'getting-started/installation', + 'getting-started/quick-start', + 'getting-started/basic-setup', + ], + }, + { + type: 'category', + label: 'Core Concepts', + items: [ + 'core-concepts/overview', + 'core-concepts/ballistics-system', + 'core-concepts/asset-types', + 'core-concepts/physical-materials', + ], + }, + { + type: 'category', + label: 'Asset Creation', + items: [ + 'assets/bullet-properties', + 'assets/material-response-maps', + 'assets/physical-material-integration', + ], + }, + { + type: 'category', + label: 'Components', + items: [ + 'components/bullet-actor', + 'components/barrel-component', + 'components/ballistic-impact-component', + ], + }, + { + type: 'category', + label: 'Mathematical Ballistics', + items: [ + 'mathematical/overview', + 'mathematical/penetration-calculations', + 'mathematical/atmospheric-effects', + 'mathematical/drag-curves', + ], + }, + { + type: 'category', + label: 'Networking', + items: [ + 'networking/overview', + 'networking/replication', + 'networking/prediction', + ], + }, + { + type: 'category', + label: 'Performance', + items: [ + 'performance/optimization', + 'performance/pooling', + 'performance/profiling', + ], + }, + { + type: 'category', + label: 'Tutorials', + items: [ + 'tutorials/first-weapon', + 'tutorials/material-setup', + 'tutorials/advanced-ballistics', + 'tutorials/multiplayer-setup', + ], + }, + { + type: 'category', + label: 'API Reference', + items: [ + 'api/overview', + 'api/bullet-actor', + 'api/barrel-component', + 'api/impact-component', + 'api/mathematical-ballistics', + 'api/events', + ], + }, + { + type: 'category', + label: 'Migration Guide', + items: [ + 'migration/from-legacy', + 'migration/new-impact-system', + 'migration/breaking-changes', + ], + }, + 'troubleshooting', + 'changelog', + ], +}; + +export default sidebars; \ No newline at end of file diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 0000000..91ffe27 --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,100 @@ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +} + +/* Custom styles for EasyBallistics */ +.hero__title { + background: linear-gradient(45deg, #ff6b35, #f7931e); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.ballistics-card { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 8px; + padding: 1rem; + margin: 1rem 0; + background: var(--ifm-background-surface-color); +} + +.ballistics-card h3 { + color: var(--ifm-color-primary); + margin-top: 0; +} + +.code-block-highlight { + background: var(--ifm-color-warning-contrast-background); + border-left: 4px solid var(--ifm-color-warning); + padding: 0.5rem; + margin: 1rem 0; +} + +.api-table { + width: 100%; + border-collapse: collapse; + margin: 1rem 0; +} + +.api-table th, +.api-table td { + border: 1px solid var(--ifm-color-emphasis-200); + padding: 0.5rem; + text-align: left; +} + +.api-table th { + background: var(--ifm-color-emphasis-100); + font-weight: bold; +} + +.warning-box { + background: var(--ifm-color-warning-contrast-background); + border: 1px solid var(--ifm-color-warning); + border-radius: 4px; + padding: 1rem; + margin: 1rem 0; +} + +.info-box { + background: var(--ifm-color-info-contrast-background); + border: 1px solid var(--ifm-color-info); + border-radius: 4px; + padding: 1rem; + margin: 1rem 0; +} + +.success-box { + background: var(--ifm-color-success-contrast-background); + border: 1px solid var(--ifm-color-success); + border-radius: 4px; + padding: 1rem; + margin: 1rem 0; +} \ No newline at end of file