// 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(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 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(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) { }