Now keeping track of useful relays in ARFlightController, and using that list instead of FlightGlobals in AntennaRelay and ARMapRenderer.
Now keeping track of useful relays in ARFlightController, and using that list instead of FlightGlobals in AntennaRelay and ARMapRenderer.

file:b/.gitattributes (new)
  * text=auto
  * eol=lf
 
  # These files are text and should be normalized (convert crlf => lf)
  *.cs text diff=csharp
  *.cfg text
  *.csproj text
  *.sln text
 
  # Images should be treated as binary
  # (binary is a macro for -text -diff)
  *.png binary
 
  // AntennaRange © 2014 toadicus
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 
  using KSP;
  using System;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  /// <summary>
  /// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange.
  /// </summary>
  [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
  public class ARConfiguration : MonoBehaviour
  {
  private const string WINDOW_POS_KEY = "configWindowPos";
  private const string REQUIRE_LOS_KEY = "requireLineOfSight";
  private const string GRACE_RATIO_KEY = "graceRatio";
  private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
  private const string FIXED_POWER_KEY = "fixedPowerCost";
  private const string PRETTY_LINES_KEY = "drawPrettyLines";
  private const string UPDATE_DELAY_KEY = "updateDelay";
  private const string USE_ADDITIVE_KEY = "useAdditiveRanges";
 
  private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
  private const string RANGE_KEY = "range";
 
  /// <summary>
  /// Indicates whether connections require line of sight.
  /// </summary>
  public static bool RequireLineOfSight
  {
  get;
  private set;
  }
 
  /// <summary>
  /// A "fudge factor" ratio that pretends planets and moons are slightly smaller than reality to make
  /// building communication constellations easier.
  /// </summary>
  public static double RadiusRatio
  {
  get;
  private set;
  }
 
  /// <summary>
  /// Indicates whether unmanned vessels require a connection for control.
  /// </summary>
  public static bool RequireConnectionForControl
  {
  get;
  private set;
  }
 
  /// <summary>
  /// If true, relays will fix their power cost when above nominal range, decreasing data rate instead.
  /// </summary>
  public static bool FixedPowerCost
  {
  get;
  private set;
  }
 
  /// <summary>
  /// Indicates whether this AntennaRange will draw pretty lines in map view.
  /// </summary>
  public static bool PrettyLines
  {
  get;
  set;
  }
 
  /// <summary>
  /// Gets the update delay.
  /// </summary>
  public static long UpdateDelay
  {
  get;
  private set;
  }
 
  /// <summary>
  /// Gets a value indicating whether AntennaRange will use additive ranges.
  /// </summary>
  public static bool UseAdditiveRanges
  {
  get;
  private set;
  }
 
  /// <summary>
  /// Gets Kerbin's relay range based on the current tracking station level.
  /// </summary>
  public static double KerbinRelayRange
  {
  get;
  private set;
  }
 
  /// <summary>
  /// Gets Kerbin's nominal relay range based on the current tracking station level.
  /// </summary>
  public static double KerbinNominalRange
  {
  get
  {
  return KerbinRelayRange / 2.8284271247461901d;
  }
  }
 
  #pragma warning disable 1591
 
  private bool showConfigWindow;
  private Rect configWindowPos;
 
  private string updateDelayStr;
  private long updateDelay;
 
  private IButton toolbarButton;
  private ApplicationLauncherButton appLauncherButton;
 
  private double[] trackingStationRanges;
 
  private System.Version runningVersion;
 
  private bool runOnce;
 
  private KSP.IO.PluginConfiguration _config;
  private KSP.IO.PluginConfiguration config
  {
  get
  {
  if (this._config == null)
  {
  this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
  }
 
  return this._config;
  }
  }
 
  public void Awake()
  {
  Tools.PostDebugMessage(this, "Waking up.");
 
  this.runningVersion = this.GetType().Assembly.GetName().Version;
 
  this.showConfigWindow = false;
  this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
 
 
  this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
 
  ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
 
  ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
  ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
 
  ARConfiguration.RequireConnectionForControl =
  this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
 
  ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
 
  ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
 
  ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
 
  ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true);
 
  this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
  GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
 
  Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
 
  ConfigNode[] tsRangeNodes = GameDatabase.Instance.GetConfigNodes(TRACKING_STATION_RANGES_KEY);
 
  if (tsRangeNodes.Length > 0)
  {
  string[] rangeValues = tsRangeNodes[0].GetValues(RANGE_KEY);
 
  this.trackingStationRanges = new double[rangeValues.Length];
 
  for (int idx = 0; idx < rangeValues.Length; idx++)
  {
  if (!double.TryParse(rangeValues[idx], out this.trackingStationRanges[idx]))
  {
  this.LogError("Could not parse value '{0}' to double; Tracking Station ranges may not work!");
  this.trackingStationRanges[idx] = 0d;
  }
  }
 
  this.Log("Loaded Tracking Station ranges from config: [{0}]", this.trackingStationRanges.SPrint());
  }
  else
  {
  this.trackingStationRanges = new double[]
  {
  51696576d,
  37152180000d,
  224770770000d
  };
 
  this.LogWarning("Failed to load Tracking Station ranges from config, using hard-coded values: [{0}]",
  this.trackingStationRanges.SPrint());
  }
 
  this.runOnce = true;
 
  Tools.PostDebugMessage(this, "Awake.");
  }
 
  public void Update()
  {
  if (
  this.runOnce &&
  (ScenarioUpgradeableFacilities.Instance != null || HighLogic.CurrentGame.Mode != Game.Modes.CAREER)
  )
  {
  this.runOnce = false;
 
  this.SetKerbinRelayRange();
  }
  }
 
  public void OnGUI()
  {
  // Only runs once, if the Toolbar is available.
  if (ToolbarManager.ToolbarAvailable)
  {
  if (this.toolbarButton == null)
  {
  Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
 
  this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
  this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
  this.toolbarButton.Text = "AR";
  this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
  this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
  this.toolbarButton.OnClick += delegate(ClickEvent e)
  {
  this.toggleConfigWindow();
  };
  }
  }
  else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
  {
  Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
 
  this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
  this.toggleConfigWindow,
  this.toggleConfigWindow,
  ApplicationLauncher.AppScenes.SPACECENTER,
  GameDatabase.Instance.GetTexture(
  "AntennaRange/Textures/appLauncherIcon",
  false
  )
  );
  }
 
  if (this.showConfigWindow)
  {
  Rect configPos = GUILayout.Window(354163056,
  this.configWindowPos,
  this.ConfigWindow,
  string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
  GUILayout.ExpandHeight(true),
  GUILayout.ExpandWidth(true)
  );
 
  configPos = Tools.ClampRectToScreen(configPos, 20);
 
  if (configPos != this.configWindowPos)
  {
  this.configWindowPos = configPos;
  this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos);
  }
  }
  }
 
  public void ConfigWindow(int _)
  {
  GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
 
  GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
  bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
  if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
  {
  ARConfiguration.RequireLineOfSight = requireLineOfSight;
  this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
  bool requireConnectionForControl =
  GUITools.Toggle(
  ARConfiguration.RequireConnectionForControl,
  "Require Connection for Probe Control"
  );
  if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
  {
  ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
  this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
  if (fixedPowerCost != ARConfiguration.FixedPowerCost)
  {
  ARConfiguration.FixedPowerCost = fixedPowerCost;
  this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  bool useAdditive = GUITools.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
  if (useAdditive != ARConfiguration.UseAdditiveRanges)
  {
  ARConfiguration.UseAdditiveRanges = useAdditive;
  this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
  if (prettyLines != ARConfiguration.PrettyLines)
  {
  ARConfiguration.PrettyLines = prettyLines;
  this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
 
  this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
 
  GUILayout.Label("ms", GUILayout.ExpandWidth(false));
 
  GUILayout.EndHorizontal();
 
  if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
  {
  ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
  this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
  }
 
  if (requireLineOfSight)
  {
  GUILayout.BeginHorizontal();
 
  double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio);
  double newRatio;
 
  GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
  newRatio = Math.Round(newRatio, 2);
 
  if (newRatio != graceRatio)
  {
  ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
  this.SaveConfigValue(GRACE_RATIO_KEY, newRatio);
  }
 
  GUILayout.EndHorizontal();
  }
 
  GUILayout.EndVertical();
 
  GUI.DragWindow();
  }
 
  public void OnDestroy()
  {
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
  GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded);
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.Destroy();
  this.toolbarButton = null;
  }
 
  if (this.appLauncherButton != null)
  {
  ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
  this.appLauncherButton = null;
  }
  }
 
  private void onSceneChangeRequested(GameScenes scene)
  {
  if (scene != GameScenes.SPACECENTER)
  {
  print("ARConfiguration: Requesting Destruction.");
  MonoBehaviour.Destroy(this);
  }
  }
 
  private void onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int lvl)
  {
  if (fac.id == "SpaceCenter/TrackingStation")
  {
  this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
  this.SetKerbinRelayRange();
  }
  }
 
  private void SetKerbinRelayRange()
  {
  int tsLevel;
 
  if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
  {
  tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
  .facilityRefs[0].FacilityLevel;
 
 
  }
  else
  {
  tsLevel = this.trackingStationRanges.Length - 1;
  }
 
  if (tsLevel < this.trackingStationRanges.Length && tsLevel >= 0)
  {
  KerbinRelayRange = this.trackingStationRanges[tsLevel];
  this.Log("Setting Kerbin's range to {0}", KerbinRelayRange);
  }
  else
  {
  this.LogError("Could not set Kerbin's range with invalid Tracking Station level {0}", tsLevel);
  }
  }
 
  private void toggleConfigWindow()
  {
  this.showConfigWindow = !this.showConfigWindow;
  this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
  }
 
  private T LoadConfigValue<T>(string key, T defaultValue)
  {
  this.config.load();
 
  return config.GetValue(key, defaultValue);
  }
 
  private void SaveConfigValue<T>(string key, T value)
  {
  this.config.load();
 
  this.config.SetValue(key, value);
 
  this.config.save();
  }
  }
  }
 
  // AntennaRange
  //
  // ARFlightController.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.
 
  #pragma warning disable 1591
 
  using KSP;
  using System;
  using System.Collections.Generic;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  [KSPAddon(KSPAddon.Startup.Flight, false)]
  public class ARFlightController : MonoBehaviour
  {
  #region Static
  private static List<IAntennaRelay> usefulRelays;
  public static IList<IAntennaRelay> UsefulRelays;
  #endregion
 
  #region Fields
  private Dictionary<ConnectionStatus, string> toolbarTextures;
  private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
 
  private ARMapRenderer mapRenderer;
 
  private IButton toolbarButton;
 
  private ApplicationLauncherButton appLauncherButton;
  private Tools.DebugLogger log;
 
  private System.Diagnostics.Stopwatch updateTimer;
  #endregion
 
  #region Properties
  public ConnectionStatus currentConnectionStatus
  {
  get;
  private set;
  }
 
  private string currentConnectionTexture
  {
  get
  {
  return this.toolbarTextures[this.currentConnectionStatus];
  }
  }
 
  private Texture currentAppLauncherTexture
  {
  get
  {
  return this.appLauncherTextures[this.currentConnectionStatus];
  }
  }
 
  public ControlTypes currentControlLock
  {
  get
  {
  if (this.lockID == string.Empty)
  {
  return ControlTypes.None;
  }
 
  return InputLockManager.GetControlLock(this.lockID);
  }
  }
 
  public string lockID
  {
  get;
  private set;
  }
 
  public ControlTypes lockSet
  {
  get
  {
  return ControlTypes.ALL_SHIP_CONTROLS;
  }
  }
 
  public Vessel vessel
  {
  get
  {
  if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
  {
  return FlightGlobals.ActiveVessel;
  }
 
  return null;
  }
  }
  #endregion
 
  #region MonoBehaviour LifeCycle
  private void Awake()
  {
  this.lockID = "ARConnectionRequired";
 
  this.log = Tools.DebugLogger.New(this);
 
  this.updateTimer = new System.Diagnostics.Stopwatch();
 
  this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
 
  this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
  this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
  this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
 
  this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
 
  this.appLauncherTextures[ConnectionStatus.None] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
  this.appLauncherTextures[ConnectionStatus.Suboptimal] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
  this.appLauncherTextures[ConnectionStatus.Optimal] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
 
  if (ToolbarManager.ToolbarAvailable)
  {
  this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
  this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
  this.toolbarButton.Text = "AntennaRange";
  this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
  this.toolbarButton.OnClick += (e) => (this.buttonToggle());
  }
 
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
  GameEvents.onVesselChange.Add(this.onVesselChange);
 
  usefulRelays = new List<IAntennaRelay>();
  UsefulRelays = usefulRelays.AsReadOnly();
  }
 
  private void FixedUpdate()
  {
  this.log.Clear();
 
  VesselCommand availableCommand;
 
  if (ARConfiguration.RequireConnectionForControl)
  {
  availableCommand = this.vessel.CurrentCommand();
  }
  else
  {
  availableCommand = VesselCommand.Crew;
  }
 
  log.AppendFormat("availableCommand: {0}\n\t" +
  "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
  "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
  "vessel.HasConnectedRelay(): {3}",
  (int)availableCommand,
  (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
  (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
  vessel.HasConnectedRelay()
  );
 
  // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
  // and the vessel does not have any connected relays...
  if (
  HighLogic.LoadedSceneIsFlight &&
  ARConfiguration.RequireConnectionForControl &&
  this.vessel != null &&
  this.vessel.vesselType != VesselType.EVA &&
  !(
  (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
  (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
  ))
  {
  // ...and if the controls are not currently locked...
  if (currentControlLock == ControlTypes.None)
  {
  // ...lock the controls.
  InputLockManager.SetControlLock(this.lockSet, this.lockID);
  }
  }
  // ...otherwise, if the controls are locked...
  else if (currentControlLock != ControlTypes.None)
  {
  // ...unlock the controls.
  InputLockManager.RemoveControlLock(this.lockID);
  }
 
  log.Print();
  }
 
  private void Update()
  {
  if (MapView.MapIsEnabled && this.mapRenderer == null)
  {
  this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
  }
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.Enabled = MapView.MapIsEnabled;
  }
 
  if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
  {
  this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
  this.buttonToggle, this.buttonToggle,
  ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
  this.appLauncherTextures[ConnectionStatus.None]
  );
  }
 
  if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
  {
  this.updateTimer.Restart();
  }
  else
  {
  return;
  }
 
  this.log.Clear();
 
  if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
  {
  Vessel vessel;
  IAntennaRelay relay;
  IList<IAntennaRelay> activeVesselRelays;
 
  usefulRelays.Clear();
 
  for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
  {
  vessel = FlightGlobals.Vessels[vIdx];
 
  if (vessel == null || vessel == FlightGlobals.ActiveVessel)
  {
  continue;
  }
 
  log.AppendFormat("Fetching best relay for vessel {0}", vessel);
 
  relay = vessel.GetBestRelay();
 
  if (relay != null)
  {
  log.AppendFormat("Finding nearest relay for best relay {0}", relay);
 
  usefulRelays.Add(relay);
  relay.FindNearestRelay();
  }
  }
 
  activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
  for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
  {
  relay = activeVesselRelays[rIdx];
 
  relay.FindNearestRelay();
  }
 
  usefulRelays.Add(RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel));
 
  if (this.toolbarButton != null || this.appLauncherButton != null)
  {
  log.Append("Checking vessel relay status.\n");
 
  this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
 
  log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
  this.currentConnectionStatus, this.currentConnectionTexture);
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.TexturePath = this.currentConnectionTexture;
 
  if (this.currentConnectionStatus == ConnectionStatus.None)
  {
  if (!this.toolbarButton.Important) this.toolbarButton.Important = true;
  }
  else
  {
  if (this.toolbarButton.Important) this.toolbarButton.Important = false;
  }
  }
  if (this.appLauncherButton != null)
  {
  this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
  }
  }
  }
 
  log.Print();
  }
 
  private void OnDestroy()
  {
  InputLockManager.RemoveControlLock(this.lockID);
 
  if (this.mapRenderer != null)
  {
  GameObject.Destroy(this.mapRenderer);
  }
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.Destroy();
  }
 
  if (this.appLauncherButton != null)
  {
  ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
  this.appLauncherButton = null;
  }
 
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
  GameEvents.onVesselChange.Remove(this.onVesselChange);
 
  print("ARFlightController: Destroyed.");
  }
  #endregion
 
  private void buttonToggle()
  {
  if (MapView.MapIsEnabled)
  {
  ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
  }
  }
 
  #region Event Handlers
  private void onSceneChangeRequested(GameScenes scene)
  {
  print("ARFlightController: Requesting Destruction.");
  MonoBehaviour.Destroy(this);
  }
 
  private void onVesselChange(Vessel vessel)
  {
  InputLockManager.RemoveControlLock(this.lockID);
  }
  #endregion
  }
  }
 
file:b/ARMapRenderer.cs (new)
  // AntennaRange
  //
  // ARMapRenderer.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.
 
  #pragma warning disable 1591
 
  using KSP;
  using System;
  using System.Collections.Generic;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  public class ARMapRenderer : MonoBehaviour
  {
  #if BENCH
  private static ulong updateCount = 0u;
  private static ulong updateTimer = 0u;
  private readonly static RollingAverage averager = new RollingAverage();
  private static long twiceAverageTime = long.MaxValue;
  #endif
 
  #region Fields
  private Dictionary<Guid, LineRenderer> vesselLineRenderers;
 
  // Debug Stuff
  #pragma warning disable 649
  private System.Diagnostics.Stopwatch timer;
  private Tools.DebugLogger log;
  private long relayStart;
  private long start;
  #pragma warning restore 649
 
  #pragma warning disable 414
  private Color thisColor;
  #pragma warning restore 414
  #endregion
 
  #region Properties
  public LineRenderer this[Guid idx]
  {
  get
  {
  LineRenderer lr;
 
  if (!this.vesselLineRenderers.TryGetValue(idx, out lr))
  {
  GameObject obj = new GameObject();
  obj.layer = 31;
 
  lr = obj.AddComponent<LineRenderer>();
 
  // lr.SetColors(Color.green, Color.green);
  lr.material = MapView.OrbitLinesMaterial;
  // lr.SetVertexCount(2);
 
  this.vesselLineRenderers[idx] = lr;
 
  return lr;
  }
 
  return lr;
  }
  }
  #endregion
 
  #region MonoBehaviour Lifecycle
  private void Awake()
  {
  if (ARConfiguration.PrettyLines)
  {
  this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
  }
 
  #if DEBUG || BENCH
  this.timer = new System.Diagnostics.Stopwatch();
  #endif
  #if DEBUG
  this.log = Tools.DebugLogger.New(this);
  #endif
  }
 
  private void OnPreCull()
  {
  if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
  {
  this.Cleanup(!HighLogic.LoadedSceneIsFlight);
 
  return;
  }
 
  #if DEBUG || BENCH
  timer.Restart();
  #endif
 
  try
  {
  log.Clear();
 
  log.AppendFormat("OnPreCull.\n");
 
  log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
  "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
  "\tMapView.MapCamera.Distance: {2}\n",
  MapView.Draw3DLines,
  MapView.MapCamera.camera.fieldOfView,
  MapView.MapCamera.Distance
  );
 
  log.AppendLine("FlightGlobals ready and Vessels list not null.");
 
  IAntennaRelay relay;
 
  for (int i = 0; i < ARFlightController.UsefulRelays.Count; i++)
  {
  relay = ARFlightController.UsefulRelays[i];
 
  if (relay == null)
  {
  log.AppendFormat("\n\tGot null relay, skipping");
  continue;
  }
 
  log.AppendFormat("\n\tDrawing pretty lines for useful relay {0}", relay);
 
  #if DEBUG
  start = timer.ElapsedMilliseconds;
  #endif
 
  this.SetRelayVertices(relay);
 
  log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
  relay, timer.ElapsedMilliseconds - start);
  }
  }
  catch (Exception ex)
  {
  this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
  this.Cleanup(false);
  }
  #if DEBUG
  finally
  {
  log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
 
  log.Print();
  }
  #endif
 
  #if BENCH
  ARMapRenderer.updateCount++;
  ARMapRenderer.updateTimer += (ulong)this.timer.ElapsedTicks;
 
  if (ARMapRenderer.updateCount >= (ulong)(8d / Time.smoothDeltaTime))
  {
  ARMapRenderer.averager.AddItem((double)ARMapRenderer.updateTimer / (double)ARMapRenderer.updateCount);
  ARMapRenderer.updateTimer = 0u;
  ARMapRenderer.updateCount = 0u;
  ARMapRenderer.twiceAverageTime = (long)(ARMapRenderer.averager.Average * 2d);
  }
 
  if (this.timer.ElapsedTicks > ARMapRenderer.twiceAverageTime)
  {
  this.Log("PreCull took significant longer than usual ({0:S3}s vs {1:S3}s)",
  (double)this.timer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
  ARMapRenderer.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
  );
  }
  #endif
  }
 
  private void OnDestroy()
  {
  this.Cleanup(true);
 
  this.Log("Destroyed");
  }
  #endregion
 
  #region Utility
  private void SetRelayVertices(IAntennaRelay relay)
  {
  log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay);
 
  if (relay.vessel == null)
  {
  log.Append("\n\t\tvessel is null, bailing out");
  return;
  }
 
  LineRenderer renderer = this[relay.vessel.id];
  Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
 
  float lineWidth;
  float d = Screen.height / 2f + 0.01f;
 
  if (MapView.Draw3DLines)
  {
  lineWidth = 0.005859375f * MapView.MapCamera.Distance;
  }
  else
  {
  lineWidth = 2f;
 
  start = MapView.MapCamera.camera.WorldToScreenPoint(start);
 
  start.z = start.z >= 0f ? d : -d;
  }
 
  renderer.SetWidth(lineWidth, lineWidth);
 
  renderer.SetPosition(0, start);
 
  int idx = 0;
 
  #if DEBUG
  relayStart = timer.ElapsedMilliseconds;
  #endif
 
  Vector3d nextPoint;
 
  renderer.enabled = true;
 
  if (!relay.CanTransmit())
  {
  thisColor = Color.red;
  }
  else
  {
  if (relay.LinkStatus == ConnectionStatus.Optimal)
  {
  thisColor = Color.green;
  }
  else
  {
  thisColor = Color.yellow;
  }
  }
 
  if (relay.KerbinDirect)
  {
  nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
  }
  else
  {
  if (relay.targetRelay == null || relay.targetRelay.vessel == null)
  {
  this.LogError(
  "SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
  relay
  );
 
  renderer.enabled = false;
  return;
  }
 
  nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
  }
 
  renderer.SetColors(thisColor, thisColor);
 
  if (!MapView.Draw3DLines)
  {
  nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
  nextPoint.z = nextPoint.z >= 0f ? d : -d;
  }
 
  idx++;
 
  renderer.SetVertexCount(idx + 1);
  renderer.SetPosition(idx, nextPoint);
 
  log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
  }
 
  private void Cleanup(bool freeObjects)
  {
  if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
  {
  IEnumerator<LineRenderer> enumerator = this.vesselLineRenderers.Values.GetEnumerator();
  LineRenderer lineRenderer;
 
  while (enumerator.MoveNext())
  {
  lineRenderer = enumerator.Current;
  lineRenderer.enabled = false;
 
  if (freeObjects)
  {
  GameObject.Destroy(lineRenderer.gameObject);
  }
  }
 
  if (freeObjects)
  {
  this.vesselLineRenderers.Clear();
  }
  }
  }
  #endregion
  }
  }
 
file:a/ARTools.cs (deleted)
using System;  
 
namespace AntennaRange  
{  
public static class Tools  
{  
private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT);  
 
[System.Diagnostics.Conditional("DEBUG")]  
public static void PostDebugMessage(string Msg)  
{  
if (HighLogic.LoadedScene > GameScenes.SPACECENTER)  
{  
debugmsg.message = Msg;  
ScreenMessages.PostScreenMessage(debugmsg, true);  
}  
 
KSPLog.print(Msg);  
}  
 
/*  
* MuMech_ToSI is a part of the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
* */  
public static string MuMech_ToSI(double d, int digits = 3, int MinMagnitude = 0, int MaxMagnitude = int.MaxValue)  
{  
float exponent = (float)Math.Log10(Math.Abs(d));  
exponent = UnityEngine.Mathf.Clamp(exponent, (float)MinMagnitude, (float)MaxMagnitude);  
 
if (exponent >= 0)  
{  
switch ((int)Math.Floor(exponent))  
{  
case 0:  
case 1:  
case 2:  
return d.ToString("F" + digits);  
case 3:  
case 4:  
case 5:  
return (d / 1e3).ToString("F" + digits) + "k";  
case 6:  
case 7:  
case 8:  
return (d / 1e6).ToString("F" + digits) + "M";  
case 9:  
case 10:  
case 11:  
return (d / 1e9).ToString("F" + digits) + "G";  
case 12:  
case 13:  
case 14:  
return (d / 1e12).ToString("F" + digits) + "T";  
case 15:  
case 16:  
case 17:  
return (d / 1e15).ToString("F" + digits) + "P";  
case 18:  
case 19:  
case 20:  
return (d / 1e18).ToString("F" + digits) + "E";  
case 21:  
case 22:  
case 23:  
return (d / 1e21).ToString("F" + digits) + "Z";  
default:  
return (d / 1e24).ToString("F" + digits) + "Y";  
}  
}  
else if (exponent < 0)  
{  
switch ((int)Math.Floor(exponent))  
{  
case -1:  
case -2:  
case -3:  
return (d * 1e3).ToString("F" + digits) + "m";  
case -4:  
case -5:  
case -6:  
return (d * 1e6).ToString("F" + digits) + "μ";  
case -7:  
case -8:  
case -9:  
return (d * 1e9).ToString("F" + digits) + "n";  
case -10:  
case -11:  
case -12:  
return (d * 1e12).ToString("F" + digits) + "p";  
case -13:  
case -14:  
case -15:  
return (d * 1e15).ToString("F" + digits) + "f";  
case -16:  
case -17:  
case -18:  
return (d * 1e18).ToString("F" + digits) + "a";  
case -19:  
case -20:  
case -21:  
return (d * 1e21).ToString("F" + digits) + "z";  
default:  
return (d * 1e24).ToString("F" + digits) + "y";  
}  
}  
else  
{  
return "0";  
}  
}  
}  
}  
 
 
file:a/AntennaRange.cfg (deleted)
//  
// AntennaRange © 2013 toadicus  
//  
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
//  
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
// 3.0 Uported License.  
//  
// Specifications:  
// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost  
// and packetSize.  
// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the  
// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.  
// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.  
//  
 
@PART[longAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 1500000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
@PART[mediumDishAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 30000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
@PART[commDish]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 80000000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
file:a/AntennaRange.cs (deleted)
/*  
* AntennaRange © 2013 toadicus  
*  
* AntennaRange provides incentive and requirements for the use of the various antenna parts.  
* Nominally, the breakdown is as follows:  
*  
* Communotron 16 - Suitable up to Kerbalsynchronous Orbit  
* Comms DTS-M1 - Suitable throughout the Kerbin subsystem  
* 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  
* copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
*  
* This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
* 3.0 Uported License.  
*  
* This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
*  
*/  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using KSP;  
using UnityEngine;  
 
namespace AntennaRange  
{  
/*  
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-  
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with  
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.  
*  
* In general, the scaling functions assume the following relation:  
*  
* D² α P/R,  
*  
* where D is the total transmission distance, P is the transmission power, and R is the data rate.  
*  
* */  
 
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay  
{  
// Call this an antenna so that you don't have to.  
[KSPField(isPersistant = true)]  
protected bool IsAntenna = true;  
 
// Stores the packetResourceCost as defined in the .cfg file.  
protected float _basepacketResourceCost;  
 
// Stores the packetSize as defined in the .cfg file.  
protected float _basepacketSize;  
 
// Every antenna is a relay.  
protected AntennaRelay relay;  
 
// Keep track of vessels with transmitters for relay purposes.  
protected List<Vessel> _relayVessels;  
 
// Sometimes we will need to communicate errors; this is how we do it.  
protected ScreenMessage ErrorMsg;  
 
// Let's make the error text pretty!  
protected UnityEngine.GUIStyle ErrorStyle;  
 
// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost  
// and packetSize.  
[KSPField(isPersistant = false)]  
public float nominalRange;  
 
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power  
// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.  
[KSPField(isPersistant = false)]  
public float maxPowerFactor;  
 
// The multipler on packetSize that defines the maximum data bandwidth of the antenna.  
[KSPField(isPersistant = false)]  
public float maxDataFactor;  
 
// This field exists to get saved to the persistence file so that relays can be found on unloaded Vessels.  
[KSPField(isPersistant = true)]  
protected float ARmaxTransmitDistance;  
 
/*  
* Properties  
* */  
// Returns the parent vessel housing this antenna.  
public new Vessel vessel  
{  
get  
{  
return base.vessel;  
}  
}  
 
// Returns the distance to the nearest relay or Kerbin, whichever is closer.  
public double transmitDistance  
{  
get  
{  
return this.relay.transmitDistance;  
}  
}  
 
// Returns the maximum distance this module can transmit  
public float maxTransmitDistance  
{  
get  
{  
return this.ARmaxTransmitDistance;  
}  
}  
 
/*  
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except  
* in that they both return floats. Here's some quick justification:  
*  
* The stock implementation of GetTransmitterScore (which I cannot override) is:  
* Score = (1 + DataResourceCost) / DataRate  
*  
* The stock DataRate and DataResourceCost are:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost / packetSize  
*  
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it  
* could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,  
* that metric is not a very reasonable one.  
*  
* Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case  
* would look like:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost  
*  
* The former case, which I've chosen to implement below, is:  
* DataRate = packetSize  
* DataResourceCost = packetResourceCost  
*  
* So... hopefully that doesn't screw with anything else.  
* */  
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in  
// terms of joules/byte  
public new float DataRate  
{  
get  
{  
this.PreTransmit_SetPacketSize();  
return this.packetSize;  
}  
}  
 
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas  
// to be scored in terms of joules/byte  
public new float DataResourceCost  
{  
get  
{  
this.PreTransmit_SetPacketResourceCost();  
 
if (this.CanTransmit())  
{  
return this.packetResourceCost;  
}  
else  
{  
return float.PositiveInfinity;  
}  
}  
}  
 
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.  
public bool relayChecked  
{  
get  
{  
return this.relay.relayChecked;  
}  
}  
 
/*  
* Methods  
* */  
// Build ALL the objects.  
public ModuleLimitedDataTransmitter () : base()  
{  
// Make the error posting prettier.  
this.ErrorStyle = new UnityEngine.GUIStyle();  
this.ErrorStyle.normal.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;  
this.ErrorStyle.active.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;  
this.ErrorStyle.hover.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;  
this.ErrorStyle.fontStyle = UnityEngine.FontStyle.Bold;  
this.ErrorStyle.padding.top = 32;  
 
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle);  
}  
 
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.  
public override void OnStart (StartState state)  
{  
base.OnStart (state);  
 
if (state >= StartState.PreLaunch)  
{  
this.relay = new AntennaRelay(vessel);  
this.relay.maxTransmitDistance = this.maxTransmitDistance;  
}  
 
// Pre-set the transmit cost and packet size when loading.  
this.PreTransmit_SetPacketResourceCost();  
this.PreTransmit_SetPacketSize();  
}  
 
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because  
// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't  
// work.  
public override void OnLoad(ConfigNode node)  
{  
this.Fields.Load(node);  
base.Fields.Load(node);  
 
this.ARmaxTransmitDistance = Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;  
 
base.OnLoad (node);  
 
this._basepacketSize = base.packetSize;  
this._basepacketResourceCost = base.packetResourceCost;  
 
Tools.PostDebugMessage(string.Format(  
"{0} loaded:\n" +  
"packetSize: {1}\n" +  
"packetResourceCost: {2}\n" +  
"nominalRange: {3}\n" +  
"maxPowerFactor: {4}\n" +  
"maxDataFactor: {5}\n",  
this.name,  
base.packetSize,  
this._basepacketResourceCost,  
this.nominalRange,  
this.maxPowerFactor,  
this.maxDataFactor  
));  
}  
 
// Post an error in the communication messages describing the reason transmission has failed. Currently there  
// is only one reason for this.  
protected void PostCannotTransmitError()  
{  
string ErrorText = string.Format (  
"Unable to transmit: out of range! Maximum range = {0}m; Current range = {1}m.",  
Tools.MuMech_ToSI((double)this.ARmaxTransmitDistance, 2),  
Tools.MuMech_ToSI((double)this.transmitDistance, 2)  
);  
 
this.ErrorMsg.message = ErrorText;  
 
ScreenMessages.PostScreenMessage(this.ErrorMsg, true);  
}  
 
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of  
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point  
// transmission fails (see CanTransmit).  
protected void PreTransmit_SetPacketResourceCost()  
{  
if (this.transmitDistance <= this.nominalRange)  
{  
base.packetResourceCost = this._basepacketResourceCost;  
}  
else  
{  
base.packetResourceCost = this._basepacketResourceCost  
* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);  
}  
}  
 
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of  
// distance. packetSize maxes out at _basepacketSize * maxDataFactor.  
protected void PreTransmit_SetPacketSize()  
{  
if (this.transmitDistance >= this.nominalRange)  
{  
base.packetSize = this._basepacketSize;  
}  
else  
{  
base.packetSize = Math.Min(  
this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),  
this._basepacketSize * this.maxDataFactor);  
}  
}  
 
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.  
public override string GetInfo()  
{  
string text = base.GetInfo();  
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";  
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.ARmaxTransmitDistance, 2) + "m\n";  
return text;  
}  
 
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.  
public new bool CanTransmit()  
{  
return this.relay.CanTransmit();  
}  
 
// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit  
// returns false.  
public new void TransmitData(List<ScienceData> dataQueue)  
{  
if (this.CanTransmit())  
{  
base.TransmitData(dataQueue);  
}  
else  
{  
this.PostCannotTransmitError ();  
}  
 
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize  
+ " packetResourceCost: " + this.packetResourceCost  
);  
}  
 
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit  
// returns false.  
public new void StartTransmission()  
{  
PreTransmit_SetPacketSize ();  
PreTransmit_SetPacketResourceCost ();  
 
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize  
+ " packetResourceCost: " + this.packetResourceCost  
);  
if (this.CanTransmit())  
{  
base.StartTransmission();  
}  
else  
{  
this.PostCannotTransmitError ();  
}  
}  
 
// When debugging, it's nice to have a button that just tells you everything.  
#if DEBUG  
[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]  
public void DebugInfo()  
{  
PreTransmit_SetPacketSize ();  
PreTransmit_SetPacketResourceCost ();  
 
string msg = string.Format(  
"'{0}'\n" +  
"_basepacketSize: {1}\n" +  
"packetSize: {2}\n" +  
"_basepacketResourceCost: {3}\n" +  
"packetResourceCost: {4}\n" +  
"maxTransmitDistance: {5}\n" +  
"transmitDistance: {6}\n" +  
"nominalRange: {7}\n" +  
"CanTransmit: {8}\n" +  
"DataRate: {9}\n" +  
"DataResourceCost: {10}\n" +  
"TransmitterScore: {11}",  
this.name,  
this._basepacketSize,  
base.packetSize,  
this._basepacketResourceCost,  
base.packetResourceCost,  
this.ARmaxTransmitDistance,  
this.transmitDistance,  
this.nominalRange,  
this.CanTransmit(),  
this.DataRate,  
this.DataResourceCost,  
ScienceUtil.GetTransmitterScore(this)  
);  
ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));  
}  
#endif  
}  
}  
  <?xml version="1.0" encoding="utf-8"?>
  <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProductVersion>8.0.30703</ProductVersion>
  <SchemaVersion>2.0</SchemaVersion>
  <ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid>
  <OutputType>Library</OutputType>
  <RootNamespace>AntennaRange</RootNamespace>
  <AssemblyName>AntennaRange</AssemblyName>
  <ReleaseVersion>1.3</ReleaseVersion>
  <SynchReleaseVersion>false</SynchReleaseVersion>
  <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  <UseMSBuildEngine>False</UseMSBuildEngine>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug</OutputPath>
  <DefineConstants>DEBUG;TRACE;</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
  </CustomCommands>
  </CustomCommands>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' ">
  <Optimize>true</Optimize>
  <OutputPath>bin\Release</OutputPath>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
  </CustomCommands>
  </CustomCommands>
  <DocumentationFile>bin\Release\AntennaRange.xml</DocumentationFile>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug</OutputPath>
  <DefineConstants>DEBUG;TRACE;</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
  </CustomCommands>
  </CustomCommands>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' ">
  <Optimize>true</Optimize>
  <OutputPath>bin\Release</OutputPath>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
  </CustomCommands>
  </CustomCommands>
  <ConsolePause>false</ConsolePause>
  </PropertyGroup>
  <ItemGroup>
  <Compile Include="Properties\AssemblyInfo.cs" />
  <Compile Include="IAntennaRelay.cs" />
  <Compile Include="ModuleLimitedDataTransmitter.cs" />
  <Compile Include="AntennaRelay.cs" />
  <Compile Include="ProtoAntennaRelay.cs" />
  <Compile Include="RelayDatabase.cs" />
  <Compile Include="RelayExtensions.cs" />
  <Compile Include="ARConfiguration.cs" />
  <Compile Include="ARFlightController.cs" />
  <Compile Include="ARMapRenderer.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <ItemGroup>
  <Reference Include="Assembly-CSharp">
  <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
  <Private>False</Private>
  </Reference>
  <Reference Include="System">
  <HintPath>..\_KSPAssemblies\System.dll</HintPath>
  <Private>False</Private>
  </Reference>
  <Reference Include="UnityEngine">
  <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
  <Private>False</Private>
  </Reference>
  </ItemGroup>
  <ItemGroup>
  <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
  <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
  <Name>ToadicusTools</Name>
  </ProjectReference>
  </ItemGroup>
  <ItemGroup>
  <None Include="GameData\AntennaRange\AntennaRange.cfg" />
  <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
  </ItemGroup>
  </Project>
  // AntennaRange
  //
  // AntennaRelay.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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
{ {
public class AntennaRelay : IAntennaRelay /// <summary>
  /// Relay code at the heart of AntennaRange
  /// </summary>
  public class AntennaRelay
{ {
// 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;
   
  /// <summary>
  /// 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;
  }
  }
   
  #if BENCH
  private static ushort relayCount = 0;
  private static ulong searchCount = 0u;
  private static ulong searchTimer = 0u;
  private readonly static RollingAverage averager = new RollingAverage(16);
  private static long doubleAverageTime = long.MaxValue;
   
   
  private System.Diagnostics.Stopwatch performanceTimer = new System.Diagnostics.Stopwatch();
  #endif
   
  private bool canTransmit;
   
  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 a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public virtual bool KerbinDirect
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// Gets or sets the nominal link distance, in meters.
  /// </summary>
  public virtual double NominalLinkSqrDistance
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// Gets or sets the maximum link distance, in meters.
  /// </summary>
  public virtual double MaximumLinkSqrDistance
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
  /// </summary>
  public virtual CelestialBody firstOccludingBody
  {
  get;
  protected set;
  }
   
  /// <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 CurrentLinkSqrDistance
{ {
get get
{ {
IAntennaRelay nearestRelay = this.FindNearestRelay(); if (this.KerbinDirect || this.targetRelay == null)
  {
// If there is no available relay nearby... return this.SqrDistanceTo(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.SqrDistanceTo(this.targetRelay);
return this.DistanceTo(nearestRelay); }
} }
} }
}  
  /// <summary>
/// <summary> /// Gets or sets the link status.
/// The maximum distance at which this relay can operate. /// </summary>
/// </summary> public virtual ConnectionStatus LinkStatus
/// <value>The max transmit distance.</value> {
public virtual float maxTransmitDistance get;
  protected set;
  }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public virtual double nominalTransmitDistance
{ {
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; get;
protected set; 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 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;
  }
   
  Tools.DebugLogger log;
  #if DEBUG
  log = Tools.DebugLogger.New(this);
  #endif
   
  #if BENCH
  this.performanceTimer.Restart();
   
  long startVesselLoopTicks;
  long totalVesselLoopTicks;
   
  string slowestLOSVesselName = string.Empty;
  long slowestLOSVesselTicks = long.MinValue;
  long startLOSVesselTicks;
  long totalLOSVesselTicks;
   
  string slowestCircularVesselName = string.Empty;
  long slowestCircularVesselTicks = long.MinValue;
  long startCircularVesselTicks;
  long totalCircularVesselTicks;
   
  long startKerbinLOSTicks;
  long totalKerbinLOSTicks;
  long statusResolutionTicks;
   
  ushort usefulVesselCount = 0;
  #endif
   
  log.AppendFormat("{0}: Target search started).", this.ToString());
   
  #if DEBUG
  try {
  #endif
   
  // Declare a bunch of variables we'll be using.
  CelestialBody bodyOccludingBestOccludedRelay = null;
  IAntennaRelay needle;
   
  double nearestRelaySqrQuotient = double.PositiveInfinity;
  double bestOccludedSqrQuotient = double.PositiveInfinity;
   
  double potentialSqrDistance;
  double maxLinkSqrDistance;
  double potentialSqrQuotient;
   
  double kerbinSqrDistance;
  double kerbinSqrQuotient;
   
  bool isCircular;
  int iterCount;
   
  // 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;
   
  /*
  * 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;
  IAntennaRelay potentialBestRelay;
  CelestialBody fob;
   
  #if BENCH
  startVesselLoopTicks = performanceTimer.ElapsedTicks;
  #endif
   
  for (int rIdx = 0; rIdx < ARFlightController.UsefulRelays.Count; rIdx++)
  {
  potentialBestRelay = ARFlightController.UsefulRelays[rIdx];
  log.AppendFormat("\n\t\tgot best vessel relay {0}",
  potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
   
  if (potentialBestRelay == null)
  {
  log.Append("\n\t\t...skipping null relay");
  continue;
  }
   
  if (potentialBestRelay == this || potentialBestRelay.vessel == this.vessel)
  {
  continue;
  }
   
  #if BENCH
  usefulVesselCount++;
  #endif
   
  // Find the distance from here to the vessel...
  log.Append("\n\tgetting distance to potential vessel");
  potentialSqrDistance = this.SqrDistanceTo(potentialBestRelay);
  log.Append("\n\tgetting best vessel relay");
   
  log.Append("\n\tgetting max link distance to potential relay");
   
  if (ARConfiguration.UseAdditiveRanges)
  {
  maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
  }
  else
  {
  maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
  }
   
  log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance);
   
  potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
   
  #if BENCH
  startLOSVesselTicks = performanceTimer.ElapsedTicks;
  #endif
   
  log.Append("\n\t\tdoing LOS check");
  // Skip vessels to which we do not have line of sight.
  if (
  ARConfiguration.RequireLineOfSight &&
  !this.vessel.hasLineOfSightTo(potentialBestRelay.vessel, out fob, ARConfiguration.RadiusRatio)
  )
  {
  #if BENCH
  totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
   
  if (totalLOSVesselTicks > slowestLOSVesselTicks)
  {
  slowestLOSVesselTicks = totalLOSVesselTicks;
  slowestLOSVesselName = vessel.vesselName;
  }
  #endif
   
  log.Append("\n\t\t...failed LOS check");
   
  log.AppendFormat("\n\t\t\t{0}: Relay {1} not in line of sight.",
  this.ToString(), potentialBestRelay);
   
  log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
  log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
  log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
   
  if (
  (potentialSqrQuotient < bestOccludedSqrQuotient) &&
  (potentialSqrQuotient <= 1d) &&
  potentialBestRelay.CanTransmit()
  )
  {
  log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
  log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
   
  this.bestOccludedRelay = potentialBestRelay;
  bodyOccludingBestOccludedRelay = fob;
  bestOccludedSqrQuotient = potentialSqrQuotient;
  }
  else
  {
  log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
  }
   
  continue;
  }
  #if BENCH
  else
  {
  totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
  }
   
  if (totalLOSVesselTicks > slowestLOSVesselTicks)
  {
  slowestLOSVesselTicks = totalLOSVesselTicks;
  slowestLOSVesselName = vessel.vesselName;
  }
  #endif
   
  log.Append("\n\t\t...passed LOS check");
   
  /*
  * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
  * */
  if (potentialSqrQuotient > nearestRelaySqrQuotient)
  {
   
  log.AppendFormat("\n\t{0}: Relay {1} discarded because it is farther than another the nearest relay.",
  this.ToString(),
  potentialBestRelay
  );
  continue;
  }
   
  log.Append("\n\t\t...passed distance check");
   
  if (potentialBestRelay.CanTransmit())
  {
  #if BENCH
  startCircularVesselTicks = performanceTimer.ElapsedTicks;
  #endif
   
  needle = potentialBestRelay;
  isCircular = false;
   
  iterCount = 0;
  while (needle != null)
  {
  iterCount++;
   
  if (needle.KerbinDirect)
  {
  break;
  }
   
  if (needle.targetRelay == null)
  {
  break;
  }
   
  if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
  {
  isCircular = true;
  break;
  }
   
  // Avoid infinite loops when we're not catching things right.
  if (iterCount > FlightGlobals.Vessels.Count)
  {
  Tools.PostErrorMessage(
  "[{0}] iterCount exceeded while checking for circular network; assuming it is circular" +
  "\n\tneedle={1}" +
  "\n\tthis.moduleRef={2}",
  this,
  needle == null ? "null" : string.Format(
  "{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
  needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
  "{0}\n\tneedle.targetRelay.vessel={1}",
  needle.targetRelay,
  needle.targetRelay.vessel == null ?
  "null" : needle.targetRelay.vessel.vesselName
  )
  ),
  this.moduleRef == null ? "null" : this.moduleRef.ToString()
  );
  isCircular = true;
  break;
  }
   
  needle = needle.targetRelay;
  }
   
  if (!isCircular)
  {
  nearestRelaySqrQuotient = potentialSqrQuotient;
  this.nearestRelay = potentialBestRelay;
   
  log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
  this.ToString(),
  this.nearestRelay.ToString(),
  Math.Sqrt(nearestRelaySqrQuotient)
  );
  }
  else
  {
  log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
  potentialBestRelay
  );
  }
   
  #if BENCH
  totalCircularVesselTicks = performanceTimer.ElapsedTicks - startCircularVesselTicks;
   
  if (totalCircularVesselTicks > slowestCircularVesselTicks)
  {
  slowestCircularVesselName = vessel.vesselName;
  slowestCircularVesselTicks = totalCircularVesselTicks;
  }
   
  #endif
  }
  }
   
  #if BENCH
  totalVesselLoopTicks = performanceTimer.ElapsedTicks - startVesselLoopTicks;
  #endif
   
  CelestialBody bodyOccludingKerbin = null;
   
  kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
  kerbinSqrDistance *= kerbinSqrDistance;
   
  if (ARConfiguration.UseAdditiveRanges)
  {
  kerbinSqrQuotient = kerbinSqrDistance /
  (this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
} }
else else
{ {
return true; kerbinSqrQuotient = kerbinSqrDistance /
} (this.maxTransmitDistance * this.maxTransmitDistance);
} }
   
/// <summary> log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
/// Finds the nearest relay. log.AppendFormat(
/// </summary> "\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
/// <returns>The nearest relay or null, if no relays in range.</returns> this,
public IAntennaRelay FindNearestRelay() this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
{ nearestRelaySqrQuotient,
// Set this relay as checked, so that we don't check it again. this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
this.relayChecked = true; bestOccludedSqrQuotient,
  kerbinSqrDistance
// Get a list of vessels within transmission range. );
List<Vessel> nearbyVessels = FlightGlobals.Vessels  
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance) #if BENCH
.ToList(); startKerbinLOSTicks = this.performanceTimer.ElapsedTicks;
  #endif
Tools.PostDebugMessage(string.Format(  
"{0}: Vessels in range: {1}", // If we don't have LOS to Kerbin, focus on relays
this.GetType().Name, if (
nearbyVessels.Count ARConfiguration.RequireLineOfSight &&
)); !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
  )
// Remove this vessel. {
nearbyVessels.RemoveAll(v => v.id == vessel.id); #if BENCH
  totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
Tools.PostDebugMessage(string.Format( #endif
"{0}: Vessels in range excluding self: {1}", log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
this.GetType().Name,  
nearbyVessels.Count // 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 (nearestRelaySqrQuotient <= 1d)
// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range. {
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
Tools.PostDebugMessage(string.Format( nearestRelaySqrQuotient, 1d);
"{0}: Found {1} nearby relays.",  
this.GetType().Name, this.KerbinDirect = false;
nearbyRelays.Count this.canTransmit = true;
)); this.targetRelay = this.nearestRelay;
  }
// Remove all relays already checked this time. // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
nearbyRelays.RemoveAll(r => r.relayChecked); else
  {
Tools.PostDebugMessage(string.Format( log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
"{0}: Found {1} nearby relays not already checked.", this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
this.GetType().Name, nearestRelaySqrQuotient, 1d);
nearbyRelays.Count  
)); this.canTransmit = false;
   
// Remove all relays that cannot transmit. // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin. // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
nearbyRelays.RemoveAll(r => !r.CanTransmit()); if (bestOccludedSqrQuotient < kerbinSqrQuotient)
  {
Tools.PostDebugMessage(string.Format( log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
"{0}: Found {1} nearby relays not already checked that can transmit.", bestOccludedRelay, kerbinSqrDistance);
this.GetType().Name,  
nearbyRelays.Count this.KerbinDirect = false;
));  
  // If the nearest relay is closer than the best occluded relay, pick it.
// Sort the available relays by distance. // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
nearbyRelays.Sort(new RelayComparer(this.vessel)); if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
  {
// Get the nearest available relay, or null if there are no available relays nearby. log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
IAntennaRelay nearestRelay = nearbyRelays.FirstOrDefault(); nearestRelaySqrQuotient, bestOccludedSqrQuotient);
   
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be this.targetRelay = this.nearestRelay;
// used next time. this.firstOccludingBody = null;
this.relayChecked = false; }
  // Otherwise, target the best occluded relay.
// Return the nearest available relay, or null if there are no available relays nearby. else
return nearestRelay; {
} log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrQuotient, bestOccludedSqrQuotient);
/// <summary>  
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. this.targetRelay = bestOccludedRelay;
/// </summary> this.firstOccludingBody = bodyOccludingBestOccludedRelay;
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> }
public AntennaRelay(Vessel v) }
{ // Otherwise, check Kerbin against the nearest relay.
this.vessel = v; // Since we have LOS, blank the first occluding body.
  else
// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start, {
// we hope it is safe enough. log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); bestOccludedRelay, kerbinSqrDistance);
}  
  // If the nearest relay is closer than Kerbin, pick it.
/* // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
* Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance. if (nearestRelaySqrQuotient < kerbinSqrQuotient)
* */ {
internal class RelayComparer : IComparer<IAntennaRelay> log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
{ nearestRelaySqrQuotient, kerbinSqrQuotient);
/// <summary>  
/// The reference Vessel (usually the active vessel). this.KerbinDirect = false;
/// </summary> this.firstOccludingBody = null;
protected Vessel referenceVessel; this.targetRelay = this.nearestRelay;
  }
// We don't want no stinking public parameterless constructors. // Otherwise, pick Kerbin.
private RelayComparer() {} else
  {
/// <summary> log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use nearestRelaySqrQuotient, kerbinSqrQuotient);
/// in sorting relays by distance.  
/// </summary> this.KerbinDirect = true;
/// <param name="reference">The reference Vessel</param> this.firstOccludingBody = bodyOccludingKerbin;
public RelayComparer(Vessel reference) this.targetRelay = null;
{ }
this.referenceVessel = reference; }
} }
  }
/// <summary> // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
/// Compare the <see cref="IAntennaRelay"/>s "one" and "two". else
/// </summary> {
/// <param name="one">The first IAntennaRelay in the comparison</param> #if BENCH
/// <param name="two">The second IAntennaRelay in the comparison</param> totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
public int Compare(IAntennaRelay one, IAntennaRelay two) #endif
{  
double distanceOne; log.AppendFormat("\n\tKerbin is in LOS.");
double distanceTwo;  
  // If the nearest relay is closer than Kerbin and in range, transmit to it.
distanceOne = one.vessel.DistanceTo(referenceVessel); if (nearestRelaySqrQuotient <= 1d)
distanceTwo = two.vessel.DistanceTo(referenceVessel); {
  log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
return distanceOne.CompareTo(distanceTwo); this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
} nearestRelaySqrQuotient, 1d);
   
  this.canTransmit = true;
   
  // If the nearestRelay is closer than Kerbin, use it.
  if (nearestRelaySqrQuotient < kerbinSqrQuotient)
  {
  log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, kerbinSqrQuotient);
   
  this.KerbinDirect = false;
  this.targetRelay = this.nearestRelay;
  }
  // Otherwise, Kerbin is closer, so use it.
  else
  {
  log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, kerbinSqrQuotient);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  }
  // If the nearest relay is out of range, we still need to check on Kerbin.
  else
  {
  log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, 1d);
   
  // If Kerbin is in range, use it.
  if (kerbinSqrQuotient <= 1d)
  {
  log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
  kerbinSqrQuotient, 1d);
   
  this.canTransmit = true;
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
  // Kerbin and bestOccludedRelay
  else
  {
  log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
  kerbinSqrQuotient, 1d);
   
  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 (bestOccludedSqrQuotient < kerbinSqrQuotient)
  {
  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 (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrQuotient, bestOccludedSqrQuotient);
   
  this.targetRelay = this.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.",
  nearestRelaySqrQuotient, bestOccludedSqrQuotient);
   
  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 (nearestRelaySqrQuotient < kerbinSqrQuotient)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrQuotient, kerbinSqrQuotient);
   
  this.KerbinDirect = false;
  this.targetRelay = this.nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrQuotient, kerbinSqrQuotient);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  }
  }
  }
  }
   
  if (ARConfiguration.UseAdditiveRanges)
  {
  if (this.KerbinDirect)
  {
  this.NominalLinkSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
  this.MaximumLinkSqrDistance = this.maxTransmitDistance * ARConfiguration.KerbinRelayRange;
  }
  else
  {
  this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance;
  this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.targetRelay.maxTransmitDistance;
  }
  }
  else
  {
  this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
  this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
  }
   
  if (this.canTransmit)
  {
  if (this.CurrentLinkSqrDistance < this.NominalLinkSqrDistance)
  {
  this.LinkStatus = ConnectionStatus.Optimal;
  }
  else
  {
  this.LinkStatus = ConnectionStatus.Suboptimal;
  }
  }
  else
  {
  this.LinkStatus = ConnectionStatus.None;
  }
   
  #if BENCH
  statusResolutionTicks = performanceTimer.ElapsedTicks - startKerbinLOSTicks - totalKerbinLOSTicks;
  #endif
   
  log.AppendFormat("\n{0}: Target search and 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
   
  #if BENCH
  AntennaRelay.searchTimer += (ulong)this.performanceTimer.ElapsedTicks;
  AntennaRelay.searchCount++;
  this.performanceTimer.Stop();
   
  double averageSearchTime = (double)AntennaRelay.searchTimer / (double)AntennaRelay.searchCount;
   
  if (AntennaRelay.searchCount >= 8000u / (ulong)ARConfiguration.UpdateDelay)
  {
  AntennaRelay.searchCount = 0u;
  AntennaRelay.searchTimer = 0u;
   
  AntennaRelay.averager.AddItem(averageSearchTime);
  AntennaRelay.doubleAverageTime = (long)(AntennaRelay.averager.Average * 2d);
  }
   
  if (this.performanceTimer.ElapsedTicks > AntennaRelay.doubleAverageTime)
  {
  System.Text.StringBuilder sb = Tools.GetStringBuilder();
   
  sb.AppendFormat(Tools.SIFormatter, "[AntennaRelay] FindNearestRelay search for {0}" +
  " took significantly longer than average ({1:S3}s vs {2:S3}s)",
  this.ToString(),
  (double)this.performanceTimer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
  (double)AntennaRelay.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\tVessel loop time: {0:S3}s",
  (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\t\tAverage vessel time for each of {1} vessels: {0:S3}s",
  (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency /
  (double)usefulVesselCount,
  usefulVesselCount
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest vessel LOS check: {0:S3}s to {1}",
  (double)slowestLOSVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
  slowestLOSVesselName
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest circular relay check: {0:S3}s for {1}",
  (double)slowestCircularVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
  slowestCircularVesselName
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\tKerbin LOS check: {0:S3}s",
  (double)totalKerbinLOSTicks / (double)System.Diagnostics.Stopwatch.Frequency
  );
   
  sb.AppendFormat(Tools.SIFormatter, "\n\tStatus resolution check: {0:S3}s",
  (double)statusResolutionTicks / (double)System.Diagnostics.Stopwatch.Frequency
  );
   
  // sb.AppendFormat(Tools.SIFormatter, "", start)
   
  Tools.PostWarningMessage(sb.ToString());
   
  Tools.PutStringBuilder(sb);
  }
  #endif
  }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
  public override string ToString()
  {
  if (this is ProtoAntennaRelay)
  {
  return (this as ProtoAntennaRelay).ToString();
  }
  return this.moduleRef.ToString();
  }
   
  /// <summary>
  /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
  /// </summary>
  /// <param name="module">The module reference underlying this AntennaRelay,
  /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
  public AntennaRelay(IAntennaRelay module)
  {
  this.moduleRef = module;
   
  #if BENCH
  AntennaRelay.relayCount++;
  #endif
   
  Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
} }
} }
} }
   
   
file:b/ChangeLog (new)
  2014-01-14 toadicus <>
 
  * ModuleLimitedDataTransmitter.cs: Added a ":" to the
  transmission communications for consistency with stock
  behavior.
 
 
file:a/Extensions.cs (deleted)
using System;  
using System.Collections.Generic;  
using System.Linq;  
 
namespace AntennaRange  
{  
/*  
* A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.  
* */  
public static class Extensions  
{  
/// <summary>  
/// Returns the distance between this Vessel and another Vessel.  
/// </summary>  
/// <param name="vesselOne">This <see cref="Vessel"/><see ></param>  
/// <param name="vesselTwo">Another <see cref="Vessel"/></param>  
public static double DistanceTo(this Vessel vesselOne, Vessel vesselTwo)  
{  
return (vesselOne.GetWorldPos3D() - vesselTwo.GetWorldPos3D()).magnitude;  
}  
 
/// <summary>  
/// Returns the distance between this Vessel and a CelestialBody  
/// </summary>  
/// <param name="vessel">This Vessel</param>  
/// <param name="body">A <see cref="CelestialBody"/></param>  
public static double DistanceTo(this Vessel vessel, CelestialBody body)  
{  
return (vessel.GetWorldPos3D() - body.position).magnitude;  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and a Vessel  
/// </summary>  
/// <param name="relay">This <see cref="IAntennaRelay"/></param>  
/// <param name="Vessel">A <see cref="Vessel"/></param>  
public static double DistanceTo(this IAntennaRelay relay, Vessel Vessel)  
{  
return relay.vessel.DistanceTo(Vessel);  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and a CelestialBody  
/// </summary>  
/// <param name="relay">This <see cref="IAntennaRelay"/></param>  
/// <param name="body">A <see cref="CelestialBody"/></param>  
public static double DistanceTo(this IAntennaRelay relay, CelestialBody body)  
{  
return relay.vessel.DistanceTo(body);  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and another IAntennaRelay  
/// </summary>  
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param>  
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>  
public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)  
{  
return relayOne.DistanceTo(relayTwo.vessel);  
}  
 
/// <summary>  
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.  
/// </summary>  
/// <param name="vessel">This <see cref="Vessel"/></param>  
public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)  
{  
Tools.PostDebugMessage(string.Format(  
"{0}: Getting antenna relays from vessel {1}.",  
"IAntennaRelay",  
vessel.name  
));  
 
List<IAntennaRelay> Transmitters;  
 
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.  
if (vessel.loaded) {  
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} is loaded.",  
"IAntennaRelay",  
vessel.name  
));  
 
// Gets a list of PartModules implementing IAntennaRelay  
Transmitters = vessel.Parts  
.SelectMany (p => p.Modules.OfType<IAntennaRelay> ())  
.ToList();  
}  
// If the vessel is not loaded, we need to find ProtoPartModuleSnapshots with a true IsAntenna field.  
else  
{  
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} is not loaded.",  
"IAntennaRelay",  
vessel.name  
));  
 
Transmitters = new List<IAntennaRelay>();  
 
// Loop through the ProtoPartModuleSnapshots in this Vessel  
foreach (ProtoPartModuleSnapshot ms in vessel.protoVessel.protoPartSnapshots.SelectMany(ps => ps.modules))  
{  
// If they are antennas...  
if (ms.IsAntenna())  
{  
// ...add a new ProtoAntennaRelay wrapper to the list.  
Transmitters.Add(new ProtoAntennaRelay(ms, vessel));  
}  
}  
}  
 
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} has {2} transmitters.",  
"IAntennaRelay",  
vessel.name,  
Transmitters.Count  
));  
 
// Return the list of IAntennaRelays  
return Transmitters;  
}  
 
// Returns true if this PartModule contains a True IsAntenna field, false otherwise.  
public static bool IsAntenna (this PartModule module)  
{  
return module.Fields.GetValue<bool> ("IsAntenna");  
}  
 
// Returns true if this ProtoPartModuleSnapshot contains a persistent True IsAntenna field, false otherwise  
public static bool IsAntenna(this ProtoPartModuleSnapshot protomodule)  
{  
bool result;  
 
return Boolean.TryParse (protomodule.moduleValues.GetValue ("IsAntenna") ?? "False", out result)  
? result : false;  
}  
}  
}  
 
 
  ACTIVE_TEXTURE_MANAGER_CONFIG
  {
  folder = AntennaRange
  enabled = true
  OVERRIDES
  {
  AntennaRange/.*
  {
  compress = true
  mipmaps = false
  scale = 1
  max_size = 0
  }
  }
  }
  // AntennaRange
  //
  // AntennaRange.cfg
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // Specifications:
  // nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by
  // packetResourceCost and packetSize.
  // maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
  // power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
  // maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  //
 
  @PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 6364
  simpleRange = 20500000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  @PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 3500000000
  simpleRange = 18000000000
  maxPowerFactor = 4
  maxDataFactor = 8
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  @PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  @packetResourceCost /= 1.414213
  nominalRange = 10000000000
  simpleRange = 56250000000
  maxPowerFactor = 16
  maxDataFactor = 2
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  TRACKING_STATION_RANGES
  {
  range = 800000
  range = 200000000000
  range = 2250000000000
  }
 
  EVA_MODULE
  {
  name = ModuleLimitedDataTransmitter
 
  nominalRange = 1389
  maxPowerFactor = 1
  maxDataFactor = 1
 
  packetInterval = 0.2
  packetSize = 1
  packetResourceCost = 6.25
 
  requiredResource = ElectricCharge
  }
 
  EVA_RESOURCE
  {
  name = ElectricCharge
  amount = 100
  maxAmount = 100
  }
 
  @EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport]
  {
  !name = DELETE
  }
 
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
  // AntennaRange
  //
  // IAntennaRelay.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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 KSP; using KSP;
using System; using System;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* Interface defining the basic functionality of AntennaRelay modules for AntennaRange. /// Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
* */ /// </summary>
public interface IAntennaRelay public interface IAntennaRelay
{ {
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value>  
Vessel vessel { get; } Vessel vessel { get; }
   
  /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  IAntennaRelay targetRelay { get; }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  bool KerbinDirect { get; }
   
  /// <summary>
  /// The link distance, in meters, at which this relay behaves nominally.
  /// </summary>
  double NominalLinkSqrDistance { get; }
   
   
  /// <summary>
  /// The link distance, in meters, beyond which this relay cannot operate.
  /// </summary>
  double MaximumLinkSqrDistance { get; }
   
/// <summary> /// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer. /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary> /// </summary>
/// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value> double CurrentLinkSqrDistance { get; }
double transmitDistance { get; }  
  /// <summary>
  /// Gets the link status.
  /// </summary>
  ConnectionStatus LinkStatus { get; }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  double nominalTransmitDistance { get; }
   
/// <summary> /// <summary>
/// The maximum distance at which this relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> double maxTransmitDistance { get; }
float maxTransmitDistance { get; }  
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// The first CelestialBody blocking line of sight to a
/// the current relay attempt.  
/// </summary> /// </summary>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> CelestialBody firstOccludingBody { get; }
bool relayChecked { get; }  
  /// <summary>
  /// Gets the Part title.
  /// </summary>
  string Title { get; }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>  
bool CanTransmit(); bool CanTransmit();
   
  /// <summary>
  /// Finds the nearest relay.
  /// </summary>
  void FindNearestRelay();
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
  /// </summary>
  string ToString();
} }
} }
   
   
  // AntennaRange
  //
  // ModuleLimitedDataTransmitter.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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 KSP;
  using System;
  using System.Collections.Generic;
  using System.Text;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  /// <summary>
  /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
  /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
  /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
  ///
  /// <para>In general, the scaling functions assume the following relation:</para>
  ///
  /// <para> D² α P/R,</para>
  ///
  /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
  /// </summary>
  public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
  {
  // Stores the packetResourceCost as defined in the .cfg file.
  private float _basepacketResourceCost;
 
  // Stores the packetSize as defined in the .cfg file.
  private float _basepacketSize;
 
  // Every antenna is a relay.
  private AntennaRelay relay;
 
  // Sometimes we will need to communicate errors; this is how we do it.
  private ScreenMessage ErrorMsg;
 
  /// <summary>
  /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
  /// prescribed by packetResourceCost and packetSize.
  /// </summary>
  [KSPField(isPersistant = false)]
  public double nominalRange;
 
  /// <summary>
  /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
  /// prescribed by packetResourceCost and packetSize.
  /// </summary>
  [KSPField(isPersistant = false)]
  public double simpleRange;
 
  /// <summary>
  /// Relay status string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
  public string UIrelayStatus;
 
  /// <summary>
  /// Relay target string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
  public string UIrelayTarget;
 
  /// <summary>
  /// Transmit distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
  public string UItransmitDistance;
 
  /// <summary>
  /// The nominal range string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
  public string UInominalLinkDistance;
 
  /// <summary>
  /// Maximum distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
  public string UImaxTransmitDistance;
 
  /// <summary>
  /// Packet size string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
  public string UIpacketSize;
 
  /// <summary>
  /// Packet cost string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
  public string UIpacketCost;
 
  /// <summary>
  /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
  /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
  /// </summary>
  [KSPField(isPersistant = false)]
  public float maxPowerFactor;
 
  /// <summary>
  /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  /// </summary>
  [KSPField(isPersistant = false)]
  public float maxDataFactor;
 
  /// <summary>
  /// The packet throttle.
  /// </summary>
  [KSPField(
  isPersistant = true,
  guiName = "Packet Throttle",
  guiUnits = "%",
  guiActive = true,
  guiActiveEditor = false
  )]
  [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
  public float packetThrottle;
 
  private bool actionUIUpdate;
 
  /*
  * Properties
  * */
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
  public new Vessel vessel
  {
  get
  {
  if (base.vessel != null)
  {
  return base.vessel;
  }
  else if (this.part != null && this.part.vessel != null)
  {
  return this.part.vessel;
  }
  else if (
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot.pVesselRef != null &&
  this.part.protoPartSnapshot.pVesselRef.vesselRef != null
  )
  {
  return this.part.protoPartSnapshot.pVesselRef.vesselRef;
  }
  else
  {
  this.LogError("Vessel and/or part reference are null, returning null vessel.");
  this.LogError(new System.Diagnostics.StackTrace().ToString());
  return null;
  }
  }
  }
 
  /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
  {
  get
  {
  if (this.relay == null)
  {
  return null;
  }
 
  return this.relay.targetRelay;
  }
  }
 
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public bool KerbinDirect
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.KerbinDirect;
  }
 
  return false;
  }
  }
 
  /// <summary>
  /// Gets or sets the nominal link distance, in meters.
  /// </summary>
  public double NominalLinkSqrDistance
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.NominalLinkSqrDistance;
  }
 
  return 0d;
  }
  }
 
  /// <summary>
  /// Gets or sets the maximum link distance, in meters.
  /// </summary>
  public double MaximumLinkSqrDistance
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.MaximumLinkSqrDistance;
  }
 
  return 0d;
  }
  }
 
  /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  public double CurrentLinkSqrDistance
  {
  get
  {
  if (this.relay == null)
  {
  return double.PositiveInfinity;
  }
 
  return this.relay.CurrentLinkSqrDistance;
  }
  }
 
  /// <summary>
  /// Gets the link status.
  /// </summary>
  public ConnectionStatus LinkStatus
  {
  get
  {
  if (this.relay == null)
  {
  return ConnectionStatus.None;
  }
 
  return this.relay.LinkStatus;
  }
  }
 
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public double nominalTransmitDistance
  {
  get
  {
  if (ARConfiguration.UseAdditiveRanges)
  {
  return this.nominalRange;
  }
  else
  {
  return this.simpleRange;
  }
  }
  }
 
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  public double maxTransmitDistance
  {
  get;
  protected set;
  }
 
  /// <summary>
  /// The first CelestialBody blocking line of sight to a
  /// </summary>
  public CelestialBody firstOccludingBody
  {
  get
  {
  return this.relay.firstOccludingBody;
  }
  }
 
  /*
  * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
  * in that they both return floats. Here's some quick justification:
  *
  * The stock implementation of GetTransmitterScore (which I cannot override) is:
  * Score = (1 + DataResourceCost) / DataRate
  *
  * The stock DataRate and DataResourceCost are:
  * DataRate = packetSize / packetInterval
  * DataResourceCost = packetResourceCost / packetSize
  *
  * So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it
  * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,
  * that metric is not a very reasonable one.
  *
  * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
  * would look like:
  * DataRate = packetSize / packetInterval
  * DataResourceCost = packetResourceCost
  *
  * The former case, which I've chosen to implement below, is:
  * DataRate = packetSize
  * DataResourceCost = packetResourceCost
  *
  * So... hopefully that doesn't screw with anything else.
  * */
  /// <summary>
  /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
  /// terms of joules/byte
  /// </summary>
  public new float DataRate
  {
  get
  {
  this.PreTransmit_SetPacketSize();
 
  if (this.CanTransmit())
  {
  return this.packetSize;
  }
  else
  {
  return float.Epsilon;
  }
  }
  }
 
  /// <summary>
  /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
  /// to be scored in terms of joules/byte
  /// </summary>
  public new double DataResourceCost
  {
  get
  {
  this.PreTransmit_SetPacketResourceCost();
 
  if (this.CanTransmit())
  {
  return this.packetResourceCost;
  }
  else
  {
  return float.PositiveInfinity;
  }
  }
  }
 
  /// <summary>
  /// Gets the Part title.
  /// </summary>
  public string Title
  {
  get
  {
  if (this.part != null && this.part.partInfo != null)
  {
  return this.part.partInfo.title;
  }
 
  return string.Empty;
  }
  }
 
  /*
  * Methods
  * */
  // Build ALL the objects.
  public ModuleLimitedDataTransmitter () : base()
  {
  this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
  this.packetThrottle = 100f;
  }
 
  /// <summary>
  /// PartModule OnAwake override; runs at Unity Awake.
  /// </summary>
  public override void OnAwake()
  {
  base.OnAwake();
 
  this._basepacketSize = base.packetSize;
  this._basepacketResourceCost = base.packetResourceCost;
 
  Tools.PostDebugMessage(string.Format(
  "{0} loaded:\n" +
  "packetSize: {1}\n" +
  "packetResourceCost: {2}\n" +
  "nominalTransmitDistance: {3}\n" +
  "maxPowerFactor: {4}\n" +
  "maxDataFactor: {5}\n",
  this.name,
  base.packetSize,
  this._basepacketResourceCost,
  this.nominalTransmitDistance,
  this.maxPowerFactor,
  this.maxDataFactor
  ));
  }
 
  /// <summary>
  /// PartModule OnStart override; runs at Unity Start.
  /// </summary>
  /// <param name="state">State.</param>
  public override void OnStart (StartState state)
  {
  base.OnStart (state);
 
  if (state >= StartState.PreLaunch)
  {
  this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
 
  this.relay = new AntennaRelay(this);
  this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
  this.relay.maxTransmitDistance = this.maxTransmitDistance;
 
  this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance);
 
  GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
  GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
  }
  }
 
  /// <summary>
  /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
  /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
  /// work.
  /// </summary>
  /// <param name="node"><see cref="ConfigNode"/> with data for this module.</param>
  public override void OnLoad(ConfigNode node)
  {
  this.Fields.Load(node);
  base.Fields.Load(node);
 
  base.OnLoad (node);
 
  this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
  }
 
  /// <summary>
  /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
  /// </summary>
  public override string GetInfo()
  {
  StringBuilder sb = Tools.GetStringBuilder();
  string text;
 
  sb.Append(base.GetInfo());
  sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
  sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m\n", this.maxTransmitDistance);
 
  text = sb.ToString();
 
  Tools.PutStringBuilder(sb);
 
  return text;
  }
 
  /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
  public new bool CanTransmit()
  {
  if (this.part == null || this.relay == null)
  {
  return false;
  }
 
  switch (this.part.State)
  {
  case PartStates.DEAD:
  case PartStates.DEACTIVATED:
  Tools.PostDebugMessage(string.Format(
  "{0}: {1} on {2} cannot transmit: {3}",
  this.GetType().Name,
  this.part.partInfo.title,
  this.vessel.vesselName,
  Enum.GetName(typeof(PartStates), this.part.State)
  ));
  return false;
  default:
  break;
  }
 
  return this.relay.CanTransmit();
  }
 
  /// <summary>
  /// Finds the nearest relay.
  /// </summary>
  public void FindNearestRelay()
  {
  if (this.relay != null)
  {
  this.relay.FindNearestRelay();
  }
  }
 
  /// <summary>
  /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  /// <param name="callback">Callback function</param>
  public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
  {
  this.LogDebug(
  "TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}",
  dataQueue.Count
  );
 
  this.FindNearestRelay();
 
  this.PreTransmit_SetPacketSize();
  this.PreTransmit_SetPacketResourceCost();
 
  if (this.CanTransmit())
  {
  ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
  this.LogDebug(
  "CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
  dataQueue.SPrint(),
  callback == null ? "null" : callback.ToString()
  );
 
  if (callback == null)
  {
  base.TransmitData(dataQueue);
  }
  else
  {
  base.TransmitData(dataQueue, callback);
  }
  }
  else
  {
  Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
 
  var logger = Tools.DebugLogger.New(this);
 
  IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
  ModuleScienceContainer scienceContainer;
  for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
  {
  scienceContainer = vesselContainers[cIdx];
 
  logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
  scienceContainer.part.partInfo.title);
 
  if (
  scienceContainer.capacity != 0 &&
  scienceContainer.GetScienceCount() >= scienceContainer.capacity
  )
  {
  logger.Append("\tInsufficient capacity, skipping.\n");
  continue;
  }
 
  List<ScienceData> dataStored = new List<ScienceData>();
 
  ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
  {
  data = dataQueue[dIdx];
  if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
  {
  logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
  continue;
  }
 
  logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
  if (scienceContainer.AddData(data))
  {
  logger.Append("done, removing from queue.\n");
 
  dataStored.Add(data);
  }
  #if DEBUG
  else
  {
  logger.Append("failed.\n");
  }
  #endif
  }
 
  dataQueue.RemoveAll(i => dataStored.Contains(i));
 
  logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
  }
 
  logger.Print();
 
  if (dataQueue.Count > 0)
  {
  StringBuilder sb = Tools.GetStringBuilder();
 
  sb.Append('[');
  sb.Append(this.part.partInfo.title);
  sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
  sb.Append("Data to be discarded:\n");
 
  ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
  {
  data = dataQueue[dIdx];
  sb.AppendFormat("\t{0}\n", data.title);
  }
 
  ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
  Tools.PostDebugMessage(sb.ToString());
 
  Tools.PutStringBuilder(sb);
  }
 
  this.PostCannotTransmitError();
  }
 
  Tools.PostDebugMessage (
  "distance: " + this.CurrentLinkSqrDistance
  + " packetSize: " + this.packetSize
  + " packetResourceCost: " + this.packetResourceCost
  );
  }
 
  /// <summary>
  /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  public new void TransmitData(List<ScienceData> dataQueue)
  {
  this.LogDebug(
  "TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
  dataQueue.Count
  );
 
  this.TransmitData(dataQueue, null);
  }
 
  /// <summary>
  /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  public new void StartTransmission()
  {
  this.FindNearestRelay();
 
  PreTransmit_SetPacketSize ();
  PreTransmit_SetPacketResourceCost ();
 
  Tools.PostDebugMessage (
  "distance: " + this.CurrentLinkSqrDistance
  + " packetSize: " + this.packetSize
  + " packetResourceCost: " + this.packetResourceCost
  );
 
  if (this.CanTransmit())
  {
  ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
  base.StartTransmission();
  }
  else
  {
  this.PostCannotTransmitError ();
  }
  }
 
  /// <summary>
  /// MonoBehaviour Update
  /// </summary>
  public void Update()
  {
  if (this.actionUIUpdate)
  {
  this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
  Math.Sqrt(this.MaximumLinkSqrDistance));
  this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
  Math.Sqrt(this.NominalLinkSqrDistance));
 
  if (this.CanTransmit())
  {
  this.UIrelayStatus = this.LinkStatus.ToString();
  this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
  Math.Sqrt(this.CurrentLinkSqrDistance));
  this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate);
  this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost);
  }
  else
  {
  if (this.relay.firstOccludingBody == null)
  {
  this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
  Math.Sqrt(this.CurrentLinkSqrDistance));
  this.UIrelayStatus = "Out of range";
  }
  else
  {
  this.UItransmitDistance = "N/A";
  this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
  }
  this.UIpacketSize = "N/A";
  this.UIpacketCost = "N/A";
  }
 
  if (this.KerbinDirect)
  {
  this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
  }
  else
  {
  this.UIrelayTarget = this.targetRelay.ToString();
  }
  }
  }
 
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
  public override string ToString()
  {
  StringBuilder sb = Tools.GetStringBuilder();
  string msg;
 
  sb.Append(this.part.partInfo.title);
 
  if (vessel != null)
  {
  sb.Append(" on ");
  sb.Append(vessel.vesselName);
  }
  else if (
  this.part != null &&
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot.pVesselRef != null
  )
  {
  sb.Append(" on ");
  sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
  }
 
  msg = sb.ToString();
 
  Tools.PutStringBuilder(sb);
 
  return msg;
  }
 
  // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
  private void onPartActionUICreate(Part eventPart)
  {
  if (eventPart == base.part)
  {
  this.actionUIUpdate = true;
  }
  }
 
  // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
  private void onPartActionUIDismiss(Part eventPart)
  {
  if (eventPart == base.part)
  {
  this.actionUIUpdate = false;
  }
  }
 
  // Post an error in the communication messages describing the reason transmission has failed. Currently there
  // is only one reason for this.
  private void PostCannotTransmitError()
  {
  string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
 
  this.ErrorMsg.message = string.Format(
  "<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
  ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
  ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
  ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
  ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
  ErrorText
  );
 
  Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);
 
  ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
  }
 
  // Before transmission, set packetResourceCost. Per above, packet cost increases with the square of
  // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
  // transmission fails (see CanTransmit).
  private void PreTransmit_SetPacketResourceCost()
  {
  if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance)
  {
  base.packetResourceCost = this._basepacketResourceCost;
  }
  else
  {
  float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance);
 
  base.packetResourceCost = this._basepacketResourceCost * rangeFactor;
  }
 
  base.packetResourceCost *= this.packetThrottle / 100f;
  }
 
  // Before transmission, set packetSize. Per above, packet size increases with the inverse square of
  // distance. packetSize maxes out at _basepacketSize * maxDataFactor.
  private void PreTransmit_SetPacketSize()
  {
  if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance)
  {
  base.packetSize = this._basepacketSize;
  }
  else
  {
  float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
 
  base.packetSize = Mathf.Min(
  this._basepacketSize * rangeFactor,
  this._basepacketSize * this.maxDataFactor
  );
  }
 
  base.packetSize *= this.packetThrottle / 100f;
  }
 
  private string buildTransmitMessage()
  {
  StringBuilder sb = Tools.GetStringBuilder();
  string msg;
 
  sb.Append("[");
  sb.Append(base.part.partInfo.title);
  sb.Append("]: ");
 
  sb.Append("Beginning transmission ");
 
  if (this.KerbinDirect)
  {
  sb.Append("directly to Kerbin.");
  }
  else
  {
  sb.Append("via ");
  sb.Append(this.relay.targetRelay);
  }
 
  msg = sb.ToString();
 
  Tools.PutStringBuilder(sb);
 
  return msg;
  }
 
  #if DEBUG
  // When debugging, it's nice to have a button that just tells you everything.
  [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
  public void DebugInfo()
  {
  PreTransmit_SetPacketSize ();
  PreTransmit_SetPacketResourceCost ();
 
  DebugPartModule.DumpClassObject(this);
  }
 
  [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
  public void PrintAllVessels()
  {
  StringBuilder sb = Tools.GetStringBuilder();
 
  sb.Append("Dumping FlightGlobals.Vessels:");
 
  Vessel vessel;
  for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
  {
  vessel = FlightGlobals.Vessels[i];
  sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
  }
 
  Tools.PostDebugMessage(sb.ToString());
 
  Tools.PutStringBuilder(sb);
  }
 
  [KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
  public void DumpRelayDB()
  {
  RelayDatabase.Instance.Dump();
  }
  #endif
  }
  }
  // AntennaRange
  //
  // AssemblyInfo.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.Reflection;
  using System.Runtime.CompilerServices;
 
  [assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
 
  // Information about this assembly is defined by the following attributes.
  // Change them to the values specific to your project.
  [assembly: AssemblyTitle("AntennaRange")]
  [assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
  [assembly: AssemblyCopyright("toadicus")]
  // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
  // The form "{Major}.{Minor}.*" will automatically update the build and revision,
  // and "{Major}.{Minor}.{Build}.*" will update just the revision.
  [assembly: AssemblyVersion("1.9.*")]
  // The following attributes are used to specify the signing key for the assembly,
  // if desired. See the Mono documentation for more information about signing.
  //[assembly: AssemblyDelaySign(false)]
  //[assembly: AssemblyKeyFile("")]
 
  2014-01-14 toadicus <>
 
  * AssemblyInfo.cs: New AssemblyInfo file for reason.
 
 
  // AntennaRange
  //
  // ProtoAntennaRelay.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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 KSP;
using System; using System;
  using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay. /// Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
* This is used for finding relays in unloaded Vessels. /// This is used for finding relays in unloaded Vessels.
* */ /// </summary>
public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
{ {
protected ProtoPartModuleSnapshot snapshot; // Stores the prototype part so we can make sure we haven't exploded or so.
  private ProtoPartSnapshot protoPart;
   
/// <summary> /// <summary>
/// The maximum distance at which this transmitter can operate. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> public override Vessel vessel
public override float maxTransmitDistance  
{ {
get get
{ {
double result; if (
Double.TryParse(snapshot.moduleValues.GetValue ("ARmaxTransmitDistance") ?? "0", out result); this.protoPart != null &&
return (float)result; this.protoPart.pVesselRef != null &&
} this.protoPart.pVesselRef.vesselRef != null
} )
   
/// <summary>  
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during  
/// the current relay attempt.  
/// </summary>  
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>  
public override bool relayChecked  
{  
get  
{  
bool result;  
Boolean.TryParse(this.snapshot.moduleValues.GetValue("relayChecked"), out result);  
return result;  
}  
protected set  
{  
if (this.snapshot.moduleValues.HasValue("relayChecked"))  
{ {
this.snapshot.moduleValues.SetValue("relayChecked", value.ToString ()); return this.protoPart.pVesselRef.vesselRef;
} }
else else
{ {
this.snapshot.moduleValues.AddValue("relayChecked", value); Tools.PostErrorMessage("{0}: Could not fetch vessel! {1}{2}{3}",
  this.ToString(),
  this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
  this.protoPart != null && this.protoPart.pVesselRef == null ?
  "\n\tthis.protoPart.pVesselRef=null" : string.Empty,
  this.protoPart != null && this.protoPart.pVesselRef != null &&
  this.protoPart.pVesselRef.vesselRef == null ?
  "\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty
  );
  return null;
} }
} }
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </summary>
/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param> public override double nominalTransmitDistance
/// <param name="vessel">The parent Vessel</param>  
public ProtoAntennaRelay(ProtoPartModuleSnapshot ms, Vessel vessel) : base(vessel)  
{ {
this.snapshot = ms; get
  {
  return this.moduleRef.nominalTransmitDistance;
  }
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  public override double maxTransmitDistance
  {
  get
  {
  return moduleRef.maxTransmitDistance;
  }
  }
   
  /// <summary>
  /// Gets the underlying part's title.
  /// </summary>
  /// <value>The title.</value>
  public string Title
  {
  get
  {
  if (this.protoPart != null && this.protoPart.partInfo != null)
  {
  return this.protoPart.partInfo.title;
  }
   
  return string.Empty;
  }
  }
   
  /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
  public override bool CanTransmit()
  {
  PartStates partState = (PartStates)this.protoPart.state;
  if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
  {
  Tools.PostDebugMessage(string.Format(
  "{0}: {1} on {2} cannot transmit: {3}",
  this.GetType().Name,
  this.Title,
  this.vessel.vesselName,
  Enum.GetName(typeof(PartStates), partState)
  ));
  return false;
  }
  return base.CanTransmit();
  }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
  public override string ToString()
  {
  System.Text.StringBuilder sb = Tools.GetStringBuilder();
   
  sb.Append(this.Title);
   
  if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
  }
   
  Tools.PutStringBuilder(sb);
   
  return sb.ToString();
  }
   
  /// <summary>
  /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
  /// </summary>
  /// <param name="prefabRelay">The module reference underlying this AntennaRelay,
  /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
  /// <param name="pps">The prototype partreference on which the module resides.</param>
  public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
  {
  this.protoPart = pps;
   
  Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
} }
} }
} }
   
   
file:b/RelayDatabase.cs (new)
  // AntennaRange
  //
  // RelayDatabase.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.
 
  #pragma warning disable 1591
 
  using KSP;
  using System;
  using System.Collections.Generic;
  using System.Text;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  public class RelayDatabase : Singleton<RelayDatabase>
  {
  /*
  * Instance members
  * */
 
  /*
  * Fields
  * */
  // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
  private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
  private Dictionary<Guid, IAntennaRelay> bestRelayTable;
 
  // Vessel.id-keyed hash table of part counts, used for caching
  private Dictionary<Guid, int> vesselPartCountTable;
 
  private int cacheHits;
  private int cacheMisses;
 
  /*
  * Properties
  * */
  // Gets the Part-hashed table of relays in a given vessel
  public IList<IAntennaRelay> this [Vessel vessel]
  {
  get
  {
  // If we don't have an entry for this vessel...
  if (!this.ContainsKey(vessel.id))
  {
  // ...Generate an entry for this vessel.
  this.AddVessel(vessel);
  this.cacheMisses++;
  }
  // If our part count disagrees with the vessel's part count...
  else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
  {
  // ...Update the our vessel in the cache
  this.UpdateVessel(vessel);
  this.cacheMisses++;
  }
  // Otherwise, it's a hit
  else
  {
  this.cacheHits++;
  }
 
  // Return the Part-hashed table of relays for this vessel
  return relayDatabase[vessel.id].AsReadOnly();
  }
  }
 
  /*
  * Methods
  * */
  // Remove a vessel from the cache, if it exists.
  public void DirtyVessel(Vessel vessel)
  {
  #if DEBUG
  Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
  vessel, new System.Diagnostics.StackTrace().ToString());
  #else
  Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
  #endif
 
  this.relayDatabase.Remove(vessel.id);
  this.vesselPartCountTable.Remove(vessel.id);
  this.bestRelayTable.Remove(vessel.id);
  }
 
  public void ClearCache()
  {
  Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
 
  this.relayDatabase.Clear();
  this.bestRelayTable.Clear();
  this.vesselPartCountTable.Clear();
  }
 
  // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
  public bool ContainsKey(Guid key)
  {
  return this.relayDatabase.ContainsKey(key);
  }
 
  // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
  public bool ContainsKey(Vessel vessel)
  {
  return this.ContainsKey(vessel.id);
  }
 
  public IAntennaRelay GetBestVesselRelay(Vessel vessel)
  {
  IAntennaRelay relay;
  if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
  {
  return relay;
  }
  else
  {
  var dump = this[vessel];
  return null;
  }
  }
 
  // Adds a vessel to the database
  // The return for this function isn't used yet, but seems useful for potential future API-uses
  private bool AddVessel(Vessel vessel)
  {
  // If this vessel is already here...
  if (this.ContainsKey(vessel))
  {
  // ...post an error
  Debug.LogWarning(string.Format(
  "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
  this.GetType().Name,
  vessel.vesselName,
  vessel.id
  ));
 
  // ...and refuse to add
  return false;
  }
  // otherwise, add the vessel to our tables...
  else
  {
  // Build an empty table...
  this.relayDatabase[vessel.id] = new List<IAntennaRelay>();
 
  // Update the empty index
  this.UpdateVessel(vessel);
 
  // Return success
  return true;
  }
  }
 
  // Update the vessel's entry in the table
  private void UpdateVessel(Vessel vessel)
  {
  // Squak if the database doesn't have the vessel
  if (!this.ContainsKey(vessel))
  {
  throw new InvalidOperationException(string.Format(
  "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
  this.GetType().Name,
  vessel.vesselName,
  vessel.id
  ));
  }
 
  List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
 
  // Actually build and assign the table
  this.getVesselRelays(vessel, ref vesselTable);
  // Set the part count
  this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
  }
 
  // Runs when a vessel is modified (or when we switch to one, to catch docking events)
  public void onVesselEvent(Vessel vessel)
  {
  // If we have this vessel in our cache...
  if (this.ContainsKey(vessel))
  {
  // If our part counts disagree (such as if a part has been added or broken off,
  // or if we've just docked or undocked)...
  if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
  {
  Tools.PostDebugMessage(string.Format(
  "{0}: dirtying cache for vessel '{1}' ({2}).",
  this.GetType().Name,
  vessel.vesselName,
  vessel.id
  ));
 
  // Dirty the cache (real vessels will never have negative part counts)
  this.DirtyVessel(vessel);
  }
  }
  }
 
  // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
  private void onSceneChange(GameScenes scene)
  {
  Tools.PostDebugMessage(
  "RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}",
  HighLogic.LoadedScene,
  scene,
  FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
  );
 
  if (scene == GameScenes.FLIGHT)
  {
  if (scene == HighLogic.LoadedScene)
  {
  if (FlightGlobals.ActiveVessel != null)
  {
  Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
  FlightGlobals.ActiveVessel.vesselName);
 
  this.onVesselEvent(FlightGlobals.ActiveVessel);
  }
  }
  else
  {
  this.ClearCache();
  }
  }
  }
 
  private void onGameLoaded(object data)
  {
  this.ClearCache();
  }
 
  // Runs when parts are undocked
  private void onPartEvent(Part part)
  {
  if (part != null && part.vessel != null)
  {
  this.onVesselEvent(part.vessel);
  }
  }
 
  // Runs when parts are coupled, as in docking
  private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
  {
  this.onPartEvent(data.from);
  this.onPartEvent(data.to);
  }
 
  // Produce a Part-hashed table of relays for the given vessel
  private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
  {
  // We're going to completely regen this table, so dump the current contents.
  relays.Clear();
 
  Tools.PostDebugMessage(string.Format(
  "{0}: Getting antenna relays from vessel {1}.",
  "IAntennaRelay",
  vessel.vesselName
  ));
 
  double bestRelayRange = double.NegativeInfinity;
  IAntennaRelay bestRelay = null;
  IAntennaRelay relay;
 
  // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
  if (vessel.loaded) {
  Tools.PostDebugMessage(string.Format(
  "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
  "IAntennaRelay",
  vessel.vesselName
  ));
 
  // Loop through the Parts in the Vessel...
  Part part;
  for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
  {
  part = vessel.Parts[partIdx];
 
  // ...loop through the PartModules in the Part...
  PartModule module;
  for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++)
  {
  module = part.Modules[modIdx];
 
  // ...if the module is a relay...
  if (module is IAntennaRelay)
  {
  relay = (module as IAntennaRelay);
 
  if (relay.maxTransmitDistance > bestRelayRange)
  {
  bestRelayRange = relay.maxTransmitDistance;
  bestRelay = relay;
  }
 
  // ...add the module to the table
  relays.Add(relay);
  // ...neglect relay objects after the first in each part.
  break;
  }
  }
  }
  }
  // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
  else
  {
  Tools.PostDebugMessage(string.Format(
  "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
  this.GetType().Name,
  vessel.vesselName
  ));
 
  // Loop through the ProtoPartModuleSnapshots in the Vessel...
  ProtoPartSnapshot pps;
  for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
  {
  pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
 
  Tools.PostDebugMessage(string.Format(
  "{0}: Searching in protopartsnapshot {1}",
  this.GetType().Name,
  pps
  ));
 
  // ...Fetch the prefab, because it's more useful for what we're doing.
  Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
 
  Tools.PostDebugMessage(string.Format(
  "{0}: Got partPrefab {1} in protopartsnapshot {2}",
  this.GetType().Name,
  partPrefab,
  pps
  ));
 
  // ...loop through the PartModules in the prefab...
  PartModule module;
  for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
  {
  module = partPrefab.Modules[modIdx];
 
  Tools.PostDebugMessage(string.Format(
  "{0}: Searching in partmodule {1}",
  this.GetType().Name,
  module
  ));
 
  // ...if the module is a relay...
  if (module is IAntennaRelay)
  {
  Tools.PostDebugMessage(string.Format(
  "{0}: partmodule {1} is antennarelay",
  this.GetType().Name,
  module
  ));
 
  relay = new ProtoAntennaRelay(module as IAntennaRelay, pps);
 
  if (relay.maxTransmitDistance > bestRelayRange)
  {
  bestRelayRange = relay.maxTransmitDistance;
  bestRelay = relay;
  }
 
  // ...build a new ProtoAntennaRelay and add it to the table
  relays.Add(relay);
  // ...neglect relay objects after the first in each part.
  break;
  }
  }
  }
  }
 
  this.bestRelayTable[vessel.id] = bestRelay;
 
  Tools.PostDebugMessage(string.Format(
  "{0}: vessel '{1}' ({2}) has {3} transmitters.",
  "IAntennaRelay",
  vessel.vesselName,
  vessel.id,
  relays.Count
  ));
  }
 
  // Construct the singleton
  private RelayDatabase()
  {
  // Initialize the databases
  this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
  this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
  this.vesselPartCountTable = new Dictionary<Guid, int>();
 
  this.cacheHits = 0;
  this.cacheMisses = 0;
 
  // Subscribe to some events
  GameEvents.onVesselWasModified.Add(this.onVesselEvent);
  GameEvents.onVesselChange.Add(this.onVesselEvent);
  GameEvents.onVesselDestroy.Add(this.onVesselEvent);
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
  GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
  GameEvents.onPartUndock.Add(this.onPartEvent);
  GameEvents.onGameStateLoad.Add(this.onGameLoaded);
  }
 
  ~RelayDatabase()
  {
  // Unsubscribe from the events
  GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
  GameEvents.onVesselChange.Remove(this.onVesselEvent);
  GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
  GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
  GameEvents.onPartUndock.Remove(this.onPartEvent);
  GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
 
  Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
 
  KSPLog.print(string.Format(
  "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
  this.GetType().Name,
  this.cacheHits,
  this.cacheMisses,
  (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
  ));
  }
 
  #if DEBUG
  public void Dump()
  {
  StringBuilder sb = Tools.GetStringBuilder();
 
  sb.Append("Dumping RelayDatabase:");
 
  var dbEnum = this.relayDatabase.GetEnumerator();
  IList<IAntennaRelay> vesselRelays;
  while (dbEnum.MoveNext())
  {
  sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
 
  vesselRelays = dbEnum.Current.Value;
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  relay = vesselRelays[rIdx];
  sb.AppendFormat("\n\t{0}", relay.ToString());
  }
  }
 
  Tools.PostDebugMessage(sb.ToString());
 
  Tools.PutStringBuilder(sb);
  }
  #endif
  }
  }
 
 
  // AntennaRange
  //
  // Extensions.cs
  //
  // Copyright © 2014-2015, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 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.Collections.Generic;
  using ToadicusTools;
 
  namespace AntennaRange
  {
  /// <summary>
  /// A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
  /// </summary>
  public static class RelayExtensions
  {
  /// <summary>
  /// Returns the distance between two IAntennaRelays.
  /// </summary>
  /// <param name="relayOne">Relay one.</param>
  /// <param name="relayTwo">Relay two.</param>
  public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
  {
  return relayOne.vessel.DistanceTo(relayTwo.vessel);
  }
 
  /// <summary>
  /// Returns the distance from this IAntennaRelay to the given CelestialBody
  /// </summary>
  /// <param name="relay">Relay.</param>
  /// <param name="body">Body.</param>
  public static double SqrDistanceTo(this IAntennaRelay relay, CelestialBody body)
  {
  double range = relay.vessel.DistanceTo(body) - body.Radius;
 
  return range * range;
  }
 
  /// <summary>
  /// Returns the distance between two IAntennaRelays.
  /// </summary>
  /// <param name="relayOne">Relay one.</param>
  /// <param name="relayTwo">Relay two.</param>
  public static double SqrDistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
  {
  return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
  }
 
  /// <summary>
  /// Returns the distance from this IAntennaRelay to the given CelestialBody
  /// </summary>
  /// <param name="relay">Relay.</param>
  /// <param name="body">Body.</param>
  public static double DistanceTo(this IAntennaRelay relay, CelestialBody body)
  {
  double range = relay.vessel.DistanceTo(body) - body.Radius;
 
  return range;
  }
 
  /// <summary>
  /// Returns the distance between this IAntennaRelay and a Vessel
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="Vessel">A <see cref="Vessel"/></param>
  public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
  {
  return relay.vessel.DistanceTo(Vessel);
  }
 
  /// <summary>
  /// Returns the distance between this IAntennaRelay and a CelestialBody
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
  public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
  {
  return relay.vessel.DistanceTo(body) - body.Radius;
  }
 
  /// <summary>
  /// Returns the distance between this IAntennaRelay and another IAntennaRelay
  /// </summary>
  /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
  /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
  public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
  {
  return relayOne.DistanceTo(relayTwo.vessel);
  }
 
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and a Vessel
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="vessel">A <see cref="Vessel"/></param>
  public static double SqrDistanceTo(this AntennaRelay relay, Vessel vessel)
  {
  return relay.vessel.sqrDistanceTo(vessel);
  }
 
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and a CelestialBody
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
  public static double SqrDistanceTo(this AntennaRelay relay, CelestialBody body)
  {
  double dist = (relay.vessel.GetWorldPos3D() - body.position).magnitude - body.Radius;
 
  return dist * dist;
  }
 
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and another IAntennaRelay
  /// </summary>
  /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
  /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
  public static double SqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
  {
  return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
  }
 
  /// <summary>
  /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
  public static IList<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
  {
  return RelayDatabase.Instance[vessel];
  }
 
  /// <summary>
  /// Determines if the specified vessel has a connected relay.
  /// </summary>
  /// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns>
  /// <param name="vessel"></param>
  public static bool HasConnectedRelay(this Vessel vessel)
  {
  IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  relay = vesselRelays[rIdx];
  if (relay.CanTransmit())
  {
  return true;
  }
  }
 
  return false;
  }
 
  /// <summary>
  /// Gets the <see cref="AntennaRange.ConnectionStatus"/> for this <see cref="Vessel"/>
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
  public static ConnectionStatus GetConnectionStatus(this Vessel vessel)
  {
  bool canTransmit = false;
 
  IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  relay = vesselRelays[rIdx];
  if (relay.LinkStatus > ConnectionStatus.None)
  {
  canTransmit = true;
 
  if (relay.LinkStatus == ConnectionStatus.Optimal)
  {
  return ConnectionStatus.Optimal;
  }
  }
  }
 
  if (canTransmit)
  {
  return ConnectionStatus.Suboptimal;
  }
  else
  {
  return ConnectionStatus.None;
  }
  }
 
  /// <summary>
  /// Gets the best relay on this Vessel. The best relay may not be able to transmit.
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
  public static IAntennaRelay GetBestRelay(this Vessel vessel)
  {
  return RelayDatabase.Instance.GetBestVesselRelay(vessel);
  }
  }
 
  #pragma warning disable 1591
  /// <summary>
  /// An Enum describing the connection status of a vessel or relay.
  /// </summary>
  public enum ConnectionStatus
  {
  None,
  Suboptimal,
  Optimal
  }
  }
 
 
file:b/toolbarIcon.xcf (new)
 Binary files /dev/null and b/toolbarIcon.xcf differ
 Binary files /dev/null and b/toolbarIcon_24x24.xcf differ
 Binary files /dev/null and b/toolbarIcon_38x38.xcf differ