First pass at geometric ranges.
First pass at geometric ranges.

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 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 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);
 
  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 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 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);
  }
 
  private void Start()
  {
  this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
  }
 
  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 (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.ActiveVessel != null)
  {
  Vessel vessel;
  IAntennaRelay relay;
  IList<IAntennaRelay> activeVesselRelays;
 
  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);
 
  relay.FindNearestRelay();
  }
  }
 
  activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
  for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
  {
  relay = activeVesselRelays[rIdx];
 
  relay.FindNearestRelay();
  }
 
  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
  {
  #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
  {
  if (this.vesselLineRenderers == null)
  {
  this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
  }
 
  LineRenderer lr;
 
  if (this.vesselLineRenderers.TryGetValue(idx, out lr))
  {
  return lr;
  }
  else
  {
  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;
  }
  }
  }
  #endregion
 
  #region MonoBehaviour Lifecycle
  private void Awake()
  {
  if (ARConfiguration.PrettyLines)
  {
  this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
  }
 
  #if DEBUG
  this.timer = new System.Diagnostics.Stopwatch();
  this.log = Tools.DebugLogger.New(this);
  #endif
  }
 
  private void OnPreCull()
  {
  if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
  {
  this.Cleanup();
 
  return;
  }
 
  #if DEBUG
  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
  );
 
  if (FlightGlobals.ready && FlightGlobals.Vessels != null)
  {
  log.AppendLine("FlightGlobals ready and Vessels list not null.");
 
  for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
  {
  Vessel vessel = FlightGlobals.Vessels[i];
 
  log.AppendFormat("\nStarting check for vessel {0} at {1}ms", vessel, timer.ElapsedMilliseconds);
 
  if (vessel == null)
  {
  log.AppendFormat("\n\tSkipping vessel {0} altogether because it is null.", vessel);
  continue;
  }
 
  switch (vessel.vesselType)
  {
  case VesselType.Debris:
  case VesselType.EVA:
  case VesselType.Unknown:
  case VesselType.SpaceObject:
  log.AppendFormat("\n\tDiscarded because vessel is of invalid type {0}",
  vessel.vesselType);
  continue;
  }
 
  log.AppendFormat("\n\tChecking vessel {0}.", vessel.vesselName);
 
  #if DEBUG
  start = timer.ElapsedMilliseconds;
  #endif
 
  IAntennaRelay vesselRelay = vessel.GetBestRelay();
 
  if (vesselRelay == null)
  {
  log.AppendFormat("\n\tGot null relay for vessel {0}", vessel.vesselName);
  continue;
  }
 
  log.AppendFormat("\n\tGot best relay {0} ({3}) for vessel {1} in {2} ms",
  vesselRelay, vessel, timer.ElapsedMilliseconds - start, vesselRelay.GetType().Name);
 
  if (vesselRelay != null)
  {
  #if DEBUG
  start = timer.ElapsedMilliseconds;
  #endif
 
  this.SetRelayVertices(vesselRelay);
 
  log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
  vessel, timer.ElapsedMilliseconds - start);
  }
  }
  }
  }
  catch (Exception ex)
  {
  this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
  this.Cleanup();
  }
  #if DEBUG
  finally
  {
  log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
 
  log.Print();
  }
  #endif
  }
 
  private void OnDestroy()
  {
  this.Cleanup();
 
  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.transmitDistance < relay.nominalTransmitDistance)
  {
  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()
  {
  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;
  GameObject.Destroy(lineRenderer.gameObject);
  }
  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;
  }
  }
   
  private bool canTransmit;
  private bool isChecked;
   
  private IAntennaRelay nearestRelay;
  private IAntennaRelay bestOccludedRelay;
   
  /// <summary>
  /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
  /// <see cref="AntennaRange.IAntennaRelay"/>
  /// </summary>
  protected IAntennaRelay moduleRef;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public Vessel vessel public virtual Vessel vessel
  {
  get
  {
  return this.moduleRef.vessel;
  }
  }
   
  /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
  /// Gets 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 transmitDistance
{ {
get get
{ {
IAntennaRelay nearestRelay = this.FindNearestRelay(); if (this.KerbinDirect || this.targetRelay == null)
  {
// If there is no available relay nearby... return this.DistanceTo(Kerbin);
if (nearestRelay == null)  
{  
// .. return the distance to Kerbin  
return this.DistanceTo(this.Kerbin);  
} }
else else
{ {
/// ...otherwise, return the distance to the nearest available relay. return this.DistanceTo(this.targetRelay);
return this.DistanceTo(nearestRelay); }
} }
} }
}  
  /// <summary>
/// <summary> /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// The maximum distance at which this relay can operate. /// </summary>
/// </summary> public virtual double nominalTransmitDistance
/// <value>The max transmit distance.</value>  
public virtual float maxTransmitDistance  
{ {
get; get;
set; set;
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// The maximum distance at which this relay can operate.
/// the current relay attempt. /// </summary>
/// </summary> /// <value>The max transmit distance.</value>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> public virtual double maxTransmitDistance
public virtual bool relayChecked {
  get;
  set;
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public virtual bool KerbinDirect
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public 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
   
  // Skip vessels that have already been checked for a nearest relay this pass.
  if (this.isChecked)
  {
  log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
  this);
  log.Print();
  return;
  }
   
  log.AppendFormat("{0}: Target search started).", this.ToString());
   
  #if DEBUG
  try {
  #endif
  // Set this vessel as checked, so that we don't check it again.
  this.isChecked = true;
   
  // Blank everything we're trying to find before the search.
  this.firstOccludingBody = null;
  this.bestOccludedRelay = null;
  this.targetRelay = null;
  this.nearestRelay = null;
   
  // Default to KerbinDirect = true in case something in here doesn't work right.
  this.KerbinDirect = true;
   
  CelestialBody bodyOccludingBestOccludedRelay = null;
  IAntennaRelay needle;
   
  // double nearestRelaySqrDistance = double.PositiveInfinity;
  // double bestOccludedSqrDistance = double.PositiveInfinity;
   
  double maxTransmitSqrDistance = double.NegativeInfinity;
   
  double nearestRelaySqrQuotient = double.PositiveInfinity;
  double bestOccludedSqrQuotient = double.PositiveInfinity;
   
  /*
  * 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;
   
  // IList<IAntennaRelay> vesselRelays;
  for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
  {
  log.AppendFormat("\nFetching vessel at index {0}", vIdx);
  potentialVessel = FlightGlobals.Vessels[vIdx];
   
  if (potentialVessel == null)
  {
  Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
  log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
  log.Print();
  return;
  }
  #if DEBUG
  else
  {
  log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
  }
  #endif
   
  // Skip vessels of the wrong type.
  log.Append("\n\tchecking vessel type");
  switch (potentialVessel.vesselType)
  {
  case VesselType.Debris:
  case VesselType.Flag:
  case VesselType.EVA:
  case VesselType.SpaceObject:
  case VesselType.Unknown:
  log.Append("\n\tSkipping because vessel is the wrong type.");
  continue;
  default:
  break;
  }
   
  log.Append("\n\tchecking if vessel is this vessel");
  // Skip vessels with the wrong ID
  if (potentialVessel.id == vessel.id)
  {
  log.Append("\n\tSkipping because vessel is this vessel.");
  continue;
  }
   
  potentialBestRelay = potentialVessel.GetBestRelay();
  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;
  }
   
  // Find the distance from here to the vessel...
  log.Append("\n\tgetting distance to potential vessel");
  double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
  log.Append("\n\tgetting best vessel relay");
   
  log.Append("\n\tgetting max link distance to potential relay");
  double maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
  log.AppendFormat("\n\tmax link distance: {0}", maxTransmitSqrDistance);
   
  double potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
   
  log.Append("\n\t\tdoing LOS check");
  // Skip vessels to which we do not have line of sight.
  if (
  ARConfiguration.RequireLineOfSight &&
  !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
  )
  {
  log.Append("\n\t\t...failed LOS check");
   
  log.AppendFormat("\n\t\t\t{0}: Vessel {1} not in line of sight.",
  this.ToString(), potentialVessel.vesselName);
   
  log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
  log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
  log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance);
   
  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;
  }
   
  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}: Vessel {1} discarded because it is farther than another the nearest relay.",
  this.ToString(),
  potentialVessel.vesselName
  );
  continue;
  }
   
  log.Append("\n\t\t...passed distance check");
   
  if (potentialBestRelay.CanTransmit())
  {
  needle = potentialBestRelay;
  bool isCircular = false;
   
  int 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
  );
  }
  }
  }
   
  CelestialBody bodyOccludingKerbin = null;
   
  double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
  kerbinSqrDistance *= kerbinSqrDistance;
   
  double kerbinSqrQuotient = kerbinSqrDistance /
  (this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
   
  log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
  log.AppendFormat(
  "\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
  this,
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient,
  this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
  bestOccludedSqrQuotient,
  kerbinSqrDistance
  );
   
  // If we don't have LOS to Kerbin, focus on relays
  if (
  ARConfiguration.RequireLineOfSight &&
  !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
  )
  {
  log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
  // nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
  // Therefore, this will only be true if a valid relay is in range.
  if (nearestRelaySqrQuotient <= 1d)
  {
  log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, maxTransmitSqrDistance);
   
  this.KerbinDirect = false;
  this.canTransmit = true;
  this.targetRelay = this.nearestRelay;
  }
  // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
  else
  {
  log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, maxTransmitSqrDistance);
   
  this.canTransmit = false;
   
  // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
  // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
  if (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);
   
  // 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.firstOccludingBody = null;
  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.firstOccludingBody = bodyOccludingKerbin;
  this.targetRelay = null;
  }
  }
  }
  }
  // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
else else
{ {
return true; log.AppendFormat("\n\tKerbin is in LOS.");
}  
} // If the nearest relay is closer than Kerbin and in range, transmit to it.
  if (nearestRelaySqrQuotient <= 1d)
/// <summary> {
/// Finds the nearest relay. log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
/// </summary> this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
/// <returns>The nearest relay or null, if no relays in range.</returns> nearestRelaySqrQuotient, 1d);
public IAntennaRelay FindNearestRelay()  
{ this.canTransmit = true;
// Set this relay as checked, so that we don't check it again.  
this.relayChecked = true; // If the nearestRelay is closer than Kerbin, use it.
  if (nearestRelaySqrQuotient < kerbinSqrQuotient)
// Get a list of vessels within transmission range. {
List<Vessel> nearbyVessels = FlightGlobals.Vessels log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance) this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
.ToList(); nearestRelaySqrQuotient, kerbinSqrQuotient);
   
Tools.PostDebugMessage(string.Format( this.KerbinDirect = false;
"{0}: Vessels in range: {1}", this.targetRelay = this.nearestRelay;
this.GetType().Name, }
nearbyVessels.Count // Otherwise, Kerbin is closer, so use it.
)); else
  {
// Remove this vessel. log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
nearbyVessels.RemoveAll(v => v.id == vessel.id); this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrQuotient, kerbinSqrQuotient);
Tools.PostDebugMessage(string.Format(  
"{0}: Vessels in range excluding self: {1}", this.KerbinDirect = true;
this.GetType().Name, this.targetRelay = null;
nearbyVessels.Count }
)); }
  // If the nearest relay is out of range, we still need to check on Kerbin.
// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range. else
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); {
  log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
Tools.PostDebugMessage(string.Format( this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
"{0}: Found {1} nearby relays.", nearestRelaySqrQuotient, 1d);
this.GetType().Name,  
nearbyRelays.Count // If Kerbin is in range, use it.
)); if (kerbinSqrQuotient <= 1d)
  {
// Remove all relays already checked this time. log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
nearbyRelays.RemoveAll(r => r.relayChecked); kerbinSqrDistance, maxTransmitSqrDistance);
   
Tools.PostDebugMessage(string.Format( this.canTransmit = true;
"{0}: Found {1} nearby relays not already checked.", this.KerbinDirect = true;
this.GetType().Name, this.targetRelay = null;
nearbyRelays.Count }
)); // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
  // Kerbin and bestOccludedRelay
// Remove all relays that cannot transmit. else
// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin. {
nearbyRelays.RemoveAll(r => !r.CanTransmit()); log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
  kerbinSqrDistance, maxTransmitSqrDistance);
Tools.PostDebugMessage(string.Format(  
"{0}: Found {1} nearby relays not already checked that can transmit.", this.canTransmit = false;
this.GetType().Name,  
nearbyRelays.Count // 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)
// Sort the available relays by distance. {
nearbyRelays.Sort(new RelayComparer(this.vessel)); log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
  bestOccludedRelay, kerbinSqrDistance);
// Get the nearest available relay, or null if there are no available relays nearby.  
IAntennaRelay nearestRelay = nearbyRelays.FirstOrDefault(); 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;
  }
  }
  }
  }
  }
   
  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
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
this.relayChecked = false; this.isChecked = false;
  }
// Return the nearest available relay, or null if there are no available relays nearby.  
return nearestRelay; /// <summary>
} /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
  /// </summary>
/// <summary> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. public override string ToString()
/// </summary> {
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> if (this is ProtoAntennaRelay)
public AntennaRelay(Vessel v) {
{ return (this as ProtoAntennaRelay).ToString();
this.vessel = v; }
  return this.moduleRef.ToString();
// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start, }
// we hope it is safe enough.  
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); /// <summary>
} /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
  /// </summary>
/* /// <param name="module">The module reference underlying this AntennaRelay,
* Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance. /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
* */ public AntennaRelay(IAntennaRelay module)
internal class RelayComparer : IComparer<IAntennaRelay> {
{ this.moduleRef = module;
/// <summary> this.isChecked = false;
/// The reference Vessel (usually the active vessel).  
/// </summary> Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
protected Vessel referenceVessel;  
   
// We don't want no stinking public parameterless constructors.  
private RelayComparer() {}  
   
/// <summary>  
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use  
/// in sorting relays by distance.  
/// </summary>  
/// <param name="reference">The reference Vessel</param>  
public RelayComparer(Vessel reference)  
{  
this.referenceVessel = reference;  
}  
   
/// <summary>  
/// Compare the <see cref="IAntennaRelay"/>s "one" and "two".  
/// </summary>  
/// <param name="one">The first IAntennaRelay in the comparison</param>  
/// <param name="two">The second IAntennaRelay in the comparison</param>  
public int Compare(IAntennaRelay one, IAntennaRelay two)  
{  
double distanceOne;  
double distanceTwo;  
   
distanceOne = one.vessel.DistanceTo(referenceVessel);  
distanceTwo = two.vessel.DistanceTo(referenceVessel);  
   
return distanceOne.CompareTo(distanceTwo);  
}  
} }
} }
} }
   
   
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.
  //
 
  // Maximum distance 51696km, about 10% past Minmus
  @PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 18277500
  maxPowerFactor = 8
  maxDataFactor = 4
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  // Maximum distance 37152180km, about 5% past Duna
  // Bonus data transmission when at short range
  @PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 18576090000
  maxPowerFactor = 4
  maxDataFactor = 8
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  // Maximum distance 224770770km, about 75% past Eeloo.
  @PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  @packetResourceCost /= 1.414213
  nominalRange = 56192692500
  maxPowerFactor = 16
  maxDataFactor = 2
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  TRACKING_STATION_RANGES
  {
  range = 51696576
  range = 37152180000
  range = 224770770000
  }
 
  EVA_MODULE
  {
  name = ModuleLimitedDataTransmitter
 
  nominalRange = 5000
  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> /// <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 transmitDistance { get; } double transmitDistance { 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 a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  bool KerbinDirect { 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>
  /// 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>
  /// 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>
  /// Maximum distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
  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.");
  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 the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  public double transmitDistance
  {
  get
  {
  if (this.relay == null)
  {
  return double.PositiveInfinity;
  }
 
  return this.relay.transmitDistance;
  }
  }
 
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public double nominalTransmitDistance
  {
  get
  {
  return this.nominalRange;
  }
  }
 
  /// <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 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 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" +
  "nominalRange: {3}\n" +
  "maxPowerFactor: {4}\n" +
  "maxDataFactor: {5}\n",
  this.name,
  base.packetSize,
  this._basepacketResourceCost,
  this.nominalRange,
  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.nominalRange;
 
  this.relay = new AntennaRelay(this);
  this.relay.maxTransmitDistance = this.maxTransmitDistance;
  this.relay.nominalTransmitDistance = this.nominalRange;
 
  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.nominalRange;
  }
 
  /// <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.nominalRange);
  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.transmitDistance
  + " 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.transmitDistance
  + " 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",
  this.MaxLinkDistance());
 
  if (this.CanTransmit())
  {
  this.UIrelayStatus = "Connected";
  this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
  this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
  this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
  }
  else
  {
  if (this.relay.firstOccludingBody == null)
  {
  this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
  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.transmitDistance <= this.NominalLinkDistance())
  {
  base.packetResourceCost = this._basepacketResourceCost;
  }
  else
  {
  float rangeFactor = (float)(this.transmitDistance / this.NominalLinkDistance());
  rangeFactor *= rangeFactor;
 
  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.transmitDistance >= this.NominalLinkDistance())
  {
  base.packetSize = this._basepacketSize;
  }
  else
  {
  float rangeFactor = (float)(this.NominalLinkDistance() / this.transmitDistance);
  rangeFactor *= rangeFactor;
 
  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 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)
  {
  return relay.vessel.sqrDistanceTo(body);
  }
 
  /// <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>
  /// Calculates the nominal link distance.
  /// </summary>
  public static double NominalLinkDistance(this IAntennaRelay relay)
  {
  if (relay.KerbinDirect)
  {
  return Math.Sqrt(relay.nominalTransmitDistance * ARConfiguration.KerbinNominalRange);
  }
  else
  {
  return Math.Sqrt(relay.nominalTransmitDistance * relay.targetRelay.nominalTransmitDistance);
  }
  }
 
  /// <summary>
  /// Calculates the maximum link distance.
  /// </summary>
  public static double MaxLinkDistance(this IAntennaRelay relay)
  {
  if (relay.KerbinDirect)
  {
  return Math.Sqrt(relay.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
  }
  else
  {
  return Math.Sqrt(relay.maxTransmitDistance * relay.targetRelay.maxTransmitDistance);
  }
  }
 
  /// <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.CanTransmit())
  {
  canTransmit = true;
 
  double quo = relay.transmitDistance / relay.NominalLinkDistance();
 
  if (quo <= 1d)
  {
  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