ARConfiguration: Changed config assignment to be persistent, and added handling for requireConnectionForControl.
ARConfiguration: Changed config assignment to be persistent, and added handling for requireConnectionForControl.

--- /dev/null
+++ b/ARConfiguration.cs
@@ -1,1 +1,149 @@
+// 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;
+
+[assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
+
+namespace AntennaRange
+{
+	[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
+	public class ARConfiguration : MonoBehaviour
+	{
+		private bool showConfigWindow;
+		private Rect configWindowPos;
+
+		private IButton toolbarButton;
+
+		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.showConfigWindow = false;
+			this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
+
+			Tools.PostDebugMessage(this, "Awake.");
+		}
+
+		public void OnGUI()
+		{
+			if (this.toolbarButton == null && ToolbarManager.ToolbarAvailable)
+			{
+				Tools.PostDebugMessage(this, "Toolbar available; initializing 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.showConfigWindow = !this.showConfigWindow;
+				};
+
+				this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
+				AntennaRelay.requireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
+				ARFlightController.requireConnectionForControl =
+					this.LoadConfigValue("requireConnectionForControl", false);
+			}
+
+			if (this.showConfigWindow)
+			{
+				Rect configPos = GUILayout.Window(354163056,
+					this.configWindowPos,
+					this.ConfigWindow,
+					"AntennaRange Configuration",
+					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.EndVertical();
+
+			GUI.DragWindow();
+		}
+
+		public void Destroy()
+		{
+			if (this.toolbarButton != null)
+			{
+				this.toolbarButton.Destroy();
+			}
+		}
+
+		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();
+		}
+	}
+}
+

--- /dev/null
+++ b/ARFlightController.cs
@@ -1,1 +1,125 @@
+// 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 ToadicusTools;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	[KSPAddon(KSPAddon.Startup.Flight, false)]
+	public class ARFlightController : MonoBehaviour
+	{
+		#region Static Members
+		public static bool requireConnectionForControl;
+		#endregion
+
+		#region Properties
+		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.GROUPS_ALL | ControlTypes.STAGING | ControlTypes.SAS | ControlTypes.RCS;
+			}
+		}
+
+		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";
+		}
+
+		protected void FixedUpdate()
+		{
+			if (
+				requireConnectionForControl &&
+				this.vessel != null &&
+				!this.vessel.hasCrewCommand() &&
+				this.vessel.IsControllable
+			)
+			{
+				if (this.vessel.HasConnectedRelay())
+				{
+					if (currentControlLock != ControlTypes.None)
+					{
+						InputLockManager.RemoveControlLock(this.lockID);
+					}
+				}
+				else
+				{
+					if (currentControlLock == ControlTypes.None)
+					{
+						InputLockManager.SetControlLock(this.lockSet, this.lockID);
+					}
+				}
+			}
+		}
+
+		protected void Destroy()
+		{
+
+		}
+		#endregion
+	}
+}
+
+

file:a/ARTools.cs (deleted)
--- a/ARTools.cs
+++ /dev/null
@@ -1,158 +1,1 @@
-// 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();
-		}
-	}
-}
-
-

--- a/AntennaRange.cfg
+++ b/AntennaRange.cfg
@@ -1,21 +1,43 @@
+// AntennaRange
 //
-// 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/
+// 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.
+// 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]
+@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
@@ -26,7 +48,7 @@
 	}
 }
 
-@PART[mediumDishAntenna]
+@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
@@ -37,7 +59,7 @@
 	}
 }
 
-@PART[commDish]
+@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{

--- /dev/null
+++ b/AntennaRange.csproj
@@ -1,1 +1,106 @@
-
+<?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.1</ReleaseVersion>
+    <SynchReleaseVersion>false</SynchReleaseVersion>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+  </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 ${ProjectDir}\AntennaRange.cfg C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
+        <Command type="AfterBuild" command="xcopy /y ${TargetFile} C:\Users\andy\Games\KSP_win\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 ${ProjectDir}\AntennaRange.cfg C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
+        <Command type="AfterBuild" command="xcopy /y ${TargetFile} C:\Users\andy\Games\KSP_win\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}/${ProjectName}.cfg /opt/games/KSP_linux/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}/${ProjectName}.cfg /opt/games/KSP_linux/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>
+    <None Include="AntennaRange.cfg">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
+      <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
+      <Name>ToadicusTools</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="Assembly-CSharp">
+      <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="System">
+      <HintPath>..\_KSPAssemblies\System.dll</HintPath>
+    </Reference>
+    <Reference Include="UnityEngine">
+      <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+</Project>

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -1,32 +1,47 @@
-// 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.
+// AntennaRange
+//
+// AntennaRelay.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
 {
 	public class AntennaRelay
 	{
+		public static bool requireLineOfSight;
+
 		// We don't have a Bard, so we'll hide Kerbin here.
 		protected CelestialBody Kerbin;
 
 		protected IAntennaRelay _nearestRelayCache;
+		protected IAntennaRelay moduleRef;
 
 		protected System.Diagnostics.Stopwatch searchTimer;
 		protected long millisecondsBetweenSearches;
@@ -35,87 +50,92 @@
 		/// Gets the parent Vessel.
 		/// </summary>
 		/// <value>The parent Vessel.</value>
-		public Vessel vessel
+		public virtual Vessel vessel
+		{
+			get
+			{
+				return this.moduleRef.vessel;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the nearest relay.
+		/// </summary>
+		/// <value>The nearest relay</value>
+		public IAntennaRelay nearestRelay
+		{
+			get
+			{
+				if (this.searchTimer.IsRunning &&
+					this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
+				{
+					this._nearestRelayCache = this.FindNearestRelay();
+					this.searchTimer.Restart();
+				}
+
+				return this._nearestRelayCache;
+			}
+			protected set
+			{
+				this._nearestRelayCache = value;
+			}
+		}
+
+		/// <summary>
+		/// Gets the transmit distance.
+		/// </summary>
+		/// <value>The transmit distance.</value>
+		public double transmitDistance
+		{
+			get
+			{
+				this.nearestRelay = this.FindNearestRelay();
+
+				// If there is no available relay nearby...
+				if (this.nearestRelay == null)
+				{
+					// .. return the distance to Kerbin
+					return this.DistanceTo(this.Kerbin);
+				}
+				else
+				{
+					/// ...otherwise, return the distance to the nearest available relay.
+					return this.DistanceTo(nearestRelay);
+				}
+			}
+		}
+
+		/// <summary>
+		/// The maximum distance at which this relay can operate.
+		/// </summary>
+		/// <value>The max transmit distance.</value>
+		public virtual float maxTransmitDistance
+		{
+			get;
+			set;
+		}
+
+		/// <summary>
+		/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
+		/// the current relay attempt.
+		/// </summary>
+		/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
+		public virtual bool relayChecked
 		{
 			get;
 			protected set;
 		}
 
 		/// <summary>
-		/// Gets or sets the nearest relay.
-		/// </summary>
-		/// <value>The nearest relay</value>
-		public IAntennaRelay nearestRelay
-		{
-			get
-			{
-				if (this.searchTimer.IsRunning &&
-					this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
-				{
-					this._nearestRelayCache = this.FindNearestRelay();
-					this.searchTimer.Restart();
-				}
-
-				return this._nearestRelayCache;
-			}
-			protected set
-			{
-				this._nearestRelayCache = value;
-			}
-		}
-
-		/// <summary>
-		/// Gets the transmit distance.
-		/// </summary>
-		/// <value>The transmit distance.</value>
-		public double transmitDistance
-		{
-			get
-			{
-				this.nearestRelay = this.FindNearestRelay();
-
-				// If there is no available relay nearby...
-				if (this.nearestRelay == null)
-				{
-					// .. return the distance to Kerbin
-					return this.DistanceTo(this.Kerbin);
-				}
-				else
-				{
-					/// ...otherwise, return the distance to the nearest available relay.
-					return this.DistanceTo(nearestRelay);
-				}
-			}
-		}
-
-		/// <summary>
-		/// The maximum distance at which this relay can operate.
-		/// </summary>
-		/// <value>The max transmit distance.</value>
-		public virtual float maxTransmitDistance
-		{
-			get;
-			set;
-		}
-
-		/// <summary>
-		/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
-		/// the current relay attempt.
-		/// </summary>
-		/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
-		public virtual bool relayChecked
-		{
-			get;
-			protected set;
-		}
-
-		/// <summary>
 		/// Determines whether this instance can transmit.
 		/// </summary>
 		/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
 		public virtual bool CanTransmit()
 		{
-			if (this.transmitDistance > this.maxTransmitDistance)
+			if (
+				this.transmitDistance > this.maxTransmitDistance ||
+				(requireLineOfSight && this.nearestRelay == null && !this.vessel.hasLineOfSightTo(this.Kerbin))
+			)
 			{
 				return false;
 			}
@@ -144,10 +164,17 @@
 
 			this.searchTimer.Start();
 
+			Tools.PostDebugMessage(string.Format(
+				"{0}: finding nearest relay for {1} ({2})",
+				this.GetType().Name,
+				this,
+				this.vessel.id
+			));
+
 			// Set this vessel as checked, so that we don't check it again.
 			RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
 
-			double nearestDistance = double.PositiveInfinity;
+			double nearestSqrDistance = double.PositiveInfinity;
 			IAntennaRelay _nearestRelay = null;
 
 			/*
@@ -159,14 +186,10 @@
 			foreach (Vessel potentialVessel in FlightGlobals.Vessels)
 			{
 				// Skip vessels that have already been checked for a nearest relay this pass.
-				try
-				{
-					if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id])
-					{
-						continue;
-					}
-				}
-				catch (KeyNotFoundException) { /* If the key doesn't exist, do nothing. */}
+				if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(potentialVessel.id))
+				{
+					continue;
+				}
 
 				// Skip vessels of the wrong type.
 				switch (potentialVessel.vesselType)
@@ -187,25 +210,52 @@
 					continue;
 				}
 
+				// Skip vessels to which we do not have line of sight.
+				if (requireLineOfSight && !this.vessel.hasLineOfSightTo(potentialVessel))
+				{
+					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...
-				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
 				 * 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;
 				}
 
-				nearestDistance = potentialDistance;
+				nearestSqrDistance = potentialSqrDistance;
 
 				foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
 				{
 					if (potentialRelay.CanTransmit())
 					{
 						_nearestRelay = potentialRelay;
+						Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
+							this.GetType().Name,
+							_nearestRelay.ToString(),
+							_nearestRelay.vessel.id
+						));
 						break;
 					}
 				}
@@ -223,9 +273,9 @@
 		/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
 		/// </summary>
 		/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
-		public AntennaRelay(Vessel v)
-		{
-			this.vessel = v;
+		public AntennaRelay(IAntennaRelay module)
+		{
+			this.moduleRef = module;
 
 			this.searchTimer = new System.Diagnostics.Stopwatch();
 			this.millisecondsBetweenSearches = 5000;
@@ -235,44 +285,15 @@
 			this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
 		}
 
-		/*
-		 * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
-		 * */
-		internal class RelayComparer : IComparer<IAntennaRelay>
-		{
-			/// <summary>
-			/// The reference Vessel (usually the active vessel).
-			/// </summary>
-			protected Vessel referenceVessel;
-
-			// We don't want no stinking public parameterless constructors.
-			private RelayComparer() {}
-
-			/// <summary>
-			/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use
-			/// in sorting relays by distance.
-			/// </summary>
-			/// <param name="reference">The reference Vessel</param>
-			public RelayComparer(Vessel reference)
-			{
-				this.referenceVessel = reference;
-			}
-
-			/// <summary>
-			/// Compare the <see cref="IAntennaRelay"/>s "one" and "two".
-			/// </summary>
-			/// <param name="one">The first IAntennaRelay in the comparison</param>
-			/// <param name="two">The second IAntennaRelay in the comparison</param>
-			public int Compare(IAntennaRelay one, IAntennaRelay two)
-			{
-				double distanceOne;
-				double distanceTwo;
-
-				distanceOne = one.vessel.DistanceTo(referenceVessel);
-				distanceTwo = two.vessel.DistanceTo(referenceVessel);
-
-				return distanceOne.CompareTo(distanceTwo);
-			}
+		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)
--- a/EventSniffer.cs
+++ /dev/null
@@ -1,207 +1,1 @@
-// 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)
--- a/Extensions.cs
+++ /dev/null
@@ -1,92 +1,1 @@
-// 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();
-		}
-
-
-	}
-}
-
-

--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -1,19 +1,30 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
 //
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// IAntennaRelay.cs
 //
-//     Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-//     Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-//     Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014, toadicus
+// All rights reserved.
 //
-// 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/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
 //
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+//    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;

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,25 +1,37 @@
-// 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.
-
+// AntennaRange
+//
+// ModuleLimitedDataTransmitter.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 System.Linq;
 using System.Text;
-using KSP;
+using ToadicusTools;
 using UnityEngine;
 
 namespace AntennaRange
@@ -82,6 +94,16 @@
 		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 		[KSPField(isPersistant = false)]
 		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;
 
@@ -195,6 +217,7 @@
 		public ModuleLimitedDataTransmitter () : base()
 		{
 			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.
@@ -204,7 +227,7 @@
 
 			if (state >= StartState.PreLaunch)
 			{
-				this.relay = new AntennaRelay(vessel);
+				this.relay = new AntennaRelay(this);
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
 
 				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
@@ -247,11 +270,7 @@
 		// is only one reason for this.
 		protected void PostCannotTransmitError()
 		{
-			string ErrorText = string.Format (
-				"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)
-				);
+			string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
 
 			this.ErrorMsg.message = string.Format(
 				"<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
@@ -297,6 +316,8 @@
 					this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
 					this._basepacketSize * this.maxDataFactor);
 			}
+
+			base.packetSize *= this.packetThrottle / 100f;
 		}
 
 		// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
@@ -330,8 +351,31 @@
 		// returns false.
 		public new void TransmitData(List<ScienceData> dataQueue)
 		{
+			this.PreTransmit_SetPacketSize();
+			this.PreTransmit_SetPacketResourceCost();
+
 			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);
 			}
 			else
@@ -451,7 +495,8 @@
 				"DataRate: {9}\n" +
 				"DataResourceCost: {10}\n" +
 				"TransmitterScore: {11}\n" +
-				"NearestRelay: {12}",
+				"NearestRelay: {12}\n" +
+				"Vessel ID: {13}",
 				this.name,
 				this._basepacketSize,
 				base.packetSize,
@@ -464,9 +509,31 @@
 				this.DataRate,
 				this.DataResourceCost,
 				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
 	}

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -1,19 +1,31 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
 //
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// AssemblyInfo.cs
 //
-//     Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-//     Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-//     Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014, toadicus
+// All rights reserved.
 //
-// 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/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
 //
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+//    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.Runtime.CompilerServices;
 
@@ -25,7 +37,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("0.6.3.*")]
+[assembly: AssemblyVersion("1.1.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -1,22 +1,35 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
 //
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// ProtoAntennaRelay.cs
 //
-//     Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-//     Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-//     Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014, toadicus
+// All rights reserved.
 //
-// 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/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
 //
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+//    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.Linq;
+using ToadicusTools;
 
 namespace AntennaRange
 {
@@ -26,11 +39,16 @@
 	 * */
 	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.
 		protected ProtoPartSnapshot protoPart;
+
+		public override Vessel vessel
+		{
+			get
+			{
+				return this.protoPart.pVesselRef.vesselRef;
+			}
+		}
 
 		/// <summary>
 		/// The maximum distance at which this transmitter can operate.
@@ -40,7 +58,7 @@
 		{
 			get
 			{
-				return relayPrefab.maxTransmitDistance;
+				return moduleRef.maxTransmitDistance;
 			}
 		}
 
@@ -87,7 +105,7 @@
 		public override string ToString()
 		{
 			return string.Format(
-				"{0} on {1}.",
+				"{0} on {1} (proto)",
 				this.title,
 				this.protoPart.pVesselRef.vesselName
 			);
@@ -98,11 +116,9 @@
 		/// </summary>
 		/// <param name="ms">The ProtoPartModuleSnapshot to wrap</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.vessel = pps.pVesselRef.vesselRef;
 		}
 
 		~ProtoAntennaRelay()

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -1,12 +1,36 @@
-// 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/
+// AntennaRange
+//
+// RelayDatabase.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 System.Linq;
+using System.Text;
+using ToadicusTools;
 using UnityEngine;
 
 namespace AntennaRange
@@ -45,6 +69,12 @@
 		// Vessel.id-keyed hash table of part counts, used for caching
 		protected Dictionary<Guid, int> vesselPartCountTable;
 
+		// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
+		public Dictionary<Guid, bool> CheckedVesselsTable;
+
+		protected int cacheHits;
+		protected int cacheMisses;
+
 		/*
 		 * Properties
 		 * */
@@ -58,12 +88,19 @@
 				{
 					// ...Generate an entry for this vessel.
 					this.AddVessel(vessel);
+					this.cacheMisses++;
 				}
 				// If our part count disagrees with the vessel's part count...
-				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+				else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
 				{
 					// ...Update the our vessel in the cache
 					this.UpdateVessel(vessel);
+					this.cacheMisses++;
+				}
+				// Otherwise, it's a hit
+				else
+				{
+					this.cacheHits++;
 				}
 
 				// Return the Part-hashed table of relays for this vessel
@@ -79,13 +116,13 @@
 		public bool AddVessel(Vessel vessel)
 		{
 			// If this vessel is already here...
-			if (relayDatabase.ContainsKey(vessel.id))
+			if (this.ContainsKey(vessel))
 			{
 				// ...post an error
 				Debug.LogWarning(string.Format(
 					"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
 					this.GetType().Name,
-					vessel.name,
+					vessel.vesselName,
 					vessel.id
 				));
 
@@ -110,12 +147,12 @@
 		public void UpdateVessel(Vessel vessel)
 		{
 			// Squak if the database doesn't have the vessel
-			if (!relayDatabase.ContainsKey(vessel.id))
+			if (!this.ContainsKey(vessel))
 			{
 				throw new InvalidOperationException(string.Format(
-					"{0}: Update called 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,
-					vessel.name,
+					vessel.vesselName,
 					vessel.id
 				));
 			}
@@ -128,6 +165,19 @@
 			this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
 		}
 
+		// Remove a vessel from the cache, if it exists.
+		public void DirtyVessel(Vessel vessel)
+		{
+			if (this.relayDatabase.ContainsKey(vessel.id))
+			{
+				this.relayDatabase.Remove(vessel.id);
+			}
+			if (this.vesselPartCountTable.ContainsKey(vessel.id))
+			{
+				this.vesselPartCountTable.Remove(vessel.id);
+			}
+		}
+
 		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
 		public bool ContainsKey(Guid key)
 		{
@@ -141,24 +191,24 @@
 		}
 
 		// Runs when a vessel is modified (or when we switch to one, to catch docking events)
-		public void onVesselWasModified(Vessel vessel)
+		public void onVesselEvent(Vessel vessel)
 		{
 			// If we have this vessel in our cache...
 			if (this.ContainsKey(vessel))
 			{
 				// If our part counts disagree (such as if a part has been added or broken off,
 				// or if we've just docked or undocked)...
-				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
 				{
 					Tools.PostDebugMessage(string.Format(
-						"{0}: dirtying cache for vessel '{1}' (id: {2}).",
+						"{0}: dirtying cache for vessel '{1}' ({2}).",
 						this.GetType().Name,
-						vessel.name,
+						vessel.vesselName,
 						vessel.id
 					));
 
 					// Dirty the cache (real vessels will never have negative part counts)
-					this.vesselPartCountTable[vessel.id] = -1;
+					this.DirtyVessel(vessel);
 				}
 			}
 		}
@@ -170,8 +220,24 @@
 			if (FlightGlobals.ActiveVessel != null)
 			{
 				// ... dirty its cache
-				this.onVesselWasModified(FlightGlobals.ActiveVessel);
-			}
+				this.onVesselEvent(FlightGlobals.ActiveVessel);
+			}
+		}
+
+		// Runs when parts are undocked
+		public void onPartEvent(Part part)
+		{
+			if (part != null && part.vessel != null)
+			{
+				this.onVesselEvent(part.vessel);
+			}
+		}
+
+		// Runs when parts are coupled, as in docking
+		public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
+		{
+			this.onPartEvent(data.from);
+			this.onPartEvent(data.to);
 		}
 
 		// Produce a Part-hashed table of relays for the given vessel
@@ -183,7 +249,7 @@
 			Tools.PostDebugMessage(string.Format(
 				"{0}: Getting antenna relays from vessel {1}.",
 				"IAntennaRelay",
-				vessel.name
+				vessel.vesselName
 			));
 
 			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
@@ -191,7 +257,7 @@
 				Tools.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
 					"IAntennaRelay",
-					vessel.name
+					vessel.vesselName
 				));
 
 				// Loop through the Parts in the Vessel...
@@ -217,7 +283,7 @@
 				Tools.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
 					this.GetType().Name,
-					vessel.name
+					vessel.vesselName
 				));
 
 				// Loop through the ProtoPartModuleSnapshots in the Vessel...
@@ -267,9 +333,10 @@
 			}
 
 			Tools.PostDebugMessage(string.Format(
-				"{0}: vessel '{1}' has {2} transmitters.",
+				"{0}: vessel '{1}' ({2}) has {3} transmitters.",
 				"IAntennaRelay",
-				vessel.name,
+				vessel.vesselName,
+				vessel.id,
 				relays.Count
 			));
 		}
@@ -278,24 +345,63 @@
 		protected RelayDatabase()
 		{
 			// Initialize the databases
-			relayDatabase =	new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
-			vesselPartCountTable = new Dictionary<Guid, int>();
+			this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+			this.vesselPartCountTable = new Dictionary<Guid, int>();
+			this.CheckedVesselsTable = new Dictionary<Guid, bool>();
+
+			this.cacheHits = 0;
+			this.cacheMisses = 0;
 
 			// Subscribe to some events
-			GameEvents.onVesselWasModified.Add(this.onVesselWasModified);
-			GameEvents.onVesselChange.Add(this.onVesselWasModified);
+			GameEvents.onVesselWasModified.Add(this.onVesselEvent);
+			GameEvents.onVesselChange.Add(this.onVesselEvent);
+			GameEvents.onVesselDestroy.Add(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
+			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
+			GameEvents.onPartUndock.Add(this.onPartEvent);
 		}
 
 		~RelayDatabase()
 		{
 			// Unsubscribe from the events
-			GameEvents.onVesselWasModified.Remove(this.onVesselWasModified);
-			GameEvents.onVesselChange.Remove(this.onVesselWasModified);
+			GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
+			GameEvents.onVesselChange.Remove(this.onVesselEvent);
+			GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
+			GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
+			GameEvents.onPartUndock.Remove(this.onPartEvent);
 
 			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
-		}
+
+			KSPLog.print(string.Format(
+				"{0} destructed.  Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
+				this.GetType().Name,
+				this.cacheHits,
+				this.cacheMisses,
+				(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
+			));
+		}
+
+		#if DEBUG
+		public void Dump()
+		{
+			StringBuilder sb = new StringBuilder();
+
+			sb.Append("Dumping RelayDatabase:");
+
+			foreach (Guid id in this.relayDatabase.Keys)
+			{
+				sb.AppendFormat("\nVessel {0}:", id);
+
+				foreach (IAntennaRelay relay in this.relayDatabase[id].Values)
+				{
+					sb.AppendFormat("\n\t{0}", relay.ToString());
+				}
+			}
+
+			Tools.PostDebugMessage(sb.ToString());
+		}
+		#endif
 	}
 }
 

--- /dev/null
+++ b/RelayExtensions.cs
@@ -1,1 +1,100 @@
+// 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);
+		}
+
+		/// <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;
+		}
+	}
+}
+
+