Probably fixed the flickering lines by only checking one relay per inactive vessel, and by setting BestVesselRelay per vessel in RelayDatabase.
Probably fixed the flickering lines by only checking one relay per inactive vessel, and by setting BestVesselRelay per vessel in RelayDatabase.

// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // 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/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
   
using KSP; using KSP;
using System; using System;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
  /// <summary>
  /// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange.
  /// </summary>
[KSPAddon(KSPAddon.Startup.SpaceCentre, false)] [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class ARConfiguration : MonoBehaviour public class ARConfiguration : MonoBehaviour
{ {
  /// <summary>
  /// Indicates whether connections require line of sight.
  /// </summary>
public static bool RequireLineOfSight public static bool RequireLineOfSight
{ {
get; get;
private set; 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 public static double RadiusRatio
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// Indicates whether unmanned vessels require a connection for control.
  /// </summary>
public static bool RequireConnectionForControl public static bool RequireConnectionForControl
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// If true, relays will fix their power cost when above nominal range, decreasing data rate instead.
  /// </summary>
public static bool FixedPowerCost public static bool FixedPowerCost
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// Indicates whether this AntennaRange will draw pretty lines in map view.
  /// </summary>
public static bool PrettyLines public static bool PrettyLines
{ {
get; get;
private set; private set;
} }
   
  #pragma warning disable 1591
   
private bool showConfigWindow; private bool showConfigWindow;
private Rect configWindowPos; private Rect configWindowPos;
   
private IButton toolbarButton; private IButton toolbarButton;
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
   
private System.Version runningVersion; private System.Version runningVersion;
   
private KSP.IO.PluginConfiguration _config; private KSP.IO.PluginConfiguration _config;
private KSP.IO.PluginConfiguration config private KSP.IO.PluginConfiguration config
{ {
get get
{ {
if (this._config == null) if (this._config == null)
{ {
this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>(); this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
} }
   
return this._config; return this._config;
} }
} }
   
public void Awake() public void Awake()
{ {
Tools.PostDebugMessage(this, "Waking up."); Tools.PostDebugMessage(this, "Waking up.");
   
this.runningVersion = this.GetType().Assembly.GetName().Version; this.runningVersion = this.GetType().Assembly.GetName().Version;
   
this.showConfigWindow = false; this.showConfigWindow = false;
this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15); this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
   
   
this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos); this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
   
ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false); ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
   
ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d)); ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d));
ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio; ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
   
ARConfiguration.RequireConnectionForControl = ARConfiguration.RequireConnectionForControl =
this.LoadConfigValue("requireConnectionForControl", false); this.LoadConfigValue("requireConnectionForControl", false);
   
ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false); ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false);
   
ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true); ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true);
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
   
Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion)); Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
   
Tools.PostDebugMessage(this, "Awake."); Tools.PostDebugMessage(this, "Awake.");
} }
   
public void OnGUI() public void OnGUI()
{ {
// Only runs once, if the Toolbar is available. // Only runs once, if the Toolbar is available.
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
if (this.toolbarButton == null) if (this.toolbarButton == null)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button."); Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
   
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
this.toolbarButton.Text = "AR"; this.toolbarButton.Text = "AR";
this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon"; this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst; this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
this.toolbarButton.OnClick += delegate(ClickEvent e) this.toolbarButton.OnClick += delegate(ClickEvent e)
{ {
this.toggleConfigWindow(); this.toggleConfigWindow();
}; };
} }
} }
else if (this.appLauncherButton == null && ApplicationLauncher.Ready) else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button."); Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
   
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
this.toggleConfigWindow, this.toggleConfigWindow,
this.toggleConfigWindow, this.toggleConfigWindow,
ApplicationLauncher.AppScenes.SPACECENTER, ApplicationLauncher.AppScenes.SPACECENTER,
GameDatabase.Instance.GetTexture( GameDatabase.Instance.GetTexture(
"AntennaRange/Textures/appLauncherIcon", "AntennaRange/Textures/appLauncherIcon",
false false
) )
); );
} }
   
if (this.showConfigWindow) if (this.showConfigWindow)
{ {
Rect configPos = GUILayout.Window(354163056, Rect configPos = GUILayout.Window(354163056,
this.configWindowPos, this.configWindowPos,
this.ConfigWindow, this.ConfigWindow,
string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor), string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
GUILayout.ExpandHeight(true), GUILayout.ExpandHeight(true),
GUILayout.ExpandWidth(true) GUILayout.ExpandWidth(true)
); );
   
configPos = Tools.ClampRectToScreen(configPos, 20); configPos = Tools.ClampRectToScreen(configPos, 20);
   
if (configPos != this.configWindowPos) if (configPos != this.configWindowPos)
{ {
this.configWindowPos = configPos; this.configWindowPos = configPos;
this.SaveConfigValue("configWindowPos", this.configWindowPos); this.SaveConfigValue("configWindowPos", this.configWindowPos);
} }
} }
} }
   
public void ConfigWindow(int _) public void ConfigWindow(int _)
{ {
GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight"); bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
if (requireLineOfSight != ARConfiguration.RequireLineOfSight) if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
{ {
ARConfiguration.RequireLineOfSight = requireLineOfSight; ARConfiguration.RequireLineOfSight = requireLineOfSight;
this.SaveConfigValue("requireLineOfSight", requireLineOfSight); this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireConnectionForControl = bool requireConnectionForControl =
GUITools.Toggle( GUITools.Toggle(
ARConfiguration.RequireConnectionForControl, ARConfiguration.RequireConnectionForControl,
"Require Connection for Probe Control" "Require Connection for Probe Control"
); );
if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl) if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
{ {
ARConfiguration.RequireConnectionForControl = requireConnectionForControl; ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl); this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost"); bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
if (fixedPowerCost != ARConfiguration.FixedPowerCost) if (fixedPowerCost != ARConfiguration.FixedPowerCost)
{ {
ARConfiguration.FixedPowerCost = fixedPowerCost; ARConfiguration.FixedPowerCost = fixedPowerCost;
this.SaveConfigValue("fixedPowerCost", fixedPowerCost); this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines"); bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
if (prettyLines != ARConfiguration.PrettyLines) if (prettyLines != ARConfiguration.PrettyLines)
{ {
ARConfiguration.PrettyLines = prettyLines; ARConfiguration.PrettyLines = prettyLines;
this.SaveConfigValue("drawPrettyLines", prettyLines); this.SaveConfigValue("drawPrettyLines", prettyLines);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
if (requireLineOfSight) if (requireLineOfSight)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio); double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio);
double newRatio; double newRatio;
   
GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio)); GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true)); newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
newRatio = Math.Round(newRatio, 2); newRatio = Math.Round(newRatio, 2);
   
if (newRatio != graceRatio) if (newRatio != graceRatio)
{ {
ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio); ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
this.SaveConfigValue("graceRatio", newRatio); this.SaveConfigValue("graceRatio", newRatio);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
   
GUILayout.EndVertical(); GUILayout.EndVertical();
   
GUI.DragWindow(); GUI.DragWindow();
} }
   
public void OnDestroy() public void OnDestroy()
{ {
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
} }
} }
   
protected void onSceneChangeRequested(GameScenes scene) protected void onSceneChangeRequested(GameScenes scene)
{ {
if (scene != GameScenes.SPACECENTER) if (scene != GameScenes.SPACECENTER)
{ {
print("ARConfiguration: Requesting Destruction."); print("ARConfiguration: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
} }
   
private void toggleConfigWindow() private void toggleConfigWindow()
{ {
this.showConfigWindow = !this.showConfigWindow; this.showConfigWindow = !this.showConfigWindow;
} }
   
private T LoadConfigValue<T>(string key, T defaultValue) private T LoadConfigValue<T>(string key, T defaultValue)
{ {
this.config.load(); this.config.load();
   
return config.GetValue(key, defaultValue); return config.GetValue(key, defaultValue);
} }
   
private void SaveConfigValue<T>(string key, T value) private void SaveConfigValue<T>(string key, T value)
{ {
this.config.load(); this.config.load();
   
this.config.SetValue(key, value); this.config.SetValue(key, value);
   
this.config.save(); this.config.save();
} }
} }
} }
   
// AntennaRange // AntennaRange
// //
// ARFlightController.cs // ARFlightController.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
  #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour public class ARFlightController : MonoBehaviour
{ {
#region Fields #region Fields
protected Dictionary<ConnectionStatus, string> connectionTextures; protected Dictionary<ConnectionStatus, string> connectionTextures;
protected Dictionary<ConnectionStatus, Texture> appLauncherTextures; protected Dictionary<ConnectionStatus, Texture> appLauncherTextures;
   
protected ARMapRenderer mapRenderer; protected ARMapRenderer mapRenderer;
   
protected IButton toolbarButton; protected IButton toolbarButton;
   
protected ApplicationLauncherButton appLauncherButton; protected ApplicationLauncherButton appLauncherButton;
protected Tools.DebugLogger log; protected Tools.DebugLogger log;
   
protected System.Diagnostics.Stopwatch updateTimer; protected System.Diagnostics.Stopwatch updateTimer;
#endregion #endregion
   
#region Properties #region Properties
public ConnectionStatus currentConnectionStatus public ConnectionStatus currentConnectionStatus
{ {
get; get;
protected set; protected set;
} }
   
protected string currentConnectionTexture protected string currentConnectionTexture
{ {
get get
{ {
return this.connectionTextures[this.currentConnectionStatus]; return this.connectionTextures[this.currentConnectionStatus];
} }
} }
   
protected Texture currentAppLauncherTexture protected Texture currentAppLauncherTexture
{ {
get get
{ {
return this.appLauncherTextures[this.currentConnectionStatus]; return this.appLauncherTextures[this.currentConnectionStatus];
} }
} }
   
public ControlTypes currentControlLock public ControlTypes currentControlLock
{ {
get get
{ {
if (this.lockID == string.Empty) if (this.lockID == string.Empty)
{ {
return ControlTypes.None; return ControlTypes.None;
} }
   
return InputLockManager.GetControlLock(this.lockID); return InputLockManager.GetControlLock(this.lockID);
} }
} }
   
public string lockID public string lockID
{ {
get; get;
protected set; protected set;
} }
   
public ControlTypes lockSet public ControlTypes lockSet
{ {
get get
{ {
return ControlTypes.ALL_SHIP_CONTROLS; return ControlTypes.ALL_SHIP_CONTROLS;
} }
} }
   
public Vessel vessel public Vessel vessel
{ {
get get
{ {
if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
return FlightGlobals.ActiveVessel; return FlightGlobals.ActiveVessel;
} }
   
return null; return null;
} }
} }
#endregion #endregion
   
#region MonoBehaviour LifeCycle #region MonoBehaviour LifeCycle
protected void Awake() protected void Awake()
{ {
this.lockID = "ARConnectionRequired"; this.lockID = "ARConnectionRequired";
   
this.log = Tools.DebugLogger.New(this); this.log = Tools.DebugLogger.New(this);
   
this.updateTimer = new System.Diagnostics.Stopwatch(); this.updateTimer = new System.Diagnostics.Stopwatch();
   
this.connectionTextures = new Dictionary<ConnectionStatus, string>(); this.connectionTextures = new Dictionary<ConnectionStatus, string>();
   
this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection"; this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal"; this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon"; this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
   
this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>(); this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
   
this.appLauncherTextures[ConnectionStatus.None] = this.appLauncherTextures[ConnectionStatus.None] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
this.appLauncherTextures[ConnectionStatus.Suboptimal] = this.appLauncherTextures[ConnectionStatus.Suboptimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
this.appLauncherTextures[ConnectionStatus.Optimal] = this.appLauncherTextures[ConnectionStatus.Optimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
   
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
   
this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None]; this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None];
this.toolbarButton.Text = "AntennaRange"; this.toolbarButton.Text = "AntennaRange";
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
this.toolbarButton.Enabled = false; this.toolbarButton.Enabled = false;
} }
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
GameEvents.onVesselChange.Add(this.onVesselChange); GameEvents.onVesselChange.Add(this.onVesselChange);
} }
   
protected void Start() protected void Start()
{ {
this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>(); this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
} }
   
protected void FixedUpdate() protected void FixedUpdate()
{ {
if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready) if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
{ {
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
ApplicationLauncher.AppScenes.FLIGHT, ApplicationLauncher.AppScenes.FLIGHT,
this.appLauncherTextures[ConnectionStatus.None] this.appLauncherTextures[ConnectionStatus.None]
); );
} }
   
this.log.Clear(); this.log.Clear();
   
VesselCommand availableCommand; VesselCommand availableCommand;
   
if (ARConfiguration.RequireConnectionForControl) if (ARConfiguration.RequireConnectionForControl)
{ {
availableCommand = this.vessel.CurrentCommand(); availableCommand = this.vessel.CurrentCommand();
} }
else else
{ {
availableCommand = VesselCommand.Crew; availableCommand = VesselCommand.Crew;
} }
   
log.AppendFormat("availableCommand: {0}\n\t" + log.AppendFormat("availableCommand: {0}\n\t" +
"(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" + "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
"(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" + "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
"vessel.HasConnectedRelay(): {3}", "vessel.HasConnectedRelay(): {3}",
(int)availableCommand, (int)availableCommand,
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew, (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe, (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
vessel.HasConnectedRelay() vessel.HasConnectedRelay()
); );
   
// If we are requiring a connection for control, the vessel does not have any adequately staffed pods, // 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... // and the vessel does not have any connected relays...
if ( if (
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
ARConfiguration.RequireConnectionForControl && ARConfiguration.RequireConnectionForControl &&
this.vessel != null && this.vessel != null &&
this.vessel.vesselType != VesselType.EVA && this.vessel.vesselType != VesselType.EVA &&
!( !(
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew || (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay() (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
)) ))
{ {
// ...and if the controls are not currently locked... // ...and if the controls are not currently locked...
if (currentControlLock == ControlTypes.None) if (currentControlLock == ControlTypes.None)
{ {
// ...lock the controls. // ...lock the controls.
InputLockManager.SetControlLock(this.lockSet, this.lockID); InputLockManager.SetControlLock(this.lockSet, this.lockID);
} }
} }
// ...otherwise, if the controls are locked... // ...otherwise, if the controls are locked...
else if (currentControlLock != ControlTypes.None) else if (currentControlLock != ControlTypes.None)
{ {
// ...unlock the controls. // ...unlock the controls.
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
   
log.Print(); log.Print();
} }
   
protected void Update() protected void Update()
{ {
if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 125L) if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 125L)
{ {
this.updateTimer.Reset(); this.updateTimer.Reset();
} }
else else
{ {
return; return;
} }
   
this.log.Clear(); this.log.Clear();
   
if ( if (
(this.toolbarButton != null || this.appLauncherButton != null) && (this.toolbarButton != null || this.appLauncherButton != null) &&
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
FlightGlobals.ActiveVessel != null 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 == FlightGlobals.ActiveVessel)
  {
  continue;
  }
   
  relay = vessel.GetBestRelay();
   
  if (relay != null)
  {
  relay.FindNearestRelay();
  }
  }
   
  activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
  for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
  {
  relay = activeVesselRelays[rIdx];
   
  relay.FindNearestRelay();
  }
   
log.Append("Checking vessel relay status.\n"); log.Append("Checking vessel relay status.\n");
   
this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus(); this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
   
log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}", log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
this.currentConnectionStatus, this.currentConnectionTexture); this.currentConnectionStatus, this.currentConnectionTexture);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.TexturePath = this.currentConnectionTexture; this.toolbarButton.TexturePath = this.currentConnectionTexture;
   
if (this.currentConnectionStatus == ConnectionStatus.None) if (this.currentConnectionStatus == ConnectionStatus.None)
{ {
this.toolbarButton.Important = true; this.toolbarButton.Important = true;
} }
else else
{ {
this.toolbarButton.Important = false; this.toolbarButton.Important = false;
} }
} }
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
this.appLauncherButton.SetTexture(this.currentAppLauncherTexture); this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
} }
} }
   
log.Print(); log.Print();
} }
   
protected void OnDestroy() protected void OnDestroy()
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
   
if (this.mapRenderer != null) if (this.mapRenderer != null)
{ {
GameObject.Destroy(this.mapRenderer); GameObject.Destroy(this.mapRenderer);
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
   
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
GameEvents.onVesselChange.Remove(this.onVesselChange); GameEvents.onVesselChange.Remove(this.onVesselChange);
   
print("ARFlightController: Destroyed."); print("ARFlightController: Destroyed.");
} }
#endregion #endregion
   
#region Event Handlers #region Event Handlers
protected void onSceneChangeRequested(GameScenes scene) protected void onSceneChangeRequested(GameScenes scene)
{ {
print("ARFlightController: Requesting Destruction."); print("ARFlightController: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
   
protected void onVesselChange(Vessel vessel) protected void onVesselChange(Vessel vessel)
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
#endregion #endregion
} }
} }
   
// AntennaRange // AntennaRange
// //
// ARMapRenderer.cs // ARMapRenderer.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
  #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class ARMapRenderer : MonoBehaviour public class ARMapRenderer : MonoBehaviour
{ {
#region Fields #region Fields
private Dictionary<Guid, LineRenderer> vesselLineRenderers; 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 #endregion
   
#region Properties #region Properties
public LineRenderer this[Guid idx] public LineRenderer this[Guid idx]
{ {
get get
{ {
if (this.vesselLineRenderers == null) if (this.vesselLineRenderers == null)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
} }
   
if (!this.vesselLineRenderers.ContainsKey(idx)) LineRenderer lr;
   
  if (this.vesselLineRenderers.TryGetValue(idx, out lr))
  {
  return lr;
  }
  else
{ {
GameObject obj = new GameObject(); GameObject obj = new GameObject();
obj.layer = 31; obj.layer = 31;
   
LineRenderer lr = obj.AddComponent<LineRenderer>(); lr = obj.AddComponent<LineRenderer>();
   
lr.SetColors(Color.green, Color.green); // lr.SetColors(Color.green, Color.green);
lr.material = MapView.OrbitLinesMaterial; lr.material = MapView.OrbitLinesMaterial;
lr.SetVertexCount(2); // lr.SetVertexCount(2);
   
this.vesselLineRenderers[idx] = lr; this.vesselLineRenderers[idx] = lr;
}  
  return lr;
return this.vesselLineRenderers[idx]; }
} }
} }
#endregion #endregion
   
#region MonoBehaviour Lifecycle #region MonoBehaviour Lifecycle
private void Awake() private void Awake()
{ {
if (ARConfiguration.PrettyLines) if (ARConfiguration.PrettyLines)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
} }
   
  #if DEBUG
  this.timer = new System.Diagnostics.Stopwatch();
  this.log = Tools.DebugLogger.New(this);
  #endif
} }
   
private void OnPreCull() private void OnPreCull()
{ {
if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines) if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
{ {
this.Cleanup(); this.Cleanup();
   
return; return;
} }
   
Tools.DebugLogger log = Tools.DebugLogger.New(this); #if DEBUG
  timer.Restart();
  #endif
   
try try
{ {
  log.Clear();
   
log.AppendFormat("OnPreCull.\n"); log.AppendFormat("OnPreCull.\n");
   
log.AppendFormat("\tMapView: Draw3DLines: {0}\n" + log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
"\tMapView.MapCamera.camera.fieldOfView: {1}\n" + "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
"\tMapView.MapCamera.Distance: {2}\n", "\tMapView.MapCamera.Distance: {2}\n",
MapView.Draw3DLines, MapView.Draw3DLines,
MapView.MapCamera.camera.fieldOfView, MapView.MapCamera.camera.fieldOfView,
MapView.MapCamera.Distance MapView.MapCamera.Distance
); );
   
log.AppendLine("vesselFrameCache cleared.");  
   
if (FlightGlobals.ready && FlightGlobals.Vessels != null) if (FlightGlobals.ready && FlightGlobals.Vessels != null)
{ {
log.AppendLine("FlightGlobals ready and Vessels list not null."); log.AppendLine("FlightGlobals ready and Vessels list not null.");
   
foreach (Vessel vessel in FlightGlobals.Vessels) 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) if (vessel == null)
{ {
log.AppendFormat("Skipping vessel {0} altogether because it is null.\n"); log.AppendFormat("\n\tSkipping vessel {0} altogether because it is null.", vessel);
continue; continue;
} }
   
log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName);  
   
switch (vessel.vesselType) switch (vessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.EVA: case VesselType.EVA:
case VesselType.Unknown: case VesselType.Unknown:
case VesselType.SpaceObject: case VesselType.SpaceObject:
log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n", log.AppendFormat("\n\tDiscarded because vessel is of invalid type {0}",
vessel.vesselType); vessel.vesselType);
continue; continue;
} }
   
log.Append("\tChecking connection status...\n"); log.AppendFormat("\n\tChecking vessel {0}.", vessel.vesselName);
   
/*if (vessel.HasConnectedRelay()) start = timer.ElapsedMilliseconds;
   
  IAntennaRelay vesselRelay = vessel.GetBestRelay();
   
  if (vesselRelay == null)
{ {
log.AppendLine("\tHas a connection, checking for the best relay to use for the line.");*/ continue;
  }
IAntennaRelay vesselRelay = null;  
float bestScore = float.PositiveInfinity; log.AppendFormat("\n\tGot best relay {0} ({3}) for vessel {1} in {2} ms",
float relayScore = float.NaN; vesselRelay, vessel, timer.ElapsedMilliseconds - start, vesselRelay.GetType().Name);
   
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) if (vesselRelay != null)
{  
relayScore = (float)relay.transmitDistance / relay.maxTransmitDistance;  
   
if (relayScore < bestScore)  
{  
bestScore = relayScore;  
vesselRelay = relay as IAntennaRelay;  
}  
}  
   
if (vesselRelay != null)  
{  
log.AppendFormat("\t...picked relay {0} with a score of {1}",  
vesselRelay, relayScore  
);  
   
this.SetRelayVertices(vesselRelay);  
}  
/*}  
else if (this.vesselLineRenderers.ContainsKey(vessel.id))  
{ {
log.AppendLine("\tDisabling line because vessel has no connection."); start = timer.ElapsedMilliseconds;
this[vessel.id].enabled = false;  
}*/ this.SetRelayVertices(vesselRelay);
   
  log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
  vessel, timer.ElapsedMilliseconds - start);
  }
} }
} }
} }
catch (Exception) catch (Exception ex)
{ {
  this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
this.Cleanup(); this.Cleanup();
} }
#if DEBUG #if DEBUG
finally finally
{ {
  log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
   
log.Print(); log.Print();
} }
#endif #endif
} }
   
private void OnDestroy() private void OnDestroy()
{ {
this.Cleanup(); this.Cleanup();
   
print("ARMapRenderer: Destroyed."); print("ARMapRenderer: Destroyed.");
} }
#endregion #endregion
   
  #region Utility
private void SetRelayVertices(IAntennaRelay relay) private void SetRelayVertices(IAntennaRelay relay)
{ {
if (relay == null) 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; return;
} }
   
LineRenderer renderer = this[relay.vessel.id]; LineRenderer renderer = this[relay.vessel.id];
  Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
Vector3d start;  
Vector3d end; 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;
   
  relayStart = timer.ElapsedMilliseconds;
   
  Vector3d nextPoint;
   
renderer.enabled = true; renderer.enabled = true;
   
if (!relay.CanTransmit()) if (!relay.CanTransmit())
{ {
renderer.SetColors(Color.red, Color.red); thisColor = Color.red;
} }
else else
{ {
if (relay.transmitDistance < relay.nominalTransmitDistance) if (relay.transmitDistance < relay.nominalTransmitDistance)
{ {
renderer.SetColors(Color.green, Color.green); thisColor = Color.green;
} }
else else
{ {
renderer.SetColors(Color.yellow, Color.yellow); thisColor = Color.yellow;
} }
} }
   
start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());  
   
if (relay.KerbinDirect) if (relay.KerbinDirect)
{ {
end = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position); nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
  relay = null;
} }
else else
{ {
if (relay.targetRelay == null) if (relay.targetRelay == null)
{ {
  renderer.enabled = false;
return; return;
} }
end = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());  
} nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
  relay = relay.targetRelay;
float lineWidth; }
   
if (MapView.Draw3DLines) renderer.SetColors(thisColor, thisColor);
{  
lineWidth = 0.005859375f * MapView.MapCamera.Distance; if (!MapView.Draw3DLines)
} {
else nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
{ nextPoint.z = nextPoint.z >= 0f ? d : -d;
lineWidth = 2f; }
   
start = MapView.MapCamera.camera.WorldToScreenPoint(start); idx++;
end = MapView.MapCamera.camera.WorldToScreenPoint(end);  
  renderer.SetVertexCount(idx + 1);
float d = Screen.height / 2f + 0.01f; renderer.SetPosition(idx, nextPoint);
start.z = start.z >= 0f ? d : -d;  
end.z = end.z >= 0f ? d : -d; log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
} }
   
renderer.SetWidth(lineWidth, lineWidth); private void Cleanup()
   
renderer.SetPosition(0, start);  
renderer.SetPosition(1, end);  
}  
   
public void Cleanup()  
{ {
if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0) if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
{ {
foreach (LineRenderer lineRenderer in this.vesselLineRenderers.Values) IEnumerator<LineRenderer> enumerator = this.vesselLineRenderers.Values.GetEnumerator();
{ LineRenderer lineRenderer;
   
  while (enumerator.MoveNext())
  {
  lineRenderer = enumerator.Current;
lineRenderer.enabled = false; lineRenderer.enabled = false;
GameObject.Destroy(lineRenderer.gameObject); GameObject.Destroy(lineRenderer.gameObject);
} }
this.vesselLineRenderers.Clear(); this.vesselLineRenderers.Clear();
} }
} }
  #endregion
} }
} }
   
   
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion> <ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid> <ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>AntennaRange</RootNamespace> <RootNamespace>AntennaRange</RootNamespace>
<AssemblyName>AntennaRange</AssemblyName> <AssemblyName>AntennaRange</AssemblyName>
<ReleaseVersion>1.3</ReleaseVersion> <ReleaseVersion>1.3</ReleaseVersion>
<SynchReleaseVersion>false</SynchReleaseVersion> <SynchReleaseVersion>false</SynchReleaseVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<UseMSBuildEngine>False</UseMSBuildEngine> <UseMSBuildEngine>False</UseMSBuildEngine>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE;</DefineConstants> <DefineConstants>DEBUG;TRACE;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" /> <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" /> <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
  <DocumentationFile>bin\Release\AntennaRange.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE;</DefineConstants> <DefineConstants>DEBUG;TRACE;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" /> <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" /> <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="IAntennaRelay.cs" /> <Compile Include="IAntennaRelay.cs" />
<Compile Include="ModuleLimitedDataTransmitter.cs" /> <Compile Include="ModuleLimitedDataTransmitter.cs" />
<Compile Include="AntennaRelay.cs" /> <Compile Include="AntennaRelay.cs" />
<Compile Include="ProtoAntennaRelay.cs" /> <Compile Include="ProtoAntennaRelay.cs" />
<Compile Include="RelayDatabase.cs" /> <Compile Include="RelayDatabase.cs" />
<Compile Include="RelayExtensions.cs" /> <Compile Include="RelayExtensions.cs" />
<Compile Include="ARConfiguration.cs" /> <Compile Include="ARConfiguration.cs" />
<Compile Include="ARFlightController.cs" /> <Compile Include="ARFlightController.cs" />
<Compile Include="ARMapRenderer.cs" /> <Compile Include="ARMapRenderer.cs" />
  <Compile Include="VesselCache.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath> <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="System"> <Reference Include="System">
<HintPath>..\_KSPAssemblies\System.dll</HintPath> <HintPath>..\_KSPAssemblies\System.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath> <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj"> <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
<Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project> <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
<Name>ToadicusTools</Name> <Name>ToadicusTools</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="GameData\AntennaRange\AntennaRange.cfg" /> <None Include="GameData\AntennaRange\AntennaRange.cfg" />
<None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" /> <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
</ItemGroup> </ItemGroup>
</Project> </Project>
// AntennaRange // AntennaRange
// //
// AntennaRelay.cs // AntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // 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; using ToadicusTools;
   
// @DONE TODO: Retool nearestRelay to always contain the nearest relay, even if out of range.  
// @DONE TODO: Retool CanTransmit to not rely on nearestRelay == null.  
// TODO: Track occluded vessels somehow.  
   
namespace AntennaRange namespace AntennaRange
{ {
  /// <summary>
  /// Relay code at the heart of AntennaRange
  /// </summary>
public class AntennaRelay public class AntennaRelay
{ {
  public static readonly System.Diagnostics.Stopwatch searchTimer = new System.Diagnostics.Stopwatch();
  public const long millisecondsBetweenSearches = 125L;
   
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
private static CelestialBody _Kerbin; private static CelestialBody _Kerbin;
   
  /// <summary>
  /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
  /// </summary>
public static CelestialBody Kerbin public static CelestialBody Kerbin
{ {
get get
{ {
if (_Kerbin == null && FlightGlobals.ready) if (_Kerbin == null && FlightGlobals.ready)
{ {
_Kerbin = FlightGlobals.GetHomeBody(); _Kerbin = FlightGlobals.GetHomeBody();
} }
   
return _Kerbin; return _Kerbin;
} }
} }
   
protected bool canTransmit; private long lastSearch;
   
  private bool canTransmit;
  private bool isChecked;
   
  private IAntennaRelay nearestRelay;
  private IAntennaRelay bestOccludedRelay;
   
  /// <summary>
  /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
  /// <see cref="AntennaRange.IAntennaRelay"/>
  /// </summary>
protected IAntennaRelay moduleRef; protected IAntennaRelay moduleRef;
   
protected System.Diagnostics.Stopwatch searchTimer;  
protected long millisecondsBetweenSearches;  
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public virtual Vessel vessel public virtual Vessel vessel
{ {
get get
{ {
return this.moduleRef.vessel; return this.moduleRef.vessel;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the nearest relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
/// <value>The nearest relay</value> public IAntennaRelay targetRelay
public IAntennaRelay nearestRelay  
{ {
get; get;
protected set; protected set;
} }
   
public IAntennaRelay bestOccludedRelay /// <summary>
  /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
  /// </summary>
  public virtual CelestialBody firstOccludingBody
{ {
get; get;
protected set; protected set;
} }
   
public IAntennaRelay targetRelay /// <summary>
  /// Gets the transmit distance.
  /// </summary>
  /// <value>The transmit distance.</value>
  public double transmitDistance
  {
  get
  {
  if (this.KerbinDirect || this.targetRelay == null)
  {
  return this.DistanceTo(Kerbin);
  }
  else
  {
  return this.DistanceTo(this.targetRelay);
  }
  }
  }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public virtual double nominalTransmitDistance
  {
  get;
  set;
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
  public virtual double maxTransmitDistance
  {
  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>
/// 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.  
/// </summary>  
/// <value>The transmit distance.</value>  
public double transmitDistance  
{  
get  
{  
this.FindNearestRelay();  
   
if (this.KerbinDirect || this.targetRelay == null)  
{  
return this.DistanceTo(Kerbin);  
}  
else  
{  
return this.DistanceTo(this.targetRelay);  
}  
}  
}  
   
public virtual double nominalTransmitDistance  
{  
get;  
set;  
}  
   
/// <summary>  
/// The maximum distance at which this relay can operate.  
/// </summary>  
/// <value>The max transmit distance.</value>  
public virtual float maxTransmitDistance  
{  
get;  
set;  
}  
   
/// <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 virtual bool relayChecked  
{  
get;  
protected set;  
}  
   
public virtual bool KerbinDirect  
{  
get;  
protected set;  
}  
   
/// <summary>  
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit() public virtual bool CanTransmit()
{ {
this.FindNearestRelay();  
return this.canTransmit; return this.canTransmit;
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
/// <returns>The nearest relay or null, if no relays in range.</returns> /// <returns>The nearest relay or null, if no relays in range.</returns>
private void FindNearestRelay() public void FindNearestRelay()
{ {
if (!this.searchTimer.IsRunning || this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches) if (!FlightGlobals.ready)
{  
this.searchTimer.Reset();  
}  
else  
{ {
return; return;
} }
   
  if (!searchTimer.IsRunning)
  {
  searchTimer.Start();
  }
   
  Tools.DebugLogger log;
  #if DEBUG
  log = Tools.DebugLogger.New(this);
  #endif
   
  long searchTime = searchTimer.ElapsedMilliseconds;
  long timeSinceLast = searchTime - this.lastSearch;
   
  if (timeSinceLast < millisecondsBetweenSearches)
  {
  log.AppendFormat(
  "{0}: Target search skipped because it's not time to search again yet ({1} - {2}) < {3})",
  this, searchTime, this.lastSearch, millisecondsBetweenSearches
  );
  log.Print();
  return;
  }
   
// Skip vessels that have already been checked for a nearest relay this pass. // Skip vessels that have already been checked for a nearest relay this pass.
if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(this.vessel.id)) if (this.isChecked)
{ {
  log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
  this);
  log.Print();
return; return;
} }
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) log.AppendFormat("{0}: Target search started at {1} ms ({2} ms since last search).",
{ this.ToString(), searchTime, timeSinceLast);
Tools.PostDebugMessage(string.Format(  
"{0}: finding nearest relay for {1}", #if DEBUG
this.GetType().Name, try {
this.ToString() #endif
));  
}  
   
// Set this vessel as checked, so that we don't check it again. // Set this vessel as checked, so that we don't check it again.
RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true; this.isChecked = true;
   
  this.lastSearch = searchTime;
   
// Blank everything we're trying to find before the search. // Blank everything we're trying to find before the search.
this.firstOccludingBody = null; this.firstOccludingBody = null;
this.bestOccludedRelay = null; this.bestOccludedRelay = null;
this.targetRelay = null; this.targetRelay = null;
this.nearestRelay = null; this.nearestRelay = null;
   
  // Default to KerbinDirect = true in case something in here doesn't work right.
  this.KerbinDirect = true;
   
CelestialBody bodyOccludingBestOccludedRelay = null; CelestialBody bodyOccludingBestOccludedRelay = null;
   
double nearestRelaySqrDistance = double.PositiveInfinity; double nearestRelaySqrDistance = double.PositiveInfinity;
double bestOccludedSqrDistance = double.PositiveInfinity; double bestOccludedSqrDistance = double.PositiveInfinity;
double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
   
/* /*
* Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too * 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 * 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 * and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison
* against future finds. * against future finds.
* */ * */
foreach (Vessel potentialVessel in FlightGlobals.Vessels) Vessel potentialVessel;
{ IList<IAntennaRelay> vesselRelays;
  for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
  {
  log.AppendFormat("\nFetching vessel at index {0}", vIdx);
  potentialVessel = FlightGlobals.Vessels[vIdx];
   
  if (potentialVessel == null)
  {
  log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
  log.Print();
  return;
  }
  #if DEBUG
  else
  {
  log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
  }
  #endif
   
// Skip vessels of the wrong type. // Skip vessels of the wrong type.
  log.Append("\n\tchecking vessel type");
switch (potentialVessel.vesselType) switch (potentialVessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.Flag: case VesselType.Flag:
case VesselType.EVA: case VesselType.EVA:
case VesselType.SpaceObject: case VesselType.SpaceObject:
case VesselType.Unknown: case VesselType.Unknown:
  log.Append("\n\tSkipping because vessel is the wrong type.");
continue; continue;
default: default:
break; break;
} }
   
  log.Append("\n\tchecking if vessel is this vessel");
// Skip vessels with the wrong ID // Skip vessels with the wrong ID
if (potentialVessel.id == vessel.id) if (potentialVessel.id == vessel.id)
{ {
  log.Append("\n\tSkipping because vessel is this vessel.");
continue; continue;
} }
   
// Find the distance from here to the vessel... // Find the distance from here to the vessel...
  log.Append("\n\tgetting distance to potential vessel");
double potentialSqrDistance = this.sqrDistanceTo(potentialVessel); double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
  log.Append("\n\tgetting vessel relays");
  vesselRelays = potentialVessel.GetAntennaRelays();
  log.AppendFormat("\n\t\tvesselRelays: {0}",
  vesselRelays == null ? "null" : vesselRelays.Count.ToString());
   
CelestialBody fob = null; CelestialBody fob = null;
   
  log.Append("\n\tdoing LOS check");
// Skip vessels to which we do not have line of sight. // Skip vessels to which we do not have line of sight.
if ( if (
ARConfiguration.RequireLineOfSight && ARConfiguration.RequireLineOfSight &&
!this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio) !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
) )
{ {
  log.Append("\n\tfailed LOS check");
this.firstOccludingBody = fob; this.firstOccludingBody = fob;
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) log.AppendFormat("\n\t{0}: Vessel {1} not in line of sight.",
{ this.ToString(), potentialVessel.vesselName);
Tools.PostDebugMessage("{6}: Vessel {0} discarded because we do not have line of sight." +  
"\npotentialSqrDistance: {1}, bestOccludedSqrDistance: {2}, maxTransmitSqrDistance: {3}" + log.AppendFormat("\n\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
"\npotentialSqrDistance < bestOccludedSqrDistance: {4}" + log.AppendFormat("\n\t\tbestOccludedSqrDistance: {0}", bestOccludedSqrDistance);
"\npotentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance): {5}", log.AppendFormat("\n\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance);
potentialVessel.vesselName,  
potentialSqrDistance, bestOccludedSqrDistance, this.maxTransmitDistance * this.maxTransmitDistance,  
potentialSqrDistance < bestOccludedSqrDistance,  
potentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance),  
this.ToString()  
);  
}  
   
if ( if (
(potentialSqrDistance < bestOccludedSqrDistance) && (potentialSqrDistance < bestOccludedSqrDistance) &&
(potentialSqrDistance < maxTransmitSqrDistance) (potentialSqrDistance < maxTransmitSqrDistance)
) )
{ {
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) log.Append("\n\t\t...vessel is close enough to check for occluded relays");
  log.AppendFormat("\n\t\tthis: {0}", this);
  log.AppendFormat("\n\t\tpotentialVessel: {0}",
  potentialVessel == null ? "null" : potentialVessel.ToString());
  log.AppendFormat("\n\t\tvesselRelays: {0}",
  vesselRelays == null ? "null" : vesselRelays.ToString());
   
  log.AppendFormat("\n\t\t{0}: Checking {1} relays on occluded vessel {2}.",
  this.ToString(),
  vesselRelays.Count,
  potentialVessel
  );
   
  IAntennaRelay occludedRelay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
Tools.PostDebugMessage("{0}: Checking {1} relays on {2}.", occludedRelay = vesselRelays[rIdx];
this.ToString(),  
potentialVessel.GetAntennaRelays().Count(), log.AppendFormat(
potentialVessel "\n\t\t{0}: Checking candidate for bestOccludedRelay: {1}" +
  "\n\t\tCanTransmit: {2}",
  this.ToString(), occludedRelay, occludedRelay.CanTransmit()
); );
}  
   
foreach (IAntennaRelay occludedRelay in potentialVessel.GetAntennaRelays())  
{  
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)  
{  
Tools.PostDebugMessage(this.ToString() + " Checking candidate for bestOccludedRelay: {0}" +  
"\n\tCanTransmit: {1}", occludedRelay, occludedRelay.CanTransmit());  
}  
   
if (occludedRelay.CanTransmit()) if (occludedRelay.CanTransmit())
{ {
this.bestOccludedRelay = occludedRelay; this.bestOccludedRelay = occludedRelay;
bodyOccludingBestOccludedRelay = fob; bodyOccludingBestOccludedRelay = fob;
bestOccludedSqrDistance = potentialSqrDistance; bestOccludedSqrDistance = potentialSqrDistance;
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) log.AppendFormat("\n\t{0}: Found new bestOccludedRelay: {1}" +
{ " (blocked by {2}; distance: {3} m)",
Tools.PostDebugMessage(this.ToString() + " Found new bestOccludedRelay: {0}" + this.ToString(),
"\nfirstOccludingBody: {1}" + occludedRelay.ToString(),
"\nbestOccludedSqrDistance: {2}", fob,
occludedRelay, potentialSqrDistance
fob, );
potentialSqrDistance  
);  
}  
break; break;
} }
} }
} }
   
  log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
continue; continue;
} }
   
  log.Append("\n\tpassed LOS check");
   
/* /*
* ...so that we can skip the vessel if it is further away than a vessel we've already checked. * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
* */ * */
if (potentialSqrDistance > nearestRelaySqrDistance) if (potentialSqrDistance > nearestRelaySqrDistance)
{ {
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)  
{ log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
Tools.PostDebugMessage("{0}: Vessel {1} discarded because it is out of range, or farther than another relay.", this.ToString(),
  potentialVessel.vesselName
  );
  continue;
  }
   
  log.Append("\n\tpassed distance check");
   
  IAntennaRelay potentialRelay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  log.AppendFormat("\n\t\tfetching vessel relay at index {0}", rIdx);
  potentialRelay = vesselRelays[rIdx];
  log.AppendFormat("\n\t\tgot relay {0}", potentialRelay == null ? "null" : potentialRelay.ToString());
   
  if (potentialRelay == null)
  {
  log.Append("\n\t\t...skipping null relay");
  continue;
  }
   
  if (
  potentialRelay.CanTransmit() &&
  (potentialRelay.targetRelay == null || potentialRelay.targetRelay.vessel != this.vessel))
  {
  // @TODO: Moved this here from outside the loop; why was it there?
  nearestRelaySqrDistance = potentialSqrDistance;
  this.nearestRelay = potentialRelay;
   
  log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
this.ToString(), this.ToString(),
potentialVessel.vesselName this.nearestRelay.ToString(),
  Math.Sqrt(nearestRelaySqrDistance)
); );
}  
continue;  
}  
   
nearestRelaySqrDistance = potentialSqrDistance;  
   
foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())  
{  
if (potentialRelay.CanTransmit() && potentialRelay.targetRelay != this)  
{  
this.nearestRelay = potentialRelay;  
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)  
{  
Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",  
this.ToString(),  
this.nearestRelay.ToString(),  
this.nearestRelay.vessel.id  
));  
}  
break; break;
} }
} }
} }
   
CelestialBody bodyOccludingKerbin = null; CelestialBody bodyOccludingKerbin = null;
   
double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius; double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
kerbinSqrDistance *= kerbinSqrDistance; kerbinSqrDistance *= kerbinSqrDistance;
   
Tools.DebugLogger log = Tools.DebugLogger.New(this); log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
   
log.AppendFormat("{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);  
   
// If we don't have LOS to Kerbin, focus on relays // If we don't have LOS to Kerbin, focus on relays
if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)) if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
{ {
log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName); log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist. // 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. // Therefore, this will only be true if a valid relay is in range.
if (nearestRelaySqrDistance <= maxTransmitSqrDistance) if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).", log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.canTransmit = true; this.canTransmit = true;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
else else
{ {
log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).", log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, target it. // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
  // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
if (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).", log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), bestOccludedRelay, kerbinSqrDistance);
bestOccludedSqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.bestOccludedRelay;  
this.firstOccludingBody = bodyOccludingBestOccludedRelay; // 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.
// Otherwise, target Kerbin and report the first body blocking it. if (nearestRelaySqrDistance < bestOccludedSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
  }
  // Otherwise, target the best occluded relay.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = bestOccludedRelay;
  this.firstOccludingBody = bodyOccludingBestOccludedRelay;
  }
  }
  // Otherwise, check Kerbin against the nearest relay.
  // Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).", log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
   
  this.firstOccludingBody = null;
   
  // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
  }
  }
  }
  // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
  else
  {
  log.AppendFormat("\n\tKerbin is in LOS.");
   
  // If the nearest relay is closer than Kerbin and in range, transmit to it.
  if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
  {
  log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrDistance, maxTransmitSqrDistance);
   
  this.canTransmit = true;
   
  // If the nearestRelay is closer than Kerbin, use it.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = this.nearestRelay;
  }
  // Otherwise, Kerbin is closer, so use it.
  else
  {
  log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
  this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
  nearestRelaySqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
this.firstOccludingBody = bodyOccludingKerbin; }
} }
} // If the nearest relay is out of range, we still need to check on Kerbin.
} else
// If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin {
else log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
{  
log.AppendFormat("\n\tKerbin is in LOS.");  
   
// If the nearest relay is closer than Kerbin and in range, transmit to it.  
if (nearestRelaySqrDistance <= maxTransmitSqrDistance)  
{  
log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).",  
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = true;  
   
// If the nearestRelay is closer than Kerbin, use it.  
if (nearestRelaySqrDistance < kerbinSqrDistance)  
{  
log.AppendFormat("\n\tPicking relay {0} over Kerbin ({1} < {2}).",  
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),  
nearestRelaySqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = false;  
this.targetRelay = this.nearestRelay;  
}  
// Otherwise, Kerbin is closer, so use it.  
else  
{  
log.AppendFormat("\n\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",  
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),  
nearestRelaySqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = true;  
this.targetRelay = null;  
}  
}  
// If the nearest relay is out of range, we still need to check on Kerbin.  
else  
{  
log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).",  
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),  
nearestRelaySqrDistance, maxTransmitSqrDistance);  
   
// If Kerbin is in range, use it. // If Kerbin is in range, use it.
if (kerbinSqrDistance <= maxTransmitSqrDistance) if (kerbinSqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\tCan transmit to Kerbin ({0} <= {1}).", log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = true; this.canTransmit = true;
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
// If Kerbin is out of range and the nearest relay is out of range, pick a second best between // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
// Kerbin and bestOccludedRelay // Kerbin and bestOccludedRelay
else else
{ {
log.AppendFormat("\n\tCan't transmit to Kerbin ({0} > {1}).", log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, use it. // 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, // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
// this is safe  
if (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).", log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), bestOccludedRelay, kerbinSqrDistance);
bestOccludedSqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = bestOccludedRelay;  
this.firstOccludingBody = bodyOccludingBestOccludedRelay; // If the nearest relay is closer than the best occluded relay, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < bestOccludedSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
  }
  // Otherwise, target the best occluded relay.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = bestOccludedRelay;
  this.firstOccludingBody = bodyOccludingBestOccludedRelay;
  }
} }
// Otherwise, target Kerbin. Since we have LOS, blank the first occluding body. // Otherwise, check Kerbin against the nearest relay.
  // Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).", log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
   
this.KerbinDirect = true;  
this.targetRelay = null;  
this.firstOccludingBody = null; this.firstOccludingBody = null;
   
  // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
} }
} }
} }
} }
   
  log.AppendFormat("{0}: Target search completed at {1} ms ({2} ms elapsed).",
  this.ToString(), searchTimer.ElapsedMilliseconds, searchTimer.ElapsedMilliseconds - searchTime);;
   
log.AppendFormat("\n{0}: Status determination complete.", this.ToString()); log.AppendFormat("\n{0}: Status determination complete.", this.ToString());
   
log.Print(); #if DEBUG
  } catch (Exception ex) {
  log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace);
  UnityEngine.Application.Quit();
  } 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.
RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id); this.isChecked = false;
} }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
public override string ToString() public override string ToString()
{ {
if (this is ProtoAntennaRelay) if (this is ProtoAntennaRelay)
{ {
return (this as ProtoAntennaRelay).ToString(); return (this as ProtoAntennaRelay).ToString();
} }
return this.moduleRef.ToString(); return this.moduleRef.ToString();
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="module">The module reference underlying this AntennaRelay,
  /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
public AntennaRelay(IAntennaRelay module) public AntennaRelay(IAntennaRelay module)
{ {
this.moduleRef = module; this.moduleRef = module;
  this.isChecked = false;
this.searchTimer = new System.Diagnostics.Stopwatch();  
this.millisecondsBetweenSearches = 125L; Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// IAntennaRelay.cs // IAntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // 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; }
   
IAntennaRelay nearestRelay { get; } /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
IAntennaRelay bestOccludedRelay { get; } /// </summary>
   
IAntennaRelay targetRelay { get; } 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; } 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>
/// The first CelestialBody blocking line of sight to a /// The first CelestialBody blocking line of sight to a
/// </summary> /// </summary>
/// <value>The first occluding body.</value>  
CelestialBody firstOccludingBody { get; } CelestialBody firstOccludingBody { get; }
   
/// <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>  
bool relayChecked { get; }  
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
/// directly with Kerbin. /// directly with Kerbin.
/// </summary> /// </summary>
bool KerbinDirect { get; } bool KerbinDirect { get; }
   
/// <summary> /// <summary>
  /// Gets the Part title.
  /// </summary>
  string Title { get; }
   
  /// <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();
   
  void FindNearestRelay();
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
  /// </summary>
string ToString(); string ToString();
   
string Title { get; }  
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;  
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range- /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files. /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
* ///
* In general, the scaling functions assume the following relation: /// <para>In general, the scaling functions assume the following relation:</para>
* ///
* D² α P/R, /// <para> D² α P/R,</para>
* ///
* where D is the total transmission distance, P is the transmission power, and R is the data rate. /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
* /// </summary>
* */  
   
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{ {
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
protected float _basepacketResourceCost; private float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
protected float _basepacketSize; private float _basepacketSize;
   
// Every antenna is a relay. // Every antenna is a relay.
protected AntennaRelay relay; private 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. // Sometimes we will need to communicate errors; this is how we do it.
protected ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost /// <summary>
// and packetSize. /// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
  /// and packetSize.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float nominalRange; public double nominalRange;
   
  /// <summary>
  /// Relay status string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Status")] [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
public string UIrelayStatus; public string UIrelayStatus;
   
  /// <summary>
  /// Relay target string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
public string UIrelayTarget; public string UIrelayTarget;
   
  /// <summary>
  /// Transmit distance string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
  /// <summary>
  /// Maximum distance string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
  /// <summary>
  /// Packet size string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
  /// <summary>
  /// Packet cost string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// <summary>
// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// 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)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
// The multipler on packetSize that defines the maximum data bandwidth of the antenna. /// <summary>
  /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
  /// <summary>
  /// The packet throttle.
  /// </summary>
[KSPField( [KSPField(
isPersistant = true, isPersistant = true,
guiName = "Packet Throttle", guiName = "Packet Throttle",
guiUnits = "%", guiUnits = "%",
guiActive = true, guiActive = true,
guiActiveEditor = false guiActiveEditor = false
)] )]
[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)] [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
public float packetThrottle; public float packetThrottle;
   
protected bool actionUIUpdate; private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
// Returns the parent vessel housing this antenna. /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
public new Vessel vessel public new Vessel vessel
{ {
get get
{ {
if (base.vessel != null) if (base.vessel != null)
{ {
return base.vessel; return base.vessel;
} }
else if (this.part != null) else if (this.part != null)
{ {
return this.part.vessel; return this.part.vessel;
} }
   
else else
{ {
return null; return null;
} }
} }
} }
   
public IAntennaRelay nearestRelay /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null; return null;
} }
   
return this.relay.nearestRelay; return this.relay.targetRelay;
} }
} }
   
public IAntennaRelay bestOccludedRelay /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  public double transmitDistance
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null;  
}  
   
return this.relay.bestOccludedRelay;  
}  
}  
   
public IAntennaRelay targetRelay  
{  
get  
{  
if (this.relay == null)  
{  
return null;  
}  
   
return this.relay.targetRelay;  
}  
}  
   
// Returns the distance to the nearest relay or Kerbin, whichever is closer.  
public double transmitDistance  
{  
get  
{  
if (this.relay == null)  
{  
return double.PositiveInfinity; return double.PositiveInfinity;
} }
   
return this.relay.transmitDistance; 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 public double nominalTransmitDistance
{ {
get get
{ {
return this.nominalRange; return this.nominalRange;
} }
} }
   
// Returns the maximum distance this module can transmit /// <summary>
public float maxTransmitDistance /// The maximum distance at which this relay can operate.
  /// </summary>
  public double maxTransmitDistance
{ {
get get
{ {
// TODO: Cache this in a way that doesn't break everything. // TODO: Cache this in a way that doesn't break everything.
return Mathf.Sqrt(this.maxPowerFactor) * this.nominalRange; return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
} }
} }
   
  /// <summary>
  /// The first CelestialBody blocking line of sight to a
  /// </summary>
public CelestialBody firstOccludingBody public CelestialBody firstOccludingBody
{ {
get get
{ {
return this.relay.firstOccludingBody; return this.relay.firstOccludingBody;
} }
} }
   
/* /*
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except * 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: * in that they both return floats. Here's some quick justification:
* *
* The stock implementation of GetTransmitterScore (which I cannot override) is: * The stock implementation of GetTransmitterScore (which I cannot override) is:
* Score = (1 + DataResourceCost) / DataRate * Score = (1 + DataResourceCost) / DataRate
* *
* The stock DataRate and DataResourceCost are: * The stock DataRate and DataResourceCost are:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost / packetSize * DataResourceCost = packetResourceCost / packetSize
* *
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it * 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, * 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. * 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 * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
* would look like: * would look like:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* The former case, which I've chosen to implement below, is: * The former case, which I've chosen to implement below, is:
* DataRate = packetSize * DataRate = packetSize
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* So... hopefully that doesn't screw with anything else. * So... hopefully that doesn't screw with anything else.
* */ * */
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in /// <summary>
// terms of joules/byte /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
  /// terms of joules/byte
  /// </summary>
public new float DataRate public new float DataRate
{ {
get get
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetSize; return this.packetSize;
} }
else else
{ {
return float.Epsilon; return float.Epsilon;
} }
} }
} }
   
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas /// <summary>
// to be scored in terms of joules/byte /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
public new float DataResourceCost /// to be scored in terms of joules/byte
  /// </summary>
  public new double DataResourceCost
{ {
get get
{ {
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetResourceCost; return this.packetResourceCost;
} }
else else
{ {
return float.PositiveInfinity; return float.PositiveInfinity;
} }
} }
} }
   
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay. /// <summary>
public bool relayChecked /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public bool KerbinDirect
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.relayChecked;  
}  
   
// If our relay is null, always return null so we're never checked.  
return true;  
}  
}  
   
public bool KerbinDirect  
{  
get  
{  
if (this.relay != null)  
{  
return this.relay.KerbinDirect; return this.relay.KerbinDirect;
} }
   
return false; return false;
} }
} }
   
  /// <summary>
  /// Gets the Part title.
  /// </summary>
public string Title public string Title
{ {
get get
{ {
if (this.part != null && this.part.partInfo != null) if (this.part != null && this.part.partInfo != null)
{ {
return this.part.partInfo.title; return this.part.partInfo.title;
} }
   
return string.Empty; return string.Empty;
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Build ALL the objects. // Build ALL the objects.
public ModuleLimitedDataTransmitter () : base() public ModuleLimitedDataTransmitter () : base()
{ {
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT); this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
this.packetThrottle = 100f; this.packetThrottle = 100f;
} }
   
  /// <summary>
  /// PartModule OnAwake override; runs at Unity Awake.
  /// </summary>
public override void OnAwake() public override void OnAwake()
{ {
base.OnAwake(); base.OnAwake();
   
this._basepacketSize = base.packetSize; this._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this._basepacketResourceCost = base.packetResourceCost;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0} loaded:\n" + "{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalRange: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this.name,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
this.nominalRange, this.nominalRange,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin. /// <summary>
  /// PartModule OnStart override; runs at Unity Start.
  /// </summary>
  /// <param name="state">State.</param>
public override void OnStart (StartState state) public override void OnStart (StartState state)
{ {
base.OnStart (state); base.OnStart (state);
   
if (state >= StartState.PreLaunch) if (state >= StartState.PreLaunch)
{ {
this.relay = new AntennaRelay(this); this.relay = new AntennaRelay(this);
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
this.relay.nominalTransmitDistance = this.nominalRange; this.relay.nominalTransmitDistance = this.nominalRange;
   
this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m"; this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
} }
} }
   
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because /// <summary>
// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
// work. /// 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) public override void OnLoad(ConfigNode node)
{ {
this.Fields.Load(node); this.Fields.Load(node);
base.Fields.Load(node); base.Fields.Load(node);
   
base.OnLoad (node); base.OnLoad (node);
} }
   
// Post an error in the communication messages describing the reason transmission has failed. Currently there /// <summary>
// is only one reason for this. /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
protected void PostCannotTransmitError() /// </summary>
{  
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).  
protected void PreTransmit_SetPacketResourceCost()  
{  
if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)  
{  
base.packetResourceCost = this._basepacketResourceCost;  
}  
else  
{  
double rangeFactor = (this.transmitDistance / this.nominalRange);  
rangeFactor *= rangeFactor;  
   
base.packetResourceCost = this._basepacketResourceCost  
* (float)rangeFactor;  
   
Tools.PostDebugMessage(  
this,  
"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",  
base.packetResourceCost,  
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.  
protected void PreTransmit_SetPacketSize()  
{  
if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)  
{  
base.packetSize = this._basepacketSize;  
}  
else  
{  
double rangeFactor = (this.nominalRange / this.transmitDistance);  
rangeFactor *= rangeFactor;  
   
base.packetSize = Math.Min(  
this._basepacketSize * (float)rangeFactor,  
this._basepacketSize * this.maxDataFactor);  
   
Tools.PostDebugMessage(  
this,  
"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",  
base.packetSize,  
rangeFactor);  
}  
   
base.packetSize *= this.packetThrottle / 100f;  
}  
   
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.  
public override string GetInfo() public override string GetInfo()
{ {
string text = base.GetInfo(); string text = base.GetInfo();
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n"; text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n"; text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
return text; return text;
} }
   
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
public new bool CanTransmit() public new bool CanTransmit()
{ {
if (this.part == null || this.relay == null) if (this.part == null || this.relay == null)
{ {
return false; return false;
} }
   
PartStates partState = this.part.State; switch (this.part.State)
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) {
{ case PartStates.DEAD:
Tools.PostDebugMessage(string.Format( case PartStates.DEACTIVATED:
"{0}: {1} on {2} cannot transmit: {3}", Tools.PostDebugMessage(string.Format(
this.GetType().Name, "{0}: {1} on {2} cannot transmit: {3}",
this.part.partInfo.title, this.GetType().Name,
this.vessel.vesselName, this.part.partInfo.title,
Enum.GetName(typeof(PartStates), partState) this.vessel.vesselName,
)); Enum.GetName(typeof(PartStates), this.part.State)
return false; ));
} return false;
  default:
  break;
  }
   
return this.relay.CanTransmit(); return this.relay.CanTransmit();
} }
   
// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit public void FindNearestRelay()
// returns false. {
public new void TransmitData(List<ScienceData> dataQueue) 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.FindNearestRelay();
   
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message.Append("["); base.TransmitData(dataQueue, callback);
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
message.Append("directly to Kerbin.");  
}  
else  
{  
message.Append("via ");  
message.Append(this.relay.nearestRelay);  
}  
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
base.TransmitData(dataQueue);  
} }
else else
{ {
Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = Tools.DebugLogger.New(this); var logger = Tools.DebugLogger.New(this);
   
foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>()) 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", logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
scienceContainer.part.partInfo.title); scienceContainer.part.partInfo.title);
   
if ( if (
scienceContainer.capacity != 0 && scienceContainer.capacity != 0 &&
scienceContainer.GetScienceCount() >= scienceContainer.capacity scienceContainer.GetScienceCount() >= scienceContainer.capacity
) )
{ {
logger.Append("\tInsufficient capacity, skipping.\n"); logger.Append("\tInsufficient capacity, skipping.\n");
continue; continue;
} }
   
List<ScienceData> dataStored = new List<ScienceData>(); List<ScienceData> dataStored = new List<ScienceData>();
   
foreach (ScienceData data in dataQueue) ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
  data = dataQueue[dIdx];
if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data)) if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
{ {
logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n"); logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
continue; continue;
} }
   
logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID); logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
if (scienceContainer.AddData(data)) if (scienceContainer.AddData(data))
{ {
logger.Append("done, removing from queue.\n"); logger.Append("done, removing from queue.\n");
   
dataStored.Add(data); dataStored.Add(data);
} }
#if DEBUG #if DEBUG
else else
{ {
logger.Append("failed.\n"); logger.Append("failed.\n");
} }
#endif #endif
} }
   
dataQueue.RemoveAll(i => dataStored.Contains(i)); dataQueue.RemoveAll(i => dataStored.Contains(i));
   
logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count); logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
} }
   
logger.Print(); logger.Print();
   
if (dataQueue.Count > 0) if (dataQueue.Count > 0)
{ {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
   
msg.Append('['); msg.Append('[');
msg.Append(this.part.partInfo.title); msg.Append(this.part.partInfo.title);
msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
msg.Append("Data to be discarded:\n"); msg.Append("Data to be discarded:\n");
   
foreach (ScienceData data in dataQueue) ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
msg.AppendFormat("\n{0}\n", data.title); data = dataQueue[dIdx];
  msg.AppendFormat("\t{0}\n", data.title);
} }
   
ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
Tools.PostDebugMessage(msg.ToString()); Tools.PostDebugMessage(msg.ToString());
} }
   
this.PostCannotTransmitError (); this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit /// <summary>
// returns false. /// 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.TransmitData(dataQueue, null);
  }
   
  /// <summary>
  /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
public new void StartTransmission() public new void StartTransmission()
{ {
  this.FindNearestRelay();
   
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message.Append("[");  
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
message.Append("directly to Kerbin.");  
}  
else  
{  
message.Append("via ");  
message.Append(this.relay.nearestRelay);  
}  
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
  /// <summary>
  /// MonoBehaviour Update
  /// </summary>
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = "Connected"; this.UIrelayStatus = "Connected";
this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
this.UImaxTransmitDistance = "N/A"; this.UImaxTransmitDistance = "N/A";
this.UIpacketSize = "N/A"; this.UIpacketSize = "N/A";
this.UIpacketCost = "N/A"; this.UIpacketCost = "N/A";
} }
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
this.UIrelayTarget = AntennaRelay.Kerbin.bodyName; this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
} }
else else
{ {
this.UIrelayTarget = this.targetRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
} }
} }
   
public void onPartActionUICreate(Part eventPart) /// <summary>
{ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
if (eventPart == base.part) /// </summary>
{ /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
this.actionUIUpdate = true;  
}  
}  
   
public void onPartActionUIDismiss(Part eventPart)  
{  
if (eventPart == base.part)  
{  
this.actionUIUpdate = false;  
}  
}  
   
public override string ToString() public override string ToString()
{ {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
   
msg.Append(this.part.partInfo.title); msg.Append(this.part.partInfo.title);
   
if (vessel != null) if (vessel != null)
{ {
msg.Append(" on "); msg.Append(" on ");
msg.Append(vessel.vesselName); msg.Append(vessel.vesselName);
} }
else if ( else if (
this.part != null && this.part != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null this.part.protoPartSnapshot.pVesselRef != null
) )
{ {
msg.Append(" on "); msg.Append(" on ");
msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName); msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
} }
   
return msg.ToString(); return msg.ToString();
} }
   
  // 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.nominalRange)
  {
  base.packetResourceCost = this._basepacketResourceCost;
  }
  else
  {
  float rangeFactor = (float)(this.transmitDistance / this.nominalRange);
  rangeFactor *= rangeFactor;
   
  base.packetResourceCost = this._basepacketResourceCost
  * rangeFactor;
   
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
  base.packetResourceCost,
  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.nominalRange)
  {
  base.packetSize = this._basepacketSize;
  }
  else
  {
  float rangeFactor = (float)(this.nominalRange / this.transmitDistance);
  rangeFactor *= rangeFactor;
   
  base.packetSize = Mathf.Min(
  this._basepacketSize * rangeFactor,
  this._basepacketSize * this.maxDataFactor);
   
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
  base.packetSize,
  rangeFactor);
  }
   
  base.packetSize *= this.packetThrottle / 100f;
  }
   
  private string buildTransmitMessage()
  {
  StringBuilder message = new StringBuilder();
   
  message.Append("[");
  message.Append(base.part.partInfo.title);
  message.Append("]: ");
   
  message.Append("Beginning transmission ");
   
  if (this.KerbinDirect)
  {
  message.Append("directly to Kerbin.");
  }
  else
  {
  message.Append("via ");
  message.Append(this.relay.targetRelay);
  }
   
  return message.ToString();
  }
   
  #if DEBUG
// When debugging, it's nice to have a button that just tells you everything. // When debugging, it's nice to have a button that just tells you everything.
   
[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)] [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
public void DebugInfo() public void DebugInfo()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
string msg = string.Format( string msg = string.Format(
"'{0}'\n" + "'{0}'\n" +
"_basepacketSize: {1}\n" + "_basepacketSize: {1}\n" +
"packetSize: {2}\n" + "packetSize: {2}\n" +
"_basepacketResourceCost: {3}\n" + "_basepacketResourceCost: {3}\n" +
"packetResourceCost: {4}\n" + "packetResourceCost: {4}\n" +
"maxTransmitDistance: {5}\n" + "maxTransmitDistance: {5}\n" +
"transmitDistance: {6}\n" + "transmitDistance: {6}\n" +
"nominalRange: {7}\n" + "nominalRange: {7}\n" +
"CanTransmit: {8}\n" + "CanTransmit: {8}\n" +
"DataRate: {9}\n" + "DataRate: {9}\n" +
"DataResourceCost: {10}\n" + "DataResourceCost: {10}\n" +
"TransmitterScore: {11}\n" + "TransmitterScore: {11}\n" +
"NearestRelay: {12}\n" + "targetRelay: {12}\n" +
"BestOccludedRelay: {13}\n" + "KerbinDirect: {13}\n" +
"KerbinDirect: {14}\n" + "Vessel ID: {14}",
"Vessel ID: {15}",  
this.name, this.name,
this._basepacketSize, this._basepacketSize,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
base.packetResourceCost, base.packetResourceCost,
this.maxTransmitDistance, this.maxTransmitDistance,
this.transmitDistance, this.transmitDistance,
this.nominalRange, this.nominalRange,
this.CanTransmit(), this.CanTransmit(),
this.DataRate, this.DataRate,
this.DataResourceCost, this.DataResourceCost,
ScienceUtil.GetTransmitterScore(this), ScienceUtil.GetTransmitterScore(this),
this.relay.nearestRelay == null ? "null" : this.relay.nearestRelay.ToString(), this.relay.targetRelay == null ? "null" : this.relay.targetRelay.ToString(),
this.relay.bestOccludedRelay == null ? "null" : this.relay.bestOccludedRelay.ToString(),  
this.KerbinDirect, this.KerbinDirect,
this.vessel.id this.vessel.id
); );
   
Tools.PostLogMessage(msg); Tools.PostLogMessage(msg);
} }
   
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels() public void PrintAllVessels()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
   
sb.Append("Dumping FlightGlobals.Vessels:"); sb.Append("Dumping FlightGlobals.Vessels:");
   
foreach (Vessel vessel in 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); sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
} }
   
/*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)] [KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
public void DumpRelayDB() public void DumpRelayDB()
{ {
RelayDatabase.Instance.Dump(); RelayDatabase.Instance.Dump();
}*/ }
  #endif
} }
} }
// AntennaRange // AntennaRange
// //
// ProtoAntennaRelay.cs // ProtoAntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Linq;  
using ToadicusTools; 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
{ {
// Stores the prototype part so we can make sure we haven't exploded or so. // Stores the prototype part so we can make sure we haven't exploded or so.
protected ProtoPartSnapshot protoPart; private ProtoPartSnapshot protoPart;
   
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
public override Vessel vessel public override Vessel vessel
{ {
get get
{ {
return this.protoPart.pVesselRef.vesselRef; if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  return this.protoPart.pVesselRef.vesselRef;
  }
  else
  {
  Tools.PostLogMessage("{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>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
public override double nominalTransmitDistance public override double nominalTransmitDistance
{ {
get get
{ {
return this.moduleRef.nominalTransmitDistance; return this.moduleRef.nominalTransmitDistance;
} }
} }
   
/// <summary> /// <summary>
/// The maximum distance at which this transmitter can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> public override double maxTransmitDistance
public override float maxTransmitDistance  
{ {
get get
{ {
return moduleRef.maxTransmitDistance; return moduleRef.maxTransmitDistance;
} }
}  
   
/// <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;  
protected set;  
} }
   
/// <summary> /// <summary>
/// Gets the underlying part's title. /// Gets the underlying part's title.
/// </summary> /// </summary>
/// <value>The title.</value> /// <value>The title.</value>
public string Title public string Title
{ {
get get
{ {
if (this.protoPart != null && this.protoPart.partInfo != null) if (this.protoPart != null && this.protoPart.partInfo != null)
{ {
return this.protoPart.partInfo.title; return this.protoPart.partInfo.title;
} }
   
return string.Empty; 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() public override bool CanTransmit()
{ {
PartStates partState = (PartStates)this.protoPart.state; PartStates partState = (PartStates)this.protoPart.state;
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.Title, this.Title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), partState) Enum.GetName(typeof(PartStates), partState)
)); ));
return false; return false;
} }
return base.CanTransmit(); 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() public override string ToString()
{ {
return string.Format( System.Text.StringBuilder sb = new System.Text.StringBuilder();
"{0} on {1}",  
this.Title, sb.Append(this.Title);
this.protoPart.pVesselRef.vesselName  
); if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
  }
   
  return sb.ToString();
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param> /// <param name="prefabRelay">The module reference underlying this AntennaRelay,
/// <param name="vessel">The parent Vessel</param> /// 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) public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
{ {
this.protoPart = pps; this.protoPart = pps;
}  
   
~ProtoAntennaRelay() Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
{  
Tools.PostDebugMessage(string.Format(  
"{0}: destroyed",  
this.ToString()  
));  
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// RelayDatabase.cs // RelayDatabase.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
  #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase public class RelayDatabase
{ {
/* /*
* Static members * Static members
* */ * */
// Singleton storage // Singleton storage
protected static RelayDatabase _instance; private static RelayDatabase _instance;
// Gets the singleton // Gets the singleton
public static RelayDatabase Instance public static RelayDatabase Instance
{ {
get get
{ {
if (_instance == null) if (_instance == null)
{ {
_instance = new RelayDatabase(); _instance = new RelayDatabase();
} }
   
return _instance; return _instance;
} }
} }
   
/* /*
* Instance members * Instance members
* */ * */
   
/* /*
* Fields * Fields
* */ * */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects. // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase; private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
  private Dictionary<Guid, IAntennaRelay> bestRelayTable;
   
// Vessel.id-keyed hash table of part counts, used for caching // Vessel.id-keyed hash table of part counts, used for caching
protected Dictionary<Guid, int> vesselPartCountTable; private Dictionary<Guid, int> vesselPartCountTable;
   
// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time. // Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
public Dictionary<Guid, bool> CheckedVesselsTable; public Dictionary<Guid, bool> CheckedVesselsTable;
   
protected int cacheHits; private int cacheHits;
protected int cacheMisses; private int cacheMisses;
   
/* /*
* Properties * Properties
* */ * */
// Gets the Part-hashed table of relays in a given vessel // Gets the Part-hashed table of relays in a given vessel
public Dictionary<int, IAntennaRelay> this [Vessel vessel] public IList<IAntennaRelay> this [Vessel vessel]
{ {
get get
{ {
// If we don't have an entry for this vessel... // If we don't have an entry for this vessel...
if (!this.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel.id))
{ {
// ...Generate an entry for this vessel. // ...Generate an entry for this vessel.
this.AddVessel(vessel); this.AddVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
{ {
// ...Update the our vessel in the cache // ...Update the our vessel in the cache
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// Otherwise, it's a hit // Otherwise, it's a hit
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
   
// Return the Part-hashed table of relays for this vessel // Return the Part-hashed table of relays for this vessel
return relayDatabase[vessel.id]; return relayDatabase[vessel.id].AsReadOnly();
} }
} }
   
/* /*
* Methods * Methods
* */ * */
  // Remove a vessel from the cache, if it exists.
  public void DirtyVessel(Vessel vessel)
  {
  this.relayDatabase.Remove(vessel.id);
  this.vesselPartCountTable.Remove(vessel.id);
  this.relayDatabase.Remove(vessel.id);
  }
   
  // 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 // Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses // The return for this function isn't used yet, but seems useful for potential future API-uses
public bool AddVessel(Vessel vessel) private bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// ...post an error // ...post an error
Debug.LogWarning(string.Format( Debug.LogWarning(string.Format(
"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.", "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// ...and refuse to add // ...and refuse to add
return false; return false;
} }
// otherwise, add the vessel to our tables... // otherwise, add the vessel to our tables...
else else
{ {
// Build an empty table... // Build an empty table...
this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>(); this.relayDatabase[vessel.id] = new List<IAntennaRelay>();
   
// Update the empty index // Update the empty index
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
   
// Return success // Return success
return true; return true;
} }
} }
   
// Update the vessel's entry in the table // Update the vessel's entry in the table
public void UpdateVessel(Vessel vessel) private void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.", "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
   
// Actually build and assign the table // Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable); this.getVesselRelays(vessel, ref vesselTable);
// Set the part count // Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count; this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
}  
   
// Remove a vessel from the cache, if it exists.  
public void DirtyVessel(Vessel vessel)  
{  
if (this.relayDatabase.ContainsKey(vessel.id))  
{  
this.relayDatabase.Remove(vessel.id);  
}  
if (this.vesselPartCountTable.ContainsKey(vessel.id))  
{  
this.vesselPartCountTable.Remove(vessel.id);  
}  
}  
   
// 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);  
} }
   
// Runs when a vessel is modified (or when we switch to one, to catch docking events) // Runs when a vessel is modified (or when we switch to one, to catch docking events)
public void onVesselEvent(Vessel vessel) public void onVesselEvent(Vessel vessel)
{ {
// If we have this vessel in our cache... // If we have this vessel in our cache...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// If our part counts disagree (such as if a part has been added or broken off, // If our part counts disagree (such as if a part has been added or broken off,
// or if we've just docked or undocked)... // or if we've just docked or undocked)...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' ({2}).", "{0}: dirtying cache for vessel '{1}' ({2}).",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// Dirty the cache (real vessels will never have negative part counts) // Dirty the cache (real vessels will never have negative part counts)
this.DirtyVessel(vessel); this.DirtyVessel(vessel);
} }
} }
} }
   
// Runs when the player requests a scene change, such as when changing vessels or leaving flight. // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
public void onSceneChange(GameScenes scene) private void onSceneChange(GameScenes scene)
{ {
// If the active vessel is a real thing... // If the active vessel is a real thing...
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
// ... dirty its cache // ... dirty its cache
this.onVesselEvent(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
} }
   
// Runs when parts are undocked // Runs when parts are undocked
public void onPartEvent(Part part) private void onPartEvent(Part part)
{ {
if (part != null && part.vessel != null) if (part != null && part.vessel != null)
{ {
this.onVesselEvent(part.vessel); this.onVesselEvent(part.vessel);
} }
} }
   
// Runs when parts are coupled, as in docking // Runs when parts are coupled, as in docking
public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
this.onPartEvent(data.from); this.onPartEvent(data.from);
this.onPartEvent(data.to); this.onPartEvent(data.to);
} }
   
// Produce a Part-hashed table of relays for the given vessel // Produce a Part-hashed table of relays for the given vessel
protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays) private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
{ {
// We're going to completely regen this table, so dump the current contents. // We're going to completely regen this table, so dump the current contents.
relays.Clear(); relays.Clear();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Getting antenna relays from vessel {1}.", "{0}: Getting antenna relays from vessel {1}.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
  double bestRelayRange = double.NegativeInfinity;
  IAntennaRelay bestRelay = null;
  IAntennaRelay relay;
   
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly. // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) { if (vessel.loaded) {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is loaded, searching for modules in loaded parts.", "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
foreach (Part part in vessel.Parts) Part part;
{ for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
  {
  part = vessel.Parts[partIdx];
   
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
foreach (PartModule module in part.Modules) PartModule module;
  for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++)
{ {
  module = part.Modules[modIdx];
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
  relay = (module as IAntennaRelay);
   
  if (relay.maxTransmitDistance > bestRelayRange)
  {
  bestRelayRange = relay.maxTransmitDistance;
  bestRelay = relay;
  }
   
// ...add the module to the table // ...add the module to the table
relays.Add(part.GetHashCode(), module as IAntennaRelay); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots. // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
else else
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is not loaded, searching for modules in prototype parts.", "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots) ProtoPartSnapshot pps;
{ for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
  {
  pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}", "{0}: Searching in protopartsnapshot {1}",
this.GetType().Name, this.GetType().Name,
pps pps
)); ));
   
// ...Fetch the prefab, because it's more useful for what we're doing. // ...Fetch the prefab, because it's more useful for what we're doing.
Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab; Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Got partPrefab {1} in protopartsnapshot {2}", "{0}: Got partPrefab {1} in protopartsnapshot {2}",
this.GetType().Name, this.GetType().Name,
partPrefab, partPrefab,
pps pps
)); ));
   
// ...loop through the PartModules in the prefab... // ...loop through the PartModules in the prefab...
foreach (PartModule module in partPrefab.Modules) PartModule module;
  for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
{ {
  module = partPrefab.Modules[modIdx];
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}", "{0}: Searching in partmodule {1}",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: partmodule {1} is antennarelay", "{0}: partmodule {1} is antennarelay",
this.GetType().Name, this.GetType().Name,
module 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 // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps)); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
  this.bestRelayTable[vessel.id] = bestRelay;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.", "{0}: vessel '{1}' ({2}) has {3} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName, vessel.vesselName,
vessel.id, vessel.id,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
protected RelayDatabase() private RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
  this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
this.CheckedVesselsTable = new Dictionary<Guid, bool>(); this.CheckedVesselsTable = new Dictionary<Guid, bool>();
   
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselEvent); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselEvent); GameEvents.onVesselChange.Add(this.onVesselEvent);
GameEvents.onVesselDestroy.Add(this.onVesselEvent); GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent); GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent); GameEvents.onPartUndock.Add(this.onPartEvent);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselEvent); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselEvent); GameEvents.onVesselChange.Remove(this.onVesselEvent);
GameEvents.onVesselDestroy.Remove(this.onVesselEvent); GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent); GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent); GameEvents.onPartUndock.Remove(this.onPartEvent);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
   
KSPLog.print(string.Format( KSPLog.print(string.Format(
"{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}", "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
this.GetType().Name, this.GetType().Name,
this.cacheHits, this.cacheHits,
this.cacheMisses, this.cacheMisses,
(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits) (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
)); ));
} }
   
#if DEBUG #if DEBUG
public void Dump() public void Dump()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
   
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
foreach (Guid id in this.relayDatabase.Keys) var dbEnum = this.relayDatabase.GetEnumerator();
{ IList<IAntennaRelay> vesselRelays;
sb.AppendFormat("\nVessel {0}:", id); while (dbEnum.MoveNext())
  {
foreach (IAntennaRelay relay in this.relayDatabase[id].Values) 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()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
} }
#endif #endif
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// Extensions.cs // Extensions.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // 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; using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin. /// A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
* */ /// </summary>
public static class RelayExtensions public static class RelayExtensions
{ {
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a Vessel /// Returns the distance between this IAntennaRelay and a Vessel
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="Vessel">A <see cref="Vessel"/></param> /// <param name="Vessel">A <see cref="Vessel"/></param>
public static double DistanceTo(this AntennaRelay relay, Vessel Vessel) public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
{ {
return relay.vessel.DistanceTo(Vessel); return relay.vessel.DistanceTo(Vessel);
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a CelestialBody /// Returns the distance between this IAntennaRelay and a CelestialBody
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="body">A <see cref="CelestialBody"/></param> /// <param name="body">A <see cref="CelestialBody"/></param>
public static double DistanceTo(this AntennaRelay relay, CelestialBody body) public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.DistanceTo(body) - body.Radius; return relay.vessel.DistanceTo(body) - body.Radius;
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and another IAntennaRelay /// Returns the distance between this IAntennaRelay and another IAntennaRelay
/// </summary> /// </summary>
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param> /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param> /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo) public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.DistanceTo(relayTwo.vessel); 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) public static double sqrDistanceTo(this AntennaRelay relay, Vessel vessel)
{ {
return relay.vessel.sqrDistanceTo(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) public static double sqrDistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.sqrDistanceTo(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) public static double sqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.vessel.sqrDistanceTo(relayTwo.vessel); return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel. /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
/// </summary> /// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param> /// <param name="vessel">This <see cref="Vessel"/></param>
public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel) public static IList<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
{ {
return RelayDatabase.Instance[vessel].Values.ToList().AsReadOnly(); return RelayDatabase.Instance[vessel];
} }
   
/// <summary> /// <summary>
/// Determines if the specified vessel has a connected relay. /// Determines if the specified vessel has a connected relay.
/// </summary> /// </summary>
/// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns>
/// <param name="vessel"></param> /// <param name="vessel"></param>
public static bool HasConnectedRelay(this Vessel vessel) public static bool HasConnectedRelay(this Vessel vessel)
{ {
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
  relay = vesselRelays[rIdx];
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
return true; return true;
} }
} }
   
return false; return false;
} }
   
  /// <summary>
  /// Gets the <see cref="AntennaRange.ConnectionStatus"/> for this <see cref="Vessel"/>
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
public static ConnectionStatus GetConnectionStatus(this Vessel vessel) public static ConnectionStatus GetConnectionStatus(this Vessel vessel)
{ {
bool canTransmit = false; bool canTransmit = false;
   
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
  relay = vesselRelays[rIdx];
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
canTransmit = true; canTransmit = true;
if (relay.transmitDistance <= relay.nominalTransmitDistance) if (relay.transmitDistance <= relay.nominalTransmitDistance)
{ {
return ConnectionStatus.Optimal; return ConnectionStatus.Optimal;
} }
} }
} }
   
if (canTransmit) if (canTransmit)
{ {
return ConnectionStatus.Suboptimal; return ConnectionStatus.Suboptimal;
} }
else else
{ {
return ConnectionStatus.None; 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 public enum ConnectionStatus
{ {
None, None,
Suboptimal, Suboptimal,
Optimal Optimal
} }
} }
   
   
file:b/VesselCache.cs (new)
  // AntennaRange © 2015 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 System;
 
  namespace AntennaRange
  {
  public class Relay
  {
  public Vessel vessel;
  public IAntennaRelay targetRelay;
  }
  }