Merge branch 'ttrefactor' of github.com:toadicus/AntennaRange into ttrefactor ttrefactor
Merge branch 'ttrefactor' of github.com:toadicus/AntennaRange into ttrefactor

// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
   
using KSP; using KSP;
using System; using System;
using ToadicusTools; using ToadicusTools.Extensions;
  using ToadicusTools.Text;
  using ToadicusTools.GUIUtils;
  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";
   
/// <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;
} }
} }
   
#pragma warning disable 1591 #pragma warning disable 1591
   
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();
   
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());
} }
   
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) if (ToolbarManager.ToolbarAvailable)
{ {
if (this.toolbarButton == null) if (this.toolbarButton == null)
{ {
this.LogDebug("Toolbar available; initializing toolbar button."); this.LogDebug("Toolbar available; initializing toolbar button.");
   
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
this.toolbarButton.Text = "AR"; this.toolbarButton.Text = "AR";
this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon"; this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst; this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
this.toolbarButton.OnClick += delegate(ClickEvent e) this.toolbarButton.OnClick += delegate(ClickEvent e)
{ {
this.toggleConfigWindow(); this.toggleConfigWindow();
}; };
} }
} }
else if (this.appLauncherButton == null && ApplicationLauncher.Ready) else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
{ {
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 = Tools.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 = GUITools.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 =
GUITools.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 = GUITools.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 = GUITools.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);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool prettyLines = GUITools.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();
   
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();
} }
} }
   
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
// //
// ARFlightController.cs // ARFlightController.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.
   
#pragma warning disable 1591 #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools.Extensions;
  using ToadicusTools.Text;
  using ToadicusTools.DebugTools;
  using ToadicusTools.Wrappers;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour public class ARFlightController : MonoBehaviour
{ {
#region Static #region Static
private static List<IAntennaRelay> usefulRelays; private static List<IAntennaRelay> usefulRelays;
public static IList<IAntennaRelay> UsefulRelays; public static IList<IAntennaRelay> UsefulRelays;
#endregion #endregion
   
#region Fields #region Fields
private Dictionary<ConnectionStatus, string> toolbarTextures; private Dictionary<ConnectionStatus, string> toolbarTextures;
private Dictionary<ConnectionStatus, Texture> appLauncherTextures; private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
   
private ARMapRenderer mapRenderer; private ARMapRenderer mapRenderer;
   
private IButton toolbarButton; private IButton toolbarButton;
   
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
private Tools.DebugLogger log; private PooledDebugLogger log;
   
private System.Diagnostics.Stopwatch updateTimer; private System.Diagnostics.Stopwatch updateTimer;
#endregion #endregion
   
#region Properties #region Properties
public ConnectionStatus currentConnectionStatus public ConnectionStatus currentConnectionStatus
{ {
get; get;
private set; private set;
} }
   
private string currentConnectionTexture private string currentConnectionTexture
{ {
get get
{ {
return this.toolbarTextures[this.currentConnectionStatus]; return this.toolbarTextures[this.currentConnectionStatus];
} }
} }
   
private Texture currentAppLauncherTexture private Texture currentAppLauncherTexture
{ {
get get
{ {
return this.appLauncherTextures[this.currentConnectionStatus]; return this.appLauncherTextures[this.currentConnectionStatus];
} }
} }
   
public ControlTypes currentControlLock public ControlTypes currentControlLock
{ {
get get
{ {
if (this.lockID == string.Empty) if (this.lockID == string.Empty)
{ {
return ControlTypes.None; return ControlTypes.None;
} }
   
return InputLockManager.GetControlLock(this.lockID); return InputLockManager.GetControlLock(this.lockID);
} }
} }
   
public string lockID public string lockID
{ {
get; get;
private set; private set;
} }
   
public ControlTypes lockSet public ControlTypes lockSet
{ {
get get
{ {
return ControlTypes.ALL_SHIP_CONTROLS; return ControlTypes.ALL_SHIP_CONTROLS;
} }
} }
   
public Vessel vessel public Vessel vessel
{ {
get get
{ {
if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
return FlightGlobals.ActiveVessel; return FlightGlobals.ActiveVessel;
} }
   
return null; return null;
} }
} }
#endregion #endregion
   
#region MonoBehaviour LifeCycle #region MonoBehaviour LifeCycle
private void Awake() private void Awake()
{ {
this.lockID = "ARConnectionRequired"; this.lockID = "ARConnectionRequired";
   
this.log = Tools.DebugLogger.New(this); this.log = PooledDebugLogger.New(this);
   
this.updateTimer = new System.Diagnostics.Stopwatch(); this.updateTimer = new System.Diagnostics.Stopwatch();
   
this.toolbarTextures = new Dictionary<ConnectionStatus, string>(); this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
   
this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection"; this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal"; this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon"; this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
   
this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>(); this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
   
this.appLauncherTextures[ConnectionStatus.None] = this.appLauncherTextures[ConnectionStatus.None] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
this.appLauncherTextures[ConnectionStatus.Suboptimal] = this.appLauncherTextures[ConnectionStatus.Suboptimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
this.appLauncherTextures[ConnectionStatus.Optimal] = this.appLauncherTextures[ConnectionStatus.Optimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
   
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
   
this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None]; this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
this.toolbarButton.Text = "AntennaRange"; this.toolbarButton.Text = "AntennaRange";
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
this.toolbarButton.OnClick += (e) => (this.buttonToggle()); this.toolbarButton.OnClick += (e) => (this.buttonToggle());
} }
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
GameEvents.onVesselChange.Add(this.onVesselChange); GameEvents.onVesselChange.Add(this.onVesselChange);
   
usefulRelays = new List<IAntennaRelay>(); usefulRelays = new List<IAntennaRelay>();
UsefulRelays = usefulRelays.AsReadOnly(); UsefulRelays = usefulRelays.AsReadOnly();
} }
   
private void FixedUpdate() private void FixedUpdate()
{ {
this.log.Clear(); this.log.Clear();
   
VesselCommand availableCommand; VesselCommand availableCommand;
   
if (ARConfiguration.RequireConnectionForControl) if (ARConfiguration.RequireConnectionForControl)
{ {
availableCommand = this.vessel.CurrentCommand(); availableCommand = this.vessel.CurrentCommand();
} }
else else
{ {
availableCommand = VesselCommand.Crew; availableCommand = VesselCommand.Crew;
} }
   
log.AppendFormat("availableCommand: {0}\n\t" + log.AppendFormat("availableCommand: {0}\n\t" +
"(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" + "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
"(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" + "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
"vessel.HasConnectedRelay(): {3}", "vessel.HasConnectedRelay(): {3}",
(int)availableCommand, (int)availableCommand,
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew, (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe, (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
vessel.HasConnectedRelay() vessel.HasConnectedRelay()
); );
   
// If we are requiring a connection for control, the vessel does not have any adequately staffed pods, // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
// and the vessel does not have any connected relays... // and the vessel does not have any connected relays...
if ( if (
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
ARConfiguration.RequireConnectionForControl && ARConfiguration.RequireConnectionForControl &&
this.vessel != null && this.vessel != null &&
this.vessel.vesselType != VesselType.EVA && this.vessel.vesselType != VesselType.EVA &&
!( !(
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew || (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay() (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
)) ))
{ {
// ...and if the controls are not currently locked... // ...and if the controls are not currently locked...
if (currentControlLock == ControlTypes.None) if (currentControlLock == ControlTypes.None)
{ {
// ...lock the controls. // ...lock the controls.
InputLockManager.SetControlLock(this.lockSet, this.lockID); InputLockManager.SetControlLock(this.lockSet, this.lockID);
} }
} }
// ...otherwise, if the controls are locked... // ...otherwise, if the controls are locked...
else if (currentControlLock != ControlTypes.None) else if (currentControlLock != ControlTypes.None)
{ {
// ...unlock the controls. // ...unlock the controls.
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
   
log.Print(); log.Print();
} }
   
private void Update() private void Update()
{ {
if (MapView.MapIsEnabled && this.mapRenderer == null) if (MapView.MapIsEnabled && this.mapRenderer == null)
{ {
this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>(); this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Enabled = MapView.MapIsEnabled; this.toolbarButton.Enabled = MapView.MapIsEnabled;
} }
   
if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready) if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
{ {
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
this.buttonToggle, this.buttonToggle, this.buttonToggle, this.buttonToggle,
ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW, ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
this.appLauncherTextures[ConnectionStatus.None] this.appLauncherTextures[ConnectionStatus.None]
); );
} }
   
if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay) if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
{ {
this.updateTimer.Restart(); this.updateTimer.Restart();
} }
else else
{ {
return; return;
} }
   
this.log.Clear(); this.log.Clear();
   
this.log.Append("[ARFlightController]: Update"); this.log.Append("[ARFlightController]: Update");
   
if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
Vessel vessel; Vessel vessel;
IAntennaRelay relay; IAntennaRelay relay;
IAntennaRelay bestActiveRelay; IAntennaRelay bestActiveRelay;
IList<IAntennaRelay> activeVesselRelays; IList<IAntennaRelay> activeVesselRelays;
   
usefulRelays.Clear(); usefulRelays.Clear();
   
for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++) for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
{ {
vessel = FlightGlobals.Vessels[vIdx]; vessel = FlightGlobals.Vessels[vIdx];
   
if (vessel == null || vessel == FlightGlobals.ActiveVessel) if (vessel == null || vessel == FlightGlobals.ActiveVessel)
{ {
continue; continue;
} }
   
log.AppendFormat("\nFetching best relay for vessel {0}", vessel); log.AppendFormat("\nFetching best relay for vessel {0}", vessel);
   
relay = vessel.GetBestRelay(); relay = vessel.GetBestRelay();
   
if (relay != null) if (relay != null)
{ {
log.AppendFormat("\n\tAdding useful relay {0}", relay); log.AppendFormat("\n\tAdding useful relay {0}", relay);
   
usefulRelays.Add(relay); usefulRelays.Add(relay);
} }
} }
   
bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);  
activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel]; activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)  
{ if (activeVesselRelays.Count > 0)
relay = activeVesselRelays[rIdx]; {
  bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);
// The best active relay will get checked with the other useful relays later.  
if (relay == bestActiveRelay) for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
  {
  relay = activeVesselRelays[rIdx];
   
  // The best active relay will get checked with the other useful relays later.
  if (relay == null || relay == bestActiveRelay)
  {
  continue;
  }
   
  log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
   
  relay.FindNearestRelay();
  }
   
  log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay);
   
  usefulRelays.Add(bestActiveRelay);
  }
   
  log.AppendFormat("\n\tDoing target searches for {0} useful relays", usefulRelays.Count);
   
  for (int uIdx = 0; uIdx < usefulRelays.Count; uIdx++)
  {
  relay = usefulRelays[uIdx];
   
  if (relay == null)
{ {
continue; continue;
} }
   
log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);  
   
relay.FindNearestRelay();  
}  
   
log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay);  
   
usefulRelays.Add(bestActiveRelay);  
   
log.AppendFormat("\n\tDoing target searches for {0} useful relays", usefulRelays.Count);  
   
for (int uIdx = 0; uIdx < usefulRelays.Count; uIdx++)  
{  
relay = usefulRelays[uIdx];  
   
log.AppendFormat("\n\tDoing target search for useful relay {0}", relay); log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
   
relay.FindNearestRelay(); relay.FindNearestRelay();
} }
   
if (this.toolbarButton != null || this.appLauncherButton != null) if (this.toolbarButton != null || this.appLauncherButton != null)
{ {
log.Append("\nChecking active vessel relay status."); log.Append("\nChecking active vessel relay status.");
   
this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus(); this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
   
log.AppendFormat("\n\tcurrentConnectionStatus: {0}, setting texture to {1}", log.AppendFormat("\n\tcurrentConnectionStatus: {0}, setting texture to {1}",
this.currentConnectionStatus, this.currentConnectionTexture); this.currentConnectionStatus, this.currentConnectionTexture);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.TexturePath = this.currentConnectionTexture; this.toolbarButton.TexturePath = this.currentConnectionTexture;
   
if (this.currentConnectionStatus == ConnectionStatus.None) if (this.currentConnectionStatus == ConnectionStatus.None)
{ {
if (!this.toolbarButton.Important) this.toolbarButton.Important = true; if (!this.toolbarButton.Important) this.toolbarButton.Important = true;
} }
else else
{ {
if (this.toolbarButton.Important) this.toolbarButton.Important = false; if (this.toolbarButton.Important) this.toolbarButton.Important = false;
} }
} }
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
this.appLauncherButton.SetTexture(this.currentAppLauncherTexture); this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
} }
} }
} }
   
log.Print(false); log.Print(false);
} }
   
private void OnDestroy() private void OnDestroy()
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
   
if (this.mapRenderer != null) if (this.mapRenderer != null)
{ {
GameObject.Destroy(this.mapRenderer); GameObject.Destroy(this.mapRenderer);
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
   
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
GameEvents.onVesselChange.Remove(this.onVesselChange); GameEvents.onVesselChange.Remove(this.onVesselChange);
   
print("ARFlightController: Destroyed."); print("ARFlightController: Destroyed.");
} }
#endregion #endregion
   
private void buttonToggle() private void buttonToggle()
{ {
if (MapView.MapIsEnabled) if (MapView.MapIsEnabled)
{ {
ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines; ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
} }
} }
   
#region Event Handlers #region Event Handlers
private void onSceneChangeRequested(GameScenes scene) private void onSceneChangeRequested(GameScenes scene)
{ {
print("ARFlightController: Requesting Destruction."); print("ARFlightController: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
   
private void onVesselChange(Vessel vessel) private void onVesselChange(Vessel vessel)
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
#endregion #endregion
} }
} }
   
// AntennaRange // AntennaRange
// //
// ARMapRenderer.cs // ARMapRenderer.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.
   
#pragma warning disable 1591 #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools.Extensions;
  using ToadicusTools.DebugTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class ARMapRenderer : MonoBehaviour public class ARMapRenderer : MonoBehaviour
{ {
#if BENCH #if BENCH
private static ulong updateCount = 0u; private static ulong updateCount = 0u;
private static ulong updateTimer = 0u; private static ulong updateTimer = 0u;
private readonly static RollingAverage averager = new RollingAverage(); private readonly static RollingAverage averager = new RollingAverage();
private static long twiceAverageTime = long.MaxValue; private static long twiceAverageTime = long.MaxValue;
#endif #endif
   
#region Fields #region Fields
private Dictionary<Guid, LineRenderer> vesselLineRenderers; private Dictionary<Guid, LineRenderer> vesselLineRenderers;
   
// Debug Stuff // Debug Stuff
#pragma warning disable 649 #pragma warning disable 649
private System.Diagnostics.Stopwatch timer; private System.Diagnostics.Stopwatch timer;
private Tools.DebugLogger log; private PooledDebugLogger log;
private long relayStart; private long relayStart;
private long start; private long start;
#pragma warning restore 649 #pragma warning restore 649
   
#pragma warning disable 414 #pragma warning disable 414
private Color thisColor; private Color thisColor;
#pragma warning restore 414 #pragma warning restore 414
#endregion #endregion
   
#region Properties #region Properties
public LineRenderer this[Guid idx] public LineRenderer this[Guid idx]
{ {
get get
{ {
LineRenderer lr; LineRenderer lr;
   
if (!this.vesselLineRenderers.TryGetValue(idx, out lr)) if (!this.vesselLineRenderers.TryGetValue(idx, out lr))
{ {
GameObject obj = new GameObject(); GameObject obj = new GameObject();
obj.layer = 31; obj.layer = 31;
   
lr = obj.AddComponent<LineRenderer>(); lr = obj.AddComponent<LineRenderer>();
   
lr.material = MapView.OrbitLinesMaterial; lr.material = MapView.OrbitLinesMaterial;
   
this.vesselLineRenderers[idx] = lr; this.vesselLineRenderers[idx] = lr;
   
return lr; return lr;
} }
   
return lr; return lr;
} }
} }
#endregion #endregion
   
#region MonoBehaviour Lifecycle #region MonoBehaviour Lifecycle
private void Awake() private void Awake()
{ {
if (ARConfiguration.PrettyLines) if (ARConfiguration.PrettyLines)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
} }
   
#if DEBUG || BENCH #if DEBUG || BENCH
this.timer = new System.Diagnostics.Stopwatch(); this.timer = new System.Diagnostics.Stopwatch();
#endif #endif
#if DEBUG #if DEBUG
this.log = Tools.DebugLogger.New(this); this.log = PooledDebugLogger.Get(this);
#endif #endif
} }
   
private void OnPreCull() private void OnPreCull()
{ {
if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines) if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
{ {
this.Cleanup(!HighLogic.LoadedSceneIsFlight); this.Cleanup(!HighLogic.LoadedSceneIsFlight);
   
return; return;
} }
   
#if DEBUG || BENCH #if DEBUG || BENCH
timer.Restart(); timer.Restart();
#endif #endif
   
try try
{ {
log.Clear(); log.Clear();
   
log.AppendFormat("OnPreCull.\n"); log.AppendFormat("OnPreCull.\n");
   
log.AppendFormat("\tMapView: Draw3DLines: {0}\n" + log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
"\tMapView.MapCamera.camera.fieldOfView: {1}\n" + "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
"\tMapView.MapCamera.Distance: {2}\n", "\tMapView.MapCamera.Distance: {2}\n",
MapView.Draw3DLines, MapView.Draw3DLines,
MapView.MapCamera.camera.fieldOfView, MapView.MapCamera.camera.fieldOfView,
MapView.MapCamera.Distance MapView.MapCamera.Distance
); );
   
log.AppendLine("FlightGlobals ready and Vessels list not null."); log.AppendLine("FlightGlobals ready and Vessels list not null.");
   
IAntennaRelay relay; IAntennaRelay relay;
   
for (int i = 0; i < ARFlightController.UsefulRelays.Count; i++) for (int i = 0; i < ARFlightController.UsefulRelays.Count; i++)
{ {
relay = ARFlightController.UsefulRelays[i]; relay = ARFlightController.UsefulRelays[i];
   
if (relay == null) if (relay == null)
{ {
log.AppendFormat("\n\tGot null relay, skipping"); log.AppendFormat("\n\tGot null relay, skipping");
continue; continue;
} }
   
log.AppendFormat("\n\tDrawing pretty lines for useful relay {0}", relay); log.AppendFormat("\n\tDrawing pretty lines for useful relay {0}", relay);
#if DEBUG #if DEBUG
start = timer.ElapsedMilliseconds; start = timer.ElapsedMilliseconds;
#endif #endif
   
this.SetRelayVertices(relay); this.SetRelayVertices(relay);
   
log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms", log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
relay, timer.ElapsedMilliseconds - start); relay, timer.ElapsedMilliseconds - start);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString()); this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
this.Cleanup(false); this.Cleanup(false);
} }
#if DEBUG #if DEBUG
finally finally
{ {
log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds); log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
   
log.Print(); log.Print();
} }
#endif #endif
   
#if BENCH #if BENCH
ARMapRenderer.updateCount++; ARMapRenderer.updateCount++;
ARMapRenderer.updateTimer += (ulong)this.timer.ElapsedTicks; ARMapRenderer.updateTimer += (ulong)this.timer.ElapsedTicks;
   
if (ARMapRenderer.updateCount >= (ulong)(8d / Time.smoothDeltaTime)) if (ARMapRenderer.updateCount >= (ulong)(8d / Time.smoothDeltaTime))
{ {
ARMapRenderer.averager.AddItem((double)ARMapRenderer.updateTimer / (double)ARMapRenderer.updateCount); ARMapRenderer.averager.AddItem((double)ARMapRenderer.updateTimer / (double)ARMapRenderer.updateCount);
ARMapRenderer.updateTimer = 0u; ARMapRenderer.updateTimer = 0u;
ARMapRenderer.updateCount = 0u; ARMapRenderer.updateCount = 0u;
ARMapRenderer.twiceAverageTime = (long)(ARMapRenderer.averager.Average * 2d); ARMapRenderer.twiceAverageTime = (long)(ARMapRenderer.averager.Average * 2d);
} }
   
if (this.timer.ElapsedTicks > ARMapRenderer.twiceAverageTime) if (this.timer.ElapsedTicks > ARMapRenderer.twiceAverageTime)
{ {
this.Log("PreCull took significant longer than usual ({0:S3}s vs {1:S3}s)", this.Log("PreCull took significant longer than usual ({0:S3}s vs {1:S3}s)",
(double)this.timer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency, (double)this.timer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency,
ARMapRenderer.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency ARMapRenderer.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency
); );
} }
#endif #endif
} }
   
private void OnDestroy() private void OnDestroy()
{ {
this.Cleanup(true); this.Cleanup(true);
   
this.Log("Destroyed"); this.Log("Destroyed");
} }
#endregion #endregion
   
#region Utility #region Utility
private void SetRelayVertices(IAntennaRelay relay) private void SetRelayVertices(IAntennaRelay relay)
{ {
log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay); log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay);
   
if (relay.vessel == null) if (relay.vessel == null)
{ {
log.Append("\n\t\tvessel is null, bailing out"); log.Append("\n\t\tvessel is null, bailing out");
return; return;
} }
   
LineRenderer renderer = this[relay.vessel.id]; LineRenderer renderer = this[relay.vessel.id];
Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D()); Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
   
float lineWidth; float lineWidth;
float d = Screen.height / 2f + 0.01f; float d = Screen.height / 2f + 0.01f;
   
if (MapView.Draw3DLines) if (MapView.Draw3DLines)
{ {
lineWidth = 0.005859375f * MapView.MapCamera.Distance; lineWidth = 0.005859375f * MapView.MapCamera.Distance;
} }
else else
{ {
lineWidth = 2f; lineWidth = 2f;
   
start = MapView.MapCamera.camera.WorldToScreenPoint(start); start = MapView.MapCamera.camera.WorldToScreenPoint(start);
   
start.z = start.z >= 0f ? d : -d; start.z = start.z >= 0f ? d : -d;
} }
   
renderer.SetWidth(lineWidth, lineWidth); renderer.SetWidth(lineWidth, lineWidth);
   
renderer.SetPosition(0, start); renderer.SetPosition(0, start);
   
int idx = 0; int idx = 0;
   
#if DEBUG #if DEBUG
relayStart = timer.ElapsedMilliseconds; relayStart = timer.ElapsedMilliseconds;
#endif #endif
   
Vector3d nextPoint; Vector3d nextPoint;
   
renderer.enabled = true; renderer.enabled = true;
   
if (!relay.CanTransmit()) if (!relay.CanTransmit())
{ {
thisColor = Color.red; thisColor = Color.red;
} }
else else
{ {
if (relay.LinkStatus == ConnectionStatus.Optimal) if (relay.LinkStatus == ConnectionStatus.Optimal)
{ {
thisColor = Color.green; thisColor = Color.green;
} }
else else
{ {
thisColor = Color.yellow; thisColor = Color.yellow;
} }
} }
   
if (relay.KerbinDirect) if (relay.KerbinDirect)
{ {
nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position); nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
} }
else else
{ {
if (relay.targetRelay == null || relay.targetRelay.vessel == null) if (relay.targetRelay == null || relay.targetRelay.vessel == null)
{ {
this.LogError( this.LogError(
"SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!", "SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
relay relay
); );
   
renderer.enabled = false; renderer.enabled = false;
return; return;
} }
   
nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D()); nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
} }
   
renderer.SetColors(thisColor, thisColor); renderer.SetColors(thisColor, thisColor);
   
if (!MapView.Draw3DLines) if (!MapView.Draw3DLines)
{ {
nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint); nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
nextPoint.z = nextPoint.z >= 0f ? d : -d; nextPoint.z = nextPoint.z >= 0f ? d : -d;
} }
   
idx++; idx++;
   
renderer.SetVertexCount(idx + 1); renderer.SetVertexCount(idx + 1);
renderer.SetPosition(idx, nextPoint); renderer.SetPosition(idx, nextPoint);
   
log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart); log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
} }
   
private void Cleanup(bool freeObjects) private void Cleanup(bool freeObjects)
{ {
if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0) if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
{ {
IEnumerator<LineRenderer> enumerator = this.vesselLineRenderers.Values.GetEnumerator(); IEnumerator<LineRenderer> enumerator = this.vesselLineRenderers.Values.GetEnumerator();
LineRenderer lineRenderer; LineRenderer lineRenderer;
   
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
lineRenderer = enumerator.Current; lineRenderer = enumerator.Current;
lineRenderer.enabled = false; lineRenderer.enabled = false;
   
if (freeObjects) if (freeObjects)
{ {
GameObject.Destroy(lineRenderer.gameObject); GameObject.Destroy(lineRenderer.gameObject);
} }
} }
   
if (freeObjects) if (freeObjects)
{ {
this.vesselLineRenderers.Clear(); this.vesselLineRenderers.Clear();
} }
} }
} }
#endregion #endregion
} }
} }
   
// 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; using ToadicusTools.DebugTools;
  using ToadicusTools.Extensions;
   
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 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;
} }
   
/// <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>
/// 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;
} }
   
Tools.DebugLogger log; PooledDebugLogger log;
#if DEBUG #if DEBUG
log = Tools.DebugLogger.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;
   
double nearestRelaySqrQuotient = double.PositiveInfinity; double nearestRelaySqrQuotient = double.PositiveInfinity;
double bestOccludedSqrQuotient = double.PositiveInfinity; double bestOccludedSqrQuotient = double.PositiveInfinity;
   
double potentialSqrDistance; double potentialSqrDistance;
double maxLinkSqrDistance; double maxLinkSqrDistance;
double potentialSqrQuotient; double potentialSqrQuotient;
   
double kerbinSqrDistance; double kerbinSqrDistance;
double kerbinSqrQuotient; double kerbinSqrQuotient;
   
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 distance to potential vessel"); log.Append("\n\tgetting distance to potential vessel");
potentialSqrDistance = this.SqrDistanceTo(potentialBestRelay); potentialSqrDistance = this.SqrDistanceTo(potentialBestRelay);
log.Append("\n\tgetting best vessel relay"); log.Append("\n\tgetting best vessel relay");
   
log.Append("\n\tgetting max link distance to potential relay"); log.Append("\n\tgetting max link distance to potential relay");
   
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance; maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
} }
else else
{ {
maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
} }
   
log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance); log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance);
   
potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance; potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
   
#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\tpotentialSqrDistance: {0}", potentialSqrDistance); log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient); log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance); log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
   
if ( if (
(potentialSqrQuotient < bestOccludedSqrQuotient) && (potentialSqrQuotient < bestOccludedSqrQuotient) &&
(potentialSqrQuotient <= 1d) && (potentialSqrQuotient <= 1d) &&
potentialBestRelay.CanTransmit() potentialBestRelay.CanTransmit()
) )
{ {
log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit"); log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay); log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
   
this.bestOccludedRelay = potentialBestRelay; this.bestOccludedRelay = potentialBestRelay;
bodyOccludingBestOccludedRelay = fob; bodyOccludingBestOccludedRelay = fob;
bestOccludedSqrQuotient = potentialSqrQuotient; bestOccludedSqrQuotient = potentialSqrQuotient;
} }
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 (potentialSqrQuotient > nearestRelaySqrQuotient) if (potentialSqrQuotient > nearestRelaySqrQuotient)
{ {
log.AppendFormat("\n\t{0}: Relay {1} discarded because it is farther than another the nearest relay.", log.AppendFormat("\n\t{0}: Relay {1} discarded because it is farther than another the nearest relay.",
this.ToString(), this.ToString(),
potentialBestRelay potentialBestRelay
); );
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)
{ {
nearestRelaySqrQuotient = potentialSqrQuotient; nearestRelaySqrQuotient = potentialSqrQuotient;
this.nearestRelay = potentialBestRelay; this.nearestRelay = potentialBestRelay;
   
log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)", log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
this.ToString(), this.ToString(),
this.nearestRelay.ToString(), this.nearestRelay.ToString(),
Math.Sqrt(nearestRelaySqrQuotient) Math.Sqrt(nearestRelaySqrQuotient)
); );
} }
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;
   
kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius; kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
kerbinSqrDistance *= kerbinSqrDistance; kerbinSqrDistance *= kerbinSqrDistance;
   
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
kerbinSqrQuotient = kerbinSqrDistance / kerbinSqrQuotient = kerbinSqrDistance /
(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange); (this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
} }
else else
{ {
kerbinSqrQuotient = kerbinSqrDistance / kerbinSqrQuotient = kerbinSqrDistance /
(this.maxTransmitDistance * this.maxTransmitDistance); (this.maxTransmitDistance * this.maxTransmitDistance);
} }
   
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}), kerbinSqrDistance={5}m²)", "\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinSqrDistance={5}m²)",
this, this,
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrQuotient, nearestRelaySqrQuotient,
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
bestOccludedSqrQuotient, bestOccludedSqrQuotient,
kerbinSqrDistance kerbinSqrDistance
); );
#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);
   
// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist. // nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
// Therefore, this will only be true if a valid relay is in range. // Therefore, this will only be true if a valid relay is in range.
if (nearestRelaySqrQuotient <= 1d) if (nearestRelaySqrQuotient <= 1d)
{ {
log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).", log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrQuotient, 1d); nearestRelaySqrQuotient, 1d);
   
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} ({1} > {2}).", log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrQuotient, 1d); nearestRelaySqrQuotient, 1d);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, check it against the nearest relay. // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
if (bestOccludedSqrQuotient < kerbinSqrQuotient) if (bestOccludedSqrQuotient < kerbinSqrQuotient)
{ {
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})",
bestOccludedRelay, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
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 nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe. // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
if (nearestRelaySqrQuotient < bestOccludedSqrQuotient) if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
nearestRelaySqrQuotient, bestOccludedSqrQuotient); nearestRelaySqrQuotient, bestOccludedSqrQuotient);
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 closer than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
nearestRelaySqrQuotient, bestOccludedSqrQuotient); nearestRelaySqrQuotient, bestOccludedSqrQuotient);
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 closer than the best occluded relay ({0} >= {1})", log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
bestOccludedRelay, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
// If the nearest relay is closer than Kerbin, pick it. // If the nearest relay is closer than Kerbin, pick it.
// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe. // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
if (nearestRelaySqrQuotient < kerbinSqrQuotient) if (nearestRelaySqrQuotient < kerbinSqrQuotient)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
this.KerbinDirect = false; this.KerbinDirect = false;
this.firstOccludingBody = null; this.firstOccludingBody = null;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// Otherwise, pick Kerbin. // Otherwise, pick Kerbin.
else else
{ {
log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
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 closer than Kerbin and in range, transmit to it. // If the nearest relay is closer than Kerbin and in range, transmit to it.
if (nearestRelaySqrQuotient <= 1d) if (nearestRelaySqrQuotient <= 1d)
{ {
log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).", log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrQuotient, 1d); nearestRelaySqrQuotient, 1d);
   
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 (nearestRelaySqrQuotient < kerbinSqrQuotient) if (nearestRelaySqrQuotient < kerbinSqrQuotient)
{ {
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(),
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
   
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(),
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
   
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\tCan't transmit to nearby relay {0} ({1} > {2}).", log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrQuotient, 1d); nearestRelaySqrQuotient, 1d);
   
// If Kerbin is in range, use it. // If Kerbin is in range, use it.
if (kerbinSqrQuotient <= 1d) if (kerbinSqrQuotient <= 1d)
{ {
log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).", log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
kerbinSqrQuotient, 1d); kerbinSqrQuotient, 1d);
   
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 ({0} > {1}).", log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
kerbinSqrQuotient, 1d); kerbinSqrQuotient, 1d);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, check it against the nearest relay. // If the best occluded relay is closer than Kerbin, check it against the nearest relay.
// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
if (bestOccludedSqrQuotient < kerbinSqrQuotient) if (bestOccludedSqrQuotient < kerbinSqrQuotient)
{ {
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})",
bestOccludedRelay, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
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 nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe. // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
if (nearestRelaySqrQuotient < bestOccludedSqrQuotient) if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
nearestRelaySqrQuotient, bestOccludedSqrQuotient); nearestRelaySqrQuotient, bestOccludedSqrQuotient);
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 closer than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
nearestRelaySqrQuotient, bestOccludedSqrQuotient); nearestRelaySqrQuotient, bestOccludedSqrQuotient);
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 closer than the best occluded relay ({0} >= {1})", log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
bestOccludedRelay, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
this.firstOccludingBody = null; this.firstOccludingBody = null;
   
// If the nearest relay is closer than Kerbin, pick it. // If the nearest relay is closer than Kerbin, pick it.
// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe. // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
if (nearestRelaySqrQuotient < kerbinSqrQuotient) if (nearestRelaySqrQuotient < kerbinSqrQuotient)
{ {
log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
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 closer than the nearest relay ({0} >= {1}), so picking it.", log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
nearestRelaySqrQuotient, kerbinSqrQuotient); nearestRelaySqrQuotient, kerbinSqrQuotient);
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.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
// //
// AntennaRange.cfg // AntennaRange.cfg
// //
// 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.
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
// 3.0 Uported License. // 3.0 Uported License.
// //
// Specifications: // Specifications:
// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by // nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by
// packetResourceCost and packetSize. // packetResourceCost and packetSize.
// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the // maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. // power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna. // maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
// //
   
@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2] @PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
{ {
@MODULE[ModuleDataTransmitter] @MODULE[ModuleDataTransmitter]
{ {
@name = ModuleLimitedDataTransmitter @name = ModuleLimitedDataTransmitter
nominalRange = 6364 nominalRange = 6364
simpleRange = 20500000 simpleRange = 20500000
maxPowerFactor = 8 maxPowerFactor = 8
maxDataFactor = 4 maxDataFactor = 4
} }
   
MODULE MODULE
{ {
name = ModuleScienceContainer name = ModuleScienceContainer
   
dataIsCollectable = true dataIsCollectable = true
dataIsStorable = false dataIsStorable = false
   
storageRange = 2 storageRange = 2
} }
} }
   
@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2] @PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
{ {
@MODULE[ModuleDataTransmitter] @MODULE[ModuleDataTransmitter]
{ {
@name = ModuleLimitedDataTransmitter @name = ModuleLimitedDataTransmitter
nominalRange = 3150000000 nominalRange = 3150000000
simpleRange = 18000000000 simpleRange = 18000000000
maxPowerFactor = 4 maxPowerFactor = 4
maxDataFactor = 8 maxDataFactor = 8
} }
   
MODULE MODULE
{ {
name = ModuleScienceContainer name = ModuleScienceContainer
   
dataIsCollectable = true dataIsCollectable = true
dataIsStorable = false dataIsStorable = false
   
storageRange = 2 storageRange = 2
} }
} }
   
@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2] @PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech]
{ {
@MODULE[ModuleDataTransmitter] @MODULE[ModuleDataTransmitter]
{ {
@name = ModuleLimitedDataTransmitter @name = ModuleLimitedDataTransmitter
@packetResourceCost /= 1.414213 @packetResourceCost /= 1.414213
nominalRange = 9250000000 nominalRange = 9250000000
simpleRange = 56250000000 simpleRange = 56250000000
maxPowerFactor = 16 maxPowerFactor = 16
maxDataFactor = 2 maxDataFactor = 2
  }
   
  MODULE
  {
  name = ModuleScienceContainer
   
  dataIsCollectable = true
  dataIsStorable = false
   
  storageRange = 2
  }
  }
   
  @PART[HighGainAntenna]:FOR[AntennaRange]:NEEDS[AsteroidDay,!RemoteTech]
  {
  @TechRequired = electronics
  @description = Repurposed for medium range probes, the HG-55 provdes high speed directional data transmission.
   
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 7774867578
  simpleRange = 25030376544
  maxPowerFactor = 2.6180339887498948
  maxDataFactor = 9
} }
   
MODULE MODULE
{ {
name = ModuleScienceContainer name = ModuleScienceContainer
   
dataIsCollectable = true dataIsCollectable = true
dataIsStorable = false dataIsStorable = false
   
storageRange = 2 storageRange = 2
} }
} }
   
TRACKING_STATION_RANGES TRACKING_STATION_RANGES
{ {
range = 800000 range = 800000
range = 200000000000 range = 200000000000
range = 2000000000000 range = 2000000000000
} }
   
EVA_MODULE EVA_MODULE
{ {
name = ModuleLimitedDataTransmitter name = ModuleLimitedDataTransmitter
   
nominalRange = 1389 nominalRange = 1389
simpleRange = 5000 simpleRange = 5000
maxPowerFactor = 1 maxPowerFactor = 1
maxDataFactor = 1 maxDataFactor = 1
   
packetInterval = 0.2 packetInterval = 0.2
packetSize = 1 packetSize = 1
packetResourceCost = 6.25 packetResourceCost = 6.25
   
requiredResource = ElectricCharge requiredResource = ElectricCharge
} }
   
EVA_RESOURCE EVA_RESOURCE
{ {
name = ElectricCharge name = ElectricCharge
amount = 100 amount = 0
maxAmount = 100 maxAmount = 100
} }
   
@EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport] @EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport]
{ {
!name = DELETE !name = DELETE
} }
   
// 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; using ToadicusTools.DebugTools;
  using ToadicusTools.Extensions;
  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 static GUISkin partTooltipSkin;
  private static GUIStyle partTooltipBodyStyle;
  private static GUIStyle partTooltipHeaderStyle;
   
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
private float _basepacketResourceCost; private float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
private float _basepacketSize; private float _basepacketSize;
   
// 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.");
this.LogError(new System.Diagnostics.StackTrace().ToString()); this.LogError(new System.Diagnostics.StackTrace().ToString());
return null; return null;
} }
} }
} }
   
/// <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
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetSize; return this.packetSize;
} }
else else
{ {
return float.Epsilon; return float.Epsilon;
} }
} }
} }
   
/// <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
{ {
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetResourceCost; return this.packetResourceCost;
} }
else else
{ {
return float.PositiveInfinity; return float.PositiveInfinity;
} }
} }
} }
   
/// <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, false, ScreenMessageStyle.UPPER_LEFT); this.ErrorMsg = new ScreenMessage("", 4f, false, 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._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this._basepacketResourceCost = base.packetResourceCost;
this.moduleInfoContent = new GUIContent(); this.moduleInfoContent = new GUIContent();
   
Tools.PostDebugMessage(string.Format( this.LogDebug("{0} loaded:\n" +
"{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.name, this,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
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.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
   
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 = string.Format(Tools.SIFormatter, "{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.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
} }
   
/// <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)
{ {
this.moduleInfoContent.text = this.GetInfo(); this.moduleInfoContent.text = this.GetInfo();
   
GUIStyle style0 = PartListTooltips.fetch.tooltipSkin.customStyles[0]; if (partTooltipSkin == null)
GUIStyle style1 = PartListTooltips.fetch.tooltipSkin.customStyles[1]; {
  UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin));
  GUISkin skin;
  for (int sIdx = 0; sIdx < skins.Length; sIdx++)
  {
  skin = (GUISkin)skins[sIdx];
   
  if (skin.name == tooltipSkinName)
  {
  partTooltipSkin = skin;
  partTooltipBodyStyle = partTooltipSkin.customStyles[0];
  partTooltipHeaderStyle = partTooltipSkin.customStyles[1];
  }
  }
   
  if (partTooltipSkin == null)
  {
  this.LogError("Could not find GUISkin {0}? Please report this!", tooltipSkinName);
  return;
  }
  else
  {
  this.Log("Loaded GUISkin {0}", tooltipSkinName);
  }
  }
   
float width = rect.width; float width = rect.width;
float orgHeight = rect.height; float orgHeight = rect.height;
float height = style0.CalcHeight(this.moduleInfoContent, width); float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width);
   
rect.height = height; rect.height = height;
   
GUI.Box(rect, this.moduleInfoContent, style0); GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle);
GUI.Label(rect, this.GetModuleTitle(), style1); GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle);
   
GUILayout.Space(height - orgHeight GUILayout.Space(height - orgHeight
- style0.padding.bottom - style0.padding.top - partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
- 2f * (style0.margin.bottom + style0.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()
{ {
StringBuilder sb = Tools.GetStringBuilder(); 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(Tools.SIFormatter, "Nominal Range to Kerbin: {0:S3}m\n", {
Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange) sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n",
); Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
sb.AppendFormat(Tools.SIFormatter, "Maximum Range to Kerbin: {0:S3}m", );
Math.Sqrt( sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m",
this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) * Math.Sqrt(
ARConfiguration.KerbinRelayRange this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
) ARConfiguration.KerbinRelayRange
); )
} );
else }
{ else
sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance); {
sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m", this.maxTransmitDistance); sb.AppendFormat("Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
} sb.AppendFormat("Maximum Range: {0:S3}m", this.maxTransmitDistance);
  }
text = sb.ToString();  
  text = sb.ToString();
Tools.PutStringBuilder(sb);  
  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:
Tools.PostDebugMessage(string.Format( 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>
/// 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>
/// <param name="callback">Callback function</param> /// <param name="callback">Callback function</param>
public new void TransmitData(List<ScienceData> dataQueue, Callback callback) public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
{ {
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
); );
   
this.FindNearestRelay(); this.FindNearestRelay();
   
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
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(),
callback == null ? "null" : callback.ToString() callback == null ? "null" : callback.ToString()
); );
   
if (callback == null) if (callback == null)
{ {
base.TransmitData(dataQueue); base.TransmitData(dataQueue);
} }
else else
{ {
base.TransmitData(dataQueue, callback); base.TransmitData(dataQueue, callback);
} }
} }
else else
{ {
Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = Tools.DebugLogger.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)
{ {
StringBuilder sb = Tools.GetStringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
   
sb.Append('[');  
sb.Append(this.part.partInfo.title);  
sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");  
sb.Append("Data to be discarded:\n");  
   
ScienceData data;  
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)  
{ {
data = dataQueue[dIdx]; sb.Append('[');
sb.AppendFormat("\t{0}\n", data.title); sb.Append(this.part.partInfo.title);
  sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
  sb.Append("Data to be discarded:\n");
   
  ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
  {
  data = dataQueue[dIdx];
  sb.AppendFormat("\t{0}\n", data.title);
  }
   
  ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  this.LogDebug(sb.ToString());
} }
   
ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
Tools.PostDebugMessage(sb.ToString());  
   
Tools.PutStringBuilder(sb);  
} }
   
this.PostCannotTransmitError(); this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( this.LogDebug(
"distance: " + this.CurrentLinkSqrDistance "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
/// <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) called, dataQueue.Count={0}", "TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
dataQueue.Count dataQueue.Count
); );
   
this.TransmitData(dataQueue, null); this.TransmitData(dataQueue, null);
} }
   
/// <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.FindNearestRelay(); this.FindNearestRelay();
   
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( 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 = string.Format(Tools.SIFormatter, "{0:S3}m", this.UImaxTransmitDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.MaximumLinkSqrDistance)); Math.Sqrt(this.MaximumLinkSqrDistance));
this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{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 = string.Format(Tools.SIFormatter, "{0:S3}m", this.UItransmitDistance = TextTools.Format("{0:S3}m",
Math.Sqrt(this.CurrentLinkSqrDistance)); Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate); this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
this.UIpacketCost = string.Format(Tools.SIFormatter, "{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 = string.Format(Tools.SIFormatter, "{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 = string.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
{ {
this.UIrelayTarget = this.targetRelay.ToString(); if (this.targetRelay != null)
  {
  this.UIrelayTarget = this.targetRelay.ToString();
  }
  else
  {
  this.UIrelayTarget = "A mysterious null entity";
  }
} }
} }
} }
   
/// <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()
{ {
StringBuilder sb = Tools.GetStringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
string msg; {
  string msg;
sb.Append(this.part.partInfo.title);  
  sb.Append(this.part.partInfo.title);
if (vessel != null)  
{ if (vessel != null)
sb.Append(" on "); {
sb.Append(vessel.vesselName); sb.Append(" on ");
} sb.Append(vessel.vesselName);
else if ( }
this.part != null && else if (
this.part.protoPartSnapshot != null && this.part != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null this.part.protoPartSnapshot != 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();
   
Tools.PutStringBuilder(sb); 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
); );
   
Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message); this.LogDebug(this.ErrorMsg.message);
   
ScreenMessages.PostScreenMessage(this.ErrorMsg, false); ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
} }
   
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of // Before transmission, set packetResourceCost. Per above, packet cost increases with the square of
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
// transmission fails (see CanTransmit). // transmission fails (see CanTransmit).
private void PreTransmit_SetPacketResourceCost() private void PreTransmit_SetPacketResourceCost()
{ {
if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance) if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance)
{ {
base.packetResourceCost = this._basepacketResourceCost; base.packetResourceCost = this._basepacketResourceCost;
} }
else else
{ {
float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance); float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance);
   
base.packetResourceCost = this._basepacketResourceCost * rangeFactor; base.packetResourceCost = this._basepacketResourceCost * rangeFactor;
} }
   
base.packetResourceCost *= this.packetThrottle / 100f; base.packetResourceCost *= this.packetThrottle / 100f;
} }
   
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of // Before transmission, set packetSize. Per above, packet size increases with the inverse square of
// distance. packetSize maxes out at _basepacketSize * maxDataFactor. // distance. packetSize maxes out at _basepacketSize * maxDataFactor.
private void PreTransmit_SetPacketSize() private void PreTransmit_SetPacketSize()
{ {
if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance) if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance)
{ {
base.packetSize = this._basepacketSize; base.packetSize = this._basepacketSize;
} }
else else
{ {
float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance); float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
   
base.packetSize = Mathf.Min( base.packetSize = Mathf.Min(
this._basepacketSize * rangeFactor, this._basepacketSize * rangeFactor,
this._basepacketSize * this.maxDataFactor this._basepacketSize * this.maxDataFactor
); );
} }
   
base.packetSize *= this.packetThrottle / 100f; base.packetSize *= this.packetThrottle / 100f;
} }
   
private string buildTransmitMessage() private string buildTransmitMessage()
{ {
StringBuilder sb = Tools.GetStringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
string msg; {
  string msg;
sb.Append("[");  
sb.Append(base.part.partInfo.title); sb.Append("[");
sb.Append("]: "); sb.Append(base.part.partInfo.title);
  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(this.relay.targetRelay); sb.Append("via ");
} sb.Append(this.relay.targetRelay);
  }
msg = sb.ToString();  
  msg = sb.ToString();
Tools.PutStringBuilder(sb);  
  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()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
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()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
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);
} }
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
[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.*")] [assembly: AssemblyVersion("1.10.1.*")]
// 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;
   
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
{ {
Tools.PostErrorMessage("{0}: Could not fetch vessel! {1}{2}{3}", this.LogError("Could not fetch vessel! {0}{1}{2}",
this.ToString(),  
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 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)
{ {
Tools.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>
/// 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()
{ {
System.Text.StringBuilder sb = Tools.GetStringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
  {
  sb.Append(this.Title);
   
sb.Append(this.Title); if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
  }
   
if (this.protoPart != null && this.protoPart.pVesselRef != null) return sb.ToString();
{  
sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);  
} }
   
Tools.PutStringBuilder(sb);  
   
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;
   
Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString()); this.Log("constructed ({0})", this.GetType().Name);
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// RelayDatabase.cs // RelayDatabase.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.
   
#pragma warning disable 1591 #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase : Singleton<RelayDatabase> public class RelayDatabase : Singleton<RelayDatabase>
{ {
/* /*
* Instance members * Instance members
* */ * */
   
/* /*
* Fields * Fields
* */ * */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects. // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
private Dictionary<Guid, List<IAntennaRelay>> relayDatabase; private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
private Dictionary<Guid, IAntennaRelay> bestRelayTable; private Dictionary<Guid, IAntennaRelay> bestRelayTable;
   
// Vessel.id-keyed hash table of part counts, used for caching // Vessel.id-keyed hash table of part counts, used for caching
private Dictionary<Guid, int> vesselPartCountTable; private Dictionary<Guid, int> vesselPartCountTable;
   
private int cacheHits; private int cacheHits;
private int cacheMisses; private int cacheMisses;
   
/* /*
* Properties * Properties
* */ * */
// Gets the Part-hashed table of relays in a given vessel // Gets the Part-hashed table of relays in a given vessel
public IList<IAntennaRelay> this [Vessel vessel] public IList<IAntennaRelay> this [Vessel vessel]
{ {
get get
{ {
// If we don't have an entry for this vessel... // If we don't have an entry for this vessel...
if (!this.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel.id))
{ {
// ...Generate an entry for this vessel. // ...Generate an entry for this vessel.
this.AddVessel(vessel); this.AddVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
{ {
// ...Update the our vessel in the cache // ...Update the our vessel in the cache
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// Otherwise, it's a hit // Otherwise, it's a hit
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
   
// Return the Part-hashed table of relays for this vessel // Return the Part-hashed table of relays for this vessel
return relayDatabase[vessel.id].AsReadOnly(); return relayDatabase[vessel.id].AsReadOnly();
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Remove a vessel from the cache, if it exists. // Remove a vessel from the cache, if it exists.
public void DirtyVessel(Vessel vessel) public void DirtyVessel(Vessel vessel)
{ {
#if DEBUG #if DEBUG
Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}", Logging.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
vessel, new System.Diagnostics.StackTrace().ToString()); vessel, new System.Diagnostics.StackTrace().ToString());
#else #else
Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName); Logging.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
#endif #endif
   
this.relayDatabase.Remove(vessel.id); this.relayDatabase.Remove(vessel.id);
this.vesselPartCountTable.Remove(vessel.id); this.vesselPartCountTable.Remove(vessel.id);
this.bestRelayTable.Remove(vessel.id); this.bestRelayTable.Remove(vessel.id);
} }
   
public void ClearCache() public void ClearCache()
{ {
Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache."); Logging.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
   
this.relayDatabase.Clear(); this.relayDatabase.Clear();
this.bestRelayTable.Clear(); this.bestRelayTable.Clear();
this.vesselPartCountTable.Clear(); this.vesselPartCountTable.Clear();
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
public bool ContainsKey(Guid key) public bool ContainsKey(Guid key)
{ {
return this.relayDatabase.ContainsKey(key); return this.relayDatabase.ContainsKey(key);
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
public bool ContainsKey(Vessel vessel) public bool ContainsKey(Vessel vessel)
{ {
return this.ContainsKey(vessel.id); return this.ContainsKey(vessel.id);
} }
   
public IAntennaRelay GetBestVesselRelay(Vessel vessel) public IAntennaRelay GetBestVesselRelay(Vessel vessel)
{ {
IAntennaRelay relay; IAntennaRelay relay;
if (this.bestRelayTable.TryGetValue(vessel.id, out relay)) if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
{ {
return relay; return relay;
} }
else else
{ {
var dump = this[vessel]; var dump = this[vessel];
return null; return null;
} }
} }
   
// Adds a vessel to the database // Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses // The return for this function isn't used yet, but seems useful for potential future API-uses
private bool AddVessel(Vessel vessel) private bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// ...post an error // ...post an error
Debug.LogWarning(string.Format( Debug.LogWarning(string.Format(
"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.", "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// ...and refuse to add // ...and refuse to add
return false; return false;
} }
// otherwise, add the vessel to our tables... // otherwise, add the vessel to our tables...
else else
{ {
// Build an empty table... // Build an empty table...
this.relayDatabase[vessel.id] = new List<IAntennaRelay>(); this.relayDatabase[vessel.id] = new List<IAntennaRelay>();
   
// Update the empty index // Update the empty index
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
   
// Return success // Return success
return true; return true;
} }
} }
   
// Update the vessel's entry in the table // Update the vessel's entry in the table
private void UpdateVessel(Vessel vessel) private void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.", "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
   
// Actually build and assign the table // Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable); this.getVesselRelays(vessel, ref vesselTable);
// Set the part count // Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count; this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
} }
   
// Runs when a vessel is modified (or when we switch to one, to catch docking events) // Runs when a vessel is modified (or when we switch to one, to catch docking events)
public void onVesselEvent(Vessel vessel) public void onVesselEvent(Vessel vessel)
{ {
// If we have this vessel in our cache... // If we have this vessel in our cache...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// If our part counts disagree (such as if a part has been added or broken off, // If our part counts disagree (such as if a part has been added or broken off,
// or if we've just docked or undocked)... // or if we've just docked or undocked)...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' ({2}).", "{0}: dirtying cache for vessel '{1}' ({2}).",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// Dirty the cache (real vessels will never have negative part counts) // Dirty the cache (real vessels will never have negative part counts)
this.DirtyVessel(vessel); this.DirtyVessel(vessel);
} }
} }
} }
   
// Runs when the player requests a scene change, such as when changing vessels or leaving flight. // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
private void onSceneChange(GameScenes scene) private void onSceneChange(GameScenes scene)
{ {
Tools.PostDebugMessage( Logging.PostDebugMessage(
"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}", "RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}",
HighLogic.LoadedScene, HighLogic.LoadedScene,
scene, scene,
FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
); );
   
if (scene == GameScenes.FLIGHT) if (scene == GameScenes.FLIGHT)
{ {
if (scene == HighLogic.LoadedScene) if (scene == HighLogic.LoadedScene)
{ {
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.", Logging.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
FlightGlobals.ActiveVessel.vesselName); FlightGlobals.ActiveVessel.vesselName);
   
this.onVesselEvent(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
} }
else else
{ {
this.ClearCache(); this.ClearCache();
} }
} }
} }
   
private void onGameLoaded(object data) private void onGameLoaded(object data)
{ {
this.ClearCache(); this.ClearCache();
} }
   
// Runs when parts are undocked // Runs when parts are undocked
private void onPartEvent(Part part) private void onPartEvent(Part part)
{ {
if (part != null && part.vessel != null) if (part != null && part.vessel != null)
{ {
this.onVesselEvent(part.vessel); this.onVesselEvent(part.vessel);
} }
} }
   
// Runs when parts are coupled, as in docking // Runs when parts are coupled, as in docking
private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
this.onPartEvent(data.from); this.onPartEvent(data.from);
this.onPartEvent(data.to); this.onPartEvent(data.to);
} }
   
// Produce a Part-hashed table of relays for the given vessel // Produce a Part-hashed table of relays for the given vessel
private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays) private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
{ {
// We're going to completely regen this table, so dump the current contents. // We're going to completely regen this table, so dump the current contents.
relays.Clear(); relays.Clear();
   
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Getting antenna relays from vessel {1}.", "{0}: Getting antenna relays from vessel {1}.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
double bestRelayRange = double.NegativeInfinity; double bestRelayRange = double.NegativeInfinity;
IAntennaRelay bestRelay = null; IAntennaRelay bestRelay = null;
IAntennaRelay relay; IAntennaRelay relay;
   
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly. // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) { if (vessel.loaded) {
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel {1} is loaded, searching for modules in loaded parts.", "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
Part part; Part part;
for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++) for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
{ {
part = vessel.Parts[partIdx]; part = vessel.Parts[partIdx];
   
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
PartModule module; PartModule module;
for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++) for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++)
{ {
module = part.Modules[modIdx]; module = part.Modules[modIdx];
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
relay = (module as IAntennaRelay); relay = (module as IAntennaRelay);
   
if (relay.maxTransmitDistance > bestRelayRange) if (relay.maxTransmitDistance > bestRelayRange)
{ {
bestRelayRange = relay.maxTransmitDistance; bestRelayRange = relay.maxTransmitDistance;
bestRelay = relay; bestRelay = relay;
} }
   
// ...add the module to the table // ...add the module to the table
relays.Add(relay); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots. // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
else else
{ {
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel {1} is not loaded, searching for modules in prototype parts.", "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
ProtoPartSnapshot pps; ProtoPartSnapshot pps;
for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++) for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
{ {
pps = vessel.protoVessel.protoPartSnapshots[ppsIdx]; pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
   
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}", "{0}: Searching in protopartsnapshot {1}",
this.GetType().Name, this.GetType().Name,
pps pps
)); ));
   
// ...Fetch the prefab, because it's more useful for what we're doing. // ...Fetch the prefab, because it's more useful for what we're doing.
Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab; Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
   
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Got partPrefab {1} in protopartsnapshot {2}", "{0}: Got partPrefab {1} in protopartsnapshot {2}",
this.GetType().Name, this.GetType().Name,
partPrefab, partPrefab,
pps pps
)); ));
   
// ...loop through the PartModules in the prefab... // ...loop through the PartModules in the prefab...
PartModule module; PartModule module;
for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++) for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
{ {
module = partPrefab.Modules[modIdx]; module = partPrefab.Modules[modIdx];
   
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}", "{0}: Searching in partmodule {1}",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: partmodule {1} is antennarelay", "{0}: partmodule {1} is antennarelay",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
relay = new ProtoAntennaRelay(module as IAntennaRelay, pps); relay = new ProtoAntennaRelay(module as IAntennaRelay, pps);
   
if (relay.maxTransmitDistance > bestRelayRange) if (relay.maxTransmitDistance > bestRelayRange)
{ {
bestRelayRange = relay.maxTransmitDistance; bestRelayRange = relay.maxTransmitDistance;
bestRelay = relay; bestRelay = relay;
} }
   
// ...build a new ProtoAntennaRelay and add it to the table // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(relay); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
this.bestRelayTable[vessel.id] = bestRelay; this.bestRelayTable[vessel.id] = bestRelay;
   
Tools.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.", "{0}: vessel '{1}' ({2}) has {3} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName, vessel.vesselName,
vessel.id, vessel.id,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
private RelayDatabase() private RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>(); this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
   
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselEvent); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselEvent); GameEvents.onVesselChange.Add(this.onVesselEvent);
GameEvents.onVesselDestroy.Add(this.onVesselEvent); GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent); GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent); GameEvents.onPartUndock.Add(this.onPartEvent);
GameEvents.onGameStateLoad.Add(this.onGameLoaded); GameEvents.onGameStateLoad.Add(this.onGameLoaded);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselEvent); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselEvent); GameEvents.onVesselChange.Remove(this.onVesselEvent);
GameEvents.onVesselDestroy.Remove(this.onVesselEvent); GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent); GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent); GameEvents.onPartUndock.Remove(this.onPartEvent);
GameEvents.onGameStateLoad.Remove(this.onGameLoaded); GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Logging.PostDebugMessage(this.GetType().Name + " destroyed.");
   
KSPLog.print(string.Format( KSPLog.print(string.Format(
"{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}", "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
this.GetType().Name, this.GetType().Name,
this.cacheHits, this.cacheHits,
this.cacheMisses, this.cacheMisses,
(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits) (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
)); ));
} }
   
#if DEBUG #if DEBUG
public void Dump() public void Dump()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
   
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
var dbEnum = this.relayDatabase.GetEnumerator(); var dbEnum = this.relayDatabase.GetEnumerator();
IList<IAntennaRelay> vesselRelays; IList<IAntennaRelay> vesselRelays;
while (dbEnum.MoveNext()) while (dbEnum.MoveNext())
{ {
sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key); sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
   
vesselRelays = dbEnum.Current.Value; vesselRelays = dbEnum.Current.Value;
IAntennaRelay relay; IAntennaRelay relay;
for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++) for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
relay = vesselRelays[rIdx]; relay = vesselRelays[rIdx];
sb.AppendFormat("\n\t{0}", relay.ToString()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Tools.PostDebugMessage(sb.ToString()); Logging.PostDebugMessage(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
#endif #endif
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// Extensions.cs // Extensions.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; using ToadicusTools.Extensions;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin. /// A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
/// </summary> /// </summary>
public static class RelayExtensions public static class RelayExtensions
{ {
/// <summary> /// <summary>
/// Returns the distance between two IAntennaRelays. /// Returns the distance between two IAntennaRelays.
/// </summary> /// </summary>
/// <param name="relayOne">Relay one.</param> /// <param name="relayOne">Relay one.</param>
/// <param name="relayTwo">Relay two.</param> /// <param name="relayTwo">Relay two.</param>
public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo) public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.vessel.DistanceTo(relayTwo.vessel); return relayOne.vessel.DistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns the distance from this IAntennaRelay to the given CelestialBody /// Returns the distance from this IAntennaRelay to the given CelestialBody
/// </summary> /// </summary>
/// <param name="relay">Relay.</param> /// <param name="relay">Relay.</param>
/// <param name="body">Body.</param> /// <param name="body">Body.</param>
public static double SqrDistanceTo(this IAntennaRelay relay, CelestialBody body) public static double SqrDistanceTo(this IAntennaRelay relay, CelestialBody body)
{ {
double range = relay.vessel.DistanceTo(body) - body.Radius; double range = relay.vessel.DistanceTo(body) - body.Radius;
   
return range * range; return range * range;
} }
   
/// <summary> /// <summary>
/// Returns the distance between two IAntennaRelays. /// Returns the distance between two IAntennaRelays.
/// </summary> /// </summary>
/// <param name="relayOne">Relay one.</param> /// <param name="relayOne">Relay one.</param>
/// <param name="relayTwo">Relay two.</param> /// <param name="relayTwo">Relay two.</param>
public static double SqrDistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo) public static double SqrDistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.vessel.sqrDistanceTo(relayTwo.vessel); return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns the distance from this IAntennaRelay to the given CelestialBody /// Returns the distance from this IAntennaRelay to the given CelestialBody
/// </summary> /// </summary>
/// <param name="relay">Relay.</param> /// <param name="relay">Relay.</param>
/// <param name="body">Body.</param> /// <param name="body">Body.</param>
public static double DistanceTo(this IAntennaRelay relay, CelestialBody body) public static double DistanceTo(this IAntennaRelay relay, CelestialBody body)
{ {
double range = relay.vessel.DistanceTo(body) - body.Radius; double range = relay.vessel.DistanceTo(body) - body.Radius;
   
return range; return range;
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a Vessel /// Returns the distance between this IAntennaRelay and a Vessel
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="Vessel">A <see cref="Vessel"/></param> /// <param name="Vessel">A <see cref="Vessel"/></param>
public static double DistanceTo(this AntennaRelay relay, Vessel Vessel) public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
{ {
return relay.vessel.DistanceTo(Vessel); return relay.vessel.DistanceTo(Vessel);
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a CelestialBody /// Returns the distance between this IAntennaRelay and a CelestialBody
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="body">A <see cref="CelestialBody"/></param> /// <param name="body">A <see cref="CelestialBody"/></param>
public static double DistanceTo(this AntennaRelay relay, CelestialBody body) public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.DistanceTo(body) - body.Radius; return relay.vessel.DistanceTo(body) - body.Radius;
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and another IAntennaRelay /// Returns the distance between this IAntennaRelay and another IAntennaRelay
/// </summary> /// </summary>
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param> /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param> /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo) public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.DistanceTo(relayTwo.vessel); return relayOne.DistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns the square of the distance between this IAntennaRelay and a Vessel /// Returns the square of the distance between this IAntennaRelay and a Vessel
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="vessel">A <see cref="Vessel"/></param> /// <param name="vessel">A <see cref="Vessel"/></param>
public static double SqrDistanceTo(this AntennaRelay relay, Vessel vessel) public static double SqrDistanceTo(this AntennaRelay relay, Vessel vessel)
{ {
return relay.vessel.sqrDistanceTo(vessel); return relay.vessel.sqrDistanceTo(vessel);
} }
   
/// <summary> /// <summary>
/// Returns the square of the distance between this IAntennaRelay and a CelestialBody /// Returns the square of the distance between this IAntennaRelay and a CelestialBody
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="body">A <see cref="CelestialBody"/></param> /// <param name="body">A <see cref="CelestialBody"/></param>
public static double SqrDistanceTo(this AntennaRelay relay, CelestialBody body) public static double SqrDistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
double dist = (relay.vessel.GetWorldPos3D() - body.position).magnitude - body.Radius; double dist = (relay.vessel.GetWorldPos3D() - body.position).magnitude - body.Radius;
   
return dist * dist; return dist * dist;
} }
   
/// <summary> /// <summary>
/// Returns the square of the distance between this IAntennaRelay and another IAntennaRelay /// Returns the square of the distance between this IAntennaRelay and another IAntennaRelay
/// </summary> /// </summary>
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param> /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param> /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
public static double SqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo) public static double SqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.vessel.sqrDistanceTo(relayTwo.vessel); return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel. /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
/// </summary> /// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param> /// <param name="vessel">This <see cref="Vessel"/></param>
public static IList<IAntennaRelay> GetAntennaRelays (this Vessel vessel) public static IList<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
{ {
return RelayDatabase.Instance[vessel]; return RelayDatabase.Instance[vessel];
} }
   
/// <summary> /// <summary>
/// Determines if the specified vessel has a connected relay. /// Determines if the specified vessel has a connected relay.
/// </summary> /// </summary>
/// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns>
/// <param name="vessel"></param> /// <param name="vessel"></param>
public static bool HasConnectedRelay(this Vessel vessel) public static bool HasConnectedRelay(this Vessel vessel)
{ {
IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel]; IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
IAntennaRelay relay; IAntennaRelay relay;
for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++) for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
relay = vesselRelays[rIdx]; relay = vesselRelays[rIdx];
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
return true; return true;
} }
} }
   
return false; return false;
} }
   
/// <summary> /// <summary>
/// Gets the <see cref="AntennaRange.ConnectionStatus"/> for this <see cref="Vessel"/> /// Gets the <see cref="AntennaRange.ConnectionStatus"/> for this <see cref="Vessel"/>
/// </summary> /// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param> /// <param name="vessel">This <see cref="Vessel"/></param>
public static ConnectionStatus GetConnectionStatus(this Vessel vessel) public static ConnectionStatus GetConnectionStatus(this Vessel vessel)
{ {
bool canTransmit = false; bool canTransmit = false;
   
IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel]; IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
IAntennaRelay relay; IAntennaRelay relay;
for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++) for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
relay = vesselRelays[rIdx]; relay = vesselRelays[rIdx];
if (relay.LinkStatus > ConnectionStatus.None) if (relay.LinkStatus > ConnectionStatus.None)
{ {
canTransmit = true; canTransmit = true;
   
if (relay.LinkStatus == ConnectionStatus.Optimal) if (relay.LinkStatus == ConnectionStatus.Optimal)
{ {
return ConnectionStatus.Optimal; return ConnectionStatus.Optimal;
} }
} }
} }
   
if (canTransmit) if (canTransmit)
{ {
return ConnectionStatus.Suboptimal; return ConnectionStatus.Suboptimal;
} }
else else
{ {
return ConnectionStatus.None; return ConnectionStatus.None;
} }
} }
   
/// <summary> /// <summary>
/// Gets the best relay on this Vessel. The best relay may not be able to transmit. /// Gets the best relay on this Vessel. The best relay may not be able to transmit.
/// </summary> /// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param> /// <param name="vessel">This <see cref="Vessel"/></param>
public static IAntennaRelay GetBestRelay(this Vessel vessel) public static IAntennaRelay GetBestRelay(this Vessel vessel)
{ {
return RelayDatabase.Instance.GetBestVesselRelay(vessel); return RelayDatabase.Instance.GetBestVesselRelay(vessel);
} }
   
/// <summary> /// <summary>
/// Logs a message on behalf of this relay /// Logs a message on behalf of this relay
/// </summary> /// </summary>
public static void Log(this AntennaRelay relay, string format, params object[] args) public static void Log(this AntennaRelay relay, string format, params object[] args)
{ {
Tools.PostLogMessage(string.Format("[{0}] {1}", relay.ToString(), format), args); ToadicusTools.Logging.PostLogMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
} }
   
/// <summary> /// <summary>
/// Logs a message on behalf of this relay /// Logs a message on behalf of this relay
/// </summary> /// </summary>
public static void Log(this AntennaRelay relay, string msg) public static void Log(this AntennaRelay relay, string msg)
{ {
Tools.PostLogMessage("[{0}] {1}", relay.ToString(), msg); ToadicusTools.Logging.PostLogMessage("[{0}] {1}", relay.ToString(), msg);
} }
   
/// <summary> /// <summary>
/// Logs a warning message on behalf of this relay /// Logs a warning message on behalf of this relay
/// </summary> /// </summary>
public static void LogWarning(this AntennaRelay relay, string format, params object[] args) public static void LogWarning(this AntennaRelay relay, string format, params object[] args)
{ {
Tools.PostWarningMessage(string.Format("[{0}] {1}", relay.ToString(), format), args); ToadicusTools.Logging.PostWarningMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
} }
   
/// <summary> /// <summary>
/// Logs a warning message on behalf of this relay /// Logs a warning message on behalf of this relay
/// </summary> /// </summary>
public static void LogWarning(this AntennaRelay relay, string msg) public static void LogWarning(this AntennaRelay relay, string msg)
{ {
Tools.PostWarningMessage("[{0}] {1}", relay.ToString(), msg); ToadicusTools.Logging.PostWarningMessage("[{0}] {1}", relay.ToString(), msg);
} }
   
/// <summary> /// <summary>
/// Logs an error message on behalf of this relay /// Logs an error message on behalf of this relay
/// </summary> /// </summary>
public static void LogError(this AntennaRelay relay, string format, params object[] args) public static void LogError(this AntennaRelay relay, string format, params object[] args)
{ {
Tools.PostErrorMessage(string.Format("[{0}] {1}", relay.ToString(), format), args); ToadicusTools.Logging.PostErrorMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
} }
   
/// <summary> /// <summary>
/// Logs an error message on behalf of this relay /// Logs an error message on behalf of this relay
/// </summary> /// </summary>
public static void LogError(this AntennaRelay relay, string msg) public static void LogError(this AntennaRelay relay, string msg)
{ {
Tools.PostErrorMessage("[{0}] {1}", relay.ToString(), msg); ToadicusTools.Logging.PostErrorMessage("[{0}] {1}", relay.ToString(), msg);
} }
   
/// <summary> /// <summary>
/// Logs a debug-only message on behalf of this relay /// Logs a debug-only message on behalf of this relay
/// </summary> /// </summary>
[System.Diagnostics.Conditional("DEBUG")] [System.Diagnostics.Conditional("DEBUG")]
public static void LogDebug(this AntennaRelay relay, string format, params object[] args) public static void LogDebug(this AntennaRelay relay, string format, params object[] args)
{ {
Tools.PostDebugMessage(string.Format("[{0}] {1}", relay.ToString(), format), args); ToadicusTools.Logging.PostDebugMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
} }
   
/// <summary> /// <summary>
/// Logs a debug-only message on behalf of this relay /// Logs a debug-only message on behalf of this relay
/// </summary> /// </summary>
[System.Diagnostics.Conditional("DEBUG")] [System.Diagnostics.Conditional("DEBUG")]
public static void LogDebug(this AntennaRelay relay, string msg) public static void LogDebug(this AntennaRelay relay, string msg)
{ {
Tools.PostDebugMessage("[{0}] {1}", relay.ToString(), msg); ToadicusTools.Logging.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
} }
} }
   
#pragma warning disable 1591 #pragma warning disable 1591
/// <summary> /// <summary>
/// An Enum describing the connection status of a vessel or relay. /// An Enum describing the connection status of a vessel or relay.
/// </summary> /// </summary>
public enum ConnectionStatus public enum ConnectionStatus
{ {
None, None,
Suboptimal, Suboptimal,
Optimal Optimal
} }
} }