void FixedUpdate() { if (!gameObject.activeInHierarchy) { return; } //floating origin and velocity offloading corrections if (!FloatingOrigin.Offset.IsZero() || !Krakensbane.GetFrameVelocity().IsZero()) { transform.position -= FloatingOrigin.OffsetNonKrakensbane; prevPosition -= FloatingOrigin.OffsetNonKrakensbane; startPosition -= FloatingOrigin.OffsetNonKrakensbane; } distanceFromStart = Vector3.Distance(transform.position, startPosition); if (Time.time - startTime < stayTime && transform.parent != null) { transform.rotation = transform.parent.rotation; transform.position = spawnTransform.position; //+(transform.parent.rigidbody.velocity*Time.fixedDeltaTime); } else { if (transform.parent != null && parentRB) { transform.parent = null; rb.isKinematic = false; rb.velocity = parentRB.velocity + Krakensbane.GetFrameVelocityV3f(); } } if (rb && !rb.isKinematic) { //physics if (FlightGlobals.RefFrameIsRotating) { rb.velocity += FlightGlobals.getGeeForceAtPosition(transform.position) * Time.fixedDeltaTime; } //guidance and attitude stabilisation scales to atmospheric density. float atmosMultiplier = Mathf.Clamp01(2.5f * (float) FlightGlobals.getAtmDensity(FlightGlobals.getStaticPressure(transform.position), FlightGlobals.getExternalTemperature(), FlightGlobals.currentMainBody)); //model transform. always points prograde transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(rb.velocity + Krakensbane.GetFrameVelocity(), transform.up), atmosMultiplier * (0.5f * (Time.time - startTime)) * 50 * Time.fixedDeltaTime); if (Time.time - startTime < thrustTime && Time.time - startTime > stayTime) { thrustVector.x = randomThrustDeviation * (1 - (Mathf.PerlinNoise(4 * Time.time, randThrustSeed) * 2)) / massScalar; //this needs to scale w/ rocket mass, or light projectiles will be thrustVector.y = randomThrustDeviation * (1 - (Mathf.PerlinNoise(randThrustSeed, 4 * Time.time) * 2)) / massScalar; //far more affected than heavier ones rb.AddRelativeForce(thrustVector); }//0.012/rocketmass - use .012 as baseline, it's the mass of hte hydra, which the randomTurstdeviation was originally calibrated for } if (Time.time - startTime > thrustTime) { foreach (var pe in pEmitters) { if (pe != null) { pe.emit = false; } } } if (Time.time - startTime > 0.1f + stayTime) { hasPenetrated = true; hasDetonated = false; penTicker = 0; currPosition = transform.position; float dist = (currPosition - prevPosition).magnitude; RocketRay = new Ray(prevPosition, currPosition - prevPosition); var hits = Physics.RaycastAll(RocketRay, dist, 9076737); if (hits.Length > 0) { var orderedHits = hits.OrderBy(x => x.distance); using (var hitsEnu = orderedHits.GetEnumerator()) { while (hitsEnu.MoveNext()) { if (!hasPenetrated || hasDetonated) { break; } RaycastHit hit = hitsEnu.Current; Part hitPart = null; KerbalEVA hitEVA = null; try { hitPart = hit.collider.gameObject.GetComponentInParent <Part>(); hitEVA = hit.collider.gameObject.GetComponentUpwards <KerbalEVA>(); } catch (NullReferenceException e) { Debug.LogWarning("[BDArmory.BDArmory]:NullReferenceException for Kinetic Hit: " + e.Message); return; } 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 = (rb.velocity - (hitPart.rb.velocity + Krakensbane.GetFrameVelocityV3f())).magnitude; } else { impactVelocity = rb.velocity.magnitude; } ProjectileUtils.ApplyDamage(hitPart, hit, 1, 1, caliber, rocketMass, impactVelocity, bulletDmgMult, distanceFromStart, explosive, false, sourceVessel, rocketName); break; } if (hitPart != null && hitPart.vessel == sourceVessel) { continue; //avoid autohit; } Vector3 impactVector = rb.velocity; if (hitPart != null && hitPart.rb != null) { // using relative velocity vector instead of just rocket velocity // since KSP vessels can easily be moving faster than rockets impactVector = rb.velocity - (hitPart.rb.velocity + Krakensbane.GetFrameVelocityV3f()); } float hitAngle = Vector3.Angle(impactVector, -hit.normal); if (ProjectileUtils.CheckGroundHit(hitPart, hit, caliber)) { ProjectileUtils.CheckBuildingHit(hit, rocketMass, rb.velocity, bulletDmgMult); Detonate(hit.point, false); return; } impactVelocity = impactVector.magnitude; if (gravitic) { var ME = hitPart.FindModuleImplementing <ModuleMassAdjust>(); if (ME == null) { ME = (ModuleMassAdjust)hitPart.AddModule("ModuleMassAdjust"); } ME.massMod += massMod; ME.duration += BDArmorySettings.WEAPON_FX_DURATION; } if (concussion) { hitPart.rb.AddForceAtPosition(impactVector.normalized * impulse, hit.point, ForceMode.Acceleration); Detonate(hit.point, false); hasDetonated = true; return; //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, rocketMass, impactVelocity); float penetrationFactor = ProjectileUtils.CalculateArmorPenetration(hitPart, anglemultiplier, hit, penetration, thickness, caliber); if (penetration > thickness) { rb.velocity = rb.velocity * (float)Math.Sqrt(thickness / penetration); if (penTicker > 0) { rb.velocity *= 0.55f; } } if (penetrationFactor > 1) { hasPenetrated = true; ProjectileUtils.ApplyDamage(hitPart, hit, 1, penetrationFactor, caliber, rocketMass, impactVelocity, bulletDmgMult, distanceFromStart, explosive, false, sourceVessel, rocketName); penTicker += 1; ProjectileUtils.CheckPartForExplosion(hitPart); if (explosive) { transform.position += (rb.velocity * Time.fixedDeltaTime) / 3; Detonate(transform.position, false); hasDetonated = true; } } else // stopped by armor { if (hitPart.rb != null && hitPart.rb.mass > 0) { float forceAverageMagnitude = impactVelocity * impactVelocity * (1f / hit.distance) * rocketMass; 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.PooledRocket]: Force Applied " + Math.Round(accelerationMagnitude, 2) + "| Vessel mass in kgs=" + hitPart.vessel.GetTotalMass() * 1000 + "| rocket effective mass =" + rocketMass); } } hasPenetrated = false; ProjectileUtils.ApplyDamage(hitPart, hit, 1, penetrationFactor, caliber, rocketMass, impactVelocity, bulletDmgMult, distanceFromStart, explosive, false, sourceVessel, rocketName); Detonate(hit.point, false); hasDetonated = true; } if (penTicker >= 2) { Detonate(hit.point, false); return; } if (rb.velocity.magnitude <= 100 && hasPenetrated && (Time.time - startTime > thrustTime)) { if (BDArmorySettings.DRAW_DEBUG_LABELS) { Debug.Log("[BDArmory.PooledRocket]: Rocket ballistic velocity too low, stopping"); } Detonate(hit.point, false); return; } if (!hasPenetrated || hasDetonated) { break; } } } } } else if (FlightGlobals.getAltitudeAtPos(currPosition) <= 0) { Detonate(currPosition, false); } prevPosition = currPosition; if (Time.time - startTime > lifeTime) // life's 10s, quite a long time for faster rockets { Detonate(transform.position, true); } if (distanceFromStart >= maxAirDetonationRange)//rockets are performance intensive, lets cull those that have flown too far away { Detonate(transform.position, false); } if (ProximityAirDetonation(distanceFromStart)) { Detonate(transform.position, false); } }
/// <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); }