// 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; }