diff --git a/BDArmory/Distribution/GameData/BDArmory/ChangeLog.txt b/BDArmory/Distribution/GameData/BDArmory/ChangeLog.txt index 93595f2e1..529d2e685 100644 --- a/BDArmory/Distribution/GameData/BDArmory/ChangeLog.txt +++ b/BDArmory/Distribution/GameData/BDArmory/ChangeLog.txt @@ -40,7 +40,12 @@ - Warheads: - Add "bulletDmgMult" field to BDCustomWarhead modules, allowing their damage to be tuned. - Bombs: - - Add Symmetrical Firing option to bombs to enable simultaneously dropping a symmetry twin bomb at the same time the selected bomb is dropped. + - Add Symmetrical Firing option to bombs to enable simultaneously dropping a symmetry twin bomb at the same time the selected bomb is dropped. + - Lasers: + - Lasers now do less damage over distance based on atmospheric and water attenuation. LASER_ATM_GAMMA and LASER_WATER_GAMMA in the settings.cfg control this relationship. Drop-off is given by exp(-GAMMA * DISTANCE * NORMALIZED_ATM_DENSITY), NORMALIZED_ATM_DENSITY is 1 for water transmission. At sea level, lasers will do more damage before 2.5 km, and less damage past 2.5 km. Underwater, lasers will not work past ~10m. In vacuum, lasers will do more damage than previously and be unaffected by attenuation. + - New realism-based formula for microwave damage (lasers with conicAoE = true) based on Friis transmission equation with conical directivity gain based on tanAngle value. Microwave damage is unaffected by atmosphere, but drops off very fast with distance. Microwaves are extremely strong at short ranges. + - Apply tanAngle and distance attenuation to electro-lasers, heat rays, and HE pulse lasers. + - Add laser configuration options to ABL.cfg part file for reference. - Missiles: - Fix graphical glitch where Helmet Mounted Display/Sight appearing in WM modules despite there not being a cockpit with a HMD. - Fix NREs that would occur when a CLOS missile has its guiding targeting pod destroyed. diff --git a/BDArmory/Distribution/GameData/BDArmory/Parts/ABL/ABL.cfg b/BDArmory/Distribution/GameData/BDArmory/Parts/ABL/ABL.cfg index 59998eae9..8ebe27c76 100644 --- a/BDArmory/Distribution/GameData/BDArmory/Parts/ABL/ABL.cfg +++ b/BDArmory/Distribution/GameData/BDArmory/Parts/ABL/ABL.cfg @@ -77,14 +77,24 @@ MODULE maxEffectiveDistance = 5000 maxTargetingRange = 5000 - maxDeviation = 0.0125 + maxDeviation = 0.0125 // Affects pulse divergence for pulse lasers, otherwise is unused for lasers ammoName = ElectricCharge requestResourceAmount = 350 weaponType = laser - laserDamage = 1600 - tanAngle = 0.0001 //controls how quickly damage scales down with distance + laserDamage = 1600 // Base laser or microwave damage. For lasers, final damage is exp(-gamma * distance * atmDensity / 1.225) / (1 + pi * tanAngle^2 * distance^2), where gamma is LASER_ATM_GAMMA for in-atmosphere or LASER_WATER_GAMMA for underwater + tanAngle = 0.0001 // Controls how quickly damage scales down with distance for lasers and microwaves, is converted to directivity for microwave weapons (Directivity = 1/(1 - cos(atan(tanAngle)))) + // LaserGrowTime = -1 // Time laser to be fired to go from base to max damage + // DynamicBeamColor = false // Beam color changes longer laser fired, for growlasers + // beamScrollRate = 0.5f // Beam texture scroll rate, for plasma beams, etc + // beamScalar = 0.01f // X scaling for beam texture. lower is more stretched + // pulseLaser = false // Pulse vs beam + // HEpulses = false // Do the pulses have blast damage + // HeatRay = false // Laser heats parts instead of doing damage + // electroLaser = false // Drains EC from target/induces EMP effects + // conicAoE = false // Is a Microwave Emitter or similar that does a conical AoE instead of a linear beam, use microwave damage formula (laserDamage * Directivity * lamda / (4 * pi * distance)^2), lambda = 0.125m (2.4 GHz) + // beamFOV = 20 // FoV angle of the beam when conicAoE = true. Targets inside FOV are affected, damage is still controlled by tanAngle isAPS = true APSType = missile diff --git a/BDArmory/Settings/BDArmorySettings.cs b/BDArmory/Settings/BDArmorySettings.cs index 0f0ca382b..9df416b8e 100644 --- a/BDArmory/Settings/BDArmorySettings.cs +++ b/BDArmory/Settings/BDArmorySettings.cs @@ -209,6 +209,8 @@ public class BDArmorySettings [BDAPersistentSettingsField] public static float ARMOR_MASS_MOD = 1f; //Armor mass multiplier [BDAPersistentSettingsField] public static bool KERBAL_ERA = true; [BDAPersistentSettingsField] public static float HMDCost = 2000f; + [BDAPersistentSettingsField] public static float LASER_ATM_GAMMA = 0.000341f; // Transmission factor for laser in atmosphere. Gives 250W on target (melts metal) at 512km from 1MW laser at 65kft altitude, based on https://en.wikipedia.org/wiki/Boeing_YAL-1#Intercept_sequence. Also gives ~0.425 scaling at 2.5km (prior scaling factor) + [BDAPersistentSettingsField] public static float LASER_WATER_GAMMA = 0.38f; // Transmission factor for laser in water. 15% at 5m, ~2% at 10m, ~0% at 100m #endregion #region FX diff --git a/BDArmory/Weapons/ModuleWeapon.cs b/BDArmory/Weapons/ModuleWeapon.cs index d65441b9b..e54970ad7 100644 --- a/BDArmory/Weapons/ModuleWeapon.cs +++ b/BDArmory/Weapons/ModuleWeapon.cs @@ -808,6 +808,7 @@ public float GetEngageRange() public float tanAngle = 0.0001f; //Angle of divergeance/2. Theoretical minimum value calculated using θ = (1.22 L/RL)/2, //where L is laser's wavelength and RL is the radius of the mirror (=gun). + private float microwaveDirectivity; // Directivity (gain) for microwave weapons, calculated from tanAngle variable //audioclip paths [KSPField] @@ -2679,7 +2680,7 @@ private void LaserBeam(string vesselname) if (!useRippleFire || !pulseLaser || fireState.Length == 1 || (useRippleFire && i == barrelIndex)) { float damage = 0; - float initialDamage = laserDamage * 0.425f; + float initialDamage = laserDamage; Transform tf = fireTransforms[i]; LineRenderer lr = laserRenderers[i]; Vector3 rayDirection = tf.forward; @@ -2735,9 +2736,10 @@ private void LaserBeam(string vesselname) KerbalEVA eva = hit.collider.gameObject.GetComponentUpwards(); Part p = eva ? eva.part : hit.collider.gameObject.GetComponentInParent(); + float distance = hit.distance; + initialDamage = (laserDamage > 0) ? LaserDamage(laserDamage, tanAngle, distance, tf.position, hit.point) : laserDamage; if (p && p.vessel && p.vessel != vessel) { - float distance = hit.distance; if (instagib) { p.AddInstagibDamage(); @@ -2754,7 +2756,7 @@ private void LaserBeam(string vesselname) ///////////////////////////////////////////////// if (!VesselModuleRegistry.IgnoredVesselTypes.Contains(p.vessel.vesselType)) { - float EMPDamage = laserDamage * (pulseLaser ? 1 : TimeWarp.fixedDeltaTime) * (BDArmorySettings.DMG_MULTIPLIER / 100); + float EMPDamage = initialDamage * (pulseLaser ? 1 : TimeWarp.fixedDeltaTime) * (BDArmorySettings.DMG_MULTIPLIER / 100); Part closestCommand = null; float distToCommandSqr = float.PositiveInfinity; //lets find out which command part is closest to the hit Vector3 commandDir = Vector3.zero; @@ -2884,9 +2886,6 @@ private void LaserBeam(string vesselname) HitpointTracker armor = p.GetComponent(); if (laserDamage > 0) { - var angularSpread = tanAngle * distance; //Scales down the damage based on the increased surface area of the area being hit by the laser. Think flashlight on a wall. - initialDamage = laserDamage / (1 + Mathf.PI * angularSpread * angularSpread) * 0.425f; - if (armor != null)// technically, lasers shouldn't do damage until armor gone, but that would require localized armor tracking instead of the monolithic model currently used { damage = (initialDamage * (pulseLaser ? 1 : TimeWarp.fixedDeltaTime)) * Mathf.Clamp((1 - (BDAMath.Sqrt(armor.Diffusivity * (armor.Density / 1000)) * armor.Armor) / initialDamage), 0.005f, 1); //old calc lacked a clamp, could potentially become negative damage @@ -2985,13 +2984,11 @@ private void LaserBeam(string vesselname) else { if (electroLaser || HeatRay) continue; - var angularSpread = tanAngle * hit.distance; //Scales down the damage based on the increased surface area of the area being hit by the laser. Think flashlight on a wall. - initialDamage = laserDamage / (1 + Mathf.PI * angularSpread * angularSpread) * 0.425f; if (!BDArmorySettings.PAINTBALL_MODE) ProjectileUtils.CheckBuildingHit(hit, initialDamage, pulseLaser); if (HEpulses) { ExplosionFx.CreateExplosion(hit.point, - (laserDamage / 10000), + (initialDamage / 10000), explModelPath, explSoundPath, ExplosionSourceType.Bullet, 1, null, vessel.vesselName, null); } } @@ -3011,7 +3008,7 @@ private void LaserBeam(string vesselname) if (HEpulses) { ExplosionFx.CreateExplosion(tf.position + rayDirection * raycastDistance, - (laserDamage / 10000), + (initialDamage / 10000), explModelPath, explSoundPath, ExplosionSourceType.Bullet, 1, null, vessel.vesselName, null); } } @@ -3028,6 +3025,21 @@ private void LaserBeam(string vesselname) } } + // Adjusts laser damage based on tanAngle, distance, firing and hit points + private float LaserDamage(float laserDamage, float tanAngle, float distance, Vector3 firingPoint, Vector3 hitPoint) + { + bool underwater = FlightGlobals.currentMainBody.ocean && FlightGlobals.getAltitudeAtPos(firingPoint) < 0 || FlightGlobals.currentMainBody.ocean && FlightGlobals.getAltitudeAtPos(hitPoint) < 0; + float firingDensity = (float)FlightGlobals.getAtmDensity(FlightGlobals.getStaticPressure(firingPoint), FlightGlobals.getExternalTemperature(firingPoint)); + float hitDensity = (float)FlightGlobals.getAtmDensity(FlightGlobals.getStaticPressure(hitPoint), FlightGlobals.getExternalTemperature(hitPoint)); + float atmDensity = (firingDensity + hitDensity) / 2f; // Average densities instead of complicated integral along firing path + + float gamma = underwater ? BDArmorySettings.LASER_WATER_GAMMA : BDArmorySettings.LASER_ATM_GAMMA; + float normDensity = underwater ? 1f : Mathf.Max(0f, atmDensity / 1.225f); + float transmission = Mathf.Exp(-gamma * distance * normDensity); + float angularSpread = tanAngle * distance; //Scales down the damage based on the increased surface area of the area being hit by the laser. Think flashlight on a wall. + return laserDamage * transmission / (1 + Mathf.PI * angularSpread * angularSpread); + } + //Conic AoE 'beam' for AoE beam weapons - tractor beams, microwave EMP, heatrays, etc. private void MicrowaveBeam(string vesselname) { @@ -3041,7 +3053,7 @@ private void MicrowaveBeam(string vesselname) } WeaponFX(); float damage = 0; - float initialDamage = laserDamage * 0.425f; + float initialDamage = laserDamage; var beamLength = engageRangeMax / 1000; var beamAngle = Mathf.Tan(beamFOV * Mathf.Deg2Rad) * beamLength; //assumes a default 1km long, 45deg angle cone model //Also, TOD - add conic AoE ingo to the GetInfo weapon infocard @@ -3066,19 +3078,18 @@ private void MicrowaveBeam(string vesselname) if (VesselModuleRegistry.IgnoredVesselTypes.Contains(loadedvessels.Current.vesselType)) continue; if (Vector3.Angle(loadedvessels.Current.CoM - fireTransforms[0].transform.position, fireTransforms[0].forward) > beamFOV / 2f) continue; - if (loadedvessels.Current.IsUnderwater()) continue; //would microwaves work underwater...? + if (loadedvessels.Current.IsUnderwater()) continue; // Microwaves don't work underwater if (!friendlyFire) //don't affect friendly targets. Something something phased array dynamic beam shaping { var wms = VesselModuleRegistry.GetModule(loadedvessels.Current); if (wms == null || wms.Team == WeaponManager.Team) continue; } - if (!loadedvessels.Current.Splashed && vessel.IsUnderwater()) //not sure if a water transition should serve as a barrier, but easily commented out + if (!loadedvessels.Current.Splashed && vessel.IsUnderwater()) //water transition is barrier continue; if (loadedvessels.Current == vessel) continue; float distance = (loadedvessels.Current.CoM - fireTransforms[0].transform.position).magnitude; if (distance > maxTargetingRange) continue; - var angularSpread = tanAngle * distance; //Scales down the damage based on the increased surface area of the area being hit by the laser. Think flashlight on a wall. - initialDamage = (laserDamage * 0.425f) / (1 + Mathf.PI * angularSpread * angularSpread); + initialDamage = MicrowaveDamage(laserDamage, microwaveDirectivity, distance); if (electroLaser) { @@ -3178,7 +3189,7 @@ private void MicrowaveBeam(string vesselname) if (HEpulses) { ExplosionFx.CreateExplosion(h.point, - (laserDamage / 10000), + (initialDamage / 10000), explModelPath, explSoundPath, ExplosionSourceType.Bullet, 1, null, vessel.vesselName, null, Hitpart: hitPart); } if (HeatRay) @@ -3250,6 +3261,15 @@ private void MicrowaveBeam(string vesselname) } } + // Adjusts microwwave damage based on tanAngle, distance, firing and directivity (gain) + private float MicrowaveDamage(float baseDamage, float directivity, float distance) + { + float wavelength = 0.124913524166667f; // 2.4 GHz wavelength, 299792458f / 2400000000 + float sqrTerm = wavelength / (4 * Mathf.PI * Mathf.Max(distance, 1f)); + float finalDamage = baseDamage * Mathf.Min(directivity * (sqrTerm * sqrTerm), 1000f); // Clamp max damage at very short ranges, https://en.wikipedia.org/wiki/Friis_transmission_equation + return finalDamage; + } + public void SetupLaserSpecifics() { //chargeSound = SoundUtils.GetAudioClip(chargeSoundPath); @@ -3259,6 +3279,7 @@ public void SetupLaserSpecifics() } Color laserColor = GUIUtils.ParseColor255(projectileColor); laserColor.a = laserColor.a / 2; + microwaveDirectivity = 2f / (1 - Mathf.Cos(Mathf.Atan(tanAngle))); // Directivity for microwaves, based on conical beam https://en.wikipedia.org/wiki/Directivity#Relation_to_beam_width if (conicAoE) { var cone = GameDatabase.Instance.GetModel(laserModelPath); @@ -6144,8 +6165,7 @@ IEnumerator KillIncomingProjectile(PooledBullet shell, PooledRocket rocket) if (delayTime < 0) { delayTime = rocket != null ? 0.5f : (shell.bulletMass * (1 - Mathf.Clamp(shell.tntMass / shell.bulletMass, 0f, 0.95f) / 2)); //for shells, laser delay time is based on shell mass/HEratio. The heavier the shell, the more mass to burn through. Don't expect to stop sabots via laser APS - var angularSpread = tanAngle * targetDistance; - delayTime /= ((laserDamage / (1 + Mathf.PI * angularSpread * angularSpread) * 0.425f) / 100); + delayTime /= LaserDamage(laserDamage, tanAngle, targetDistance, part.rb.position, targetPosition) / 100f; if (delayTime < TimeWarp.fixedDeltaTime) delayTime = 0; } yield return new WaitForSeconds(delayTime);