/// <summary> /// Check for bullet collision in the upcoming period. /// </summary> /// <param name="period">Period to consider, typically Time.fixedDeltaTime</param> /// <param name="reverse">Also perform raycast in reverse to detect collisions from rays starting within an object.</param> /// <returns>true if a collision is detected, false otherwise.</returns> public bool CheckBulletCollision(float period, bool reverse = false) { //reset our hit variables to default state hasPenetrated = true; hasDetonated = false; hasRicocheted = false; penTicker = 0; currPosition = transform.position; float dist = currentVelocity.magnitude * period; bulletRay = new Ray(currPosition, currentVelocity + 0.5f * period * FlightGlobals.getGeeForceAtPosition(transform.position)); var hitCount = Physics.RaycastNonAlloc(bulletRay, hits, dist, 9076737); if (hitCount == hits.Length) // If there's a whole bunch of stuff in the way (unlikely), then we need to increase the size of our hits buffer. { hits = Physics.RaycastAll(bulletRay, dist, 9076737); hitCount = hits.Length; } int reverseHitCount = 0; if (reverse) { reverseHitCount = Physics.RaycastNonAlloc(new Ray(currPosition + currentVelocity * period, -currentVelocity), reverseHits, dist, 9076737); if (reverseHitCount == reverseHits.Length) { reverseHits = Physics.RaycastAll(new Ray(currPosition + currentVelocity * period, -currentVelocity), dist, 9076737); reverseHitCount = reverseHits.Length; } for (int i = 0; i < reverseHitCount; ++i) { reverseHits[i].distance = dist - reverseHits[i].distance; } } if (hitCount + reverseHitCount > 0) { var orderedHits = hits.Take(hitCount).Concat(reverseHits.Take(reverseHitCount)).OrderBy(x => x.distance); using (var hitsEnu = orderedHits.GetEnumerator()) { RaycastHit hit; Part hitPart; KerbalEVA hitEVA; while (hitsEnu.MoveNext()) { if (!hasPenetrated || hasRicocheted || hasDetonated) { return(true); } hit = hitsEnu.Current; hitPart = null; hitEVA = null; try { hitPart = hit.collider.gameObject.GetComponentInParent <Part>(); hitEVA = hit.collider.gameObject.GetComponentUpwards <KerbalEVA>(); } catch (NullReferenceException e) { Debug.Log("[BDArmory.PooledBullet]:NullReferenceException for Ballistic Hit: " + e.Message); return(true); } if (hitPart != null && ProjectileUtils.IsIgnoredPart(hitPart)) { continue; // Ignore ignored parts. } if (hitEVA != null) { hitPart = hitEVA.part; // relative velocity, separate from the below statement, because the hitpart might be assigned only above if (hitPart.rb != null) { impactVelocity = (currentVelocity * dragVelocityFactor - (hitPart.rb.velocity + Krakensbane.GetFrameVelocityV3f())).magnitude; } else { impactVelocity = currentVelocity.magnitude * dragVelocityFactor; } distanceTraveled += hit.distance; ProjectileUtils.ApplyDamage(hitPart, hit, 1, 1, caliber, bulletMass, impactVelocity, bulletDmgMult, distanceTraveled, explosive, incendiary, hasRicocheted, sourceVessel, bullet.name, team); ExplosiveDetonation(hitPart, hit, bulletRay); KillBullet(); // Kerbals are too thick-headed for penetration... return(true); } if (hitPart != null && hitPart.vessel == sourceVessel) { continue; //avoid autohit; } Vector3 impactVector = currentVelocity; if (hitPart != null && hitPart.rb != null) { // using relative velocity vector instead of just bullet velocity // since KSP vessels might move faster than bullets impactVector = currentVelocity * dragVelocityFactor - (hitPart.rb.velocity + Krakensbane.GetFrameVelocityV3f()); } float hitAngle = Vector3.Angle(impactVector, -hit.normal); if (ProjectileUtils.CheckGroundHit(hitPart, hit, caliber)) { ProjectileUtils.CheckBuildingHit(hit, bulletMass, currentVelocity, bulletDmgMult); if (!RicochetScenery(hitAngle)) { ExplosiveDetonation(hitPart, hit, bulletRay); KillBullet(); distanceTraveled += hit.distance; return(true); } else { DoRicochet(hitPart, hit, hitAngle, hit.distance / dist, period); return(true); } } //Standard Pipeline Hitpoints, Armor and Explosives impactVelocity = impactVector.magnitude; if (massMod != 0) { var ME = hitPart.FindModuleImplementing <ModuleMassAdjust>(); if (ME == null) { ME = (ModuleMassAdjust)hitPart.AddModule("ModuleMassAdjust"); } ME.massMod += massMod; ME.duration += BDArmorySettings.WEAPON_FX_DURATION; } if (impulse != 0 && hitPart.rb != null) { hitPart.rb.AddForceAtPosition(impactVector.normalized * impulse, hit.point, ForceMode.Acceleration); ProjectileUtils.ApplyScore(hitPart, sourceVessel, distanceTraveled, 0, bullet.name); break; //impulse rounds shouldn't penetrate/do damage } float anglemultiplier = (float)Math.Cos(Math.PI * hitAngle / 180.0); float thickness = ProjectileUtils.CalculateThickness(hitPart, anglemultiplier); float penetration = ProjectileUtils.CalculatePenetration(caliber, bulletMass, impactVelocity, apBulletMod); float penetrationFactor = ProjectileUtils.CalculateArmorPenetration(hitPart, anglemultiplier, hit, penetration, thickness, caliber); if (penetration > thickness) { currentVelocity = currentVelocity * (float)Math.Sqrt(thickness / penetration); if (penTicker > 0) { currentVelocity *= 0.55f; } flightTimeElapsed -= period; } if (penetrationFactor >= 2) { //its not going to bounce if it goes right through hasRicocheted = false; } else { if (RicochetOnPart(hitPart, hit, hitAngle, impactVelocity, hit.distance / dist, period)) { hasRicocheted = true; } } if (penetrationFactor > 1 && !hasRicocheted) //fully penetrated continue ballistic damage { hasPenetrated = true; ProjectileUtils.ApplyDamage(hitPart, hit, 1, penetrationFactor, caliber, bulletMass, impactVelocity, bulletDmgMult, distanceTraveled, explosive, incendiary, hasRicocheted, sourceVessel, bullet.name, team); penTicker += 1; ProjectileUtils.CheckPartForExplosion(hitPart); //Explosive bullets that penetrate should explode shortly after //if penetration is very great, they will have moved on //checking velocity as they would not be able to come out the other side //if (explosive && penetrationFactor < 3 || currentVelocity.magnitude <= 800f) if (explosive) { //move bullet transform.position += (currentVelocity * period) / 3; distanceTraveled += hit.distance; ExplosiveDetonation(hitPart, hit, bulletRay); hasDetonated = true; KillBullet(); return(true); } } else if (!hasRicocheted) // explosive bullets that get stopped by armor will explode { if (hitPart.rb != null && hitPart.rb.mass > 0) { float forceAverageMagnitude = impactVelocity * impactVelocity * (1f / hit.distance) * (bulletMass - tntMass); float accelerationMagnitude = forceAverageMagnitude / (hitPart.vessel.GetTotalMass() * 1000); hitPart.rb.AddForceAtPosition(impactVector.normalized * accelerationMagnitude, hit.point, ForceMode.Acceleration); if (BDArmorySettings.DRAW_DEBUG_LABELS) { Debug.Log("[BDArmory.PooledBullet]: Force Applied " + Math.Round(accelerationMagnitude, 2) + "| Vessel mass in kgs=" + hitPart.vessel.GetTotalMass() * 1000 + "| bullet effective mass =" + (bulletMass - tntMass)); } } distanceTraveled += hit.distance; hasPenetrated = false; ProjectileUtils.ApplyDamage(hitPart, hit, 1, penetrationFactor, caliber, bulletMass, impactVelocity, bulletDmgMult, distanceTraveled, explosive, incendiary, hasRicocheted, sourceVessel, bullet.name, team); ExplosiveDetonation(hitPart, hit, bulletRay); hasDetonated = true; KillBullet(); return(true); } ///////////////////////////////////////////////////////////////////////////////// // penetrated after a few ticks ///////////////////////////////////////////////////////////////////////////////// //penetrating explosive //richochets if ((penTicker >= 2 && explosive) || (hasRicocheted && explosive)) { //detonate ExplosiveDetonation(hitPart, hit, bulletRay, airDetonation); distanceTraveled += hit.distance; return(true); } //bullet should not go any further if moving too slowly after hit //smaller caliber rounds would be too deformed to do any further damage if (currentVelocity.magnitude <= 100 && hasPenetrated) { if (BDArmorySettings.DRAW_DEBUG_LABELS) { Debug.Log("[BDArmory.PooledBullet]: Bullet Velocity too low, stopping"); } KillBullet(); distanceTraveled += hit.distance; return(true); } } //end While } //end enumerator } //end of hits return(false); }