Added reflective recompiling of antenna parts after config changes and tracking station upgrades.
Added reflective recompiling of antenna parts after config changes and tracking station upgrades.

// 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 KSP.UI.Screens; using KSP.UI.Screens;
using System; using System;
  using System.Collections.Generic;
  using System.Reflection;
using ToadicusTools.Extensions; using ToadicusTools.Extensions;
using ToadicusTools.Text; using ToadicusTools.Text;
using ToadicusTools.GUIUtils; using ToadicusTools.GUIUtils;
using ToadicusTools.Wrappers; using ToadicusTools.Wrappers;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange. /// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange.
/// </summary> /// </summary>
[KSPAddon(KSPAddon.Startup.SpaceCentre, false)] [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class ARConfiguration : MonoBehaviour public class ARConfiguration : MonoBehaviour
{ {
private const string WINDOW_POS_KEY = "configWindowPos"; private const string WINDOW_POS_KEY = "configWindowPos";
private const string REQUIRE_LOS_KEY = "requireLineOfSight"; private const string REQUIRE_LOS_KEY = "requireLineOfSight";
private const string GRACE_RATIO_KEY = "graceRatio"; private const string GRACE_RATIO_KEY = "graceRatio";
private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl"; private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
private const string FIXED_POWER_KEY = "fixedPowerCost"; private const string FIXED_POWER_KEY = "fixedPowerCost";
private const string PRETTY_LINES_KEY = "drawPrettyLines"; private const string PRETTY_LINES_KEY = "drawPrettyLines";
private const string UPDATE_DELAY_KEY = "updateDelay"; private const string UPDATE_DELAY_KEY = "updateDelay";
private const string USE_ADDITIVE_KEY = "useAdditiveRanges"; private const string USE_ADDITIVE_KEY = "useAdditiveRanges";
   
private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES"; private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
private const string RANGE_KEY = "range"; private const string RANGE_KEY = "range";
   
private const string USE_TOOLBAR_KEY = "useToolbarIfAvailable"; private const string USE_TOOLBAR_KEY = "useToolbarIfAvailable";
   
/// <summary> /// <summary>
/// Indicates whether connections require line of sight. /// Indicates whether connections require line of sight.
/// </summary> /// </summary>
public static bool RequireLineOfSight public static bool RequireLineOfSight
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// A "fudge factor" ratio that pretends planets and moons are slightly smaller than reality to make /// A "fudge factor" ratio that pretends planets and moons are slightly smaller than reality to make
/// building communication constellations easier. /// building communication constellations easier.
/// </summary> /// </summary>
public static double RadiusRatio public static double RadiusRatio
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Indicates whether unmanned vessels require a connection for control. /// Indicates whether unmanned vessels require a connection for control.
/// </summary> /// </summary>
public static bool RequireConnectionForControl public static bool RequireConnectionForControl
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// If true, relays will fix their power cost when above nominal range, decreasing data rate instead. /// If true, relays will fix their power cost when above nominal range, decreasing data rate instead.
/// </summary> /// </summary>
public static bool FixedPowerCost public static bool FixedPowerCost
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Indicates whether this AntennaRange will draw pretty lines in map view. /// Indicates whether this AntennaRange will draw pretty lines in map view.
/// </summary> /// </summary>
public static bool PrettyLines public static bool PrettyLines
{ {
get; get;
set; set;
} }
   
/// <summary> /// <summary>
/// Gets the update delay. /// Gets the update delay.
/// </summary> /// </summary>
public static long UpdateDelay public static long UpdateDelay
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether AntennaRange will use additive ranges. /// Gets a value indicating whether AntennaRange will use additive ranges.
/// </summary> /// </summary>
public static bool UseAdditiveRanges public static bool UseAdditiveRanges
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Gets Kerbin's relay range based on the current tracking station level. /// Gets Kerbin's relay range based on the current tracking station level.
/// </summary> /// </summary>
public static double KerbinRelayRange public static double KerbinRelayRange
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Gets Kerbin's nominal relay range based on the current tracking station level. /// Gets Kerbin's nominal relay range based on the current tracking station level.
/// </summary> /// </summary>
public static double KerbinNominalRange public static double KerbinNominalRange
{ {
get get
{ {
return KerbinRelayRange / 2.8284271247461901d; return KerbinRelayRange / 2.8284271247461901d;
} }
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether we should use Toolbar if available. /// Gets a value indicating whether we should use Toolbar if available.
/// </summary> /// </summary>
/// <value><c>true</c> if we should use Toolbar if available; otherwise, <c>false</c>.</value> /// <value><c>true</c> if we should use Toolbar if available; otherwise, <c>false</c>.</value>
public static bool UseToolbarIfAvailable public static bool UseToolbarIfAvailable
{ {
get; get;
private set; private set;
} }
   
#pragma warning disable 1591 #pragma warning disable 1591
   
  private static MethodInfo partLoader_CompilePartInfo;
   
private bool showConfigWindow; private bool showConfigWindow;
private Rect configWindowPos; private Rect configWindowPos;
   
private string updateDelayStr; private string updateDelayStr;
private long updateDelay; private long updateDelay;
   
private IButton toolbarButton; private IButton toolbarButton;
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
   
private double[] trackingStationRanges; private double[] trackingStationRanges;
   
private System.Version runningVersion; private System.Version runningVersion;
   
private bool runOnce; private bool runOnce;
   
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()
{ {
this.LogDebug("Waking up."); this.LogDebug("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(WINDOW_POS_KEY, this.configWindowPos); this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
   
ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false); ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
   
ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d)); ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio; ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
   
ARConfiguration.RequireConnectionForControl = ARConfiguration.RequireConnectionForControl =
this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false); this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
   
ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false); ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
   
ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true); ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
   
ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L); ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
   
ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true); ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true);
   
ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true); ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
   
ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L); ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
this.updateDelayStr = ARConfiguration.UpdateDelay.ToString(); this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
   
ARConfiguration.UseToolbarIfAvailable = this.LoadConfigValue(USE_TOOLBAR_KEY, true); ARConfiguration.UseToolbarIfAvailable = this.LoadConfigValue(USE_TOOLBAR_KEY, true);
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded); GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
   
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));
   
ConfigNode[] tsRangeNodes = GameDatabase.Instance.GetConfigNodes(TRACKING_STATION_RANGES_KEY); ConfigNode[] tsRangeNodes = GameDatabase.Instance.GetConfigNodes(TRACKING_STATION_RANGES_KEY);
   
if (tsRangeNodes.Length > 0) if (tsRangeNodes.Length > 0)
{ {
string[] rangeValues = tsRangeNodes[0].GetValues(RANGE_KEY); string[] rangeValues = tsRangeNodes[0].GetValues(RANGE_KEY);
   
this.trackingStationRanges = new double[rangeValues.Length]; this.trackingStationRanges = new double[rangeValues.Length];
   
for (int idx = 0; idx < rangeValues.Length; idx++) for (int idx = 0; idx < rangeValues.Length; idx++)
{ {
if (!double.TryParse(rangeValues[idx], out this.trackingStationRanges[idx])) if (!double.TryParse(rangeValues[idx], out this.trackingStationRanges[idx]))
{ {
this.LogError("Could not parse value '{0}' to double; Tracking Station ranges may not work!"); this.LogError("Could not parse value '{0}' to double; Tracking Station ranges may not work!");
this.trackingStationRanges[idx] = 0d; this.trackingStationRanges[idx] = 0d;
} }
} }
   
this.Log("Loaded Tracking Station ranges from config: [{0}]", this.trackingStationRanges.SPrint()); this.Log("Loaded Tracking Station ranges from config: [{0}]", this.trackingStationRanges.SPrint());
} }
else else
{ {
this.trackingStationRanges = new double[] this.trackingStationRanges = new double[]
{ {
51696576d, 51696576d,
37152180000d, 37152180000d,
224770770000d 224770770000d
}; };
   
this.LogWarning("Failed to load Tracking Station ranges from config, using hard-coded values: [{0}]", this.LogWarning("Failed to load Tracking Station ranges from config, using hard-coded values: [{0}]",
this.trackingStationRanges.SPrint()); this.trackingStationRanges.SPrint());
  }
   
  if (partLoader_CompilePartInfo == null)
  {
  partLoader_CompilePartInfo = typeof(PartLoader).GetMethod(
  "CompilePartInfo",
  BindingFlags.NonPublic | BindingFlags.Instance
  );
   
  this.Log("Fetched PartLoader.CompilePartInfo: {0}", partLoader_CompilePartInfo);
} }
   
this.runOnce = true; this.runOnce = true;
   
this.LogDebug("Awake."); this.LogDebug("Awake.");
} }
   
public void Update() public void Update()
{ {
if ( if (
this.runOnce && this.runOnce &&
(ScenarioUpgradeableFacilities.Instance != null || HighLogic.CurrentGame.Mode != Game.Modes.CAREER) (ScenarioUpgradeableFacilities.Instance != null || HighLogic.CurrentGame.Mode != Game.Modes.CAREER)
) )
{ {
this.runOnce = false; this.runOnce = false;
   
this.SetKerbinRelayRange(); this.SetKerbinRelayRange();
} }
} }
   
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 && ARConfiguration.UseToolbarIfAvailable) if (ToolbarManager.ToolbarAvailable && ARConfiguration.UseToolbarIfAvailable)
{ {
if (this.toolbarButton == null) if (this.toolbarButton == null)
{ {
this.LogDebug("Toolbar available; initializing toolbar button."); this.LogDebug("Toolbar available; initializing toolbar button.");
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
   
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)
{ {
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
this.toolbarButton = null; this.toolbarButton = null;
} }
   
this.LogDebug("Toolbar available; initializing AppLauncher button."); this.LogDebug("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 = WindowTools.ClampRectToScreen(configPos, 20); configPos = WindowTools.ClampRectToScreen(configPos, 20);
   
if (configPos != this.configWindowPos) if (configPos != this.configWindowPos)
{ {
this.configWindowPos = configPos; this.configWindowPos = configPos;
this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos); this.SaveConfigValue(WINDOW_POS_KEY, 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 = Layout.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight"); bool requireLineOfSight = Layout.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
if (requireLineOfSight != ARConfiguration.RequireLineOfSight) if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
{ {
ARConfiguration.RequireLineOfSight = requireLineOfSight; ARConfiguration.RequireLineOfSight = requireLineOfSight;
this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight); this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireConnectionForControl = bool requireConnectionForControl =
Layout.Toggle( Layout.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(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl); this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool fixedPowerCost = Layout.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost"); bool fixedPowerCost = Layout.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
if (fixedPowerCost != ARConfiguration.FixedPowerCost) if (fixedPowerCost != ARConfiguration.FixedPowerCost)
{ {
ARConfiguration.FixedPowerCost = fixedPowerCost; ARConfiguration.FixedPowerCost = fixedPowerCost;
this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost); this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool useAdditive = Layout.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges"); bool useAdditive = Layout.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
if (useAdditive != ARConfiguration.UseAdditiveRanges) if (useAdditive != ARConfiguration.UseAdditiveRanges)
{ {
ARConfiguration.UseAdditiveRanges = useAdditive; ARConfiguration.UseAdditiveRanges = useAdditive;
this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive); this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
   
  this.updateModuleInfos();
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool prettyLines = Layout.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines"); bool prettyLines = Layout.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
if (prettyLines != ARConfiguration.PrettyLines) if (prettyLines != ARConfiguration.PrettyLines)
{ {
ARConfiguration.PrettyLines = prettyLines; ARConfiguration.PrettyLines = prettyLines;
this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines); this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool useToolbar = Layout.Toggle(ARConfiguration.UseToolbarIfAvailable, "Use Blizzy's Toolbar, if Available"); bool useToolbar = Layout.Toggle(ARConfiguration.UseToolbarIfAvailable, "Use Blizzy's Toolbar, if Available");
if (useToolbar != ARConfiguration.UseToolbarIfAvailable) if (useToolbar != ARConfiguration.UseToolbarIfAvailable)
{ {
ARConfiguration.UseToolbarIfAvailable = useToolbar; ARConfiguration.UseToolbarIfAvailable = useToolbar;
this.SaveConfigValue(USE_TOOLBAR_KEY, useToolbar); this.SaveConfigValue(USE_TOOLBAR_KEY, useToolbar);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false)); GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
   
this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f)); this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
   
GUILayout.Label("ms", GUILayout.ExpandWidth(false)); GUILayout.Label("ms", GUILayout.ExpandWidth(false));
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay)) if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
{ {
ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500); ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
this.updateDelayStr = ARConfiguration.UpdateDelay.ToString(); this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
} }
   
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(GRACE_RATIO_KEY, newRatio); this.SaveConfigValue(GRACE_RATIO_KEY, 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);
GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded); GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
this.toolbarButton = null; this.toolbarButton = null;
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
} }
   
private void onSceneChangeRequested(GameScenes scene) private 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 onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int lvl) private void onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int lvl)
{ {
if (fac.id == "SpaceCenter/TrackingStation") if (fac.id == "SpaceCenter/TrackingStation")
{ {
this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl); this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
this.SetKerbinRelayRange(); this.SetKerbinRelayRange();
   
  this.updateModuleInfos();
  }
  }
   
  private void updateModuleInfos()
  {
  if (PartLoader.Instance != null && PartLoader.Instance.parts != null)
  {
  this.Log("Updating module infos in PartLoader");
  this.updateModuleInfos(PartLoader.Instance.parts);
  }
   
  if (RDTestSceneLoader.Instance != null && RDTestSceneLoader.Instance.partsList != null)
  {
  this.Log("Updating module infos in RDTestSceneLoader");
  this.updateModuleInfos(RDTestSceneLoader.Instance.partsList);
  }
  }
   
  private void updateModuleInfos(List<AvailablePart> partsList)
  {
  if (partLoader_CompilePartInfo == null)
  {
  this.LogError("Cannot recompile part info; partLoader_CompilePartInfo not found.");
  return;
  }
   
  if (PartLoader.Instance == null)
  {
  this.LogError("Cannot recompile part info; PartLoader.Instance is null.");
  return;
  }
   
  // We need to go find all of the prefabs and update them, because Squad broke IModuleInfo.
  AvailablePart availablePart;
  Part partPrefab;
  PartModule modulePrefab;
  object[] compileArgs = new object[2];
   
  this.Log("Updating module infos...");
   
  for (int apIdx = 0; apIdx < partsList.Count; apIdx++)
  {
  availablePart = partsList[apIdx];
   
  if (availablePart == null)
  {
  continue;
  }
   
  partPrefab = availablePart.partPrefab;
   
  if (partPrefab == null || partPrefab.Modules == null)
  {
  continue;
  }
   
  for (int pmIdx = 0; pmIdx < partPrefab.Modules.Count; pmIdx++)
  {
  modulePrefab = partPrefab.Modules[pmIdx];
   
  if (modulePrefab == null)
  {
  continue;
  }
   
  if (modulePrefab is IAntennaRelay)
  {
  this.Log("Found prefab IAntennaRelay {0}", modulePrefab);
   
  this.Log("Recompiling part and module info for {0}", availablePart.name);
   
  availablePart.moduleInfos.Clear();
  availablePart.resourceInfos.Clear();
   
  compileArgs[0] = availablePart;
  compileArgs[1] = partPrefab;
   
  partLoader_CompilePartInfo.Invoke(PartLoader.Instance, compileArgs);
   
  break;
  }
  }
} }
} }
   
private void SetKerbinRelayRange() private void SetKerbinRelayRange()
{ {
int tsLevel; int tsLevel;
   
if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
{ {
tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"] tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
.facilityRefs[0].FacilityLevel; .facilityRefs[0].FacilityLevel;
} }
else else
{ {
tsLevel = this.trackingStationRanges.Length - 1; tsLevel = this.trackingStationRanges.Length - 1;
} }
   
if (tsLevel < this.trackingStationRanges.Length && tsLevel >= 0) if (tsLevel < this.trackingStationRanges.Length && tsLevel >= 0)
{ {
KerbinRelayRange = this.trackingStationRanges[tsLevel]; KerbinRelayRange = this.trackingStationRanges[tsLevel];
this.Log("Setting Kerbin's range to {0}", KerbinRelayRange); this.Log("Setting Kerbin's range to {0}", KerbinRelayRange);
} }
else else
{ {
this.LogError("Could not set Kerbin's range with invalid Tracking Station level {0}", tsLevel); this.LogError("Could not set Kerbin's range with invalid Tracking Station level {0}", tsLevel);
} }
} }
   
private void toggleConfigWindow() private void toggleConfigWindow()
{ {
this.showConfigWindow = !this.showConfigWindow; this.showConfigWindow = !this.showConfigWindow;
this.updateDelayStr = ARConfiguration.UpdateDelay.ToString(); this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
} }
   
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
// //
// AntennaRelay.cs // AntennaRelay.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, 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 ToadicusTools.DebugTools; using ToadicusTools.DebugTools;
using ToadicusTools.Extensions; using ToadicusTools.Extensions;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// Relay code at the heart of AntennaRange /// Relay code at the heart of AntennaRange
/// </summary> /// </summary>
public class AntennaRelay public class AntennaRelay
{ {
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
private static CelestialBody _Kerbin; private static CelestialBody _Kerbin;
   
/// <summary> /// <summary>
/// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
/// </summary> /// </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;
} }
} }
   
#if BENCH #if BENCH
private static ushort relayCount = 0; private static ushort relayCount = 0;
private static ulong searchCount = 0u; private static ulong searchCount = 0u;
private static ulong searchTimer = 0u; private static ulong searchTimer = 0u;
private readonly static RollingAverage averager = new RollingAverage(16); private readonly static RollingAverage averager = new RollingAverage(16);
private static long doubleAverageTime = long.MaxValue; private static long doubleAverageTime = long.MaxValue;
   
   
private System.Diagnostics.Stopwatch performanceTimer = new System.Diagnostics.Stopwatch(); private System.Diagnostics.Stopwatch performanceTimer = new System.Diagnostics.Stopwatch();
#endif #endif
   
private bool canTransmit; private bool canTransmit;
   
private IAntennaRelay nearestRelay; private IAntennaRelay nearestRelay;
private IAntennaRelay bestOccludedRelay; private IAntennaRelay bestOccludedRelay;
   
/// <summary> /// <summary>
/// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
/// <see cref="AntennaRange.IAntennaRelay"/> /// <see cref="AntennaRange.IAntennaRelay"/>
/// </summary> /// </summary>
protected IAntennaRelay moduleRef; protected IAntennaRelay moduleRef;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public virtual Vessel vessel public virtual Vessel vessel
{ {
get get
{ {
return this.moduleRef.vessel; return this.moduleRef.vessel;
} }
} }
   
/// <summary> /// <summary>
/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
public IAntennaRelay targetRelay public IAntennaRelay targetRelay
{ {
get; get;
protected set; protected set;
} }
   
/// <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>
public virtual bool KerbinDirect public virtual bool KerbinDirect
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets or sets the nominal link distance, in meters. /// Gets or sets the nominal link distance, in meters.
/// </summary> /// </summary>
public virtual double NominalLinkSqrDistance public virtual double NominalLinkSqrDistance
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets or sets the maximum link distance, in meters. /// Gets or sets the maximum link distance, in meters.
/// </summary> /// </summary>
public virtual double MaximumLinkSqrDistance public virtual double MaximumLinkSqrDistance
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight. /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
/// </summary> /// </summary>
public virtual CelestialBody firstOccludingBody public virtual CelestialBody firstOccludingBody
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the transmit distance. /// Gets the transmit distance.
/// </summary> /// </summary>
/// <value>The transmit distance.</value> /// <value>The transmit distance.</value>
public double CurrentLinkSqrDistance public double CurrentLinkSqrDistance
{ {
get get
{ {
if (this.KerbinDirect || this.targetRelay == null) if (this.KerbinDirect || this.targetRelay == null)
{ {
return this.SqrDistanceTo(Kerbin); return this.SqrDistanceTo(Kerbin);
} }
else else
{ {
return this.SqrDistanceTo(this.targetRelay); return this.SqrDistanceTo(this.targetRelay);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the current link resource rate in EC/MiT. /// Gets the current link resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The current link resource rate in EC/MiT.</value> /// <value>The current link resource rate in EC/MiT.</value>
public virtual RelayDataCost CurrentLinkCost public virtual RelayDataCost CurrentLinkCost
{ {
get get
{ {
return new RelayDataCost(this.moduleRef.PacketResourceCost, this.moduleRef.PacketSize); return this.moduleRef?.CurrentLinkCost ?? RelayDataCost.Infinity;
  }
  set
  {
  throw new NotImplementedException(
  string.Format(
  "{0} must not assign CurrentLinkCost. This is probably a bug.",
  this.GetType().FullName
  )
  );
} }
} }
   
/// <summary> /// <summary>
/// Gets the current network link cost back to Kerbin, in EC/MiT. /// Gets the current network link cost back to Kerbin, in EC/MiT.
/// </summary> /// </summary>
/// <value>The current network link cost back to Kerbin, in EC/MiT.</value> /// <value>The current network link cost back to Kerbin, in EC/MiT.</value>
public virtual RelayDataCost CurrentNetworkLinkCost public virtual RelayDataCost CurrentNetworkLinkCost
{ {
get get
{ {
RelayDataCost cost = new RelayDataCost(); RelayDataCost cost = new RelayDataCost();
   
IAntennaRelay relay = this.moduleRef; IAntennaRelay relay = this.moduleRef;
   
ushort iters = 0; ushort iters = 0;
while (relay != null) while (relay != null)
{ {
cost += new RelayDataCost(relay.PacketResourceCost, relay.PacketSize); cost += relay.CurrentLinkCost;
   
if (relay.KerbinDirect) if (relay.KerbinDirect)
{ {
break; break;
} }
   
iters++; iters++;
   
if (iters > 255) if (iters > 255)
{ {
this.LogError("Bailing out of AntennaRelay.CurrentNetworkLinkCost because it looks like " + this.LogError("Bailing out of AntennaRelay.CurrentNetworkLinkCost because it looks like " +
"we're stuck in an infinite loop. This is probably a bug."); "we're stuck in an infinite loop. This is probably a bug.");
break; break;
} }
   
relay = relay.targetRelay; relay = relay.targetRelay;
} }
   
return cost; return cost;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the link status. /// Gets or sets the link status.
/// </summary> /// </summary>
public virtual ConnectionStatus LinkStatus public virtual ConnectionStatus LinkStatus
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </summary>
public virtual double nominalTransmitDistance public virtual double nominalTransmitDistance
{ {
get; get;
set; set;
} }
   
/// <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> /// <value>The max transmit distance.</value>
public virtual double maxTransmitDistance public virtual double maxTransmitDistance
{ {
get; get;
set; set;
} }
/*  
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except  
* in that they both return floats. Here's some quick justification:  
*  
* The stock implementation of GetTransmitterScore (which I cannot override) is:  
* Score = (1 + DataResourceCost) / DataRate  
*  
* The stock DataRate and DataResourceCost are:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost / packetSize  
*  
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it  
* could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,  
* that metric is not a very reasonable one.  
*  
* Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case  
* would look like:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost  
*  
* The former case, which I've chosen to implement below, is:  
* DataRate = packetSize  
* DataResourceCost = packetResourceCost  
*  
* So... hopefully that doesn't screw with anything else.  
* */  
/// <summary>  
/// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in  
/// terms of joules/byte  
/// </summary>  
public virtual float DataRate  
{  
get  
{  
if (this.CanTransmit())  
{  
return this.moduleRef.PacketSize;  
}  
else  
{  
return float.Epsilon;  
}  
}  
}  
   
/// <summary>  
/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas  
/// to be scored in terms of joules/byte  
/// </summary>  
public virtual double DataResourceCost  
{  
get  
{  
if (this.CanTransmit())  
{  
return this.moduleRef.PacketResourceCost;  
}  
else  
{  
return float.PositiveInfinity;  
}  
}  
}  
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit() public virtual bool CanTransmit()
{ {
return this.canTransmit; return this.canTransmit;
} }
   
/// <summary> /// <summary>
/// Recalculates the transmission rates. /// Recalculates the transmission rates.
/// </summary> /// </summary>
public void RecalculateTransmissionRates() public void RecalculateTransmissionRates()
{ {
if (!this.canTransmit) { if (!this.canTransmit) {
this.moduleRef.PacketSize = 0f; this.moduleRef.CurrentLinkCost = RelayDataCost.Infinity;
this.moduleRef.PacketResourceCost = float.PositiveInfinity;  
return; return;
} }
   
RelayDataCost cost = this.GetPotentialLinkCost(this.CurrentLinkSqrDistance, this.NominalLinkSqrDistance); RelayDataCost cost = this.GetPotentialLinkCost(this.CurrentLinkSqrDistance, this.NominalLinkSqrDistance);
   
this.moduleRef.PacketSize = cost.PacketSize; this.moduleRef.CurrentLinkCost = cost;
this.moduleRef.PacketResourceCost = cost.PacketResourceCost;  
} }
   
/// <summary> /// <summary>
/// Gets the potential link cost, in EC/MiT. /// Gets the potential link cost, in EC/MiT.
/// </summary> /// </summary>
/// <returns>The potential link cost, in EC/MiT.</returns> /// <returns>The potential link cost, in EC/MiT.</returns>
/// <param name="currentSqrDistance">Square of the current distance to the target</param> /// <param name="currentSqrDistance">Square of the current distance to the target</param>
/// <param name="nominalSqrDistance">Square of the nominal range to the target.</param> /// <param name="nominalSqrDistance">Square of the nominal range to the target.</param>
public RelayDataCost GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance) public RelayDataCost GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance)
{ {
RelayDataCost linkCost = new RelayDataCost(); RelayDataCost linkCost = new RelayDataCost();
   
float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance); float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance);
   
linkCost.PacketSize = this.moduleRef.PacketSize; RelayDataCost baseCost = this.moduleRef?.BaseLinkCost ?? RelayDataCost.Infinity;
   
if (ARConfiguration.FixedPowerCost) if (ARConfiguration.FixedPowerCost)
{ {
linkCost.PacketResourceCost = this.moduleRef.BasePacketResourceCost; linkCost.PacketResourceCost = baseCost.PacketResourceCost;
   
linkCost.PacketSize = Mathf.Min( linkCost.PacketSize = Mathf.Min(
this.moduleRef.BasePacketSize * rangeFactor, baseCost.PacketSize * rangeFactor,
this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor baseCost.PacketSize * this.moduleRef.MaxDataFactor
); );
} }
else else
{ {
if (currentSqrDistance > nominalSqrDistance) if (currentSqrDistance > nominalSqrDistance)
{ {
linkCost.PacketSize = this.moduleRef.BasePacketSize; linkCost.PacketSize = baseCost.PacketSize;
linkCost.PacketResourceCost = this.moduleRef.BasePacketResourceCost / rangeFactor; linkCost.PacketResourceCost = baseCost.PacketResourceCost / rangeFactor;
} }
else else
{ {
linkCost.PacketSize = Mathf.Min( linkCost.PacketSize = Mathf.Min(
this.moduleRef.BasePacketSize * rangeFactor, baseCost.PacketSize * rangeFactor,
this.moduleRef.BasePacketSize * this.moduleRef.MaxDataFactor baseCost.PacketSize * this.moduleRef.MaxDataFactor
); );
linkCost.PacketResourceCost = this.moduleRef.BasePacketResourceCost; linkCost.PacketResourceCost = baseCost.PacketResourceCost;
} }
} }
   
linkCost.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f; linkCost.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f;
linkCost.PacketSize *= this.moduleRef.PacketThrottle / 100f; linkCost.PacketSize *= this.moduleRef.PacketThrottle / 100f;
   
return linkCost; return linkCost;
} }
   
/// <summary> /// <summary>
/// Gets the potential link cost, in EC/MiT. /// Gets the potential link cost, in EC/MiT.
/// </summary> /// </summary>
/// <returns>The potential link cost, in EC/MiT.</returns> /// <returns>The potential link cost, in EC/MiT.</returns>
/// <param name="potentialTarget">Potential target relay.</param> /// <param name="potentialTarget">Potential target relay.</param>
public RelayDataCost GetPotentialLinkCost(IAntennaRelay potentialTarget) public RelayDataCost GetPotentialLinkCost(IAntennaRelay potentialTarget)
{ {
if (potentialTarget == null) if (potentialTarget == null)
{ {
return RelayDataCost.Infinity; return RelayDataCost.Infinity;
} }
   
double currentSqrDistance = this.SqrDistanceTo(potentialTarget); double currentSqrDistance = this.SqrDistanceTo(potentialTarget);
   
if (currentSqrDistance > this.MaxLinkSqrDistanceTo(potentialTarget)) if (currentSqrDistance > this.MaxLinkSqrDistanceTo(potentialTarget))
{ {
return RelayDataCost.Infinity; return RelayDataCost.Infinity;
} }
   
double nominalSqrDistance; double nominalSqrDistance;
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
nominalSqrDistance = this.nominalTransmitDistance * potentialTarget.nominalTransmitDistance; nominalSqrDistance = this.nominalTransmitDistance * potentialTarget.nominalTransmitDistance;
} }
else else
{ {
nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance; nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
} }
   
return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance); return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
} }
   
/// <summary> /// <summary>
/// Gets the potential link cost, in EC/MiT. /// Gets the potential link cost, in EC/MiT.
/// </summary> /// </summary>
/// <returns>The potential link cost, in EC/MiT.</returns> /// <returns>The potential link cost, in EC/MiT.</returns>
/// <param name="body">Potential target Body</param> /// <param name="body">Potential target Body</param>
public RelayDataCost GetPotentialLinkCost(CelestialBody body) public RelayDataCost GetPotentialLinkCost(CelestialBody body)
{ {
if (body == null || body != Kerbin) if (body == null || body != Kerbin)
{ {
return RelayDataCost.Infinity; return RelayDataCost.Infinity;
} }
   
double currentSqrDistance = this.SqrDistanceTo(body); double currentSqrDistance = this.SqrDistanceTo(body);
   
if (currentSqrDistance > this.MaxLinkSqrDistanceTo(body)) if (currentSqrDistance > this.MaxLinkSqrDistanceTo(body))
{ {
return RelayDataCost.Infinity; return RelayDataCost.Infinity;
} }
   
double nominalSqrDistance; double nominalSqrDistance;
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
nominalSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange; nominalSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
} }
else else
{ {
nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance; nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
} }
   
return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance); return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
} }
   
/// <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>
public void FindNearestRelay() public void FindNearestRelay()
{ {
if (!FlightGlobals.ready) if (!FlightGlobals.ready)
{ {
return; return;
} }
   
PooledDebugLogger log; PooledDebugLogger log;
#if DEBUG #if DEBUG
log = PooledDebugLogger.New(this); log = PooledDebugLogger.New(this);
#endif #endif
   
#if BENCH #if BENCH
this.performanceTimer.Restart(); this.performanceTimer.Restart();
   
long startVesselLoopTicks; long startVesselLoopTicks;
long totalVesselLoopTicks; long totalVesselLoopTicks;
   
string slowestLOSVesselName = string.Empty; string slowestLOSVesselName = string.Empty;
long slowestLOSVesselTicks = long.MinValue; long slowestLOSVesselTicks = long.MinValue;
long startLOSVesselTicks; long startLOSVesselTicks;
long totalLOSVesselTicks; long totalLOSVesselTicks;
   
string slowestCircularVesselName = string.Empty; string slowestCircularVesselName = string.Empty;
long slowestCircularVesselTicks = long.MinValue; long slowestCircularVesselTicks = long.MinValue;
long startCircularVesselTicks; long startCircularVesselTicks;
long totalCircularVesselTicks; long totalCircularVesselTicks;
   
long startKerbinLOSTicks; long startKerbinLOSTicks;
long totalKerbinLOSTicks; long totalKerbinLOSTicks;
long statusResolutionTicks; long statusResolutionTicks;
   
ushort usefulVesselCount = 0; ushort usefulVesselCount = 0;
#endif #endif
   
log.AppendFormat("{0}: Target search started).", this.ToString()); log.AppendFormat("{0}: Target search started).", this.ToString());
   
#if DEBUG #if DEBUG
try { try {
#endif #endif
   
// Declare a bunch of variables we'll be using. // Declare a bunch of variables we'll be using.
CelestialBody bodyOccludingBestOccludedRelay = null; CelestialBody bodyOccludingBestOccludedRelay = null;
IAntennaRelay needle; IAntennaRelay needle;
   
RelayDataCost cheapestRelayRate = RelayDataCost.Infinity; RelayDataCost cheapestRelayRate = RelayDataCost.Infinity;
RelayDataCost cheapestOccludedRelayRate = RelayDataCost.Infinity; RelayDataCost cheapestOccludedRelayRate = RelayDataCost.Infinity;
   
RelayDataCost potentialRelayRate; RelayDataCost potentialRelayRate;
   
RelayDataCost kerbinRelayRate = this.GetPotentialLinkCost(Kerbin); RelayDataCost kerbinRelayRate = this.GetPotentialLinkCost(Kerbin);
   
bool isCircular; bool isCircular;
int iterCount; int iterCount;
   
// 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. // Default to KerbinDirect = true in case something in here doesn't work right.
this.KerbinDirect = true; this.KerbinDirect = true;
   
/* /*
* Loop through the useful relays as determined by ARFlightController and check each for line of sight and * Loop through the useful relays as determined by ARFlightController and check each for line of sight and
* distance, searching for the relay with the best distance/maxRange ratio that is in sight, in range, and * distance, searching for the relay with the best distance/maxRange ratio that is in sight, in range, and
* can transmit, also stashing the "best" relay outside of line of sight for failure report. * can transmit, also stashing the "best" relay outside of line of sight for failure report.
* */ * */
IAntennaRelay potentialBestRelay; IAntennaRelay potentialBestRelay;
CelestialBody fob; CelestialBody fob;
   
#if BENCH #if BENCH
startVesselLoopTicks = performanceTimer.ElapsedTicks; startVesselLoopTicks = performanceTimer.ElapsedTicks;
#endif #endif
for (int rIdx = 0; rIdx < ARFlightController.UsefulRelays.Count; rIdx++) for (int rIdx = 0; rIdx < ARFlightController.UsefulRelays.Count; rIdx++)
{ {
potentialBestRelay = ARFlightController.UsefulRelays[rIdx]; potentialBestRelay = ARFlightController.UsefulRelays[rIdx];
log.AppendFormat("\n\tgot useful relay {0}", log.AppendFormat("\n\tgot useful relay {0}",
potentialBestRelay == null ? "null" : potentialBestRelay.ToString()); potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
   
if (potentialBestRelay == null) if (potentialBestRelay == null)
{ {
log.Append("\n\t...skipping null relay"); log.Append("\n\t...skipping null relay");
continue; continue;
} }
   
if (potentialBestRelay == this || potentialBestRelay.vessel == this.vessel) if (potentialBestRelay == this || potentialBestRelay.vessel == this.vessel)
{ {
log.AppendFormat( log.AppendFormat(
"\n\t...skipping relay {0} because it or its vessel ({1}) is the same as ours" + "\n\t...skipping relay {0} because it or its vessel ({1}) is the same as ours" +
"\n\t\t(our vessel is {2})", "\n\t\t(our vessel is {2})",
potentialBestRelay, potentialBestRelay,
potentialBestRelay.vessel == null ? "null" : potentialBestRelay.vessel.vesselName, potentialBestRelay.vessel == null ? "null" : potentialBestRelay.vessel.vesselName,
this.vessel == null ? "null" : this.vessel.vesselName this.vessel == null ? "null" : this.vessel.vesselName
); );
continue; continue;
} }
   
#if BENCH #if BENCH
usefulVesselCount++; usefulVesselCount++;
#endif #endif
   
// Find the distance from here to the vessel... // Find the distance from here to the vessel...
log.Append("\n\tgetting cost to potential vessel"); log.Append("\n\tgetting cost to potential vessel");
potentialRelayRate = potentialBestRelay.CurrentNetworkLinkCost + this.GetPotentialLinkCost(potentialBestRelay); potentialRelayRate = potentialBestRelay.CurrentNetworkLinkCost +
  this.GetPotentialLinkCost(potentialBestRelay);
log.AppendFormat("\n\tpotentialRelayRate = {0} ({1} + {2})", potentialRelayRate, potentialBestRelay.CurrentNetworkLinkCost, this.GetPotentialLinkCost(potentialBestRelay));  
  log.AppendFormat(
  "\n\tpotentialRelayRate = {0} ({1} + {2})",
  potentialRelayRate,
  potentialBestRelay.CurrentNetworkLinkCost,
  this.GetPotentialLinkCost(potentialBestRelay)
  );
   
#if BENCH #if BENCH
startLOSVesselTicks = performanceTimer.ElapsedTicks; startLOSVesselTicks = performanceTimer.ElapsedTicks;
#endif #endif
   
log.Append("\n\t\tdoing LOS check"); log.Append("\n\t\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(potentialBestRelay.vessel, out fob, ARConfiguration.RadiusRatio) !this.vessel.hasLineOfSightTo(potentialBestRelay.vessel, out fob, ARConfiguration.RadiusRatio)
) )
{ {
#if BENCH #if BENCH
totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks; totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
   
if (totalLOSVesselTicks > slowestLOSVesselTicks) if (totalLOSVesselTicks > slowestLOSVesselTicks)
{ {
slowestLOSVesselTicks = totalLOSVesselTicks; slowestLOSVesselTicks = totalLOSVesselTicks;
slowestLOSVesselName = vessel.vesselName; slowestLOSVesselName = vessel.vesselName;
} }
#endif #endif
   
log.Append("\n\t\t...failed LOS check"); log.Append("\n\t\t...failed LOS check");
   
log.AppendFormat("\n\t\t\t{0}: Relay {1} not in line of sight.", log.AppendFormat("\n\t\t\t{0}: Relay {1} not in line of sight.",
this.ToString(), potentialBestRelay); this.ToString(), potentialBestRelay);
log.AppendFormat("\n\t\t\tpotentialRelayRate: {0}", potentialRelayRate); log.AppendFormat("\n\t\t\tpotentialRelayRate: {0}", potentialRelayRate);
log.AppendFormat("\n\t\t\tcheapestOccludedRelayRate: {0}", cheapestOccludedRelayRate); log.AppendFormat("\n\t\t\tcheapestOccludedRelayRate: {0}", cheapestOccludedRelayRate);
   
if ( if (
(potentialRelayRate < cheapestRelayRate) && (potentialRelayRate < cheapestRelayRate) &&
this.IsInRangeOf(potentialBestRelay) && this.IsInRangeOf(potentialBestRelay) &&
potentialBestRelay.CanTransmit() potentialBestRelay.CanTransmit()
) )
{ {
log.Append("\n\t\t...vessel is cheapest and close enough and potentialBestRelay can transmit"); log.Append("\n\t\t...vessel is cheapest and in range and potentialBestRelay can transmit");
log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay); log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
   
this.bestOccludedRelay = potentialBestRelay; this.bestOccludedRelay = potentialBestRelay;
bodyOccludingBestOccludedRelay = fob; bodyOccludingBestOccludedRelay = fob;
cheapestOccludedRelayRate = potentialRelayRate; cheapestOccludedRelayRate = potentialRelayRate;
} }
else else
{ {
log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on"); log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
} }
continue; continue;
} }
#if BENCH #if BENCH
else else
{ {
totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks; totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks;
} }
   
if (totalLOSVesselTicks > slowestLOSVesselTicks) if (totalLOSVesselTicks > slowestLOSVesselTicks)
{ {
slowestLOSVesselTicks = totalLOSVesselTicks; slowestLOSVesselTicks = totalLOSVesselTicks;
slowestLOSVesselName = vessel.vesselName; slowestLOSVesselName = vessel.vesselName;
} }
#endif #endif
   
log.Append("\n\t\t...passed LOS check"); log.Append("\n\t\t...passed LOS check");
   
/* /*
* ...so that we can skip the vessel if it is further away than a vessel we've already checked. * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
* */ * */
if (potentialRelayRate > cheapestRelayRate) if (potentialRelayRate > cheapestRelayRate)
{ {
log.AppendFormat( log.AppendFormat(
"\n\t{0}: Relay {1} discarded because it is more expensive than the cheapest relay." + "\n\t{0}: Relay {1} discarded because it is more expensive than the cheapest relay." +
"\n\t\t({2}, {3} > {4})", "\n\t\t({2}, {3} > {4})",
this.ToString(), this.ToString(),
potentialBestRelay, potentialBestRelay,
this.nearestRelay == null ? "NULL" : this.nearestRelay.ToString(), this.nearestRelay == null ? "NULL" : this.nearestRelay.ToString(),
potentialRelayRate, cheapestRelayRate potentialRelayRate, cheapestRelayRate
); );
continue; continue;
} }
   
log.Append("\n\t\t...passed distance check"); log.Append("\n\t\t...passed distance check");
   
if (potentialBestRelay.CanTransmit()) if (potentialBestRelay.CanTransmit())
{ {
#if BENCH #if BENCH
startCircularVesselTicks = performanceTimer.ElapsedTicks; startCircularVesselTicks = performanceTimer.ElapsedTicks;
#endif #endif
   
needle = potentialBestRelay; needle = potentialBestRelay;
isCircular = false; isCircular = false;
   
iterCount = 0; iterCount = 0;
while (needle != null) while (needle != null)
{ {
iterCount++; iterCount++;
   
if (needle.KerbinDirect) if (needle.KerbinDirect)
{ {
break; break;
} }
   
if (needle.targetRelay == null) if (needle.targetRelay == null)
{ {
break; break;
} }
   
if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef) if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
{ {
isCircular = true; isCircular = true;
break; break;
} }
   
// Avoid infinite loops when we're not catching things right. // Avoid infinite loops when we're not catching things right.
if (iterCount > FlightGlobals.Vessels.Count) if (iterCount > FlightGlobals.Vessels.Count)
{ {
this.LogError( this.LogError(
"iterCount exceeded while checking for circular network; assuming it is circular" + "iterCount exceeded while checking for circular network; assuming it is circular" +
"\n\tneedle={0}" + "\n\tneedle={0}" +
"\n\tthis.moduleRef={1}", "\n\tthis.moduleRef={1}",
needle == null ? "null" : string.Format( needle == null ? "null" : string.Format(
"{0}, needle.KerbinDirect={1}, needle.targetRelay={2}", "{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format( needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
"{0}\n\tneedle.targetRelay.vessel={1}", "{0}\n\tneedle.targetRelay.vessel={1}",
needle.targetRelay, needle.targetRelay,
needle.targetRelay.vessel == null ? needle.targetRelay.vessel == null ?
"null" : needle.targetRelay.vessel.vesselName "null" : needle.targetRelay.vessel.vesselName
) )
), ),
this.moduleRef == null ? "null" : this.moduleRef.ToString() this.moduleRef == null ? "null" : this.moduleRef.ToString()
); );
isCircular = true; isCircular = true;
break; break;
} }
   
needle = needle.targetRelay; needle = needle.targetRelay;
} }
   
if (!isCircular) if (!isCircular)
{ {
cheapestRelayRate = potentialRelayRate; cheapestRelayRate = potentialRelayRate;
this.nearestRelay = potentialBestRelay; this.nearestRelay = potentialBestRelay;
   
log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)", log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)",
this.ToString(), this.ToString(),
this.nearestRelay.ToString(), this.nearestRelay.ToString(),
cheapestRelayRate cheapestRelayRate
); );
} }
else else
{ {
log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping", log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
potentialBestRelay potentialBestRelay
); );
} }
   
#if BENCH #if BENCH
totalCircularVesselTicks = performanceTimer.ElapsedTicks - startCircularVesselTicks; totalCircularVesselTicks = performanceTimer.ElapsedTicks - startCircularVesselTicks;
   
if (totalCircularVesselTicks > slowestCircularVesselTicks) if (totalCircularVesselTicks > slowestCircularVesselTicks)
{ {
slowestCircularVesselName = vessel.vesselName; slowestCircularVesselName = vessel.vesselName;
slowestCircularVesselTicks = totalCircularVesselTicks; slowestCircularVesselTicks = totalCircularVesselTicks;
} }
   
#endif #endif
} }
} }
   
#if BENCH #if BENCH
totalVesselLoopTicks = performanceTimer.ElapsedTicks - startVesselLoopTicks; totalVesselLoopTicks = performanceTimer.ElapsedTicks - startVesselLoopTicks;
#endif #endif
   
CelestialBody bodyOccludingKerbin = null; CelestialBody bodyOccludingKerbin = null;
   
log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name); log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
log.AppendFormat( log.AppendFormat(
"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinRelayRate={5} EC/MiT)", "\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinRelayRate={5} EC/MiT)",
this, this,
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
cheapestRelayRate, cheapestRelayRate,
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
cheapestOccludedRelayRate, cheapestOccludedRelayRate,
kerbinRelayRate kerbinRelayRate
); );
#if BENCH #if BENCH
startKerbinLOSTicks = this.performanceTimer.ElapsedTicks; startKerbinLOSTicks = this.performanceTimer.ElapsedTicks;
#endif #endif
   
// If we don't have LOS to Kerbin, focus on relays // If we don't have LOS to Kerbin, focus on relays
if ( if (
ARConfiguration.RequireLineOfSight && ARConfiguration.RequireLineOfSight &&
!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio) !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
) )
{ {
#if BENCH #if BENCH
totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks; totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
#endif #endif
log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName); log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
// If we're in range of the "nearest" (actually cheapest) relay, use it. // If we're in range of the "nearest" (actually cheapest) relay, use it.
if (this.IsInRangeOf(this.nearestRelay)) if (this.IsInRangeOf(this.nearestRelay))
{ {
log.AppendFormat("\n\t\tCan transmit to nearby relay {0}).", log.AppendFormat("\n\t\tCan transmit to nearby relay {0}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString() this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
); );
   
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\t\tCan't transmit to nearby relay {0}.", log.AppendFormat("\n\t\tCan't transmit to nearby relay {0}.",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString() this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
); );
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay. // If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
// Since cheapestOccludedRelayRate is infinity if there are no occluded relays, this is safe // Since cheapestOccludedRelayRate is infinity if there are no occluded relays, this is safe
if (cheapestOccludedRelayRate < kerbinRelayRate) if (cheapestOccludedRelayRate < kerbinRelayRate)
{ {
log.AppendFormat("\n\t\t\tBest occluded relay is cheaper than Kerbin ({0} < {1})", log.AppendFormat("\n\t\t\tBest occluded relay is cheaper than Kerbin ({0} < {1})",
cheapestOccludedRelayRate, kerbinRelayRate); cheapestOccludedRelayRate, kerbinRelayRate);
this.KerbinDirect = false; this.KerbinDirect = false;
   
// If the nearest relay is cheaper than the best occluded relay, pick it. // If the nearest relay is cheaper than the best occluded relay, pick it.
// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
if (cheapestRelayRate < cheapestOccludedRelayRate) if (cheapestRelayRate < cheapestOccludedRelayRate)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
cheapestRelayRate, cheapestOccludedRelayRate); cheapestRelayRate, cheapestOccludedRelayRate);
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
this.firstOccludingBody = null; this.firstOccludingBody = null;
} }
// Otherwise, target the best occluded relay. // Otherwise, target the best occluded relay.
else else
{ {
log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
cheapestRelayRate, cheapestOccludedRelayRate); cheapestRelayRate, cheapestOccludedRelayRate);
this.targetRelay = bestOccludedRelay; this.targetRelay = bestOccludedRelay;
this.firstOccludingBody = bodyOccludingBestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay;
} }
} }
// Otherwise, check Kerbin against the "nearest" (cheapest) relay. // Otherwise, check Kerbin against the "nearest" (cheapest) relay.
else else
{ {
log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})", log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
cheapestOccludedRelayRate, kerbinRelayRate); cheapestOccludedRelayRate, kerbinRelayRate);
// If the "nearest" (cheapest) relay is cheaper than Kerbin, pick it. // If the "nearest" (cheapest) relay is cheaper than Kerbin, pick it.
// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
if (cheapestRelayRate < kerbinRelayRate) if (cheapestRelayRate < kerbinRelayRate)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
   
// Since we have LOS, blank the first occluding body. // Since we have LOS, blank the first occluding body.
this.firstOccludingBody = null; this.firstOccludingBody = null;
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// Otherwise, pick Kerbin. // Otherwise, pick Kerbin.
else else
{ {
log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
this.KerbinDirect = true; this.KerbinDirect = true;
this.firstOccludingBody = bodyOccludingKerbin; this.firstOccludingBody = bodyOccludingKerbin;
this.targetRelay = null; this.targetRelay = null;
} }
} }
} }
} }
// If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
else else
{ {
#if BENCH #if BENCH
totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks; totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks;
#endif #endif
   
log.AppendFormat("\n\tKerbin is in LOS."); log.AppendFormat("\n\tKerbin is in LOS.");
   
// If the nearest relay is in range, we can transmit. // If the nearest relay is in range, we can transmit.
if (this.IsInRangeOf(this.nearestRelay)) if (this.IsInRangeOf(this.nearestRelay))
{ {
log.AppendFormat("\n\t\tCan transmit to nearby relay {0} (in range).", log.AppendFormat("\n\t\tCan transmit to nearby relay {0} (in range).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString() this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
); );
   
this.canTransmit = true; this.canTransmit = true;
   
// If the nearestRelay is closer than Kerbin, use it. // If the nearestRelay is closer than Kerbin, use it.
if (cheapestRelayRate < kerbinRelayRate) if (cheapestRelayRate < kerbinRelayRate)
{ {
log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).", log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// Otherwise, Kerbin is closer, so use it. // Otherwise, Kerbin is closer, so use it.
else else
{ {
log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).", log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
   
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
} }
// If the nearest relay is out of range, we still need to check on Kerbin. // If the nearest relay is out of range, we still need to check on Kerbin.
else else
{ {
log.AppendFormat("\n\t\tCheapest relay {0} is out of range.", log.AppendFormat("\n\t\tCheapest relay {0} is out of range.",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString() this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
); );
   
// If Kerbin is in range, use it. // If Kerbin is in range, use it.
if (this.IsInRangeOf(Kerbin)) if (this.IsInRangeOf(Kerbin))
{ {
log.AppendFormat("\n\t\t\tCan transmit to Kerbin (in range)."); log.AppendFormat("\n\t\t\tCan transmit to Kerbin (in range).");
   
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\t\t\tCan't transmit to Kerbin (out of range)."); log.AppendFormat("\n\t\t\tCan't transmit to Kerbin (out of range).");
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay. // If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
if (cheapestOccludedRelayRate < kerbinRelayRate) if (cheapestOccludedRelayRate < kerbinRelayRate)
{ {
log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})", log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
cheapestOccludedRelayRate, kerbinRelayRate); cheapestOccludedRelayRate, kerbinRelayRate);
this.KerbinDirect = false; this.KerbinDirect = false;
   
// If the nearest relay is closer than the best occluded relay, pick it. // If the nearest relay is closer than the best occluded relay, pick it.
// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
if (cheapestRelayRate < cheapestOccludedRelayRate) if (cheapestRelayRate < cheapestOccludedRelayRate)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
cheapestRelayRate, cheapestOccludedRelayRate); cheapestRelayRate, cheapestOccludedRelayRate);
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
this.firstOccludingBody = null; this.firstOccludingBody = null;
} }
// Otherwise, target the best occluded relay. // Otherwise, target the best occluded relay.
else else
{ {
log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and cheaper than the cheapest relay ({0} >= {1}), so picking it.",
cheapestRelayRate, cheapestOccludedRelayRate); cheapestRelayRate, cheapestOccludedRelayRate);
this.targetRelay = bestOccludedRelay; this.targetRelay = bestOccludedRelay;
this.firstOccludingBody = bodyOccludingBestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay;
} }
} }
// Otherwise, check Kerbin against the nearest relay. // Otherwise, check Kerbin against the nearest relay.
// Since we have LOS, blank the first occluding body. // Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})", log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
cheapestOccludedRelayRate, kerbinRelayRate); cheapestOccludedRelayRate, kerbinRelayRate);
this.firstOccludingBody = null; this.firstOccludingBody = null;
   
// If the nearest relay is cheaper than Kerbin, pick it. // If the nearest relay is cheaper than Kerbin, pick it.
// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
if (cheapestRelayRate < kerbinRelayRate) if (cheapestRelayRate < kerbinRelayRate)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// Otherwise, pick Kerbin. // Otherwise, pick Kerbin.
else else
{ {
log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
cheapestRelayRate, kerbinRelayRate); cheapestRelayRate, kerbinRelayRate);
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
} }
} }
} }
} }
   
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
this.NominalLinkSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange; this.NominalLinkSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
this.MaximumLinkSqrDistance = this.maxTransmitDistance * ARConfiguration.KerbinRelayRange; this.MaximumLinkSqrDistance = this.maxTransmitDistance * ARConfiguration.KerbinRelayRange;
} }
else else
{ {
this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance; this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance;
this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.targetRelay.maxTransmitDistance; this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.targetRelay.maxTransmitDistance;
} }
} }
else else
{ {
this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance; this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
} }
   
if (this.canTransmit) if (this.canTransmit)
{ {
if (this.CurrentLinkSqrDistance < this.NominalLinkSqrDistance) if (this.CurrentLinkSqrDistance < this.NominalLinkSqrDistance)
{ {
this.LinkStatus = ConnectionStatus.Optimal; this.LinkStatus = ConnectionStatus.Optimal;
} }
else else
{ {
this.LinkStatus = ConnectionStatus.Suboptimal; this.LinkStatus = ConnectionStatus.Suboptimal;
} }
} }
else else
{ {
this.LinkStatus = ConnectionStatus.None; this.LinkStatus = ConnectionStatus.None;
} }
   
#if BENCH #if BENCH
statusResolutionTicks = performanceTimer.ElapsedTicks - startKerbinLOSTicks - totalKerbinLOSTicks; statusResolutionTicks = performanceTimer.ElapsedTicks - startKerbinLOSTicks - totalKerbinLOSTicks;
#endif #endif
   
log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString()); log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString());
#if DEBUG #if DEBUG
} catch (Exception ex) { } catch (Exception ex) {
log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace); log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace);
#if QUIT_ON_EXCEPTION #if QUIT_ON_EXCEPTION
UnityEngine.Application.Quit(); UnityEngine.Application.Quit();
#endif #endif
} finally { } finally {
#endif #endif
log.Print(false); log.Print(false);
#if DEBUG #if DEBUG
} }
#endif #endif
   
#if BENCH #if BENCH
AntennaRelay.searchTimer += (ulong)this.performanceTimer.ElapsedTicks; AntennaRelay.searchTimer += (ulong)this.performanceTimer.ElapsedTicks;
AntennaRelay.searchCount++; AntennaRelay.searchCount++;
this.performanceTimer.Stop(); this.performanceTimer.Stop();
   
double averageSearchTime = (double)AntennaRelay.searchTimer / (double)AntennaRelay.searchCount; double averageSearchTime = (double)AntennaRelay.searchTimer / (double)AntennaRelay.searchCount;
   
if (AntennaRelay.searchCount >= 8000u / (ulong)ARConfiguration.UpdateDelay) if (AntennaRelay.searchCount >= 8000u / (ulong)ARConfiguration.UpdateDelay)
{ {
AntennaRelay.searchCount = 0u; AntennaRelay.searchCount = 0u;
AntennaRelay.searchTimer = 0u; AntennaRelay.searchTimer = 0u;
   
AntennaRelay.averager.AddItem(averageSearchTime); AntennaRelay.averager.AddItem(averageSearchTime);
AntennaRelay.doubleAverageTime = (long)(AntennaRelay.averager.Average * 2d); AntennaRelay.doubleAverageTime = (long)(AntennaRelay.averager.Average * 2d);
} }
   
if (this.performanceTimer.ElapsedTicks > AntennaRelay.doubleAverageTime) if (this.performanceTimer.ElapsedTicks > AntennaRelay.doubleAverageTime)
{ {
System.Text.StringBuilder sb = Tools.GetStringBuilder(); System.Text.StringBuilder sb = Tools.GetStringBuilder();
   
sb.AppendFormat(Tools.SIFormatter, "[AntennaRelay] FindNearestRelay search for {0}" + sb.AppendFormat(Tools.SIFormatter, "[AntennaRelay] FindNearestRelay search for {0}" +
" took significantly longer than average ({1:S3}s vs {2:S3}s)", " took significantly longer than average ({1:S3}s vs {2:S3}s)",
this.ToString(), this.ToString(),
(double)this.performanceTimer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency, (double)this.performanceTimer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
(double)AntennaRelay.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency (double)AntennaRelay.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\tVessel loop time: {0:S3}s", sb.AppendFormat(Tools.SIFormatter, "\n\tVessel loop time: {0:S3}s",
(double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\t\tAverage vessel time for each of {1} vessels: {0:S3}s", sb.AppendFormat(Tools.SIFormatter, "\n\t\tAverage vessel time for each of {1} vessels: {0:S3}s",
(double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency / (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency /
(double)usefulVesselCount, (double)usefulVesselCount,
usefulVesselCount usefulVesselCount
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest vessel LOS check: {0:S3}s to {1}", sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest vessel LOS check: {0:S3}s to {1}",
(double)slowestLOSVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency, (double)slowestLOSVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
slowestLOSVesselName slowestLOSVesselName
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest circular relay check: {0:S3}s for {1}", sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest circular relay check: {0:S3}s for {1}",
(double)slowestCircularVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency, (double)slowestCircularVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency,
slowestCircularVesselName slowestCircularVesselName
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\tKerbin LOS check: {0:S3}s", sb.AppendFormat(Tools.SIFormatter, "\n\tKerbin LOS check: {0:S3}s",
(double)totalKerbinLOSTicks / (double)System.Diagnostics.Stopwatch.Frequency (double)totalKerbinLOSTicks / (double)System.Diagnostics.Stopwatch.Frequency
); );
   
sb.AppendFormat(Tools.SIFormatter, "\n\tStatus resolution check: {0:S3}s", sb.AppendFormat(Tools.SIFormatter, "\n\tStatus resolution check: {0:S3}s",
(double)statusResolutionTicks / (double)System.Diagnostics.Stopwatch.Frequency (double)statusResolutionTicks / (double)System.Diagnostics.Stopwatch.Frequency
); );
   
// sb.AppendFormat(Tools.SIFormatter, "", start) // sb.AppendFormat(Tools.SIFormatter, "", start)
   
this.LogWarning(sb.ToString()); this.LogWarning(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
#endif #endif
} }
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns> /// <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.AntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="module">The module reference underlying this AntennaRelay, /// <param name="module">The module reference underlying this AntennaRelay,
/// as an <see cref="AntennaRange.IAntennaRelay"/></param> /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
public AntennaRelay(IAntennaRelay module) public AntennaRelay(IAntennaRelay module)
{ {
this.KerbinDirect = true; this.KerbinDirect = true;
this.moduleRef = module; this.moduleRef = module;
   
#if BENCH #if BENCH
AntennaRelay.relayCount++; AntennaRelay.relayCount++;
#endif #endif
   
this.LogDebug("{0}: constructed {1}", this.GetType().Name, this.ToString()); this.LogDebug("{0}: constructed {1}", this.GetType().Name, this.ToString());
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// IAntennaRelay.cs // IAntennaRelay.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, 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> /// <summary>
/// Interface defining the basic functionality of AntennaRelay modules for AntennaRange. /// Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
/// </summary> /// </summary>
public interface IAntennaRelay public interface IAntennaRelay
{ {
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
Vessel vessel { get; } Vessel vessel { get; }
   
/// <summary> /// <summary>
/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
IAntennaRelay targetRelay { get; } IAntennaRelay targetRelay { get; }
   
/// <summary> /// <summary>
/// Gets or sets the data capacity of a packet, in MiT/packet /// Gets the current link resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The data capacity of a packet, in MiT/packet</value> /// <value>The current link resource rate in EC/MiT.</value>
float PacketSize { get; set; } RelayDataCost CurrentLinkCost { get; set; }
   
/// <summary> /// <summary>
/// Gets the base data capacity of a packet, in MiT/packet /// Gets the base link resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The base data capacity of a packet, in MiT/packet</value> /// <value>The base link resource rate in EC/MiT.</value>
float BasePacketSize { get; } RelayDataCost BaseLinkCost { get; }
   
/// <summary>  
/// Gets or sets the resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The resource cost of a packet, in EC/packet</value>  
float PacketResourceCost { get; set; }  
   
/// <summary>  
/// Gets the base resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The base resource cost of a packet, in EC/packet</value>  
float BasePacketResourceCost { get; }  
   
/// <summary> /// <summary>
/// Gets the packet throttle. /// Gets the packet throttle.
/// </summary> /// </summary>
/// <value>The packet throttle in range [0..100].</value> /// <value>The packet throttle in range [0..100].</value>
float PacketThrottle { get; } float PacketThrottle { get; }
   
/// <summary> /// <summary>
/// Gets the max data factor. /// Gets the max data factor.
/// </summary> /// </summary>
/// <value>The max data factor.</value> /// <value>The max data factor.</value>
float MaxDataFactor { get; } float MaxDataFactor { get; }
   
/// <summary> /// <summary>
/// Gets the data resource cost in EC/MiT. /// Gets the data resource cost in EC/MiT.
/// </summary> /// </summary>
/// <value>The data resource cost in EC/MiT.</value> /// <value>The data resource cost in EC/MiT.</value>
double DataResourceCost { get; } double DataResourceCost { get; }
   
/// <summary> /// <summary>
/// Gets the current network resource rate in EC/MiT. /// Gets the current network resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The current network resource rate in EC/MiT.</value> /// <value>The current network resource rate in EC/MiT.</value>
RelayDataCost CurrentNetworkLinkCost { get; } RelayDataCost CurrentNetworkLinkCost { 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>
/// The link distance, in meters, at which this relay behaves nominally. /// The link distance, in meters, at which this relay behaves nominally.
/// </summary> /// </summary>
double NominalLinkSqrDistance { get; } double NominalLinkSqrDistance { get; }
   
/// <summary> /// <summary>
/// The link distance, in meters, beyond which this relay cannot operate. /// The link distance, in meters, beyond which this relay cannot operate.
/// </summary> /// </summary>
double MaximumLinkSqrDistance { get; } double MaximumLinkSqrDistance { get; }
   
/// <summary> /// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer. /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary> /// </summary>
double CurrentLinkSqrDistance { get; } double CurrentLinkSqrDistance { get; }
   
/// <summary> /// <summary>
/// Gets the link status. /// Gets the link status.
/// </summary> /// </summary>
ConnectionStatus LinkStatus { get; } ConnectionStatus LinkStatus { get; }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </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>
double maxTransmitDistance { get; } double maxTransmitDistance { get; }
   
/// <summary> /// <summary>
/// The first CelestialBody blocking line of sight to a /// The first CelestialBody blocking line of sight to a
/// </summary> /// </summary>
CelestialBody firstOccludingBody { get; } CelestialBody firstOccludingBody { get; }
   
/// <summary> /// <summary>
/// Gets the Part title. /// Gets the Part title.
/// </summary> /// </summary>
string Title { get; } string Title { get; }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// <c>true</c> if this instance can transmit; otherwise, <c>false</c>. /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </summary>
bool CanTransmit(); bool CanTransmit();
   
/// <summary> /// <summary>
/// Recalculates the transmission rates. /// Recalculates the transmission rates.
/// </summary> /// </summary>
void RecalculateTransmissionRates(); void RecalculateTransmissionRates();
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
void FindNearestRelay(); void FindNearestRelay();
   
/// <summary> /// <summary>
/// Recalculates the max range; useful for making sure we're using additive ranges when enabled. /// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
/// </summary> /// </summary>
void RecalculateMaxRange(); void RecalculateMaxRange();
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
/// </summary> /// </summary>
string ToString(); string ToString();
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, 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.Text; using System.Text;
using ToadicusTools.DebugTools; using ToadicusTools.DebugTools;
using ToadicusTools.Extensions; using ToadicusTools.Extensions;
using ToadicusTools.Text; using ToadicusTools.Text;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
/// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
/// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para> /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
/// ///
/// <para>In general, the scaling functions assume the following relation:</para> /// <para>In general, the scaling functions assume the following relation:</para>
/// ///
/// <para> D² α P/R,</para> /// <para> D² α P/R,</para>
/// ///
/// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para> /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
/// </summary> /// </summary>
public class ModuleLimitedDataTransmitter public class ModuleLimitedDataTransmitter
: ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
{ {
private const string tooltipSkinName = "PartTooltipSkin"; private const string tooltipSkinName = "PartTooltipSkin";
private static GUISkin partTooltipSkin; private static GUISkin partTooltipSkin;
private static GUIStyle partTooltipBodyStyle; private static GUIStyle partTooltipBodyStyle;
private static GUIStyle partTooltipHeaderStyle; private static GUIStyle partTooltipHeaderStyle;
   
// Every antenna is a relay. // Every antenna is a relay.
private AntennaRelay relay; private AntennaRelay relay;
   
// 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.
private ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// Used in module info panes for part tooltips in the editor and R&D // Used in module info panes for part tooltips in the editor and R&D
private GUIContent moduleInfoContent; private GUIContent moduleInfoContent;
   
/// <summary> /// <summary>
/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
/// prescribed by packetResourceCost and packetSize. /// prescribed by packetResourceCost and packetSize.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public double nominalRange; public double nominalRange;
   
/// <summary> /// <summary>
/// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
/// prescribed by packetResourceCost and packetSize. /// prescribed by packetResourceCost and packetSize.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public double simpleRange; public double simpleRange;
   
/// <summary> /// <summary>
/// Relay status string for use in action menus. /// Relay status string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Status")] [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
public string UIrelayStatus; public string UIrelayStatus;
   
/// <summary> /// <summary>
/// Relay target string for use in action menus. /// Relay target string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
public string UIrelayTarget; public string UIrelayTarget;
   
/// <summary> /// <summary>
/// Transmit distance string for use in action menus. /// Transmit distance string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
/// <summary> /// <summary>
/// The nominal range string for use in action menus. /// The nominal range string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")] [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
public string UInominalLinkDistance; public string UInominalLinkDistance;
   
/// <summary> /// <summary>
/// Maximum distance string for use in action menus. /// Maximum distance string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
/// <summary> /// <summary>
/// Packet size string for use in action menus. /// Packet size string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
/// <summary> /// <summary>
/// Packet cost string for use in action menus. /// Packet cost string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
/// <summary> /// <summary>
/// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
/// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
/// <summary> /// <summary>
/// The multipler on packetSize that defines the maximum data bandwidth of the antenna. /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
/// <summary> /// <summary>
/// The packet throttle. /// The packet throttle.
/// </summary> /// </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;
   
private bool actionUIUpdate; private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </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 && this.part.vessel != null) else if (this.part != null && this.part.vessel != null)
{ {
return this.part.vessel; return this.part.vessel;
} }
else if ( else if (
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null && this.part.protoPartSnapshot.pVesselRef != null &&
this.part.protoPartSnapshot.pVesselRef.vesselRef != null this.part.protoPartSnapshot.pVesselRef.vesselRef != null
) )
{ {
return this.part.protoPartSnapshot.pVesselRef.vesselRef; return this.part.protoPartSnapshot.pVesselRef.vesselRef;
} }
else else
{ {
this.LogError("Vessel and/or part reference are null, returning null vessel."); this.LogError("Vessel and/or part reference are null, returning null vessel.");
#if DEBUG && VERBOSE #if DEBUG && VERBOSE
this.LogError(new System.Diagnostics.StackTrace().ToString()); this.LogError(new System.Diagnostics.StackTrace().ToString());
#endif #endif
return null; return null;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the data capacity of a packet, in MiT/packet /// Gets the current link resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The data capacity of a packet, in MiT/packet</value> /// <value>The current link resource rate in EC/MiT.</value>
public float PacketSize public RelayDataCost CurrentLinkCost
{ {
get get
{ {
return this.packetSize; return new RelayDataCost(this.packetResourceCost, this.packetSize);
} }
set set
{ {
this.packetSize = value; this.packetResourceCost = value.PacketResourceCost;
} this.packetSize = value.PacketSize;
} }
  }
/// <summary>  
/// Gets the base data capacity of a packet, in MiT/packet /// <summary>
/// </summary> /// Gets the base link resource rate in EC/MiT.
/// <value>The base data capacity of a packet, in MiT/packet</value> /// </summary>
public float BasePacketSize /// <value>The base link resource rate in EC/MiT.</value>
{ public RelayDataCost BaseLinkCost
get;  
private set;  
}  
   
/// <summary>  
/// Gets or sets the resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The resource cost of a packet, in EC/packet</value>  
public float PacketResourceCost  
{  
get  
{  
return this.packetResourceCost;  
}  
set  
{  
this.packetResourceCost = value;  
}  
}  
   
/// <summary>  
/// Gets the base resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The base resource cost of a packet, in EC/packet</value>  
public float BasePacketResourceCost  
{ {
get; get;
private set; private set;
} }
   
/// <summary> /// <summary>
/// Gets the packet throttle. /// Gets the packet throttle.
/// </summary> /// </summary>
/// <value>The packet throttle in range [0..100].</value> /// <value>The packet throttle in range [0..100].</value>
public float PacketThrottle public float PacketThrottle
{ {
get get
{ {
return this.packetThrottle; return this.packetThrottle;
} }
} }
   
/// <summary> /// <summary>
/// Gets the max data factor. /// Gets the max data factor.
/// </summary> /// </summary>
/// <value>The max data factor.</value> /// <value>The max data factor.</value>
public float MaxDataFactor public float MaxDataFactor
{ {
get get
{ {
return this.maxDataFactor; return this.maxDataFactor;
} }
} }
   
/// <summary> /// <summary>
/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
public IAntennaRelay targetRelay public IAntennaRelay targetRelay
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null; return null;
} }
   
return this.relay.targetRelay; return this.relay.targetRelay;
} }
} }
   
/// <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>
public bool KerbinDirect public bool KerbinDirect
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.KerbinDirect; return this.relay.KerbinDirect;
} }
   
return false; return false;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the nominal link distance, in meters. /// Gets or sets the nominal link distance, in meters.
/// </summary> /// </summary>
public double NominalLinkSqrDistance public double NominalLinkSqrDistance
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.NominalLinkSqrDistance; return this.relay.NominalLinkSqrDistance;
} }
   
return 0d; return 0d;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the maximum link distance, in meters. /// Gets or sets the maximum link distance, in meters.
/// </summary> /// </summary>
public double MaximumLinkSqrDistance public double MaximumLinkSqrDistance
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.MaximumLinkSqrDistance; return this.relay.MaximumLinkSqrDistance;
} }
   
return 0d; return 0d;
} }
} }
   
/// <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>
public double CurrentLinkSqrDistance public double CurrentLinkSqrDistance
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return double.PositiveInfinity; return double.PositiveInfinity;
} }
   
return this.relay.CurrentLinkSqrDistance; return this.relay.CurrentLinkSqrDistance;
} }
} }
   
/// <summary> /// <summary>
/// Gets the link status. /// Gets the link status.
/// </summary> /// </summary>
public ConnectionStatus LinkStatus public ConnectionStatus LinkStatus
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return ConnectionStatus.None; return ConnectionStatus.None;
} }
   
return this.relay.LinkStatus; return this.relay.LinkStatus;
} }
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </summary>
public double nominalTransmitDistance public double nominalTransmitDistance
{ {
get get
{ {
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
return this.nominalRange; return this.nominalRange;
} }
else else
{ {
return this.simpleRange; return this.simpleRange;
} }
} }
} }
   
/// <summary> /// <summary>
/// The maximum distance at which this relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
public double maxTransmitDistance public double maxTransmitDistance
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// The first CelestialBody blocking line of sight to a /// The first CelestialBody blocking line of sight to a
/// </summary> /// </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.
* */ * */
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
/// terms of joules/byte /// terms of joules/byte
/// </summary> /// </summary>
public new float DataRate public new float DataRate
{ {
get get
{ {
if (this.relay == null) if (this.CanTransmit())
{ {
return float.PositiveInfinity; return this.packetSize;
} }
  else
return this.relay.DataRate; {
  return float.Epsilon;
  }
} }
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
/// to be scored in terms of joules/byte /// to be scored in terms of joules/byte
/// </summary> /// </summary>
public new double DataResourceCost public new double DataResourceCost
{ {
get get
{ {
if (this.relay == null) if (this.CanTransmit())
{ {
return double.PositiveInfinity; return this.packetResourceCost;
} }
  else
return this.relay.DataResourceCost; {
  return float.PositiveInfinity;
  }
} }
} }
   
/// <summary> /// <summary>
/// Gets the current network resource rate in EC/MiT. /// Gets the current network resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The current network resource rate in EC/MiT.</value> /// <value>The current network resource rate in EC/MiT.</value>
public RelayDataCost CurrentNetworkLinkCost public RelayDataCost CurrentNetworkLinkCost
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return RelayDataCost.Infinity; return RelayDataCost.Infinity;
} }
   
return this.relay.CurrentNetworkLinkCost; return this.relay.CurrentNetworkLinkCost;
} }
} }
   
/// <summary> /// <summary>
/// Gets the Part title. /// Gets the Part title.
/// </summary> /// </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, ScreenMessageStyle.UPPER_LEFT); this.ErrorMsg = new ScreenMessage("", 4f, ScreenMessageStyle.UPPER_LEFT);
this.packetThrottle = 100f; this.packetThrottle = 100f;
} }
   
/// <summary> /// <summary>
/// PartModule OnAwake override; runs at Unity Awake. /// PartModule OnAwake override; runs at Unity Awake.
/// </summary> /// </summary>
public override void OnAwake() public override void OnAwake()
{ {
base.OnAwake(); base.OnAwake();
   
this.BasePacketSize = base.packetSize; this.BaseLinkCost = new RelayDataCost(base.packetSize, base.packetResourceCost);
this.BasePacketResourceCost = base.packetResourceCost;  
this.moduleInfoContent = new GUIContent(); this.moduleInfoContent = new GUIContent();
   
this.LogDebug("{0} loaded:\n" + this.LogDebug("{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalTransmitDistance: {3}\n" + "nominalTransmitDistance: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this, this,
base.packetSize, base.packetSize,
this.BasePacketResourceCost, this.packetResourceCost,
this.nominalTransmitDistance, this.nominalTransmitDistance,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
); );
} }
   
/// <summary> /// <summary>
/// PartModule OnStart override; runs at Unity Start. /// PartModule OnStart override; runs at Unity Start.
/// </summary> /// </summary>
/// <param name="state">State.</param> /// <param name="state">State.</param>
public override void OnStart (StartState state) public override void OnStart (StartState state)
{ {
base.OnStart (state); base.OnStart (state);
   
this.RecalculateMaxRange(); this.RecalculateMaxRange();
   
if (state >= StartState.PreLaunch) if (state >= StartState.PreLaunch)
{ {
this.relay = new AntennaRelay(this); this.relay = new AntennaRelay(this);
this.relay.nominalTransmitDistance = this.nominalTransmitDistance; this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
   
this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance); this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance);
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
} }
} }
   
/// <summary> /// <summary>
/// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
/// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
/// work. /// work.
/// </summary> /// </summary>
/// <param name="node"><see cref="ConfigNode"/> with data for this module.</param> /// <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);
   
this.RecalculateMaxRange(); this.RecalculateMaxRange();
} }
   
/// <summary> /// <summary>
/// Gets the human-friendly module title. /// Gets the human-friendly module title.
/// </summary> /// </summary>
public string GetModuleTitle() public string GetModuleTitle()
{ {
return "Comms Transceiver"; return "Comms Transceiver";
} }
   
/// <summary> /// <summary>
/// Returns drawTooltipWidget as a callback for part tooltips. /// Returns drawTooltipWidget as a callback for part tooltips.
/// </summary> /// </summary>
public Callback<Rect> GetDrawModulePanelCallback() public Callback<Rect> GetDrawModulePanelCallback()
{ {
return this.drawTooltipWidget; return this.drawTooltipWidget;
} }
   
// Called by Squad's part tooltip system when drawing tooltips. // Called by Squad's part tooltip system when drawing tooltips.
// HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118 // HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
private void drawTooltipWidget(Rect rect) private void drawTooltipWidget(Rect rect)
{ {
  /*
  * Removed all this because Squad doesn't even call it anymore.
  *
this.moduleInfoContent.text = this.GetInfo(); this.moduleInfoContent.text = this.GetInfo();
   
if (partTooltipSkin == null) if (partTooltipSkin == null)
{ {
UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin)); UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin));
GUISkin skin; GUISkin skin;
for (int sIdx = 0; sIdx < skins.Length; sIdx++) for (int sIdx = 0; sIdx < skins.Length; sIdx++)
{ {
skin = (GUISkin)skins[sIdx]; skin = (GUISkin)skins[sIdx];
   
if (skin.name == tooltipSkinName) if (skin.name == tooltipSkinName)
{ {
partTooltipSkin = skin; partTooltipSkin = skin;
partTooltipBodyStyle = partTooltipSkin.customStyles[0]; partTooltipBodyStyle = partTooltipSkin.customStyles[0];
partTooltipHeaderStyle = partTooltipSkin.customStyles[1]; partTooltipHeaderStyle = partTooltipSkin.customStyles[1];
} }
} }
   
if (partTooltipSkin == null) if (partTooltipSkin == null)
{ {
this.LogError("Could not find GUISkin {0}? Please report this!", tooltipSkinName); this.LogError("Could not find GUISkin {0}? Please report this!", tooltipSkinName);
return; return;
} }
else else
{ {
this.Log("Loaded GUISkin {0}", tooltipSkinName); this.Log("Loaded GUISkin {0}", tooltipSkinName);
} }
} }
   
float width = rect.width; float width = rect.width;
float orgHeight = rect.height; float orgHeight = rect.height;
float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width); float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width);
   
rect.height = height; rect.height = height;
   
GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle); GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle);
GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle); GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle);
   
GUILayout.Space(height - orgHeight GUILayout.Space(height - orgHeight
- partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top - partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
- 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top) - 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top)
); );*/
} }
   
/// <summary> /// <summary>
/// Returns an empty string, because we don't really have a "primary field" like some modules do. /// Returns an empty string, because we don't really have a "primary field" like some modules do.
/// </summary> /// </summary>
public string GetPrimaryField() public string GetPrimaryField()
{ {
return string.Empty; return string.Empty;
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description. /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
/// </summary> /// </summary>
public override string GetInfo() public override string GetInfo()
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
string text; string text;
   
sb.Append(base.GetInfo()); sb.Append(base.GetInfo());
   
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n", sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n",
Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange) Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
); );
sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m", sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m",
Math.Sqrt( Math.Sqrt(
this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
ARConfiguration.KerbinRelayRange ARConfiguration.KerbinRelayRange
) )
); );
} }
else else
{ {
sb.AppendFormat("Nominal Range: {0:S3}m\n", this.nominalTransmitDistance); sb.AppendFormat("Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
sb.AppendFormat("Maximum Range: {0:S3}m", this.maxTransmitDistance); sb.AppendFormat("Maximum Range: {0:S3}m", this.maxTransmitDistance);
} }
   
text = sb.ToString(); text = sb.ToString();
   
return text; return text;
} }
} }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// <c>true</c> if this instance can transmit; otherwise, <c>false</c>. /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </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;
} }
   
switch (this.part.State) switch (this.part.State)
{ {
case PartStates.DEAD: case PartStates.DEAD:
case PartStates.DEACTIVATED: case PartStates.DEACTIVATED:
this.LogDebug( this.LogDebug(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.part.partInfo.title, this.part.partInfo.title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), this.part.State) Enum.GetName(typeof(PartStates), this.part.State)
); );
return false; return false;
default: default:
break; break;
} }
   
return this.relay.CanTransmit(); return this.relay.CanTransmit();
} }
   
/// <summary> /// <summary>
/// Recalculates the transmission rates. /// Recalculates the transmission rates.
/// </summary> /// </summary>
public void RecalculateTransmissionRates() public void RecalculateTransmissionRates()
{ {
if (this.relay != null) if (this.relay != null)
{ {
this.relay.RecalculateTransmissionRates(); this.relay.RecalculateTransmissionRates();
} }
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
public void FindNearestRelay() public void FindNearestRelay()
{ {
if (this.relay != null) if (this.relay != null)
{ {
this.relay.FindNearestRelay(); this.relay.FindNearestRelay();
} }
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
/// returns false. /// returns false.
/// </summary> /// </summary>
/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param> /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
public new void TransmitData(List<ScienceData> dataQueue) public new void TransmitData(List<ScienceData> dataQueue)
{ {
this.LogDebug( this.LogDebug(
"TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}", "TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}",
dataQueue.Count dataQueue.Count
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
this.LogDebug( this.LogDebug(
"CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}", "CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
dataQueue.SPrint() dataQueue.SPrint()
); );
   
base.TransmitData(dataQueue); base.TransmitData(dataQueue);
} }
else else
{ {
this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title); this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = PooledDebugLogger.New(this); var logger = PooledDebugLogger.New(this);
   
IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>(); IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
ModuleScienceContainer scienceContainer; ModuleScienceContainer scienceContainer;
for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++) for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
{ {
scienceContainer = vesselContainers[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>();
   
ScienceData data; ScienceData data;
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++) for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
data = dataQueue[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)
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
sb.Append('['); sb.Append('[');
sb.Append(this.part.partInfo.title); sb.Append(this.part.partInfo.title);
sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
sb.Append("Data to be discarded:\n"); sb.Append("Data to be discarded:\n");
   
ScienceData data; ScienceData data;
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++) for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
data = dataQueue[dIdx]; data = dataQueue[dIdx];
sb.AppendFormat("\t{0}\n", data.title); sb.AppendFormat("\t{0}\n", data.title);
} }
   
ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
this.LogDebug(sb.ToString()); this.LogDebug(sb.ToString());
} }
} }
   
this.PostCannotTransmitError(); this.PostCannotTransmitError();
} }
   
this.LogDebug( this.LogDebug(
"distance: " + this.CurrentLinkSqrDistance "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
/// returns false. /// returns false.
/// </summary> /// </summary>
public new void StartTransmission() public new void StartTransmission()
{ {
this.LogDebug( this.LogDebug(
"distance: " + this.CurrentLinkSqrDistance "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
/// <summary> /// <summary>
/// MonoBehaviour Update /// MonoBehaviour Update
/// </summary> /// </summary>
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.UImaxTransmitDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.MaximumLinkSqrDistance)); Math.Sqrt(this.MaximumLinkSqrDistance));
this.UInominalLinkDistance = TextTools.Format("{0:S3}m", this.UInominalLinkDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.NominalLinkSqrDistance)); Math.Sqrt(this.NominalLinkSqrDistance));
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = this.LinkStatus.ToString(); this.UIrelayStatus = this.LinkStatus.ToString();
this.UItransmitDistance = TextTools.Format("{0:S3}m", this.UItransmitDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.CurrentLinkSqrDistance)); Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate); this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost); this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost);
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
this.UItransmitDistance = TextTools.Format("{0:S3}m", this.UItransmitDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.CurrentLinkSqrDistance)); Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UItransmitDistance = "N/A"; this.UItransmitDistance = "N/A";
this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
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
{ {
if (this.targetRelay != null) if (this.targetRelay != null)
{ {
this.UIrelayTarget = this.targetRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
else else
{ {
this.UIrelayTarget = "A mysterious null entity"; this.UIrelayTarget = "A mysterious null entity";
} }
} }
} }
} }
   
/// <summary> /// <summary>
/// Recalculates the max range; useful for making sure we're using additive ranges when enabled. /// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
/// </summary> /// </summary>
public void RecalculateMaxRange() public void RecalculateMaxRange()
{ {
this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
   
#if DEBUG #if DEBUG
this.Log("Recalculated max range: sqrt({0}) * {1} = {2}", this.Log("Recalculated max range: sqrt({0}) * {1} = {2}",
this.maxPowerFactor, this.nominalTransmitDistance, this.maxTransmitDistance); this.maxPowerFactor, this.nominalTransmitDistance, this.maxTransmitDistance);
#endif #endif
} }
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
public override string ToString() public override string ToString()
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
string msg; string msg;
   
if (this.part != null && this.part.partInfo != null) if (this.part != null && this.part.partInfo != null)
{ {
sb.Append(this.part.partInfo.title); sb.Append(this.part.partInfo.title);
} }
else else
{ {
sb.Append(this.GetType().Name); sb.Append(this.GetType().Name);
} }
   
if (vessel != null) if (vessel != null)
{ {
sb.Append(" on "); sb.Append(" on ");
sb.Append(vessel.vesselName); sb.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)
{ {
sb.Append(" on "); sb.Append(" on ");
sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName); sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
return msg; return msg;
} }
} }
   
// When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty. // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
private void onPartActionUICreate(Part eventPart) private void onPartActionUICreate(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = true; this.actionUIUpdate = true;
} }
} }
   
// When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty. // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
private void onPartActionUIDismiss(Part eventPart) private void onPartActionUIDismiss(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = false; this.actionUIUpdate = false;
} }
} }
   
// Post an error in the communication messages describing the reason transmission has failed. Currently there // Post an error in the communication messages describing the reason transmission has failed. Currently there
// is only one reason for this. // is only one reason for this.
private void PostCannotTransmitError() private void PostCannotTransmitError()
{ {
string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!"); string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
   
this.ErrorMsg.message = string.Format( this.ErrorMsg.message = string.Format(
"<color='#{0}{1}{2}{3}'><b>{4}</b></color>", "<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
ErrorText ErrorText
); );
   
this.LogDebug(this.ErrorMsg.message); this.LogDebug(this.ErrorMsg.message);
   
ScreenMessages.PostScreenMessage(this.ErrorMsg); ScreenMessages.PostScreenMessage(this.ErrorMsg);
} }
   
private string buildTransmitMessage() private string buildTransmitMessage()
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
string msg; string msg;
   
sb.Append("["); sb.Append("[");
sb.Append(base.part.partInfo.title); sb.Append(base.part.partInfo.title);
sb.Append("]: "); sb.Append("]: ");
   
sb.Append("Beginning transmission "); sb.Append("Beginning transmission ");
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
sb.Append("directly to Kerbin."); sb.Append("directly to Kerbin.");
} }
else else
{ {
sb.Append("via "); sb.Append("via ");
sb.Append(this.relay.targetRelay); sb.Append(this.relay.targetRelay);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
return msg; return msg;
} }
} }
   
#if DEBUG #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()
{ {
if (this.relay != null) if (this.relay != null)
this.relay.RecalculateTransmissionRates(); this.relay.RecalculateTransmissionRates();
   
DebugPartModule.DumpClassObject(this); DebugPartModule.DumpClassObject(this);
} }
   
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels() public void PrintAllVessels()
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
sb.Append("Dumping FlightGlobals.Vessels:"); sb.Append("Dumping FlightGlobals.Vessels:");
   
Vessel vessel; Vessel vessel;
for (int i = 0; i < FlightGlobals.Vessels.Count; i++) for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
{ {
vessel = FlightGlobals.Vessels[i]; vessel = FlightGlobals.Vessels[i];
sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id); sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
} }
ToadicusTools.Logging.PostDebugMessage(sb.ToString()); ToadicusTools.Logging.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 #endif
} }
} }
// AntennaRange // AntennaRange
// //
// AssemblyInfo.cs // AssemblyInfo.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, 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.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
   
[assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)] [assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
   
// Information about this assembly is defined by the following attributes. // Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project. // Change them to the values specific to your project.
[assembly: AssemblyTitle("AntennaRange")] [assembly: AssemblyTitle("AntennaRange")]
[assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")] [assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
[assembly: AssemblyCopyright("toadicus")] [assembly: AssemblyCopyright("toadicus")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision, // The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision. // and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.10.3.*")] [assembly: AssemblyVersion("1.11.0.*")]
// The following attributes are used to specify the signing key for the assembly, // The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing. // if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)] //[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")] //[assembly: AssemblyKeyFile("")]
   
// AntennaRange // AntennaRange
// //
// ProtoAntennaRelay.cs // ProtoAntennaRelay.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, 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 ToadicusTools; using ToadicusTools;
using ToadicusTools.Text; using ToadicusTools.Text;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <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> /// </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.
private ProtoPartSnapshot protoPart; private ProtoPartSnapshot protoPart;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
public override Vessel vessel public override Vessel vessel
{ {
get get
{ {
if ( if (
this.protoPart != null && this.protoPart != null &&
this.protoPart.pVesselRef != null && this.protoPart.pVesselRef != null &&
this.protoPart.pVesselRef.vesselRef != null this.protoPart.pVesselRef.vesselRef != null
) )
{ {
return this.protoPart.pVesselRef.vesselRef; return this.protoPart.pVesselRef.vesselRef;
} }
else else
{ {
this.LogError("Could not fetch vessel! {0}{1}{2}", this.LogError("Could not fetch vessel! {0}{1}{2}",
this.protoPart == null ? "\n\tprotoPart=null" : string.Empty, this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
this.protoPart != null && this.protoPart.pVesselRef == null ? this.protoPart != null && this.protoPart.pVesselRef == null ?
"\n\tthis.protoPart.pVesselRef=null" : string.Empty, "\n\tthis.protoPart.pVesselRef=null" : string.Empty,
this.protoPart != null && this.protoPart.pVesselRef != null && this.protoPart != null && this.protoPart.pVesselRef != null &&
this.protoPart.pVesselRef.vesselRef == null ? this.protoPart.pVesselRef.vesselRef == null ?
"\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty "\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty
); );
return null; return null;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the data capacity of a packet, in MiT/packet /// Gets the base link resource rate in EC/MiT.
/// </summary> /// </summary>
/// <value>The data capacity of a packet, in MiT/packet</value> /// <value>The base link resource rate in EC/MiT.</value>
public float PacketSize public RelayDataCost BaseLinkCost
{ {
get get;
{ private set;
if (this.moduleRef == null) }
{  
return float.NaN; /// <summary>
} /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
  /// to be scored in terms of joules/byte
return this.moduleRef.PacketSize; /// </summary>
} public double DataResourceCost
set {
{ get
if (this.moduleRef == null) {
{ if (this.CanTransmit())
return; {
} return this.moduleRef.DataResourceCost;
  }
this.moduleRef.PacketSize = value; else
} {
} return float.PositiveInfinity;
  }
/// <summary>  
/// Gets the base data capacity of a packet, in MiT/packet  
/// </summary>  
/// <value>The base data capacity of a packet, in MiT/packet</value>  
public float BasePacketSize  
{  
get  
{  
if (this.moduleRef == null)  
{  
return float.NaN;  
}  
   
return this.moduleRef.BasePacketSize;  
}  
}  
   
/// <summary>  
/// Gets or sets the resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The resource cost of a packet, in EC/packet</value>  
public float PacketResourceCost  
{  
get  
{  
if (this.moduleRef == null)  
{  
return float.NaN;  
}  
   
return this.moduleRef.PacketResourceCost;  
}  
set  
{  
if (this.moduleRef == null)  
{  
return;  
}  
   
this.moduleRef.PacketResourceCost = value;  
}  
}  
   
/// <summary>  
/// Gets the base resource cost of a packet, in EC/packet  
/// </summary>  
/// <value>The base resource cost of a packet, in EC/packet</value>  
public float BasePacketResourceCost  
{  
get  
{  
if (this.moduleRef == null)  
{  
return float.NaN;  
}  
   
return this.moduleRef.BasePacketResourceCost;  
} }
} }
   
/// <summary> /// <summary>
/// Gets the packet throttle. /// Gets the packet throttle.
/// </summary> /// </summary>
/// <value>The packet throttle in range [0..100].</value> /// <value>The packet throttle in range [0..100].</value>
public float PacketThrottle public float PacketThrottle
{ {
get get
{ {
if (this.moduleRef == null) if (this.moduleRef == null)
{ {
return float.NaN; return float.NaN;
} }
   
return this.moduleRef.PacketThrottle; return this.moduleRef.PacketThrottle;
} }
} }
   
/// <summary> /// <summary>
/// Gets the max data factor. /// Gets the max data factor.
/// </summary> /// </summary>
/// <value>The max data factor.</value> /// <value>The max data factor.</value>
public float MaxDataFactor public float MaxDataFactor
{ {
get get
{ {
if (this.moduleRef == null) if (this.moduleRef == null)
{ {
return float.NaN; return float.NaN;
} }
   
return this.moduleRef.MaxDataFactor; return this.moduleRef.MaxDataFactor;
} }
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </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 relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
public override double maxTransmitDistance public override double maxTransmitDistance
{ {
get get
{ {
return moduleRef.maxTransmitDistance; return moduleRef.maxTransmitDistance;
} }
} }
   
/// <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> /// <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>. /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </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)
{ {
Logging.PostDebugMessage(string.Format( Logging.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> /// <summary>
/// Recalculates the max range; useful for making sure we're using additive ranges when enabled. /// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
/// </summary> /// </summary>
public void RecalculateMaxRange() public void RecalculateMaxRange()
{ {
if (this.moduleRef != null) if (this.moduleRef != null)
{ {
this.moduleRef.RecalculateMaxRange(); this.moduleRef.RecalculateMaxRange();
} }
} }
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
public override string ToString() public override string ToString()
{ {
using (PooledStringBuilder sb = PooledStringBuilder.Get()) using (PooledStringBuilder sb = PooledStringBuilder.Get())
{ {
sb.Append(this.Title); sb.Append(this.Title);
   
if (this.protoPart != null && this.protoPart.pVesselRef != null) if (this.protoPart != null && this.protoPart.pVesselRef != null)
{ {
sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName); sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
} }
   
return sb.ToString(); return sb.ToString();
} }
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="prefabRelay">The module reference underlying this AntennaRelay, /// <param name="prefabRelay">The module reference underlying this AntennaRelay,
/// as an <see cref="AntennaRange.IAntennaRelay"/></param> /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
/// <param name="pps">The prototype partreference on which the module resides.</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;
   
this.Log("constructed ({0})", this.GetType().Name); this.Log("constructed ({0})", this.GetType().Name);
   
this.RecalculateMaxRange(); this.RecalculateMaxRange();
} }
} }
} }