Create README.md
Create README.md

Just the part dev portion so far, taken from http://goo.gl/3scCXl

// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
   
using KSP; using KSP;
using System; using System;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <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 REQUIRE_LOS_KEY = "requireLineOfSight";
  private const string GRACE_RATIO_KEY = "graceRatio";
  private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
  private const string FIXED_POWER_KEY = "fixedPowerCost";
  private const string PRETTY_LINES_KEY = "drawPrettyLines";
  private const string UPDATE_DELAY_KEY = "updateDelay";
   
/// <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;
  }
   
  public static long UpdateDelay
  {
  get;
private set; private set;
} }
   
#pragma warning disable 1591 #pragma warning disable 1591
   
private bool showConfigWindow; private bool showConfigWindow;
private Rect configWindowPos; private Rect configWindowPos;
   
  private string updateDelayStr;
  private long updateDelay;
   
private IButton toolbarButton; private IButton toolbarButton;
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
   
private System.Version runningVersion; private System.Version runningVersion;
   
private KSP.IO.PluginConfiguration _config; private KSP.IO.PluginConfiguration _config;
private KSP.IO.PluginConfiguration config private KSP.IO.PluginConfiguration config
{ {
get get
{ {
if (this._config == null) if (this._config == null)
{ {
this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>(); this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
} }
   
return this._config; return this._config;
} }
} }
   
public void Awake() public void Awake()
{ {
Tools.PostDebugMessage(this, "Waking up."); Tools.PostDebugMessage(this, "Waking up.");
   
this.runningVersion = this.GetType().Assembly.GetName().Version; this.runningVersion = this.GetType().Assembly.GetName().Version;
   
this.showConfigWindow = false; this.showConfigWindow = false;
this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15); this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
   
   
this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos); this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
   
ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false); ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
   
ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d)); ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio; ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
   
ARConfiguration.RequireConnectionForControl = ARConfiguration.RequireConnectionForControl =
this.LoadConfigValue("requireConnectionForControl", false); this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
   
ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false); ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
   
ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true); ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
   
  ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
   
  this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
   
Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion)); Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
   
Tools.PostDebugMessage(this, "Awake."); Tools.PostDebugMessage(this, "Awake.");
} }
   
public void OnGUI() public void OnGUI()
{ {
// Only runs once, if the Toolbar is available. // Only runs once, if the Toolbar is available.
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
if (this.toolbarButton == null) if (this.toolbarButton == null)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button."); Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
   
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
this.toolbarButton.Text = "AR"; this.toolbarButton.Text = "AR";
this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon"; this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst; this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
this.toolbarButton.OnClick += delegate(ClickEvent e) this.toolbarButton.OnClick += delegate(ClickEvent e)
{ {
this.toggleConfigWindow(); this.toggleConfigWindow();
}; };
} }
} }
else if (this.appLauncherButton == null && ApplicationLauncher.Ready) else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button."); Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
   
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
this.toggleConfigWindow, this.toggleConfigWindow,
this.toggleConfigWindow, this.toggleConfigWindow,
ApplicationLauncher.AppScenes.SPACECENTER, ApplicationLauncher.AppScenes.SPACECENTER,
GameDatabase.Instance.GetTexture( GameDatabase.Instance.GetTexture(
"AntennaRange/Textures/appLauncherIcon", "AntennaRange/Textures/appLauncherIcon",
false false
) )
); );
} }
   
if (this.showConfigWindow) if (this.showConfigWindow)
{ {
Rect configPos = GUILayout.Window(354163056, Rect configPos = GUILayout.Window(354163056,
this.configWindowPos, this.configWindowPos,
this.ConfigWindow, this.ConfigWindow,
string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor), string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
GUILayout.ExpandHeight(true), GUILayout.ExpandHeight(true),
GUILayout.ExpandWidth(true) GUILayout.ExpandWidth(true)
); );
   
configPos = Tools.ClampRectToScreen(configPos, 20); configPos = Tools.ClampRectToScreen(configPos, 20);
   
if (configPos != this.configWindowPos) if (configPos != this.configWindowPos)
{ {
this.configWindowPos = configPos; this.configWindowPos = configPos;
this.SaveConfigValue("configWindowPos", this.configWindowPos); this.SaveConfigValue(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 = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
if (requireLineOfSight != ARConfiguration.RequireLineOfSight) if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
{ {
ARConfiguration.RequireLineOfSight = requireLineOfSight; ARConfiguration.RequireLineOfSight = requireLineOfSight;
this.SaveConfigValue("requireLineOfSight", requireLineOfSight); this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireConnectionForControl = bool requireConnectionForControl =
GUITools.Toggle( GUITools.Toggle(
ARConfiguration.RequireConnectionForControl, ARConfiguration.RequireConnectionForControl,
"Require Connection for Probe Control" "Require Connection for Probe Control"
); );
if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl) if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
{ {
ARConfiguration.RequireConnectionForControl = requireConnectionForControl; ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl); this.SaveConfigValue(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 = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
if (fixedPowerCost != ARConfiguration.FixedPowerCost) if (fixedPowerCost != ARConfiguration.FixedPowerCost)
{ {
ARConfiguration.FixedPowerCost = fixedPowerCost; ARConfiguration.FixedPowerCost = fixedPowerCost;
this.SaveConfigValue("fixedPowerCost", fixedPowerCost); this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines"); bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
if (prettyLines != ARConfiguration.PrettyLines) if (prettyLines != ARConfiguration.PrettyLines)
{ {
ARConfiguration.PrettyLines = prettyLines; ARConfiguration.PrettyLines = prettyLines;
this.SaveConfigValue("drawPrettyLines", prettyLines); this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
  GUILayout.BeginHorizontal();
   
  GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
   
  this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
   
  GUILayout.Label("ms", GUILayout.ExpandWidth(false));
   
  GUILayout.EndHorizontal();
   
  if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
  {
  ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
  this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
  }
   
if (requireLineOfSight) if (requireLineOfSight)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio); double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio);
double newRatio; double newRatio;
   
GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio)); GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true)); newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
newRatio = Math.Round(newRatio, 2); newRatio = Math.Round(newRatio, 2);
   
if (newRatio != graceRatio) if (newRatio != graceRatio)
{ {
ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio); ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
this.SaveConfigValue("graceRatio", newRatio); this.SaveConfigValue(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);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
} }
} }
   
protected void onSceneChangeRequested(GameScenes scene) protected void onSceneChangeRequested(GameScenes scene)
{ {
if (scene != GameScenes.SPACECENTER) if (scene != GameScenes.SPACECENTER)
{ {
print("ARConfiguration: Requesting Destruction."); print("ARConfiguration: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
} }
   
private void toggleConfigWindow() private void toggleConfigWindow()
{ {
this.showConfigWindow = !this.showConfigWindow; this.showConfigWindow = !this.showConfigWindow;
  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;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour public class ARFlightController : MonoBehaviour
{ {
#region Fields #region Fields
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 Tools.DebugLogger 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 = Tools.DebugLogger.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.Enabled = false; 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);
} }
   
private void Start() private void Start()
{ {
this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>(); this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
} }
   
private void FixedUpdate() private void FixedUpdate()
{ {
if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)  
{  
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(  
ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,  
this.appLauncherTextures[ConnectionStatus.None]  
);  
}  
   
this.log.Clear(); this.log.Clear();
   
VesselCommand availableCommand; VesselCommand availableCommand;
   
if (ARConfiguration.RequireConnectionForControl) if (ARConfiguration.RequireConnectionForControl)
{ {
availableCommand = this.vessel.CurrentCommand(); availableCommand = this.vessel.CurrentCommand();
} }
else else
{ {
availableCommand = VesselCommand.Crew; availableCommand = VesselCommand.Crew;
} }
   
log.AppendFormat("availableCommand: {0}\n\t" + log.AppendFormat("availableCommand: {0}\n\t" +
"(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" + "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
"(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" + "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
"vessel.HasConnectedRelay(): {3}", "vessel.HasConnectedRelay(): {3}",
(int)availableCommand, (int)availableCommand,
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew, (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe, (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
vessel.HasConnectedRelay() vessel.HasConnectedRelay()
); );
   
// If we are requiring a connection for control, the vessel does not have any adequately staffed pods, // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
// and the vessel does not have any connected relays... // and the vessel does not have any connected relays...
if ( if (
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
ARConfiguration.RequireConnectionForControl && ARConfiguration.RequireConnectionForControl &&
this.vessel != null && this.vessel != null &&
this.vessel.vesselType != VesselType.EVA && this.vessel.vesselType != VesselType.EVA &&
!( !(
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew || (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay() (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
)) ))
{ {
// ...and if the controls are not currently locked... // ...and if the controls are not currently locked...
if (currentControlLock == ControlTypes.None) if (currentControlLock == ControlTypes.None)
{ {
// ...lock the controls. // ...lock the controls.
InputLockManager.SetControlLock(this.lockSet, this.lockID); InputLockManager.SetControlLock(this.lockSet, this.lockID);
} }
} }
// ...otherwise, if the controls are locked... // ...otherwise, if the controls are locked...
else if (currentControlLock != ControlTypes.None) else if (currentControlLock != ControlTypes.None)
{ {
// ...unlock the controls. // ...unlock the controls.
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
   
log.Print(); log.Print();
} }
   
private void Update() private void Update()
{ {
if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 16L) if (this.toolbarButton != null)
  {
  this.toolbarButton.Enabled = MapView.MapIsEnabled;
  }
   
  if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
  {
  this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
  this.buttonToggle, this.buttonToggle,
  ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
  this.appLauncherTextures[ConnectionStatus.None]
  );
  }
   
  if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
{ {
this.updateTimer.Restart(); this.updateTimer.Restart();
} }
else else
{ {
return; return;
} }
   
this.log.Clear(); this.log.Clear();
   
if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ActiveVessel != null) if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ActiveVessel != null)
{ {
Vessel vessel; Vessel vessel;
IAntennaRelay relay; IAntennaRelay relay;
IList<IAntennaRelay> activeVesselRelays; IList<IAntennaRelay> activeVesselRelays;
   
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("Fetching best relay for vessel {0}", vessel); log.AppendFormat("Fetching best relay for vessel {0}", vessel);
   
relay = vessel.GetBestRelay(); relay = vessel.GetBestRelay();
   
if (relay != null) if (relay != null)
{ {
log.AppendFormat("Finding nearest relay for best relay {0}", relay); log.AppendFormat("Finding nearest relay for best relay {0}", relay);
   
relay.FindNearestRelay(); relay.FindNearestRelay();
} }
} }
   
activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel]; activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++) for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
{ {
relay = activeVesselRelays[rIdx]; relay = activeVesselRelays[rIdx];
   
relay.FindNearestRelay(); relay.FindNearestRelay();
} }
   
if (this.toolbarButton != null || this.appLauncherButton != null) if (this.toolbarButton != null || this.appLauncherButton != null)
{ {
log.Append("Checking vessel relay status.\n"); log.Append("Checking vessel relay status.\n");
   
this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus(); this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
   
log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}", log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
this.currentConnectionStatus, this.currentConnectionTexture); this.currentConnectionStatus, this.currentConnectionTexture);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.TexturePath = this.currentConnectionTexture; this.toolbarButton.TexturePath = this.currentConnectionTexture;
   
if (this.currentConnectionStatus == ConnectionStatus.None) if (this.currentConnectionStatus == ConnectionStatus.None)
{ {
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(); log.Print();
} }
   
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()
  {
  if (MapView.MapIsEnabled)
  {
  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
// //
// 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;
   
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;
} }
} }
   
private bool canTransmit; private bool canTransmit;
private bool isChecked; private bool isChecked;
   
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 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 transmitDistance public double transmitDistance
{ {
get get
{ {
if (this.KerbinDirect || this.targetRelay == null) if (this.KerbinDirect || this.targetRelay == null)
{ {
return this.DistanceTo(Kerbin); return this.DistanceTo(Kerbin);
} }
else else
{ {
return this.DistanceTo(this.targetRelay); return this.DistanceTo(this.targetRelay);
} }
} }
} }
   
/// <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>
/// 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>
/// 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; Tools.DebugLogger log;
#if DEBUG #if DEBUG
log = Tools.DebugLogger.New(this); log = Tools.DebugLogger.New(this);
#endif #endif
   
// Skip vessels that have already been checked for a nearest relay this pass. // Skip vessels that have already been checked for a nearest relay this pass.
if (this.isChecked) if (this.isChecked)
{ {
log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.", log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
this); this);
log.Print(); log.Print();
return; return;
} }
   
log.AppendFormat("{0}: Target search started).", this.ToString()); log.AppendFormat("{0}: Target search started).", this.ToString());
   
#if DEBUG #if DEBUG
try { try {
#endif #endif
// Set this vessel as checked, so that we don't check it again. // Set this vessel as checked, so that we don't check it again.
this.isChecked = true; this.isChecked = true;
   
// 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;
   
CelestialBody bodyOccludingBestOccludedRelay = null; CelestialBody bodyOccludingBestOccludedRelay = null;
IAntennaRelay needle; IAntennaRelay needle;
   
double nearestRelaySqrDistance = double.PositiveInfinity; double nearestRelaySqrDistance = double.PositiveInfinity;
double bestOccludedSqrDistance = double.PositiveInfinity; double bestOccludedSqrDistance = double.PositiveInfinity;
double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
   
/* /*
* Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
* far away. When we find a candidate, get through its antennae for relays which have not been checked yet * far away. When we find a candidate, get through its antennae for relays which have not been checked yet
* and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison * and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison
* against future finds. * against future finds.
* */ * */
Vessel potentialVessel; Vessel potentialVessel;
IAntennaRelay potentialBestRelay; IAntennaRelay potentialBestRelay;
CelestialBody fob; CelestialBody fob;
   
// IList<IAntennaRelay> vesselRelays; // IList<IAntennaRelay> vesselRelays;
for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++) for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
{ {
log.AppendFormat("\nFetching vessel at index {0}", vIdx); log.AppendFormat("\nFetching vessel at index {0}", vIdx);
potentialVessel = FlightGlobals.Vessels[vIdx]; potentialVessel = FlightGlobals.Vessels[vIdx];
if (potentialVessel == null) if (potentialVessel == null)
{ {
Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx); Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx); log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
log.Print(); log.Print();
return; return;
} }
#if DEBUG #if DEBUG
else else
{ {
log.AppendFormat("\n\tGot vessel {0}", potentialVessel); log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
} }
#endif #endif
   
// Skip vessels of the wrong type. // Skip vessels of the wrong type.
log.Append("\n\tchecking vessel type"); log.Append("\n\tchecking vessel type");
switch (potentialVessel.vesselType) switch (potentialVessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.Flag: case VesselType.Flag:
case VesselType.EVA: case VesselType.EVA:
case VesselType.SpaceObject: case VesselType.SpaceObject:
case VesselType.Unknown: case VesselType.Unknown:
log.Append("\n\tSkipping because vessel is the wrong type."); log.Append("\n\tSkipping because vessel is the wrong type.");
continue; continue;
default: default:
break; break;
} }
log.Append("\n\tchecking if vessel is this vessel"); log.Append("\n\tchecking if vessel is this vessel");
// Skip vessels with the wrong ID // Skip vessels with the wrong ID
if (potentialVessel.id == vessel.id) if (potentialVessel.id == vessel.id)
{ {
log.Append("\n\tSkipping because vessel is this vessel."); log.Append("\n\tSkipping because vessel is this vessel.");
continue; continue;
} }
   
// Find the distance from here to the vessel... // Find the distance from here to the vessel...
log.Append("\n\tgetting distance to potential vessel"); log.Append("\n\tgetting distance to potential vessel");
double potentialSqrDistance = this.sqrDistanceTo(potentialVessel); double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
log.Append("\n\tgetting best vessel relay"); log.Append("\n\tgetting best vessel relay");
   
potentialBestRelay = potentialVessel.GetBestRelay(); potentialBestRelay = potentialVessel.GetBestRelay();
log.AppendFormat("\n\t\tgot best vessel relay {0}", log.AppendFormat("\n\t\tgot best vessel relay {0}",
potentialBestRelay == null ? "null" : potentialBestRelay.ToString()); potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
   
if (potentialBestRelay == null) if (potentialBestRelay == null)
{ {
log.Append("\n\t\t...skipping null relay"); log.Append("\n\t\t...skipping null relay");
continue; continue;
} }
   
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(potentialVessel, out fob, ARConfiguration.RadiusRatio) !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
) )
{ {
log.Append("\n\t\t...failed LOS check"); log.Append("\n\t\t...failed LOS check");
   
log.AppendFormat("\n\t\t\t{0}: Vessel {1} not in line of sight.", log.AppendFormat("\n\t\t\t{0}: Vessel {1} not in line of sight.",
this.ToString(), potentialVessel.vesselName); this.ToString(), potentialVessel.vesselName);
log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance); log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
log.AppendFormat("\n\t\t\tbestOccludedSqrDistance: {0}", bestOccludedSqrDistance); log.AppendFormat("\n\t\t\tbestOccludedSqrDistance: {0}", bestOccludedSqrDistance);
log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance); log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxTransmitSqrDistance);
   
if ( if (
(potentialSqrDistance < bestOccludedSqrDistance) && (potentialSqrDistance < bestOccludedSqrDistance) &&
(potentialSqrDistance < maxTransmitSqrDistance) && (potentialSqrDistance < maxTransmitSqrDistance) &&
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;
bestOccludedSqrDistance = potentialSqrDistance; bestOccludedSqrDistance = potentialSqrDistance;
} }
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;
} }
   
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 (potentialSqrDistance > nearestRelaySqrDistance) if (potentialSqrDistance > nearestRelaySqrDistance)
{ {
log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.", log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
this.ToString(), this.ToString(),
potentialVessel.vesselName potentialVessel.vesselName
); );
continue; continue;
} }
   
log.Append("\n\t\t...passed distance check"); log.Append("\n\t\t...passed distance check");
   
if (potentialBestRelay.CanTransmit()) if (potentialBestRelay.CanTransmit())
{ {
needle = potentialBestRelay; needle = potentialBestRelay;
bool isCircular = false; bool isCircular = false;
   
  int iterCount = 0;
while (needle != null) while (needle != null)
{ {
  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;
  break;
  }
   
  // Avoid infinite loops when we're not catching things right.
  if (iterCount > FlightGlobals.Vessels.Count)
  {
  Tools.PostErrorMessage(
  "[{0}] iterCount exceeded while checking for circular network; assuming it is circular" +
  "\n\tneedle={1}" +
  "\n\tthis.moduleRef={2}",
  this,
  needle == null ? "null" : string.Format(
  "{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
  needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
  "{0}\n\tneedle.targetRelay.vessel={1}",
  needle.targetRelay,
  needle.targetRelay.vessel == null ?
  "null" : needle.targetRelay.vessel.vesselName
  )
  ),
  this.moduleRef == null ? "null" : this.moduleRef.ToString()
  );
isCircular = true; isCircular = true;
break; break;
} }
   
needle = needle.targetRelay; needle = needle.targetRelay;
} }
   
if (!isCircular) if (!isCircular)
{ {
nearestRelaySqrDistance = potentialSqrDistance; nearestRelaySqrDistance = potentialSqrDistance;
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(nearestRelaySqrDistance) Math.Sqrt(nearestRelaySqrDistance)
); );
} }
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
); );
} }
} }
} }
   
CelestialBody bodyOccludingKerbin = null; CelestialBody bodyOccludingKerbin = null;
   
double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius; double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
kerbinSqrDistance *= kerbinSqrDistance; kerbinSqrDistance *= kerbinSqrDistance;
   
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}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)", "\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
this, this,
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, nearestRelaySqrDistance,
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
bestOccludedSqrDistance, bestOccludedSqrDistance,
kerbinSqrDistance kerbinSqrDistance
); );
   
// If we don't have LOS to Kerbin, focus on relays // If we don't have LOS to Kerbin, focus on relays
if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)) if (
  ARConfiguration.RequireLineOfSight &&
  !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
  )
{ {
log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName); log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist. // nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
// Therefore, this will only be true if a valid relay is in range. // Therefore, this will only be true if a valid relay is in range.
if (nearestRelaySqrDistance <= maxTransmitSqrDistance) if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\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(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.canTransmit = true; this.canTransmit = true;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
else else
{ {
log.AppendFormat("\n\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(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
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 (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
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 (nearestRelaySqrDistance < bestOccludedSqrDistance) if (nearestRelaySqrDistance < bestOccludedSqrDistance)
{ {
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.",
nearestRelaySqrDistance, bestOccludedSqrDistance); nearestRelaySqrDistance, bestOccludedSqrDistance);
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.",
nearestRelaySqrDistance, bestOccludedSqrDistance); nearestRelaySqrDistance, bestOccludedSqrDistance);
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 (nearestRelaySqrDistance < kerbinSqrDistance) if (nearestRelaySqrDistance < kerbinSqrDistance)
{ {
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.",
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
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.",
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
this.KerbinDirect = true; this.KerbinDirect = true;
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
{ {
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 (nearestRelaySqrDistance <= maxTransmitSqrDistance) if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
{ {
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(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
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 (nearestRelaySqrDistance < kerbinSqrDistance) if (nearestRelaySqrDistance < kerbinSqrDistance)
{ {
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(),
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
   
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(),
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
   
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(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
// If Kerbin is in range, use it. // If Kerbin is in range, use it.
if (kerbinSqrDistance <= maxTransmitSqrDistance) if (kerbinSqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).", log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = true; this.canTransmit = true;
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
// If Kerbin is out of range and the nearest relay is out of range, pick a second best between // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
// Kerbin and bestOccludedRelay // Kerbin and bestOccludedRelay
else else
{ {
log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).", log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, 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 (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
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 (nearestRelaySqrDistance < bestOccludedSqrDistance) if (nearestRelaySqrDistance < bestOccludedSqrDistance)
{ {
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.",
nearestRelaySqrDistance, bestOccludedSqrDistance); nearestRelaySqrDistance, bestOccludedSqrDistance);
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.",
nearestRelaySqrDistance, bestOccludedSqrDistance); nearestRelaySqrDistance, bestOccludedSqrDistance);
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 (nearestRelaySqrDistance < kerbinSqrDistance) if (nearestRelaySqrDistance < kerbinSqrDistance)
{ {
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.",
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
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.",
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
} }
} }
} }
} }
   
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
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
this.isChecked = false; this.isChecked = false;
} }
   
/// <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;
this.isChecked = false; this.isChecked = false;
   
Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString()); Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
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 : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{ {
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
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;
   
/// <summary> /// <summary>
/// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost /// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
/// and packetSize. /// and packetSize.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public double nominalRange; public double nominalRange;
   
/// <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>
/// 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 Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
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 (
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot.pVesselRef != null &&
  this.part.protoPartSnapshot.pVesselRef.vesselRef != null
  )
  {
  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.");
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 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 transmitDistance public double transmitDistance
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return double.PositiveInfinity; return double.PositiveInfinity;
} }
   
return this.relay.transmitDistance; return this.relay.transmitDistance;
} }
} }
   
/// <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
{ {
return this.nominalRange; return this.nominalRange;
} }
} }
   
/// <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 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 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;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0} loaded:\n" + "{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalRange: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this.name,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
this.nominalRange, this.nominalRange,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
/// <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);
   
if (state >= StartState.PreLaunch) if (state >= StartState.PreLaunch)
{ {
  this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
   
this.relay = new AntennaRelay(this); this.relay = new AntennaRelay(this);
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
this.relay.nominalTransmitDistance = this.nominalRange; this.relay.nominalTransmitDistance = this.nominalRange;
   
this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m"; this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
} }
} }
   
/// <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.nominalRange; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
} }
   
/// <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()
{ {
string text = base.GetInfo(); string text = base.GetInfo();
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n"; text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n"; text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
return text; return text;
} }
   
/// <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( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.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); Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = Tools.DebugLogger.New(this); var logger = Tools.DebugLogger.New(this);
   
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(); StringBuilder sb = Tools.GetStringBuilder();
   
sb.Append('['); sb.Append('[');
sb.Append(this.part.partInfo.title); sb.Append(this.part.partInfo.title);
sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
sb.Append("Data to be discarded:\n"); sb.Append("Data to be discarded:\n");
   
ScienceData data; ScienceData data;
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++) for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
data = dataQueue[dIdx]; data = dataQueue[dIdx];
sb.AppendFormat("\t{0}\n", data.title); sb.AppendFormat("\t{0}\n", data.title);
} }
   
ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
   
this.PostCannotTransmitError(); this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
/// <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 ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
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)
{ {
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = "Connected"; this.UIrelayStatus = "Connected";
this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
this.UImaxTransmitDistance = "N/A";  
this.UIpacketSize = "N/A"; this.UIpacketSize = "N/A";
this.UIpacketCost = "N/A"; this.UIpacketCost = "N/A";
} }
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
this.UIrelayTarget = AntennaRelay.Kerbin.bodyName; this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
} }
else else
{ {
this.UIrelayTarget = this.targetRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
} }
} }
   
/// <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(); StringBuilder sb = Tools.GetStringBuilder();
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(" on ");
sb.Append(vessel.vesselName); sb.Append(vessel.vesselName);
} }
else if ( else if (
this.part != null && this.part != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null this.part.protoPartSnapshot.pVesselRef != null
) )
{ {
sb.Append(" on "); sb.Append(" on ");
sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName); sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
Tools.PutStringBuilder(sb); 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); Tools.PostDebugMessage(this.GetType().Name + ": " + 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.transmitDistance <= this.nominalRange) if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
{ {
base.packetResourceCost = this._basepacketResourceCost; base.packetResourceCost = this._basepacketResourceCost;
} }
else else
{ {
float rangeFactor = (float)(this.transmitDistance / this.nominalRange); float rangeFactor = (float)(this.transmitDistance / this.nominalRange);
rangeFactor *= rangeFactor; rangeFactor *= rangeFactor;
   
base.packetResourceCost = this._basepacketResourceCost base.packetResourceCost = this._basepacketResourceCost
* rangeFactor; * 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.transmitDistance >= this.nominalRange) if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
{ {
base.packetSize = this._basepacketSize; base.packetSize = this._basepacketSize;
} }
else else
{ {
float rangeFactor = (float)(this.nominalRange / this.transmitDistance); float rangeFactor = (float)(this.nominalRange / this.transmitDistance);
rangeFactor *= rangeFactor; rangeFactor *= rangeFactor;
   
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(); StringBuilder sb = Tools.GetStringBuilder();
string msg; string msg;
   
sb.Append("["); sb.Append("[");
sb.Append(base.part.partInfo.title); sb.Append(base.part.partInfo.title);
sb.Append("]: "); sb.Append("]: ");
   
sb.Append("Beginning transmission "); sb.Append("Beginning transmission ");
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
sb.Append("directly to Kerbin."); sb.Append("directly to Kerbin.");
} }
else else
{ {
sb.Append("via "); sb.Append("via ");
sb.Append(this.relay.targetRelay); sb.Append(this.relay.targetRelay);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
Tools.PutStringBuilder(sb); 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.8.*")] [assembly: AssemblyVersion("1.9.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("")]
   
file:b/README.md (new)
  # AntennaRange
  A KSP mod that enforces and encourages the use of the bigger antennas.
 
  # For Part Developers
  ## The Fields
  AntennaRange extends and augments the functionality of the stock ModuleDataTransmitter through the new ModuleLimitedDataTransmitter class. This class uses five additional configurable fields to define the part's behavior.
 
  nominalRange is the range, in meters, at which the part should function identically to the stock module, i.e. without any modification to the power cost or packet size. This is used along with maxPowerFactor to calculate the maximum range of the part.
  simpleRange is the same as nominalRange, but is used when the mod is in "simple" mode. In general it will probably need to be a much larger number than nominalRange.
  maxPowerFactor effectively sets the maximum range of the antenna by essentially capping how much the power may be "turned up" to get better range. I originally used 8 for this number, because it felt right. I've since used 4 (for my DTS) and 16 (for my Comm. 88-88). You don't want this number to be too high, or small probes will go uncontrollable a lot when transmitting.
  maxDataFactor defines the maximum "speed up" bonus that comes from using antennas at less their nominal range. I originally used 4 for this number for all parts; the DTS has a higher bonus now.
 
  Note that all of the fields needed for Squad's ModuleDataTransmitter still need to be filled out. Depending on how you're defining your parts, they might need to go in your AntennaRange patch, or they might already be defined on the base part.
 
  ## The Mechanic
  In general, the scaling functions assume the relation `D² α P/R,` where D is the total transmission distance, P is the transmission power, and R is the data rate. Data rate increases as range decreases below nominalRange: `R α nominalRange² / D²`. By default, power use increases as range increases above nominalRange: `P α D² / nominalRange²`. Optionally, power use may remain fixed, and data rate instead decreases as range increases above nominalRange: `R α nominalRange² / D²`.
 
  ## Patch Conventions
  To maximize cross-compatibility, please consider the following conventions for ModuleManager patches regarding AntennaRange:
 
  When providing new definitions for your own parts, always specify a :FOR[YourModHere] pass name.
  Whenever changing default AntennaRange definitions (e.g. if you were going to rebalance my antennas to suit your mod), please do so in the :AFTER[AntennaRange] pass.
  I recommend providing all optional functionality (e.g. enabling RemoteTech vs. AntennaRange modules) in separate patches using :NEEDS[] blocks.
 
  A sample AntennaRange configuration for an all-new mod part might look like this:
  ```
  @PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
  {
  MODULE
  {
  // ### Module Definition ###
  name = ModuleLimitedDataTransmitter
 
  // ### Squad Definitions ###
  // Delay between transmission packets, in seconds
  packetInterval = 0.10
 
  // Data capacity of nominal transmission packets, in MiT
  packetSize = 2
 
  // Resource cost of nominal transmission packets, in units
  packetResourceCost = 20.0
 
  // Resource name to be consumed by transmission
  requiredResource = ElectricCharge
 
  // Animation module index, 0-based, of the antenna extend/retract animation
  DeployFxModules = 0
 
  // ### AntennaRange Defintions ###
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "additive" ranges.
  nominalRange = 10000000000
 
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "simple" ranges.
  simpleRange = 56250000000
 
  // The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
  // transmitter. Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
  maxPowerFactor = 16
 
  // The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
  // transmitter.
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
  ```
 
  This example assumes that the base part definition does not include a ModuleDataTransmitter module, or any RT modules. If the base part definition includes a ModuleDataTransmitter module, a sample AntennaRange patch could look like this:
  ```
  @PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 10000000000
  simpleRange = 56250000000
  maxPowerFactor = 16
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
  ```
 
  IIRC, RemoteTech parts should not have ModuleDataTransmitter definitions. In that case, to facilitate RT, AR, and Stock compatibility, a suite of patches like this might be appropriate:
 
  ```
  // If we don't have RemoteTech, add a stock ModuleDataTransmitter first.
  @PART[modPartName]:NEEDS[!RemoteTech]:BEFORE[YourModName]
  {
  MODULE
  {
  // ### Module Definition ###
  name = ModuleDataTransmitter
 
  // ### Squad Definitions ###
  // Delay between transmission packets, in seconds
  packetInterval = 0.10
 
  // Data capacity of nominal transmission packets, in MiT
  packetSize = 2
 
  // Resource cost of nominal transmission packets, in units
  packetResourceCost = 20.0
 
  // Resource name to be consumed by transmission
  requiredResource = ElectricCharge
 
  // Animation module index, 0-based, of the antenna extend/retract animation
  DeployFxModules = 0
  }
  }
 
  // If AntennaRange is installed, convert that to a ModuleLimitedDataTransmitter
  @PART[modPartName]:NEEDS[AntennaRange,!RemoteTech]:FOR[YourModName]
  {
  @MODULE[ModuleDataTransmitter]
  {
  // ### Module Redefinition ###
  @name = ModuleLimitedDataTransmitter
 
  // ### AntennaRange Defintions ###
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "additive" ranges.
  nominalRange = 10000000000
 
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "simple" ranges.
  simpleRange = 56250000000
 
  // The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
  // transmitter. Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
  maxPowerFactor = 16
 
  // The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
  // transmitter.
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  // If RemoteTech is installed, do their module(s) instead
  @PART[modPartName]:NEEDS[RemoteTech]:FOR[YourModName]
  {
  // RemoteTech module(s) here
  }
  ```
 
  ## Useful Formulas
 
  ### Per Antenna
  `nominalRange` is a given, and is never calculated
  `maxPowerFactor` is a given, and is never calculated
  `maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)`
 
  ### Per Link
  A "link" is any connected pair of antennas.
  `NominalLinkDistance = sqrt(nominalRange1 * nominalRange2)`
  `MaxLinkDistance = sqrt(maxTransmitDistance1 * maxTransmitDistance2)`
 
  Therefore, to find the `MaxLinkDistance` from two sets of `nominalRange` and `maxPowerFactor`:
  `MaxLinkDistance = sqrt(nominalRange1 * sqrt(maxPowerFactor1) * nominalRange2 * sqrt(maxPowerFactor2))`
 
  To find a single antenna's `nominalRange` from a desired `maxTransmitDistance` given its `maxPowerFactor`:
  `nominalRange = maxTransmitDistance / sqrt(maxPowerFactor)`
 
  To find a single antenna's desired maximum range given the desired maximum link distance and another set `maxTransmitDistance`:
  `maxTransmitDistance1 = MaxLinkDistance * MaxLinkDistance / maxTransmitDistance2`
 
  Remember that `maxPowerFactor` may differ between antennas (and does, in my lastest configs: longAntenna is 8, mediumDish is 4, commDish is 16).
 
  Currently Kerbin's `maxPowerFactor` is hard-coded as 8.
 
  Feel free to use this spreadsheet for balancing antennas if it's useful to you: https://goo.gl/ChsbfL
 
  ## On Balance
  In my configs I've balanced the three stock antennas to cover all of the stock solar system. Since you're introducing five more antennas and working with OPM, you will probably want to change the behavior of the stock parts and diversify the range to gradually cover the whole OPM system. Since you have some parts specifically designed for use in planetary subsystems, their balance when transmitting to other parts is probably more important than their balance when transmitting to Kerbin. For longer range parts designed to make the whole interplanetary leap, the inverse is probably true.
 
  Feel free to ask questions! If anything's unclear or you just want to bounce balance ideas off of me, don't be shy. I'm always happy to help.