334 lines
8.9 KiB
C++
334 lines
8.9 KiB
C++
// 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);
|
|
|
|
// Initialize firing barrel pointer
|
|
FiringBarrel = nullptr;
|
|
|
|
// Create the new ballistic impact component
|
|
BallisticImpactComponent = CreateDefaultSubobject<UEBBallisticImpactComponent>(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;
|
|
}
|
|
|
|
// Spalling Implementation
|
|
bool AEBBullet::ShouldGenerateSpalling(FVector ImpactVelocity, UPhysicalMaterial* Material) const
|
|
{
|
|
if (!EnableSpalling || IsSpallFragment || !Material || !MaterialResponseMap)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(Material);
|
|
if (!ResponseEntry || !ResponseEntry->EnableSpalling)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float ImpactSpeed = ImpactVelocity.Size();
|
|
return ImpactSpeed >= ResponseEntry->SpallVelocityThreshold;
|
|
}
|
|
|
|
void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, UPhysicalMaterial* Material, AActor* HitActor)
|
|
{
|
|
if (!ShouldGenerateSpalling(ImpactVelocity, Material))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(Material);
|
|
if (!ResponseEntry)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSubclassOf<AEBBullet> FragmentClass = ResponseEntry->SpallFragmentClass;
|
|
if (!FragmentClass)
|
|
{
|
|
FragmentClass = GetClass();
|
|
}
|
|
|
|
int32 FragmentCount = FMath::RandRange(1, ResponseEntry->SpallFragmentCount);
|
|
float SpreadAngleRad = FMath::DegreesToRadians(ResponseEntry->SpallSpreadAngle);
|
|
float BaseVelocityMagnitude = ImpactVelocity.Size() * ResponseEntry->SpallVelocityMultiplier;
|
|
float FragmentMass = GetEffectiveMass() * ResponseEntry->SpallMassMultiplier;
|
|
|
|
FVector BaseDirection = UKismetMathLibrary::GetReflectionVector(ImpactVelocity.GetSafeNormal(), ImpactNormal);
|
|
|
|
for (int32 i = 0; i < FragmentCount; i++)
|
|
{
|
|
FVector FragmentDirection = RandomStream.VRandCone(BaseDirection, SpreadAngleRad);
|
|
float VelocityVariation = RandomStream.FRandRange(0.7f, 1.3f);
|
|
FVector FragmentVelocity = FragmentDirection * BaseVelocityMagnitude * VelocityVariation;
|
|
|
|
FVector SpawnLocation = ImpactLocation + ImpactNormal * 2.0f;
|
|
|
|
if (HasAuthority())
|
|
{
|
|
AEBBullet* Fragment = GetWorld()->SpawnActor<AEBBullet>(FragmentClass, SpawnLocation, FragmentVelocity.Rotation());
|
|
if (Fragment)
|
|
{
|
|
Fragment->SetOwner(GetOwner());
|
|
Fragment->SetInstigator(GetInstigator());
|
|
Fragment->Velocity = FragmentVelocity;
|
|
Fragment->Mass = FragmentMass;
|
|
Fragment->OwnerSafe = false;
|
|
Fragment->IsSpallFragment = true;
|
|
Fragment->SetFiringBarrel(GetFiringBarrel());
|
|
|
|
if (Fragment->MaterialResponseMap == nullptr)
|
|
{
|
|
Fragment->MaterialResponseMap = MaterialResponseMap;
|
|
}
|
|
|
|
Fragment->IgnoredActors.Add(HitActor);
|
|
Fragment->IgnoredActors.Add(this);
|
|
Fragment->IgnoredActors.Append(IgnoredActors);
|
|
}
|
|
}
|
|
|
|
OnSpallFragmentGenerated(SpawnLocation, FragmentVelocity, FragmentMass, Material);
|
|
}
|
|
}
|
|
|
|
void AEBBullet::OnSpallFragmentGenerated_Implementation(FVector FragmentLocation, FVector FragmentVelocity, float FragmentMass, UPhysicalMaterial* Material)
|
|
{
|
|
} |