Mostly refactoring, and removing the onVesselDestroy event subscription, which wasn't being very useful.
[AntennaRange.git] / AntennaRelay.cs
blob:a/AntennaRelay.cs -> blob:b/AntennaRelay.cs
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // AntennaRelay.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// Communotron 88-88 - Suitable throughout the Kerbol system.  
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // this list of conditions and the following disclaimer.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
  /// <summary>
  /// Relay code at the heart of AntennaRange
  /// </summary>
public class AntennaRelay public class AntennaRelay
{ {
  private static readonly System.Diagnostics.Stopwatch searchTimer = new System.Diagnostics.Stopwatch();
  private const long millisecondsBetweenSearches = 125L;
   
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
protected CelestialBody Kerbin; private static CelestialBody _Kerbin;
   
protected System.Diagnostics.Stopwatch searchTimer; /// <summary>
protected long millisecondsBetweenSearches; /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
  /// </summary>
  public static CelestialBody Kerbin
  {
  get
  {
  if (_Kerbin == null && FlightGlobals.ready)
  {
  _Kerbin = FlightGlobals.GetHomeBody();
  }
   
  return _Kerbin;
  }
  }
   
  private long lastSearch;
   
  private bool canTransmit;
  private bool isChecked;
   
  private IAntennaRelay nearestRelay;
  private IAntennaRelay bestOccludedRelay;
   
  /// <summary>
  /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
  /// <see cref="AntennaRange.IAntennaRelay"/>
  /// </summary>
  protected IAntennaRelay moduleRef;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public Vessel vessel public virtual Vessel vessel
  {
  get
  {
  return this.moduleRef.vessel;
  }
  }
   
  /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets or sets the nearest relay. /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
/// </summary> /// </summary>
/// <value>The nearest relay</value> public virtual CelestialBody firstOccludingBody
public IAntennaRelay nearestRelay  
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the transmit distance. /// Gets the transmit distance.
/// </summary> /// </summary>
/// <value>The transmit distance.</value> /// <value>The transmit distance.</value>
public double transmitDistance public double transmitDistance
{ {
get get
{ {
this.nearestRelay = this.FindNearestRelay(); if (this.KerbinDirect || this.targetRelay == null)
  {
// If there is no available relay nearby... return this.DistanceTo(Kerbin);
if (nearestRelay == null)  
{  
// .. return the distance to Kerbin  
return this.DistanceTo(this.Kerbin);  
} }
else else
{ {
/// ...otherwise, return the distance to the nearest available relay. return this.DistanceTo(this.targetRelay);
return this.DistanceTo(nearestRelay); }
} }
} }
}  
  /// <summary>
/// <summary> /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// The maximum distance at which this relay can operate. /// </summary>
/// </summary> public virtual double nominalTransmitDistance
/// <value>The max transmit distance.</value>  
public virtual float maxTransmitDistance  
{ {
get; get;
set; set;
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// The maximum distance at which this relay can operate.
/// the current relay attempt. /// </summary>
/// </summary> /// <value>The max transmit distance.</value>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> public virtual double maxTransmitDistance
public virtual bool relayChecked {
  get;
  set;
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public virtual bool KerbinDirect
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit() public virtual bool CanTransmit()
{ {
if (this.transmitDistance > this.maxTransmitDistance) return this.canTransmit;
{ }
return false;  
} /// <summary>
  /// Finds the nearest relay.
  /// </summary>
  /// <returns>The nearest relay or null, if no relays in range.</returns>
  public void FindNearestRelay()
  {
  if (!FlightGlobals.ready)
  {
  return;
  }
   
  if (!searchTimer.IsRunning)
  {
  searchTimer.Start();
  }
   
  Tools.DebugLogger log;
  #if DEBUG
  log = Tools.DebugLogger.New(this);
  #endif
   
  long searchTime = searchTimer.ElapsedMilliseconds;
  long timeSinceLast = searchTime - this.lastSearch;
   
  if (timeSinceLast < millisecondsBetweenSearches)
  {
  log.AppendFormat(
  "{0}: Target search skipped because it's not time to search again yet ({1} - {2}) < {3})",
  this, searchTime, this.lastSearch, millisecondsBetweenSearches
  );
  log.Print();
  return;
  }
   
  // Skip vessels that have already been checked for a nearest relay this pass.
  if (this.isChecked)
  {
  log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
  this);
  log.Print();
  return;
  }
   
  log.AppendFormat("{0}: Target search started at {1} ms ({2} ms since last search).",
  this.ToString(), searchTime, timeSinceLast);
   
  #if DEBUG
  try {
  #endif
  // Set this vessel as checked, so that we don't check it again.
  this.isChecked = true;
   
  this.lastSearch = searchTime;
   
  // Blank everything we're trying to find before the search.
  this.firstOccludingBody = null;
  this.bestOccludedRelay = null;
  this.targetRelay = null;
  this.nearestRelay = null;
   
  // Default to KerbinDirect = true in case something in here doesn't work right.
  this.KerbinDirect = true;
   
  CelestialBody bodyOccludingBestOccludedRelay = null;
   
  double nearestRelaySqrDistance = double.PositiveInfinity;
  double bestOccludedSqrDistance = double.PositiveInfinity;
  double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
   
  /*
  * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
  * far away. When we find a candidate, get through its antennae for relays which have not been checked yet
  * and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison
  * against future finds.
  * */
  Vessel potentialVessel;
  IList<IAntennaRelay> vesselRelays;
  for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
  {
  log.AppendFormat("\nFetching vessel at index {0}", vIdx);
  potentialVessel = FlightGlobals.Vessels[vIdx];
   
  if (potentialVessel == null)
  {
  log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
  log.Print();
  return;
  }
  #if DEBUG
  else
  {
  log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
  }
  #endif
   
  // Skip vessels of the wrong type.
  log.Append("\n\tchecking vessel type");
  switch (potentialVessel.vesselType)
  {
  case VesselType.Debris:
  case VesselType.Flag:
  case VesselType.EVA:
  case VesselType.SpaceObject:
  case VesselType.Unknown:
  log.Append("\n\tSkipping because vessel is the wrong type.");
  continue;
  default:
  break;
  }
   
  log.Append("\n\tchecking if vessel is this vessel");
  // Skip vessels with the wrong ID
  if (potentialVessel.id == vessel.id)
  {
  log.Append("\n\tSkipping because vessel is this vessel.");
  continue;
  }
   
  // Find the distance from here to the vessel...
  log.Append("\n\tgetting distance to potential vessel");
  double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
  log.Append("\n\tgetting vessel relays");
  vesselRelays = potentialVessel.GetAntennaRelays();
  log.AppendFormat("\n\t\tvesselRelays: {0}",
  vesselRelays == null ? "null" : vesselRelays.Count.ToString());
   
  CelestialBody fob = null;
   
  log.Append("\n\tdoing LOS check");
  // Skip vessels to which we do not have line of sight.
  if (
  ARConfiguration.RequireLineOfSight &&
  !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
  )
  {
  log.Append("\n\tfailed LOS check");
  this.firstOccludingBody = fob;
   
  log.AppendFormat("\n\t{0}: Vessel {1} not in line of sight.",
  this.ToString(), potentialVessel.vesselName);
   
  log.AppendFormat("\n\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
  log.AppendFormat("\n\t\tbestOccludedSqrDistance: {0}", bestOccludedSqrDistance);
  log.AppendFormat("\n\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance);
   
  if (
  (potentialSqrDistance < bestOccludedSqrDistance) &&
  (potentialSqrDistance < maxTransmitSqrDistance)
  )
  {
  log.Append("\n\t\t...vessel is close enough to check for occluded relays");
  log.AppendFormat("\n\t\tthis: {0}", this);
  log.AppendFormat("\n\t\tpotentialVessel: {0}",
  potentialVessel == null ? "null" : potentialVessel.ToString());
  log.AppendFormat("\n\t\tvesselRelays: {0}",
  vesselRelays == null ? "null" : vesselRelays.ToString());
   
  log.AppendFormat("\n\t\t{0}: Checking {1} relays on occluded vessel {2}.",
  this.ToString(),
  vesselRelays.Count,
  potentialVessel
  );
   
  IAntennaRelay occludedRelay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  occludedRelay = vesselRelays[rIdx];
   
  log.AppendFormat(
  "\n\t\t{0}: Checking candidate for bestOccludedRelay: {1}" +
  "\n\t\tCanTransmit: {2}",
  this.ToString(), occludedRelay, occludedRelay.CanTransmit()
  );
   
  if (occludedRelay.CanTransmit())
  {
  this.bestOccludedRelay = occludedRelay;
  bodyOccludingBestOccludedRelay = fob;
  bestOccludedSqrDistance = potentialSqrDistance;
   
  log.AppendFormat("\n\t{0}: Found new bestOccludedRelay: {1}" +
  " (blocked by {2}; distance: {3} m)",
  this.ToString(),
  occludedRelay.ToString(),
  fob,
  potentialSqrDistance
  );
  break;
  }
  }
  }
   
  log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
  continue;
  }
   
  log.Append("\n\tpassed LOS check");
   
  /*
  * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
  * */
  if (potentialSqrDistance > nearestRelaySqrDistance)
  {
   
  log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
  this.ToString(),
  potentialVessel.vesselName
  );
  continue;
  }
   
  log.Append("\n\tpassed distance check");
   
  IAntennaRelay potentialRelay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  log.AppendFormat("\n\t\tfetching vessel relay at index {0}", rIdx);
  potentialRelay = vesselRelays[rIdx];
  log.AppendFormat("\n\t\tgot relay {0}", potentialRelay == null ? "null" : potentialRelay.ToString());
   
  if (potentialRelay == null)
  {
  log.Append("\n\t\t...skipping null relay");
  continue;
  }
   
  if (
  potentialRelay.CanTransmit() &&
  (potentialRelay.targetRelay == null || potentialRelay.targetRelay.vessel != this.vessel))
  {
  // @TODO: Moved this here from outside the loop; why was it there?
  nearestRelaySqrDistance = potentialSqrDistance;
  this.nearestRelay = potentialRelay;
   
  log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
  this.ToString(),
  this.nearestRelay.ToString(),
  Math.Sqrt(nearestRelaySqrDistance)
  );
   
  break;
  }
  }
  }
   
  CelestialBody bodyOccludingKerbin = null;
   
  double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
  kerbinSqrDistance *= kerbinSqrDistance;
   
  log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
   
  // If we don't have LOS to Kerbin, focus on relays
  if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
  {
  log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
  // nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
  // Therefore, this will only be true if a valid relay is in range.
  if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
  {
  log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrDistance, maxTransmitSqrDistance);
   
  this.KerbinDirect = false;
  this.canTransmit = true;
  this.targetRelay = this.nearestRelay;
  }
  // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
  else
  {
  log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrDistance, maxTransmitSqrDistance);
   
  this.canTransmit = false;
   
  // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
  // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
  if (bestOccludedSqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
  bestOccludedRelay, kerbinSqrDistance);
   
  this.KerbinDirect = false;
   
  // If the nearest relay is closer than the best occluded relay, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < bestOccludedSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
  }
  // Otherwise, target the best occluded relay.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = bestOccludedRelay;
  this.firstOccludingBody = bodyOccludingBestOccludedRelay;
  }
  }
  // Otherwise, check Kerbin against the nearest relay.
  // Since we have LOS, blank the first occluding body.
  else
  {
  log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
  bestOccludedRelay, kerbinSqrDistance);
   
  this.firstOccludingBody = null;
   
  // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  }
  }
  }
  // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
else else
{ {
return true; log.AppendFormat("\n\tKerbin is in LOS.");
}  
} // If the nearest relay is closer than Kerbin and in range, transmit to it.
  if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
/// <summary> {
/// Finds the nearest relay. log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
/// </summary> this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
/// <returns>The nearest relay or null, if no relays in range.</returns> nearestRelaySqrDistance, maxTransmitSqrDistance);
public IAntennaRelay FindNearestRelay()  
{ this.canTransmit = true;
if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)  
{ // If the nearestRelay is closer than Kerbin, use it.
return this.nearestRelay; if (nearestRelaySqrDistance < kerbinSqrDistance)
} {
  log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
if (this.searchTimer.IsRunning) this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
{ nearestRelaySqrDistance, kerbinSqrDistance);
this.searchTimer.Stop();  
this.searchTimer.Reset(); this.KerbinDirect = false;
} this.targetRelay = this.nearestRelay;
  }
this.searchTimer.Start(); // Otherwise, Kerbin is closer, so use it.
  else
// Set this relay as checked, so that we don't check it again. {
this.relayChecked = true; log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
// Get a list of vessels within transmission range. nearestRelaySqrDistance, kerbinSqrDistance);
List<Vessel> nearbyVessels = FlightGlobals.Vessels  
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance) this.KerbinDirect = true;
.ToList(); this.targetRelay = null;
  }
nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris); }
  // If the nearest relay is out of range, we still need to check on Kerbin.
Tools.PostDebugMessage(string.Format( else
"{0}: Non-debris vessels in range: {1}", {
this.GetType().Name, log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
nearbyVessels.Count this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
)); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
// Remove this vessel. // If Kerbin is in range, use it.
nearbyVessels.RemoveAll(v => v.id == vessel.id); if (kerbinSqrDistance <= maxTransmitSqrDistance)
  {
Tools.PostDebugMessage(string.Format( log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
"{0}: Vessels in range excluding self: {1}", kerbinSqrDistance, maxTransmitSqrDistance);
this.GetType().Name,  
nearbyVessels.Count this.canTransmit = true;
)); this.KerbinDirect = true;
  this.targetRelay = null;
// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range. }
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
  // Kerbin and bestOccludedRelay
Tools.PostDebugMessage(string.Format( else
"{0}: Found {1} nearby relays.", {
this.GetType().Name, log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
nearbyRelays.Count kerbinSqrDistance, maxTransmitSqrDistance);
));  
  this.canTransmit = false;
// Remove all relays already checked this time.  
nearbyRelays.RemoveAll(r => r.relayChecked); // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
  // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
Tools.PostDebugMessage(string.Format( if (bestOccludedSqrDistance < kerbinSqrDistance)
"{0}: Found {1} nearby relays not already checked.", {
this.GetType().Name, log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
nearbyRelays.Count bestOccludedRelay, kerbinSqrDistance);
));  
  this.KerbinDirect = false;
// Remove all relays that cannot transmit.  
// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin. // If the nearest relay is closer than the best occluded relay, pick it.
nearbyRelays.RemoveAll(r => !r.CanTransmit()); // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < bestOccludedSqrDistance)
Tools.PostDebugMessage(string.Format( {
"{0}: Found {1} nearby relays not already checked that can transmit.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
this.GetType().Name, nearestRelaySqrDistance, bestOccludedSqrDistance);
nearbyRelays.Count  
)); this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
// Sort the available relays by distance. }
nearbyRelays.Sort(new RelayComparer(this.vessel)); // Otherwise, target the best occluded relay.
  else
// Get the nearest available relay, or null if there are no available relays nearby. {
IAntennaRelay _nearestRelay = nearbyRelays.FirstOrDefault(); log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
// If we have a nearby relay...  
if (_nearestRelay != null) this.targetRelay = bestOccludedRelay;
{ this.firstOccludingBody = bodyOccludingBestOccludedRelay;
// ...but that relay is farther than Kerbin... }
if (this.DistanceTo(_nearestRelay) > this.DistanceTo(Kerbin)) }
{ // Otherwise, check Kerbin against the nearest relay.
// ...just use Kerbin. // Since we have LOS, blank the first occluding body.
_nearestRelay = null; else
} {
} log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
  bestOccludedRelay, kerbinSqrDistance);
   
  this.firstOccludingBody = null;
   
  // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  }
  }
  }
  }
   
  log.AppendFormat("{0}: Target search completed at {1} ms ({2} ms elapsed).",
  this.ToString(), searchTimer.ElapsedMilliseconds, searchTimer.ElapsedMilliseconds - searchTime);;
   
  log.AppendFormat("\n{0}: Status determination complete.", this.ToString());
   
  #if DEBUG
  } catch (Exception ex) {
  log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace);
  #if QUIT_ON_EXCEPTION
  UnityEngine.Application.Quit();
  #endif
  } finally {
  #endif
  log.Print(false);
  #if DEBUG
  }
  #endif
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
this.relayChecked = false; this.isChecked = false;
  }
// Return the nearest available relay, or null if there are no available relays nearby.  
return _nearestRelay; /// <summary>
} /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
  /// </summary>
/// <summary> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. public override string ToString()
/// </summary> {
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> if (this is ProtoAntennaRelay)
public AntennaRelay(Vessel v) {
{ return (this as ProtoAntennaRelay).ToString();
this.vessel = v; }
  return this.moduleRef.ToString();
this.searchTimer = new System.Diagnostics.Stopwatch(); }
this.millisecondsBetweenSearches = 5000;  
  /// <summary>
// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start, /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
// we hope it is safe enough. /// </summary>
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); /// <param name="module">The module reference underlying this AntennaRelay,
} /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
  public AntennaRelay(IAntennaRelay module)
/* {
* Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance. this.moduleRef = module;
* */ this.isChecked = false;
internal class RelayComparer : IComparer<IAntennaRelay>  
{ Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
/// <summary>  
/// The reference Vessel (usually the active vessel).  
/// </summary>  
protected Vessel referenceVessel;  
   
// We don't want no stinking public parameterless constructors.  
private RelayComparer() {}  
   
/// <summary>  
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use  
/// in sorting relays by distance.  
/// </summary>  
/// <param name="reference">The reference Vessel</param>  
public RelayComparer(Vessel reference)  
{  
this.referenceVessel = reference;  
}  
   
/// <summary>  
/// Compare the <see cref="IAntennaRelay"/>s "one" and "two".  
/// </summary>  
/// <param name="one">The first IAntennaRelay in the comparison</param>  
/// <param name="two">The second IAntennaRelay in the comparison</param>  
public int Compare(IAntennaRelay one, IAntennaRelay two)  
{  
double distanceOne;  
double distanceTwo;  
   
distanceOne = one.vessel.DistanceTo(referenceVessel);  
distanceTwo = two.vessel.DistanceTo(referenceVessel);  
   
return distanceOne.CompareTo(distanceTwo);  
}  
} }
} }
} }