AntennaRange.cfg: Balance adjustments for the EVA transmitter.
AntennaRange.cfg: Balance adjustments for the EVA transmitter.

  // AntennaRange © 2014 toadicus
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 
  using KSP;
  using System;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
  public class ARConfiguration : MonoBehaviour
  {
  private bool showConfigWindow;
  private Rect configWindowPos;
 
  private IButton toolbarButton;
  private ApplicationLauncherButton appLauncherButton;
 
  private System.Version runningVersion;
 
  private KSP.IO.PluginConfiguration _config;
  private KSP.IO.PluginConfiguration config
  {
  get
  {
  if (this._config == null)
  {
  this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
  }
 
  return this._config;
  }
  }
 
  public void Awake()
  {
  Tools.PostDebugMessage(this, "Waking up.");
 
  this.runningVersion = this.GetType().Assembly.GetName().Version;
 
  this.showConfigWindow = false;
  this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
 
 
  this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
 
  AntennaRelay.requireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
 
  AntennaRelay.radiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d));
  AntennaRelay.radiusRatio *= AntennaRelay.radiusRatio;
 
  ARFlightController.requireConnectionForControl =
  this.LoadConfigValue("requireConnectionForControl", false);
 
  ModuleLimitedDataTransmitter.fixedPowerCost = this.LoadConfigValue("fixedPowerCost", false);
 
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 
  Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
 
  Tools.PostDebugMessage(this, "Awake.");
  }
 
  public void OnGUI()
  {
  // Only runs once, if the Toolbar is available.
  if (ToolbarManager.ToolbarAvailable)
  {
  if (this.toolbarButton == null)
  {
  Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
 
  this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
  this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
  this.toolbarButton.Text = "AR";
  this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
  this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
  this.toolbarButton.OnClick += delegate(ClickEvent e)
  {
  this.toggleConfigWindow();
  };
  }
  }
  else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
  {
  Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
 
  this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
  this.toggleConfigWindow,
  this.toggleConfigWindow,
  ApplicationLauncher.AppScenes.SPACECENTER,
  GameDatabase.Instance.GetTexture(
  "AntennaRange/Textures/appLauncherIcon",
  false
  )
  );
  }
 
  if (this.showConfigWindow)
  {
  Rect configPos = GUILayout.Window(354163056,
  this.configWindowPos,
  this.ConfigWindow,
  string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
  GUILayout.ExpandHeight(true),
  GUILayout.ExpandWidth(true)
  );
 
  configPos = Tools.ClampRectToScreen(configPos, 20);
 
  if (configPos != this.configWindowPos)
  {
  this.configWindowPos = configPos;
  this.SaveConfigValue("configWindowPos", this.configWindowPos);
  }
  }
  }
 
  public void ConfigWindow(int _)
  {
  GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
 
  GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
  bool requireLineOfSight = GUILayout.Toggle(AntennaRelay.requireLineOfSight, "Require Line of Sight");
  if (requireLineOfSight != AntennaRelay.requireLineOfSight)
  {
  AntennaRelay.requireLineOfSight = requireLineOfSight;
  this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
  bool requireConnectionForControl =
  GUILayout.Toggle(
  ARFlightController.requireConnectionForControl,
  "Require Connection for Probe Control"
  );
  if (requireConnectionForControl != ARFlightController.requireConnectionForControl)
  {
  ARFlightController.requireConnectionForControl = requireConnectionForControl;
  this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
  }
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  bool fixedPowerCost = GUILayout.Toggle(ModuleLimitedDataTransmitter.fixedPowerCost, "Use Fixed Power Cost");
  if (fixedPowerCost != ModuleLimitedDataTransmitter.fixedPowerCost)
  {
  ModuleLimitedDataTransmitter.fixedPowerCost = fixedPowerCost;
  this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
  }
 
  GUILayout.EndHorizontal();
 
  if (requireLineOfSight)
  {
  GUILayout.BeginHorizontal();
 
  double graceRatio = 1d - Math.Sqrt(AntennaRelay.radiusRatio);
  double newRatio;
 
  GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
 
  GUILayout.EndHorizontal();
 
  GUILayout.BeginHorizontal();
 
  newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
  newRatio = Math.Round(newRatio, 2);
 
  if (newRatio != graceRatio)
  {
  AntennaRelay.radiusRatio = (1d - newRatio) * (1d - newRatio);
  this.SaveConfigValue("graceRatio", newRatio);
  }
 
  GUILayout.EndHorizontal();
  }
 
  GUILayout.EndVertical();
 
  GUI.DragWindow();
  }
 
  public void OnDestroy()
  {
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.Destroy();
  }
 
  if (this.appLauncherButton != null)
  {
  ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
  }
  }
 
  protected void onSceneChangeRequested(GameScenes scene)
  {
  if (scene != GameScenes.SPACECENTER)
  {
  print("ARConfiguration: Requesting Destruction.");
  MonoBehaviour.Destroy(this);
  }
  }
 
  private void toggleConfigWindow()
  {
  this.showConfigWindow = !this.showConfigWindow;
  }
 
  private T LoadConfigValue<T>(string key, T defaultValue)
  {
  this.config.load();
 
  return config.GetValue(key, defaultValue);
  }
 
  private void SaveConfigValue<T>(string key, T value)
  {
  this.config.load();
 
  this.config.SetValue(key, value);
 
  this.config.save();
  }
  }
  }
 
  // AntennaRange
  //
  // ARFlightController.cs
  //
  // Copyright © 2014, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
  using KSP;
  using System;
  using System.Collections.Generic;
  using ToadicusTools;
  using UnityEngine;
 
  namespace AntennaRange
  {
  [KSPAddon(KSPAddon.Startup.Flight, false)]
  public class ARFlightController : MonoBehaviour
  {
  #region Static Members
  public static bool requireConnectionForControl;
  #endregion
 
  #region Fields
  protected Dictionary<ConnectionStatus, string> connectionTextures;
  protected Dictionary<ConnectionStatus, Texture> appLauncherTextures;
 
  protected IButton toolbarButton;
 
  protected ApplicationLauncherButton appLauncherButton;
  #endregion
 
  #region Properties
  public ConnectionStatus currentConnectionStatus
  {
  get;
  protected set;
  }
 
  protected string currentConnectionTexture
  {
  get
  {
  return this.connectionTextures[this.currentConnectionStatus];
  }
  }
 
  protected Texture currentAppLauncherTexture
  {
  get
  {
  return this.appLauncherTextures[this.currentConnectionStatus];
  }
  }
 
  public ControlTypes currentControlLock
  {
  get
  {
  if (this.lockID == string.Empty)
  {
  return ControlTypes.None;
  }
 
  return InputLockManager.GetControlLock(this.lockID);
  }
  }
 
  public string lockID
  {
  get;
  protected set;
  }
 
  public ControlTypes lockSet
  {
  get
  {
  return ControlTypes.ALL_SHIP_CONTROLS;
  }
  }
 
  public Vessel vessel
  {
  get
  {
  if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
  {
  return FlightGlobals.ActiveVessel;
  }
 
  return null;
  }
  }
  #endregion
 
  #region MonoBehaviour LifeCycle
  protected void Awake()
  {
  this.lockID = "ARConnectionRequired";
 
  this.connectionTextures = new Dictionary<ConnectionStatus, string>();
 
  this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
  this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
  this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
 
  this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
 
  this.appLauncherTextures[ConnectionStatus.None] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
  this.appLauncherTextures[ConnectionStatus.Suboptimal] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
  this.appLauncherTextures[ConnectionStatus.Optimal] =
  GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
 
  if (ToolbarManager.ToolbarAvailable)
  {
  this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
  this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None];
  this.toolbarButton.Text = "AntennaRange";
  this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
  this.toolbarButton.Enabled = false;
  }
 
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
  GameEvents.onVesselChange.Add(this.onVesselChange);
  }
 
  protected void FixedUpdate()
  {
  if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
  {
  this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
  ApplicationLauncher.AppScenes.FLIGHT,
  this.appLauncherTextures[ConnectionStatus.None]
  );
  }
 
  Tools.DebugLogger log = Tools.DebugLogger.New(this);
 
  VesselCommand availableCommand;
 
  if (requireConnectionForControl)
  {
  availableCommand = this.vessel.CurrentCommand();
  }
  else
  {
  availableCommand = VesselCommand.Crew;
  }
 
  log.AppendFormat("availableCommand: {0}\n\t" +
  "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
  "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
  "vessel.HasConnectedRelay(): {3}",
  (int)availableCommand,
  (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
  (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
  vessel.HasConnectedRelay()
  );
 
  // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
  // and the vessel does not have any connected relays...
  if (
  HighLogic.LoadedSceneIsFlight &&
  requireConnectionForControl &&
  this.vessel != null &&
  this.vessel.vesselType != VesselType.EVA &&
  !(
  (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
  (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
  ))
  {
  // ...and if the controls are not currently locked...
  if (currentControlLock == ControlTypes.None)
  {
  // ...lock the controls.
  InputLockManager.SetControlLock(this.lockSet, this.lockID);
  }
  }
  // ...otherwise, if the controls are locked...
  else if (currentControlLock != ControlTypes.None)
  {
  // ...unlock the controls.
  InputLockManager.RemoveControlLock(this.lockID);
  }
 
  if (
  (this.toolbarButton != null || this.appLauncherButton != null) &&
  HighLogic.LoadedSceneIsFlight &&
  FlightGlobals.ActiveVessel != null
  )
  {
  log.Append("Checking vessel relay status.\n");
 
  List<ModuleLimitedDataTransmitter> relays =
  FlightGlobals.ActiveVessel.getModulesOfType<ModuleLimitedDataTransmitter>();
 
  log.AppendFormat("\t...found {0} relays\n", relays.Count);
 
  bool vesselCanTransmit = false;
  bool vesselHasOptimalRelay = false;
 
  foreach (ModuleLimitedDataTransmitter relay in relays)
  {
  log.AppendFormat("\tvesselCanTransmit: {0}, vesselHasOptimalRelay: {1}\n",
  vesselCanTransmit, vesselHasOptimalRelay);
 
  log.AppendFormat("\tChecking relay {0}\n" +
  "\t\tCanTransmit: {1}, transmitDistance: {2}, nominalRange: {3}\n",
  relay,
  relay.CanTransmit(),
  relay.transmitDistance,
  relay.nominalRange
  );
 
  bool relayCanTransmit = relay.CanTransmit();
 
  if (!vesselCanTransmit && relayCanTransmit)
  {
  vesselCanTransmit = true;
  }
 
  if (!vesselHasOptimalRelay &&
  relayCanTransmit &&
  relay.transmitDistance <= (double)relay.nominalRange)
  {
  vesselHasOptimalRelay = true;
  }
 
  if (vesselCanTransmit && vesselHasOptimalRelay)
  {
  break;
  }
  }
 
  log.AppendFormat("Done checking. vesselCanTransmit: {0}, vesselHasOptimalRelay: {1}\n",
  vesselCanTransmit, vesselHasOptimalRelay);
 
  if (vesselHasOptimalRelay)
  {
  this.currentConnectionStatus = ConnectionStatus.Optimal;
  }
  else if (vesselCanTransmit)
  {
  this.currentConnectionStatus = ConnectionStatus.Suboptimal;
  }
  else
  {
  this.currentConnectionStatus = ConnectionStatus.None;
  }
 
  log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
  this.currentConnectionStatus, this.currentConnectionTexture);
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.TexturePath = this.currentConnectionTexture;
  }
  if (this.appLauncherButton != null)
  {
  this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
  }
  }
 
  log.Print();
  }
 
  protected void OnDestroy()
  {
  InputLockManager.RemoveControlLock(this.lockID);
 
  if (this.toolbarButton != null)
  {
  this.toolbarButton.Destroy();
  }
 
  if (this.appLauncherButton != null)
  {
  ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
  this.appLauncherButton = null;
  }
 
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
  GameEvents.onVesselChange.Remove(this.onVesselChange);
 
  print("ARFlightController: Destroyed.");
  }
  #endregion
 
  #region Event Handlers
  protected void onSceneChangeRequested(GameScenes scene)
  {
  print("ARFlightController: Requesting Destruction.");
  MonoBehaviour.Destroy(this);
  }
 
  protected void onVesselChange(Vessel vessel)
  {
  InputLockManager.RemoveControlLock(this.lockID);
  }
  #endregion
 
  public enum ConnectionStatus
  {
  None,
  Suboptimal,
  Optimal
  }
  }
  }
 
file:a/ARTools.cs (deleted)
// AntennaRange © 2014 toadicus  
//  
// AntennaRange provides incentive and requirements for the use of the various antenna parts.  
// Nominally, the breakdown is as follows:  
//  
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit  
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem  
// Communotron 88-88 - Suitable throughout the Kerbol system.  
//  
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
//  
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
// 3.0 Uported License.  
//  
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
 
using System;  
 
namespace AntennaRange  
{  
public static class Tools  
{  
private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT);  
// Function that posts messages to the screen and the log when DEBUG is defined.  
[System.Diagnostics.Conditional("DEBUG")]  
public static void PostDebugMessage(string Msg)  
{  
if (HighLogic.LoadedScene > GameScenes.SPACECENTER)  
{  
debugmsg.message = Msg;  
ScreenMessages.PostScreenMessage(debugmsg, true);  
}  
 
KSPLog.print(Msg);  
}  
 
/*  
* MuMech_ToSI is a part of the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
* */  
public static string MuMech_ToSI(double d, int digits = 3, int MinMagnitude = 0, int MaxMagnitude = int.MaxValue)  
{  
float exponent = (float)Math.Log10(Math.Abs(d));  
exponent = UnityEngine.Mathf.Clamp(exponent, (float)MinMagnitude, (float)MaxMagnitude);  
 
if (exponent >= 0)  
{  
switch ((int)Math.Floor(exponent))  
{  
case 0:  
case 1:  
case 2:  
return d.ToString("F" + digits);  
case 3:  
case 4:  
case 5:  
return (d / 1e3).ToString("F" + digits) + "k";  
case 6:  
case 7:  
case 8:  
return (d / 1e6).ToString("F" + digits) + "M";  
case 9:  
case 10:  
case 11:  
return (d / 1e9).ToString("F" + digits) + "G";  
case 12:  
case 13:  
case 14:  
return (d / 1e12).ToString("F" + digits) + "T";  
case 15:  
case 16:  
case 17:  
return (d / 1e15).ToString("F" + digits) + "P";  
case 18:  
case 19:  
case 20:  
return (d / 1e18).ToString("F" + digits) + "E";  
case 21:  
case 22:  
case 23:  
return (d / 1e21).ToString("F" + digits) + "Z";  
default:  
return (d / 1e24).ToString("F" + digits) + "Y";  
}  
}  
else if (exponent < 0)  
{  
switch ((int)Math.Floor(exponent))  
{  
case -1:  
case -2:  
case -3:  
return (d * 1e3).ToString("F" + digits) + "m";  
case -4:  
case -5:  
case -6:  
return (d * 1e6).ToString("F" + digits) + "μ";  
case -7:  
case -8:  
case -9:  
return (d * 1e9).ToString("F" + digits) + "n";  
case -10:  
case -11:  
case -12:  
return (d * 1e12).ToString("F" + digits) + "p";  
case -13:  
case -14:  
case -15:  
return (d * 1e15).ToString("F" + digits) + "f";  
case -16:  
case -17:  
case -18:  
return (d * 1e18).ToString("F" + digits) + "a";  
case -19:  
case -20:  
case -21:  
return (d * 1e21).ToString("F" + digits) + "z";  
default:  
return (d * 1e24).ToString("F" + digits) + "y";  
}  
}  
else  
{  
return "0";  
}  
}  
 
public static T Min<T>(params T[] values) where T : IComparable<T>  
{  
if (values.Length < 2)  
{  
throw new ArgumentException("Min must be called with at least two arguments.");  
}  
 
IComparable<T> minValue = values[0];  
 
for (long i = 1; i < values.LongLength; i++)  
{  
IComparable<T> value = values[i];  
 
if (value.CompareTo((T)minValue) < 0)  
{  
minValue = value;  
}  
}  
 
return (T)minValue;  
}  
 
public static void Restart(this System.Diagnostics.Stopwatch stopwatch)  
{  
stopwatch.Reset();  
stopwatch.Start();  
}  
}  
}  
 
 
file:a/AntennaRange.cfg (deleted)
//  
// AntennaRange © 2013 toadicus  
//  
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
//  
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
// 3.0 Uported License.  
//  
// Specifications:  
// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost  
// and packetSize.  
// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the  
// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.  
// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.  
//  
 
@PART[longAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 1500000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
@PART[mediumDishAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 30000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
@PART[commDish]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 80000000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
 
  <?xml version="1.0" encoding="utf-8"?>
  <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProductVersion>8.0.30703</ProductVersion>
  <SchemaVersion>2.0</SchemaVersion>
  <ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid>
  <OutputType>Library</OutputType>
  <RootNamespace>AntennaRange</RootNamespace>
  <AssemblyName>AntennaRange</AssemblyName>
  <ReleaseVersion>1.3</ReleaseVersion>
  <SynchReleaseVersion>false</SynchReleaseVersion>
  <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  <UseMSBuildEngine>False</UseMSBuildEngine>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug</OutputPath>
  <DefineConstants>DEBUG;TRACE;</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
  </CustomCommands>
  </CustomCommands>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' ">
  <Optimize>true</Optimize>
  <OutputPath>bin\Release</OutputPath>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
  </CustomCommands>
  </CustomCommands>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug</OutputPath>
  <DefineConstants>DEBUG;TRACE;</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <ConsolePause>false</ConsolePause>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
  </CustomCommands>
  </CustomCommands>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' ">
  <Optimize>true</Optimize>
  <OutputPath>bin\Release</OutputPath>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
  <CustomCommands>
  <CustomCommands>
  <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
  </CustomCommands>
  </CustomCommands>
  <ConsolePause>false</ConsolePause>
  </PropertyGroup>
  <ItemGroup>
  <Compile Include="Properties\AssemblyInfo.cs" />
  <Compile Include="IAntennaRelay.cs" />
  <Compile Include="ModuleLimitedDataTransmitter.cs" />
  <Compile Include="AntennaRelay.cs" />
  <Compile Include="ProtoAntennaRelay.cs" />
  <Compile Include="RelayDatabase.cs" />
  <Compile Include="RelayExtensions.cs" />
  <Compile Include="ARConfiguration.cs" />
  <Compile Include="ARFlightController.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <ItemGroup>
  <Reference Include="Assembly-CSharp">
  <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
  <Private>False</Private>
  </Reference>
  <Reference Include="System">
  <HintPath>..\_KSPAssemblies\System.dll</HintPath>
  <Private>False</Private>
  </Reference>
  <Reference Include="UnityEngine">
  <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
  <Private>False</Private>
  </Reference>
  </ItemGroup>
  <ItemGroup>
  <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
  <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
  <Name>ToadicusTools</Name>
  </ProjectReference>
  </ItemGroup>
  <ItemGroup>
  <None Include="GameData\AntennaRange\AntennaRange.cfg" />
  <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
  </ItemGroup>
  </Project>
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // AntennaRelay.cs
// Nominally, the breakdown is as follows: //
// // Copyright © 2014, toadicus
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // All rights reserved.
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem //
// Communotron 88-88 - Suitable throughout the Kerbol system. // Redistribution and use in source and binary forms, with or without modification,
// // are permitted provided that the following conditions are met:
// 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/ // 1. Redistributions of source code must retain the above copyright notice,
// // this list of conditions and the following disclaimer.
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike //
// 3.0 Uported License. // 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 software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
  using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
public class AntennaRelay public class AntennaRelay
{ {
  public static bool requireLineOfSight;
  public static double radiusRatio;
   
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
protected CelestialBody Kerbin; protected CelestialBody Kerbin;
   
  protected CelestialBody _firstOccludingBody;
   
protected IAntennaRelay _nearestRelayCache; protected IAntennaRelay _nearestRelayCache;
protected IAntennaRelay moduleRef; protected IAntennaRelay moduleRef;
   
protected System.Diagnostics.Stopwatch searchTimer; protected System.Diagnostics.Stopwatch searchTimer;
protected long millisecondsBetweenSearches; protected long millisecondsBetweenSearches;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public virtual Vessel vessel public virtual Vessel vessel
{ {
get get
{ {
return this.moduleRef.vessel; return this.moduleRef.vessel;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the nearest relay. /// Gets or sets the nearest relay.
/// </summary> /// </summary>
/// <value>The nearest relay</value> /// <value>The nearest relay</value>
public IAntennaRelay nearestRelay public IAntennaRelay nearestRelay
{ {
get get
{ {
if (this.searchTimer.IsRunning && if (this.searchTimer.IsRunning &&
this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches) this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
{ {
this._nearestRelayCache = this.FindNearestRelay(); this._nearestRelayCache = this.FindNearestRelay();
this.searchTimer.Restart(); this.searchTimer.Restart();
} }
   
return this._nearestRelayCache; return this._nearestRelayCache;
} }
protected set protected set
{ {
this._nearestRelayCache = value; this._nearestRelayCache = value;
} }
} }
   
/// <summary> /// <summary>
  /// Gets the first occluding body.
  /// </summary>
  /// <value>The first occluding body.</value>
  public CelestialBody firstOccludingBody
  {
  get
  {
  return this._firstOccludingBody;
  }
  }
   
  /// <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
{ {
this.nearestRelay = this.FindNearestRelay(); this.nearestRelay = this.FindNearestRelay();
   
// If there is no available relay nearby... // If there is no available relay nearby...
if (this.nearestRelay == null) if (this.nearestRelay == null)
{ {
// .. return the distance to Kerbin // .. return the distance to Kerbin
return this.DistanceTo(this.Kerbin); return this.DistanceTo(this.Kerbin);
} }
else else
{ {
/// ...otherwise, return the distance to the nearest available relay. /// ...otherwise, return the distance to the nearest available relay.
return this.DistanceTo(nearestRelay); return this.DistanceTo(nearestRelay);
} }
} }
} }
   
/// <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 float maxTransmitDistance public virtual float maxTransmitDistance
{ {
get; get;
set; set;
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
/// the current relay attempt. /// the current relay attempt.
/// </summary> /// </summary>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
public virtual bool relayChecked public virtual bool relayChecked
{ {
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()
{ {
if (this.transmitDistance > this.maxTransmitDistance) if (
  this.transmitDistance > this.maxTransmitDistance ||
  (
  requireLineOfSight &&
  this.nearestRelay == null &&
  !this.vessel.hasLineOfSightTo(this.Kerbin, out this._firstOccludingBody, radiusRatio)
  )
  )
{ {
return false; return false;
} }
else else
{ {
return true; return true;
} }
} }
   
/// <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 IAntennaRelay FindNearestRelay() public IAntennaRelay FindNearestRelay()
{ {
if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches) if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
{ {
return this.nearestRelay; return this.nearestRelay;
} }
   
if (this.searchTimer.IsRunning) if (this.searchTimer.IsRunning)
{ {
this.searchTimer.Stop(); this.searchTimer.Stop();
this.searchTimer.Reset(); this.searchTimer.Reset();
} }
   
this.searchTimer.Start(); this.searchTimer.Start();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: finding nearest relay for {1} ({2})", "{0}: finding nearest relay for {1} ({2})",
this.GetType().Name, this.GetType().Name,
this, this,
this.vessel.id this.vessel.id
)); ));
   
  this._firstOccludingBody = null;
   
// Set this vessel as checked, so that we don't check it again. // Set this vessel as checked, so that we don't check it again.
RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true; RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
   
double nearestDistance = double.PositiveInfinity; double nearestSqrDistance = double.PositiveInfinity;
IAntennaRelay _nearestRelay = null; IAntennaRelay _nearestRelay = null;
   
/* /*
* Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
* far away. When we find a candidate, get through its antennae for relays which have not been checked yet * far away. When we find a candidate, get through its antennae for relays which have not been checked yet
* and that can transmit. Once we find a suitable candidate, assign it to _nearestRelay for comparison * and that can transmit. Once we find a suitable candidate, assign it to _nearestRelay for comparison
* against future finds. * against future finds.
* */ * */
foreach (Vessel potentialVessel in FlightGlobals.Vessels) foreach (Vessel potentialVessel in FlightGlobals.Vessels)
{ {
// 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.
try if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(potentialVessel.id))
{ {
if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id]) continue;
{ }
continue;  
}  
}  
catch (KeyNotFoundException) { /* If the key doesn't exist, don't skip it. */}  
   
// Skip vessels of the wrong type. // Skip vessels of the wrong 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:
continue; continue;
default: default:
break; break;
} }
   
// Skip vessels with the wrong ID // Skip vessels with the wrong ID
if (potentialVessel.id == vessel.id) if (potentialVessel.id == vessel.id)
{ {
continue; continue;
} }
   
  // Skip vessels to which we do not have line of sight.
  if (requireLineOfSight &&
  !this.vessel.hasLineOfSightTo(potentialVessel, out this._firstOccludingBody, radiusRatio))
  {
  Tools.PostDebugMessage(
  this,
  "Vessel {0} discarded because we do not have line of sight.",
  potentialVessel.vesselName
  );
  continue;
  }
   
// Find the distance from here to the vessel... // Find the distance from here to the vessel...
double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude; double potentialSqrDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).sqrMagnitude;
   
/* /*
* ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a * ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a
* vessel we've already checked. * vessel we've already checked.
* */ * */
if (potentialDistance > Tools.Min(this.maxTransmitDistance, nearestDistance, vessel.DistanceTo(Kerbin))) if (
{ potentialSqrDistance > Tools.Min(
  this.maxTransmitDistance * this.maxTransmitDistance,
  nearestSqrDistance,
  this.vessel.sqrDistanceTo(Kerbin)
  )
  )
  {
  Tools.PostDebugMessage(
  this,
  "Vessel {0} discarded because it is out of range, or farther than another relay.",
  potentialVessel.vesselName
  );
continue; continue;
} }
   
nearestDistance = potentialDistance; nearestSqrDistance = potentialSqrDistance;
   
foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays()) foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
{ {
if (potentialRelay.CanTransmit()) if (potentialRelay.CanTransmit())
{ {
_nearestRelay = potentialRelay; _nearestRelay = potentialRelay;
Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})", Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
this.GetType().Name, this.GetType().Name,
_nearestRelay.ToString(), _nearestRelay.ToString(),
_nearestRelay.vessel.id _nearestRelay.vessel.id
)); ));
break; break;
} }
} }
} }
   
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id); RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
   
// Return the nearest available relay, or null if there are no available relays nearby. // Return the nearest available relay, or null if there are no available relays nearby.
return _nearestRelay; return _nearestRelay;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
public AntennaRelay(IAntennaRelay module) public AntennaRelay(IAntennaRelay module)
{ {
this.moduleRef = module; this.moduleRef = module;
   
this.searchTimer = new System.Diagnostics.Stopwatch(); this.searchTimer = new System.Diagnostics.Stopwatch();
this.millisecondsBetweenSearches = 5000; this.millisecondsBetweenSearches = 5000;
   
// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start, // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
// we hope it is safe enough. // we hope it is safe enough.
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
} }
   
  static AntennaRelay()
  {
  var config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
   
  config.load();
   
  AntennaRelay.requireLineOfSight = config.GetValue<bool>("requireLineOfSight", false);
   
  config.save();
  }
} }
} }
   
   
file:a/EventSniffer.cs (deleted)
// AntennaRange © 2014 toadicus  
//  
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
 
#if DEBUG  
using KSP;  
using System;  
using System.Text;  
using UnityEngine;  
 
namespace AntennaRange  
{  
[KSPAddon(KSPAddon.Startup.Flight, false)]  
public class EventSniffer : MonoBehaviour  
{  
public void Awake()  
{  
GameEvents.onSameVesselDock.Add(this.onSameVesselDockUndock);  
GameEvents.onSameVesselUndock.Add(this.onSameVesselDockUndock);  
GameEvents.onPartUndock.Add(this.onPartUndock);  
GameEvents.onUndock.Add(this.onReportEvent);  
GameEvents.onPartCouple.Add(this.onPartCouple);  
GameEvents.onPartJointBreak.Add(this.onPartJointBreak);  
}  
 
public void Destroy()  
{  
GameEvents.onSameVesselDock.Remove(this.onSameVesselDockUndock);  
GameEvents.onSameVesselUndock.Remove(this.onSameVesselDockUndock);  
GameEvents.onPartUndock.Remove(this.onPartUndock);  
GameEvents.onUndock.Remove(this.onReportEvent);  
GameEvents.onPartCouple.Remove(this.onPartCouple);  
GameEvents.onPartJointBreak.Remove(this.onPartJointBreak);  
}  
 
public void onSameVesselDockUndock(GameEvents.FromToAction<ModuleDockingNode, ModuleDockingNode> data)  
{  
this.FromModuleToModuleHelper(  
this.getStringBuilder(),  
new GameEvents.FromToAction<PartModule, PartModule>(data.from, data.to)  
);  
}  
 
public void onPartJointBreak(PartJoint joint)  
{  
this.PartJointHelper(this.getStringBuilder(), joint);  
}  
 
public void onPartUndock(Part part)  
{  
this.PartEventHelper(this.getStringBuilder(), part);  
}  
 
public void onReportEvent(EventReport data)  
{  
this.EventReportHelper(this.getStringBuilder(), data);  
}  
 
public void onPartCouple(GameEvents.FromToAction<Part, Part> data)  
{  
this.FromPartToPartHelper(this.getStringBuilder(), data);  
}  
 
internal void EventReportHelper(StringBuilder sb, EventReport data)  
{  
sb.Append("\n\tOrigin Part:");  
this.appendPartAncestry(sb, data.origin);  
 
sb.AppendFormat(  
"\n\tother: '{0}'" +  
"\n\tmsg: '{1}'" +  
"\n\tsender: '{2}'",  
data.other,  
data.msg,  
data.sender  
);  
 
Debug.Log(sb.ToString());  
}  
 
internal void PartEventHelper(StringBuilder sb, Part part)  
{  
this.appendPartAncestry(sb, part);  
 
Debug.Log(sb.ToString());  
}  
 
internal void FromPartToPartHelper(StringBuilder sb, GameEvents.FromToAction<Part, Part> data)  
{  
sb.Append("\n\tFrom:");  
 
this.appendPartAncestry(sb, data.from);  
 
sb.Append("\n\tTo:");  
 
this.appendPartAncestry(sb, data.to);  
 
Debug.Log(sb.ToString());  
}  
 
internal void FromModuleToModuleHelper(StringBuilder sb, GameEvents.FromToAction<PartModule, PartModule> data)  
{  
sb.Append("\n\tFrom:");  
 
this.appendModuleAncestry(sb, data.from);  
 
sb.Append("\n\tTo:");  
 
this.appendModuleAncestry(sb, data.to);  
 
Debug.Log(sb.ToString());  
}  
 
internal void PartJointHelper(StringBuilder sb, PartJoint joint)  
{  
sb.Append("PartJoint: ");  
if (joint != null)  
{  
sb.Append(joint);  
this.appendPartAncestry(sb, joint.Host);  
}  
else  
{  
sb.Append("null");  
}  
 
Debug.Log(sb.ToString());  
}  
 
internal StringBuilder appendModuleAncestry(StringBuilder sb, PartModule module, uint tabs = 1)  
{  
sb.Append('\n');  
for (ushort i=0; i < tabs; i++)  
{  
sb.Append('\t');  
}  
sb.Append("Module: ");  
 
if (module != null)  
{  
sb.Append(module.moduleName);  
this.appendPartAncestry(sb, module.part, tabs + 1u);  
}  
else  
{  
sb.Append("null");  
}  
 
return sb;  
}  
 
internal StringBuilder appendPartAncestry(StringBuilder sb, Part part, uint tabs = 1)  
{  
sb.Append('\n');  
for (ushort i=0; i < tabs; i++)  
{  
sb.Append('\t');  
}  
sb.Append("Part: ");  
 
if (part != null)  
{  
sb.AppendFormat("'{0}' ({1})", part.partInfo.title, part.partName);  
this.appendVessel(sb, part.vessel, tabs + 1u);  
}  
else  
{  
sb.Append("null");  
}  
 
return sb;  
}  
 
internal StringBuilder appendVessel(StringBuilder sb, Vessel vessel, uint tabs = 1)  
{  
sb.Append('\n');  
for (ushort i=0; i < tabs; i++)  
{  
sb.Append('\t');  
}  
sb.Append("Vessel: ");  
 
if (vessel != null)  
{  
sb.AppendFormat("'{0}' ({1})", vessel.vesselName, vessel.id);  
}  
else  
{  
sb.Append("null");  
}  
 
return sb;  
}  
 
internal StringBuilder getStringBuilder()  
{  
StringBuilder sb = new StringBuilder();  
sb.AppendFormat("{0}: called {1} ",  
this.GetType().Name,  
new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name  
);  
return sb;  
}  
}  
}  
#endif  
file:a/Extensions.cs (deleted)
// AntennaRange © 2014 toadicus  
//  
// AntennaRange provides incentive and requirements for the use of the various antenna parts.  
// Nominally, the breakdown is as follows:  
//  
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit  
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem  
// Communotron 88-88 - Suitable throughout the Kerbol system.  
//  
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
//  
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
// 3.0 Uported License.  
//  
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
 
using System;  
using System.Collections.Generic;  
using System.Linq;  
 
namespace AntennaRange  
{  
/*  
* A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.  
* */  
public static class Extensions  
{  
/// <summary>  
/// Returns the distance between this Vessel and another Vessel.  
/// </summary>  
/// <param name="vesselOne">This <see cref="Vessel"/><see ></param>  
/// <param name="vesselTwo">Another <see cref="Vessel"/></param>  
public static double DistanceTo(this Vessel vesselOne, Vessel vesselTwo)  
{  
return (vesselOne.GetWorldPos3D() - vesselTwo.GetWorldPos3D()).magnitude;  
}  
 
/// <summary>  
/// Returns the distance between this Vessel and a CelestialBody  
/// </summary>  
/// <param name="vessel">This Vessel</param>  
/// <param name="body">A <see cref="CelestialBody"/></param>  
public static double DistanceTo(this Vessel vessel, CelestialBody body)  
{  
return (vessel.GetWorldPos3D() - body.position).magnitude;  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and a Vessel  
/// </summary>  
/// <param name="relay">This <see cref="IAntennaRelay"/></param>  
/// <param name="Vessel">A <see cref="Vessel"/></param>  
public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)  
{  
return relay.vessel.DistanceTo(Vessel);  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and a CelestialBody  
/// </summary>  
/// <param name="relay">This <see cref="IAntennaRelay"/></param>  
/// <param name="body">A <see cref="CelestialBody"/></param>  
public static double DistanceTo(this AntennaRelay relay, CelestialBody body)  
{  
return relay.vessel.DistanceTo(body);  
}  
 
/// <summary>  
/// Returns the distance between this IAntennaRelay and another IAntennaRelay  
/// </summary>  
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param>  
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>  
public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)  
{  
return relayOne.DistanceTo(relayTwo.vessel);  
}  
 
/// <summary>  
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.  
/// </summary>  
/// <param name="vessel">This <see cref="Vessel"/></param>  
public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)  
{  
return RelayDatabase.Instance[vessel].Values.ToList();  
}  
 
 
}  
}  
 
 
  ACTIVE_TEXTURE_MANAGER_CONFIG
  {
  folder = AntennaRange
  enabled = true
  OVERRIDES
  {
  AntennaRange/.*
  {
  compress = true
  mipmaps = false
  scale = 1
  max_size = 0
  }
  }
  }
  // AntennaRange
  //
  // AntennaRange.cfg
  //
  // Copyright © 2014, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // Specifications:
  // nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by
  // packetResourceCost and packetSize.
  // maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
  // power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
  // maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  //
 
  @PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 1500000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  @PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 30000000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  @PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 80000000000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
 
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  EVA_MODULE
  {
  name = ModuleLimitedDataTransmitter
 
  nominalRange = 5000
  maxPowerFactor = 1
  maxDataFactor = 1
 
  packetInterval = 0.2
  packetSize = 1
  packetResourceCost = 6.25
 
  requiredResource = ElectricCharge
  }
 
  EVA_RESOURCE
  {
  name = ElectricCharge
  amount = 100
  maxAmount = 100
  }
 
  @EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport]
  {
  !name = DELETE
  }
 
  @SUBCATEGORY[*]:HAS[#title[Data?Transmitter]]:FOR[AntennaRange:]NEEDS[FilterExtensions]
  {
  FILTER
  {
  CHECK
  {
  type = moduleName
  value = ModuleLimitedDataTransmitter
  }
  }
  }
 
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
 Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // IAntennaRelay.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// Communotron 88-88 - Suitable throughout the Kerbol system.  
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // this list of conditions and the following disclaimer.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /*
* Interface defining the basic functionality of AntennaRelay modules for AntennaRange. * Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
* */ * */
public interface IAntennaRelay public interface IAntennaRelay
{ {
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
Vessel vessel { get; } Vessel vessel { get; }
   
/// <summary> /// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer. /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary> /// </summary>
/// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value> /// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>
double transmitDistance { get; } double transmitDistance { get; }
   
/// <summary> /// <summary>
/// The maximum distance at which this relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> /// <value>The max transmit distance.</value>
float maxTransmitDistance { get; } float maxTransmitDistance { get; }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
/// the current relay attempt. /// the current relay attempt.
/// </summary> /// </summary>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
bool relayChecked { get; } bool relayChecked { get; }
   
/// <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>
bool CanTransmit(); bool CanTransmit();
} }
} }
   
   
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // ModuleLimitedDataTransmitter.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// Communotron 88-88 - Suitable throughout the Kerbol system.  
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // this list of conditions and the following disclaimer.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
  using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using KSP; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /*
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range- * ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with * finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files. * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
* *
* In general, the scaling functions assume the following relation: * In general, the scaling functions assume the following relation:
* *
* D² α P/R, * D² α P/R,
* *
* where D is the total transmission distance, P is the transmission power, and R is the data rate. * where D is the total transmission distance, P is the transmission power, and R is the data rate.
* *
* */ * */
   
/* /*
* Fields * Fields
* */ * */
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{ {
  // If true, use a fixed power cost at the configured value and degrade data rates instead of increasing power
  // requirements.
  public static bool fixedPowerCost;
   
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
protected float _basepacketResourceCost; protected float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
protected float _basepacketSize; protected float _basepacketSize;
   
// Every antenna is a relay. // Every antenna is a relay.
protected AntennaRelay relay; protected AntennaRelay relay;
   
// Keep track of vessels with transmitters for relay purposes. // Keep track of vessels with transmitters for relay purposes.
protected List<Vessel> _relayVessels; protected List<Vessel> _relayVessels;
   
// Sometimes we will need to communicate errors; this is how we do it. // Sometimes we will need to communicate errors; this is how we do it.
protected ScreenMessage ErrorMsg; protected ScreenMessage ErrorMsg;
   
// 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.
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float nominalRange; public float nominalRange;
   
  [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
  public string UIrelayStatus;
   
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power // 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.
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
// The multipler on packetSize that defines the maximum data bandwidth of the antenna. // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
  [KSPField(
  isPersistant = true,
  guiName = "Packet Throttle",
  guiUnits = "%",
  guiActive = true,
  guiActiveEditor = false
  )]
  [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
  public float packetThrottle;
   
protected bool actionUIUpdate; protected bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
// Returns the parent vessel housing this antenna. // Returns the parent vessel housing this antenna.
public new Vessel vessel public new Vessel vessel
{ {
get get
{ {
return base.vessel; return base.vessel;
} }
} }
   
// Returns the distance to the nearest relay or Kerbin, whichever is closer. // Returns the distance to the nearest relay or Kerbin, whichever is closer.
public double transmitDistance public double transmitDistance
{ {
get get
{ {
return this.relay.transmitDistance; return this.relay.transmitDistance;
} }
} }
   
// Returns the maximum distance this module can transmit // Returns the maximum distance this module can transmit
public float maxTransmitDistance public float maxTransmitDistance
{ {
get get
{ {
return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange; return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
} }
} }
   
/* /*
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
* in that they both return floats. Here's some quick justification: * in that they both return floats. Here's some quick justification:
* *
* The stock implementation of GetTransmitterScore (which I cannot override) is: * The stock implementation of GetTransmitterScore (which I cannot override) is:
* Score = (1 + DataResourceCost) / DataRate * Score = (1 + DataResourceCost) / DataRate
* *
* The stock DataRate and DataResourceCost are: * The stock DataRate and DataResourceCost are:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost / packetSize * DataResourceCost = packetResourceCost / packetSize
* *
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it * So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it
* could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way, * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,
* that metric is not a very reasonable one. * that metric is not a very reasonable one.
* *
* Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
* would look like: * would look like:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* The former case, which I've chosen to implement below, is: * The former case, which I've chosen to implement below, is:
* DataRate = packetSize * DataRate = packetSize
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* So... hopefully that doesn't screw with anything else. * So... hopefully that doesn't screw with anything else.
* */ * */
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in // Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
// terms of joules/byte // terms of joules/byte
public new float DataRate public new float DataRate
{ {
get get
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetSize; return this.packetSize;
} }
else else
{ {
return float.Epsilon; return float.Epsilon;
} }
} }
} }
   
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas // 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
public new float DataResourceCost public new float DataResourceCost
{ {
get get
{ {
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetResourceCost; return this.packetResourceCost;
} }
else else
{ {
return float.PositiveInfinity; return float.PositiveInfinity;
} }
} }
} }
   
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay. // Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
public bool relayChecked public bool relayChecked
{ {
get get
{ {
return this.relay.relayChecked; return this.relay.relayChecked;
} }
} }
   
/* /*
* 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;
  }
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.  
public override void OnStart (StartState state) public override void OnAwake()
{ {
base.OnStart (state); base.OnAwake();
   
if (state >= StartState.PreLaunch)  
{  
this.relay = new AntennaRelay(vessel);  
this.relay.maxTransmitDistance = this.maxTransmitDistance;  
   
this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";  
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);  
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);  
}  
}  
   
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because  
// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't  
// work.  
public override void OnLoad(ConfigNode node)  
{  
this.Fields.Load(node);  
base.Fields.Load(node);  
   
base.OnLoad (node);  
   
this._basepacketSize = base.packetSize; this._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this._basepacketResourceCost = base.packetResourceCost;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0} loaded:\n" + "{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalRange: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this.name,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
this.nominalRange, this.nominalRange,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
  // At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
  public override void OnStart (StartState state)
  {
  base.OnStart (state);
   
  if (state >= StartState.PreLaunch)
  {
  this.relay = new AntennaRelay(this);
  this.relay.maxTransmitDistance = this.maxTransmitDistance;
   
  this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
   
  GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
  GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
  }
  }
   
  // When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
  // overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
  // work.
  public override void OnLoad(ConfigNode node)
  {
  this.Fields.Load(node);
  base.Fields.Load(node);
   
  base.OnLoad (node);
  }
   
// 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.
protected void PostCannotTransmitError() protected void PostCannotTransmitError()
{ {
string ErrorText = string.Format ( string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
"Unable to transmit: out of range! Maximum range = {0}m; Current range = {1}m.",  
Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2),  
Tools.MuMech_ToSI((double)this.transmitDistance, 2)  
);  
   
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).
protected void PreTransmit_SetPacketResourceCost() protected void PreTransmit_SetPacketResourceCost()
{ {
if (this.transmitDistance <= this.nominalRange) if (fixedPowerCost || this.transmitDistance <= this.nominalRange)
{ {
base.packetResourceCost = this._basepacketResourceCost; base.packetResourceCost = this._basepacketResourceCost;
} }
else else
{ {
  double rangeFactor = (this.transmitDistance / this.nominalRange);
  rangeFactor *= rangeFactor;
   
base.packetResourceCost = this._basepacketResourceCost base.packetResourceCost = this._basepacketResourceCost
* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2); * (float)rangeFactor;
}  
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
  base.packetResourceCost,
  rangeFactor);
  }
   
  base.packetResourceCost *= this.packetThrottle / 100f;
} }
   
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of // 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.
protected void PreTransmit_SetPacketSize() protected void PreTransmit_SetPacketSize()
{ {
if (this.transmitDistance >= this.nominalRange) if (!fixedPowerCost && this.transmitDistance >= this.nominalRange)
{ {
base.packetSize = this._basepacketSize; base.packetSize = this._basepacketSize;
} }
else else
{ {
  double rangeFactor = (this.nominalRange / this.transmitDistance);
  rangeFactor *= rangeFactor;
   
base.packetSize = Math.Min( base.packetSize = Math.Min(
this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2), this._basepacketSize * (float)rangeFactor,
this._basepacketSize * this.maxDataFactor); this._basepacketSize * this.maxDataFactor);
}  
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
  base.packetSize,
  rangeFactor);
  }
   
  base.packetSize *= this.packetThrottle / 100f;
} }
   
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description. // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
public override string GetInfo() public override string GetInfo()
{ {
string text = base.GetInfo(); string text = base.GetInfo();
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n"; text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n"; text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
return text; return text;
} }
   
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
public new bool CanTransmit() public new bool CanTransmit()
{ {
  if (this.part == null || this.relay == null)
  {
  return false;
  }
   
PartStates partState = this.part.State; PartStates partState = this.part.State;
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.part.partInfo.title, this.part.partInfo.title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), partState) Enum.GetName(typeof(PartStates), partState)
)); ));
return false; return false;
} }
return this.relay.CanTransmit(); return this.relay.CanTransmit();
} }
   
// 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.
public new void TransmitData(List<ScienceData> dataQueue) public new void TransmitData(List<ScienceData> dataQueue)
{ {
  this.PreTransmit_SetPacketSize();
  this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
  StringBuilder message = new StringBuilder();
   
  message.Append("[");
  message.Append(base.part.partInfo.title);
  message.Append("]: ");
   
  message.Append("Beginning transmission ");
   
  if (this.relay.nearestRelay == null)
  {
  message.Append("directly to Kerbin.");
  }
  else
  {
  message.Append("via ");
  message.Append(this.relay.nearestRelay);
  }
   
  ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
base.TransmitData(dataQueue); base.TransmitData(dataQueue);
} }
else else
{ {
  Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
  var logger = Tools.DebugLogger.New(this);
   
  foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())
  {
  logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
  scienceContainer.part.partInfo.title);
   
  if (
  scienceContainer.capacity != 0 &&
  scienceContainer.GetScienceCount() >= scienceContainer.capacity
  )
  {
  logger.Append("\tInsufficient capacity, skipping.\n");
  continue;
  }
   
  List<ScienceData> dataStored = new List<ScienceData>();
   
  foreach (ScienceData data in dataQueue)
  {
  if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
  {
  logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
  continue;
  }
   
  logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
  if (scienceContainer.AddData(data))
  {
  logger.Append("done, removing from queue.\n");
   
  dataStored.Add(data);
  }
  #if DEBUG
  else
  {
  logger.Append("failed.\n");
  }
  #endif
  }
   
  dataQueue.RemoveAll(i => dataStored.Contains(i));
   
  logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
  }
   
  logger.Print();
   
  if (dataQueue.Count > 0)
  {
  StringBuilder msg = new StringBuilder();
   
  msg.Append('[');
  msg.Append(this.part.partInfo.title);
  msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
  msg.Append("Data to be discarded:\n");
   
  foreach (ScienceData data in dataQueue)
  {
  msg.AppendFormat("\n{0}\n", data.title);
  }
   
  ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  Tools.PostDebugMessage(msg.ToString());
  }
   
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit // Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
// returns false. // returns false.
public new void StartTransmission() public new void StartTransmission()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
   
message.Append("["); message.Append("[");
message.Append(base.part.partInfo.title); message.Append(base.part.partInfo.title);
message.Append("]: "); message.Append("]: ");
   
message.Append("Beginning transmission "); message.Append("Beginning transmission ");
   
if (this.relay.nearestRelay == null) if (this.relay.nearestRelay == null)
{ {
message.Append("directly to Kerbin."); message.Append("directly to Kerbin.");
} }
else else
{ {
message.Append("via "); message.Append("via ");
message.Append(this.relay.nearestRelay); message.Append(this.relay.nearestRelay);
} }
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; if (this.CanTransmit())
this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A"; {
this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "N/A"; this.UIrelayStatus = string.Intern("Connected");
  this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
  this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
  this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
  }
  else
  {
  if (this.relay.firstOccludingBody == null)
  {
  this.UIrelayStatus = string.Intern("Out of range");
  }
  else
  {
  this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
  }
  this.UImaxTransmitDistance = "N/A";
  this.UIpacketSize = "N/A";
  this.UIpacketCost = "N/A";
  }
} }
} }
   
public void onPartActionUICreate(Part eventPart) public void onPartActionUICreate(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = true; this.actionUIUpdate = true;
} }
} }
   
public void onPartActionUIDismiss(Part eventPart) public void onPartActionUIDismiss(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = false; this.actionUIUpdate = false;
} }
} }
   
public override string ToString() public override string ToString()
{ {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
   
msg.Append(this.part.partInfo.title); msg.Append(this.part.partInfo.title);
   
if (vessel != null) if (vessel != null)
{ {
msg.Append(" on "); msg.Append(" on ");
msg.Append(vessel.vesselName); msg.Append(vessel.vesselName);
} }
   
return msg.ToString(); return msg.ToString();
} }
   
// 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.
#if DEBUG #if DEBUG
[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)] [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
public void DebugInfo() public void DebugInfo()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
string msg = string.Format( string msg = string.Format(
"'{0}'\n" + "'{0}'\n" +
"_basepacketSize: {1}\n" + "_basepacketSize: {1}\n" +
"packetSize: {2}\n" + "packetSize: {2}\n" +
"_basepacketResourceCost: {3}\n" + "_basepacketResourceCost: {3}\n" +
"packetResourceCost: {4}\n" + "packetResourceCost: {4}\n" +
"maxTransmitDistance: {5}\n" + "maxTransmitDistance: {5}\n" +
"transmitDistance: {6}\n" + "transmitDistance: {6}\n" +
"nominalRange: {7}\n" + "nominalRange: {7}\n" +
"CanTransmit: {8}\n" + "CanTransmit: {8}\n" +
"DataRate: {9}\n" + "DataRate: {9}\n" +
"DataResourceCost: {10}\n" + "DataResourceCost: {10}\n" +
"TransmitterScore: {11}\n" + "TransmitterScore: {11}\n" +
"NearestRelay: {12}", "NearestRelay: {12}\n" +
  "Vessel ID: {13}",
this.name, this.name,
this._basepacketSize, this._basepacketSize,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
base.packetResourceCost, base.packetResourceCost,
this.maxTransmitDistance, this.maxTransmitDistance,
this.transmitDistance, this.transmitDistance,
this.nominalRange, this.nominalRange,
this.CanTransmit(), this.CanTransmit(),
this.DataRate, this.DataRate,
this.DataResourceCost, this.DataResourceCost,
ScienceUtil.GetTransmitterScore(this), ScienceUtil.GetTransmitterScore(this),
this.relay.FindNearestRelay() this.relay.FindNearestRelay(),
  this.vessel.id
); );
ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT)); Tools.PostDebugMessage(msg);
  }
   
  [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
  public void PrintAllVessels()
  {
  StringBuilder sb = new StringBuilder();
   
  sb.Append("Dumping FlightGlobals.Vessels:");
   
  foreach (Vessel vessel in FlightGlobals.Vessels)
  {
  sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
  }
   
  Tools.PostDebugMessage(sb.ToString());
  }
   
  [KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
  public void DumpRelayDB()
  {
  RelayDatabase.Instance.Dump();
} }
#endif #endif
} }
} }
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // AssemblyInfo.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// Communotron 88-88 - Suitable throughout the Kerbol system.  
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // this list of conditions and the following disclaimer.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
   
  [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("0.6.3.*")] [assembly: AssemblyVersion("1.6.*")]
// The following attributes are used to specify the signing key for the assembly, // The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing. // if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)] //[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")] //[assembly: AssemblyKeyFile("")]
   
   
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // ProtoAntennaRelay.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// Communotron 88-88 - Suitable throughout the Kerbol system.  
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // this list of conditions and the following disclaimer.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
  using KSP;
using System; using System;
using System.Linq; using System.Linq;
  using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /*
* Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay. * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
* This is used for finding relays in unloaded Vessels. * This is used for finding relays in unloaded Vessels.
* */ * */
public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
{ {
// Stores the relay prefab  
protected IAntennaRelay relayPrefab;  
   
// Stores the prototype part so we can make sure we haven't exploded or so. // Stores the prototype part so we can make sure we haven't exploded or so.
protected ProtoPartSnapshot protoPart; protected ProtoPartSnapshot protoPart;
   
  public override Vessel vessel
  {
  get
  {
  return this.protoPart.pVesselRef.vesselRef;
  }
  }
   
/// <summary> /// <summary>
/// The maximum distance at which this transmitter can operate. /// The maximum distance at which this transmitter can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> /// <value>The max transmit distance.</value>
public override float maxTransmitDistance public override float maxTransmitDistance
{ {
get get
{ {
return relayPrefab.maxTransmitDistance; return moduleRef.maxTransmitDistance;
} }
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
/// the current relay attempt. /// the current relay attempt.
/// </summary> /// </summary>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
public override bool relayChecked public override bool relayChecked
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the underlying part's title. /// Gets the underlying part's title.
/// </summary> /// </summary>
/// <value>The title.</value> /// <value>The title.</value>
public string title public string title
{ {
get get
{ {
return this.protoPart.partInfo.title; return this.protoPart.partInfo.title;
} }
} }
   
public override bool CanTransmit() public override bool CanTransmit()
{ {
PartStates partState = (PartStates)this.protoPart.state; PartStates partState = (PartStates)this.protoPart.state;
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.title, this.title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), partState) Enum.GetName(typeof(PartStates), partState)
)); ));
return false; return false;
} }
return base.CanTransmit(); return base.CanTransmit();
} }
   
public override string ToString() public override string ToString()
{ {
return string.Format( return string.Format(
"{0} on {1}.", "{0} on {1} (proto)",
this.title, this.title,
this.protoPart.pVesselRef.vesselName this.protoPart.pVesselRef.vesselName
); );
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param> /// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
/// <param name="vessel">The parent Vessel</param> /// <param name="vessel">The parent Vessel</param>
public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(pps.pVesselRef.vesselRef) public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
{ {
this.relayPrefab = prefabRelay;  
this.protoPart = pps; this.protoPart = pps;
this.vessel = pps.pVesselRef.vesselRef;  
} }
   
~ProtoAntennaRelay() ~ProtoAntennaRelay()
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: destroyed", "{0}: destroyed",
this.ToString() this.ToString()
)); ));
} }
} }
} }
   
   
// AntennaRange © 2014 toadicus // AntennaRange
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // RelayDatabase.cs
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ //
  // Copyright © 2014, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;  
using System.Text; using System.Text;
  using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase public class RelayDatabase
{ {
/* /*
* Static members * Static members
* */ * */
// Singleton storage // Singleton storage
protected static RelayDatabase _instance; protected static RelayDatabase _instance;
// Gets the singleton // Gets the singleton
public static RelayDatabase Instance public static RelayDatabase Instance
{ {
get get
{ {
if (_instance == null) if (_instance == null)
{ {
_instance = new RelayDatabase(); _instance = new RelayDatabase();
} }
   
return _instance; return _instance;
} }
} }
   
/* /*
* Instance members * Instance members
* */ * */
   
/* /*
* Fields * Fields
* */ * */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects. // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase; protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
   
// Vessel.id-keyed hash table of part counts, used for caching // Vessel.id-keyed hash table of part counts, used for caching
protected Dictionary<Guid, int> vesselPartCountTable; protected Dictionary<Guid, int> vesselPartCountTable;
   
// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time. // Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
public Dictionary<Guid, bool> CheckedVesselsTable; public Dictionary<Guid, bool> CheckedVesselsTable;
   
protected int cacheHits; protected int cacheHits;
protected int cacheMisses; protected int cacheMisses;
   
/* /*
* Properties * Properties
* */ * */
// Gets the Part-hashed table of relays in a given vessel // Gets the Part-hashed table of relays in a given vessel
public Dictionary<int, IAntennaRelay> this [Vessel vessel] public Dictionary<int, IAntennaRelay> this [Vessel vessel]
{ {
get get
{ {
// If we don't have an entry for this vessel... // If we don't have an entry for this vessel...
if (!this.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel.id))
{ {
// ...Generate an entry for this vessel. // ...Generate an entry for this vessel.
this.AddVessel(vessel); this.AddVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
{ {
// ...Update the our vessel in the cache // ...Update the our vessel in the cache
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// Otherwise, it's a hit // Otherwise, it's a hit
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
   
// Return the Part-hashed table of relays for this vessel // Return the Part-hashed table of relays for this vessel
return relayDatabase[vessel.id]; return relayDatabase[vessel.id];
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Adds a vessel to the database // Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses // The return for this function isn't used yet, but seems useful for potential future API-uses
public bool AddVessel(Vessel vessel) public bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// ...post an error // ...post an error
Debug.LogWarning(string.Format( Debug.LogWarning(string.Format(
"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.", "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// ...and refuse to add // ...and refuse to add
return false; return false;
} }
// otherwise, add the vessel to our tables... // otherwise, add the vessel to our tables...
else else
{ {
// Build an empty table... // Build an empty table...
this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>(); this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
   
// Update the empty index // Update the empty index
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
   
// Return success // Return success
return true; return true;
} }
} }
   
// Update the vessel's entry in the table // Update the vessel's entry in the table
public void UpdateVessel(Vessel vessel) public void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.", "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
   
// Actually build and assign the table // Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable); this.getVesselRelays(vessel, ref vesselTable);
// Set the part count // Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count; this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
} }
   
// Remove a vessel from the cache, if it exists. // Remove a vessel from the cache, if it exists.
public void DirtyVessel(Vessel vessel) public void DirtyVessel(Vessel vessel)
{ {
if (this.relayDatabase.ContainsKey(vessel.id)) if (this.relayDatabase.ContainsKey(vessel.id))
{ {
this.relayDatabase.Remove(vessel.id); this.relayDatabase.Remove(vessel.id);
} }
if (this.vesselPartCountTable.ContainsKey(vessel.id)) if (this.vesselPartCountTable.ContainsKey(vessel.id))
{ {
this.vesselPartCountTable.Remove(vessel.id); this.vesselPartCountTable.Remove(vessel.id);
} }
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
public bool ContainsKey(Guid key) public bool ContainsKey(Guid key)
{ {
return this.relayDatabase.ContainsKey(key); return this.relayDatabase.ContainsKey(key);
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
public bool ContainsKey(Vessel vessel) public bool ContainsKey(Vessel vessel)
{ {
return this.ContainsKey(vessel.id); return this.ContainsKey(vessel.id);
} }
   
// Runs when a vessel is modified (or when we switch to one, to catch docking events) // Runs when a vessel is modified (or when we switch to one, to catch docking events)
public void onVesselEvent(Vessel vessel) public void onVesselEvent(Vessel vessel)
{ {
// If we have this vessel in our cache... // If we have this vessel in our cache...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// If our part counts disagree (such as if a part has been added or broken off, // If our part counts disagree (such as if a part has been added or broken off,
// or if we've just docked or undocked)... // or if we've just docked or undocked)...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' ({2}).", "{0}: dirtying cache for vessel '{1}' ({2}).",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// Dirty the cache (real vessels will never have negative part counts) // Dirty the cache (real vessels will never have negative part counts)
this.DirtyVessel(vessel); this.DirtyVessel(vessel);
} }
} }
} }
   
// Runs when the player requests a scene change, such as when changing vessels or leaving flight. // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
public void onSceneChange(GameScenes scene) public void onSceneChange(GameScenes scene)
{ {
// If the active vessel is a real thing... // If the active vessel is a real thing...
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
// ... dirty its cache // ... dirty its cache
this.onVesselEvent(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
} }
   
// Runs when parts are undocked // Runs when parts are undocked
public void onPartEvent(Part part) public void onPartEvent(Part part)
{ {
if (part != null && part.vessel != null) if (part != null && part.vessel != null)
{ {
this.onVesselEvent(part.vessel); this.onVesselEvent(part.vessel);
} }
} }
   
// Runs when parts are coupled, as in docking // Runs when parts are coupled, as in docking
public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
this.onPartEvent(data.from); this.onPartEvent(data.from);
this.onPartEvent(data.to); this.onPartEvent(data.to);
} }
   
// Produce a Part-hashed table of relays for the given vessel // Produce a Part-hashed table of relays for the given vessel
protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays) protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
{ {
// We're going to completely regen this table, so dump the current contents. // We're going to completely regen this table, so dump the current contents.
relays.Clear(); relays.Clear();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Getting antenna relays from vessel {1}.", "{0}: Getting antenna relays from vessel {1}.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly. // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) { if (vessel.loaded) {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is loaded, searching for modules in loaded parts.", "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
foreach (Part part in vessel.Parts) foreach (Part part in vessel.Parts)
{ {
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
foreach (PartModule module in part.Modules) foreach (PartModule module in part.Modules)
{ {
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
// ...add the module to the table // ...add the module to the table
relays.Add(part.GetHashCode(), module as IAntennaRelay); relays.Add(part.GetHashCode(), module as IAntennaRelay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots. // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
else else
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is not loaded, searching for modules in prototype parts.", "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots) foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}", "{0}: Searching in protopartsnapshot {1}",
this.GetType().Name, this.GetType().Name,
pps pps
)); ));
   
// ...Fetch the prefab, because it's more useful for what we're doing. // ...Fetch the prefab, because it's more useful for what we're doing.
Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab; Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Got partPrefab {1} in protopartsnapshot {2}", "{0}: Got partPrefab {1} in protopartsnapshot {2}",
this.GetType().Name, this.GetType().Name,
partPrefab, partPrefab,
pps pps
)); ));
   
// ...loop through the PartModules in the prefab... // ...loop through the PartModules in the prefab...
foreach (PartModule module in partPrefab.Modules) foreach (PartModule module in partPrefab.Modules)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}", "{0}: Searching in partmodule {1}",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: partmodule {1} is antennarelay", "{0}: partmodule {1} is antennarelay",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...build a new ProtoAntennaRelay and add it to the table // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps)); relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.", "{0}: vessel '{1}' ({2}) has {3} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName, vessel.vesselName,
vessel.id, vessel.id,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
protected RelayDatabase() protected RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
this.CheckedVesselsTable = new Dictionary<Guid, bool>(); this.CheckedVesselsTable = new Dictionary<Guid, bool>();
   
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselEvent); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselEvent); GameEvents.onVesselChange.Add(this.onVesselEvent);
GameEvents.onVesselDestroy.Add(this.onVesselEvent); GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent); GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent); GameEvents.onPartUndock.Add(this.onPartEvent);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselEvent); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselEvent); GameEvents.onVesselChange.Remove(this.onVesselEvent);
GameEvents.onVesselDestroy.Remove(this.onVesselEvent); GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent); GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent); GameEvents.onPartUndock.Remove(this.onPartEvent);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
   
KSPLog.print(string.Format( KSPLog.print(string.Format(
"{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}", "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
this.GetType().Name, this.GetType().Name,
this.cacheHits, this.cacheHits,
this.cacheMisses, this.cacheMisses,
(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits) (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
)); ));
} }
   
#if DEBUG #if DEBUG
public void Dump() public void Dump()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
   
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
foreach (Guid id in this.relayDatabase.Keys) foreach (Guid id in this.relayDatabase.Keys)
{ {
sb.AppendFormat("\nVessel {0}:", id); sb.AppendFormat("\nVessel {0}:", id);
   
foreach (IAntennaRelay relay in this.relayDatabase[id].Values) foreach (IAntennaRelay relay in this.relayDatabase[id].Values)
{ {
sb.AppendFormat("\n\t{0}", relay.ToString()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
} }
#endif #endif
} }
} }
   
   
  // AntennaRange
  //
  // Extensions.cs
  //
  // Copyright © 2014, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using ToadicusTools;
 
  namespace AntennaRange
  {
  /*
  * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
  * */
  public static class RelayExtensions
  {
  /// <summary>
  /// Returns the distance between this IAntennaRelay and a Vessel
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="Vessel">A <see cref="Vessel"/></param>
  public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
  {
  return relay.vessel.DistanceTo(Vessel);
  }
 
  /// <summary>
  /// Returns the distance between this IAntennaRelay and a CelestialBody
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
  public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
  {
  return relay.vessel.DistanceTo(body) - body.Radius;
  }
 
  /// <summary>
  /// Returns the distance between this IAntennaRelay and another IAntennaRelay
  /// </summary>
  /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
  /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
  public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
  {
  return relayOne.DistanceTo(relayTwo.vessel);
  }
 
  /// <summary>
  /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
  public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
  {
  return RelayDatabase.Instance[vessel].Values.ToList();
  }
 
  /// <summary>
  /// Determines if the specified vessel has a connected relay.
  /// </summary>
  /// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns>
  /// <param name="vessel"></param>
  public static bool HasConnectedRelay(this Vessel vessel)
  {
  foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)
  {
  if (relay.CanTransmit())
  {
  return true;
  }
  }
 
  return false;
  }
  }
  }
 
 
file:b/toolbarIcon.xcf (new)
 Binary files /dev/null and b/toolbarIcon.xcf differ
 Binary files /dev/null and b/toolbarIcon_24x24.xcf differ
 Binary files /dev/null and b/toolbarIcon_38x38.xcf differ