RelayDatabase:
RelayDatabase:
* Added new CheckedVesselsTable to keep track of vessels as we check them instead of relays.
* Added listeners for onVesselDestroy and onPartCouple to improve reliability. Hopefully.

file:b/ARTools.cs (new)
--- /dev/null
+++ b/ARTools.cs
@@ -1,1 +1,158 @@
+// 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,50 +1,50 @@
-//

-// 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

-	}

-}

+//
+// 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
+	}
+}
+

file:a/AntennaRange.cs (deleted)
--- a/AntennaRange.cs
+++ /dev/null
@@ -1,439 +1,1 @@
-/*
- * AntennaRange © 2013 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 KSP;
-
-namespace AntennaRange
-{
-	/*
-	 * 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
-	 * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
-	 * 
-	 * In general, the scaling functions assume the following relation:
-	 * 
-	 *     D² α P/R,
-	 * 
-	 * where D is the total transmission distance, P is the transmission power, and R is the data rate.
-	 * 
-	 * */
-	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter
-	{
-		// Stores the packetResourceCost as defined in the .cfg file.
-		protected float _basepacketResourceCost;
-
-		// Stores the packetSize as defined in the .cfg file.
-		protected float _basepacketSize;
-
-		// We don't have a Bard, so we're hiding Kerbin here.
-		protected CelestialBody _Kerbin;
-
-		// Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes.
-		protected double transmitDistance
-		{
-			get
-			{
-				Vector3d KerbinPos = this._Kerbin.position;
-				Vector3d ActivePos = base.vessel.GetWorldPos3D();
-
-				return (ActivePos - KerbinPos).magnitude;
-			}
-		}
-
-		// Returns the maximum distance this module can transmit
-		public double maxTransmitDistance
-		{
-			get
-			{
-				return Math.Sqrt (this.maxPowerFactor) * this.nominalRange;
-			}
-		}
-
-		// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
-		// and packetSize.
-		[KSPField(isPersistant = false)]
-		public float nominalRange;
-
-		// The multiplier on packetResourceCost that defines the maximum power output of the antenna.  When the power
-		// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
-		[KSPField(isPersistant = false)]
-		public float maxPowerFactor;
-
-		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
-		[KSPField(isPersistant = false)]
-		public float maxDataFactor;
-
-
-		/*
-		 * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
-		 * in that they both return floats.  Here's some quick justification:
-		 * 
-		 * The stock implementation of GetTransmitterScore (which I cannot override) is:
-		 * 		Score = (1 + DataResourceCost) / DataRate
-		 * 
-		 * The stock DataRate and DataResourceCost are:
-		 * 		DataRate = packetSize / packetInterval
-		 * 		DataResourceCost = packetResourceCost / packetSize
-		 * 
-		 * So, the resulting score is essentially in terms of joules per byte per baud.  Rearranging that a bit, it
-		 * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte.  Either way,
-		 * that metric is not a very reasonable one.
-		 * 
-		 * Two metrics that might make more sense are joules per byte or joules per byte per second.  The latter case
-		 * would look like:
-		 * 		DataRate = packetSize / packetInterval
-		 * 		DataResourceCost = packetResourceCost
-		 * 
-		 * The former case, which I've chosen to implement below, is:
-		 * 		DataRate = packetSize
-		 * 		DataResourceCost = packetResourceCost
-		 * 
-		 * So... hopefully that doesn't screw with anything else.
-		 * */
-		// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
-		// terms of joules/byte
-		public new float DataRate
-		{
-			get
-			{
-				PreTransmit_SetPacketSize();
-				return this.packetSize;
-			}
-		}
-
-		// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
-		// to be scored in terms of joules/byte
-		public new float DataResourceCost
-		{
-			get
-			{
-				PreTransmit_SetPacketResourceCost();
-				return this.packetResourceCost;
-			}
-		}
-
-		// Build ALL the objects.
-		public ModuleLimitedDataTransmitter () : base() { }
-
-		// 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._Kerbin == null)
-			{
-				// Go fetch Kerbin, because it is tricksy and hides from us.
-				List<CelestialBody> bodies = FlightGlobals.Bodies;
-
-				foreach (CelestialBody body in bodies)
-				{
-					if (body.name == "Kerbin")
-					{
-						this._Kerbin = body;
-						break;
-					}
-				}
-			}
-		}
-
-		// 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._basepacketResourceCost = base.packetResourceCost;
-
-			Tools.PostDebugMessage(string.Format(
-				"{0} loaded:\n" +
-				"packetSize: {1}\n" +
-				"packetResourceCost: {2}\n" +
-				"nominalRange: {3}\n" +
-				"maxPowerFactor: {4}\n" +
-				"maxDataFactor: {5}\n",
-				this.name,
-				base.packetSize,
-				this._basepacketResourceCost,
-				this.nominalRange,
-				this.maxPowerFactor,
-				this.maxDataFactor
-			));
-		}
-
-		// Post an error in the communication messages describing the reason transmission has failed.  Currently there
-		// is only one reason for this.
-		protected void PostCannotTransmitError()
-		{
-			string ErrorText = string.Format (
-				"Unable to transmit: out of range!  Maximum range = {0}; Current range = {1}.",
-				Tools.MuMech_ToSI((double)this.maxTransmitDistance),
-				Tools.MuMech_ToSI((double)this.transmitDistance));
-			ScreenMessages.PostScreenMessage (new ScreenMessage (ErrorText, 4f, ScreenMessageStyle.UPPER_LEFT));
-		}
-
-		// Before transmission, set packetResourceCost.  Per above, packet cost increases with the square of
-		// distance.  packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
-		// transmission fails (see CanTransmit).
-		protected void PreTransmit_SetPacketResourceCost()
-		{
-			if (this.transmitDistance <= this.nominalRange)
-			{
-				base.packetResourceCost = this._basepacketResourceCost;
-			}
-			else
-			{
-				base.packetResourceCost = this._basepacketResourceCost
-					* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
-			}
-		}
-
-		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
-		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
-		protected void PreTransmit_SetPacketSize()
-		{
-			if (this.transmitDistance >= this.nominalRange)
-			{
-				base.packetSize = this._basepacketSize;
-			}
-			else
-			{
-				base.packetSize = Math.Min(
-					this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
-					this._basepacketSize * this.maxDataFactor);
-			}
-		}
-
-		// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
-		public override string GetInfo()
-		{
-			string text = base.GetInfo();
-			text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange) + "\n";
-			text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance) + "\n";
-			return text;
-		}
-
-		// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
-		public new bool CanTransmit()
-		{
-			if (this.transmitDistance > this.maxTransmitDistance)
-			{
-				return false;
-			}
-			return true;
-		}
-
-		// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
-		// returns false.
-		public new void TransmitData(List<ScienceData> dataQueue)
-		{
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
-
-			Tools.PostDebugMessage (
-				"distance: " + this.transmitDistance
-				+ " packetSize: " + this.packetSize
-				+ " packetResourceCost: " + this.packetResourceCost
-			);
-			if (this.CanTransmit())
-			{
-				base.TransmitData(dataQueue);
-			}
-			else
-			{
-				this.PostCannotTransmitError ();
-			}
-		}
-
-		// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
-		// returns false.
-		public new void StartTransmission()
-		{
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
-
-			Tools.PostDebugMessage (
-				"distance: " + this.transmitDistance
-				+ " packetSize: " + this.packetSize
-				+ " packetResourceCost: " + this.packetResourceCost
-				);
-			if (this.CanTransmit())
-			{
-				base.StartTransmission();
-			}
-			else
-			{
-				this.PostCannotTransmitError ();
-			}
-		}
-
-		// When debugging, it's nice to have a button that just tells you everything.
-		#if DEBUG
-		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
-		public void DebugInfo()
-		{
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
-
-			string msg = string.Format(
-				"'{0}'\n" + 
-				"_basepacketSize: {1}\n" +
-				"packetSize: {2}\n" +
-				"_basepacketResourceCost: {3}\n" +
-				"packetResourceCost: {4}\n" +
-				"maxTransmitDistance: {5}\n" +
-				"transmitDistance: {6}\n" +
-				"nominalRange: {7}\n" +
-				"CanTransmit: {8}\n" +
-				"DataRate: {9}\n" +
-				"DataResourceCost: {10}\n" +
-				"TransmitterScore: {11}",
-				this.name,
-				this._basepacketSize,
-				base.packetSize,
-				this._basepacketResourceCost,
-				base.packetResourceCost,
-				this.maxTransmitDistance,
-				this.transmitDistance,
-				this.nominalRange,
-				this.CanTransmit(),
-				this.DataRate,
-				this.DataResourceCost,
-				ScienceUtil.GetTransmitterScore(this)
-				);
-			ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));
-		}
-		#endif
-	}
-
-	public static class Tools
-	{
-		// When debugging, be verbose.  The Conditional attribute prevents this from firing when not DEBUGging.
-		[System.Diagnostics.Conditional("DEBUG")]
-		public static void PostDebugMessage(string Msg)
-		{
-			if (HighLogic.LoadedScene > GameScenes.SPACECENTER)
-			{
-				ScreenMessage Message = new ScreenMessage(Msg, 4f, ScreenMessageStyle.LOWER_CENTER);
-				ScreenMessages.PostScreenMessage(Message);
-			}
-			else
-			{
-				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";
-			}
-		}
-	}
-}
-

file:b/AntennaRelay.cs (new)
--- /dev/null
+++ b/AntennaRelay.cs
@@ -1,1 +1,280 @@
-
+// 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
+{
+	public class AntennaRelay
+	{
+		// We don't have a Bard, so we'll hide Kerbin here.
+		protected CelestialBody Kerbin;
+
+		protected IAntennaRelay _nearestRelayCache;
+
+		protected System.Diagnostics.Stopwatch searchTimer;
+		protected long millisecondsBetweenSearches;
+
+		/// <summary>
+		/// Gets the parent Vessel.
+		/// </summary>
+		/// <value>The parent Vessel.</value>
+		public Vessel vessel
+		{
+			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)
+			{
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+		}
+
+		/// <summary>
+		/// Finds the nearest relay.
+		/// </summary>
+		/// <returns>The nearest relay or null, if no relays in range.</returns>
+		public IAntennaRelay FindNearestRelay()
+		{
+			if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
+			{
+				return this.nearestRelay;
+			}
+
+			if (this.searchTimer.IsRunning)
+			{
+				this.searchTimer.Stop();
+				this.searchTimer.Reset();
+			}
+
+			this.searchTimer.Start();
+
+			// Set this vessel as checked, so that we don't check it again.
+			RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
+
+			double nearestDistance = double.PositiveInfinity;
+			IAntennaRelay _nearestRelay = null;
+
+			/*
+			 * 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
+			 * and that can transmit.  Once we find a suitable candidate, assign it to _nearestRelay for comparison
+			 * against future finds.
+			 * */
+			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. */}
+
+				// Skip vessels of the wrong type.
+				switch (potentialVessel.vesselType)
+				{
+					case VesselType.Debris:
+					case VesselType.Flag:
+					case VesselType.EVA:
+					case VesselType.SpaceObject:
+					case VesselType.Unknown:
+						continue;
+					default:
+						break;
+				}
+
+				// Skip vessels with the wrong ID
+				if (potentialVessel.id == vessel.id)
+				{
+					continue;
+				}
+
+				// Find the distance from here to the vessel...
+				double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude;
+
+				/*
+				 * ...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)))
+				{
+					continue;
+				}
+
+				nearestDistance = potentialDistance;
+
+				foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
+				{
+					if (potentialRelay.CanTransmit())
+					{
+						_nearestRelay = potentialRelay;
+						break;
+					}
+				}
+			}
+
+			// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
+			// used next time.
+			RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
+
+			// Return the nearest available relay, or null if there are no available relays nearby.
+			return _nearestRelay;
+		}
+
+		/// <summary>
+		/// 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;
+
+			this.searchTimer = new System.Diagnostics.Stopwatch();
+			this.millisecondsBetweenSearches = 5000;
+
+			// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
+			// we hope it is safe enough.
+			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);
+			}
+		}
+	}
+}
+
+

file:b/ChangeLog (new)
--- /dev/null
+++ b/ChangeLog
@@ -1,1 +1,7 @@
+2014-01-14  toadicus  <>
 
+	* ModuleLimitedDataTransmitter.cs: Added a ":" to the
+	  transmission communications for consistency with stock
+	  behavior.
+
+

file:b/EventSniffer.cs (new)
--- /dev/null
+++ b/EventSniffer.cs
@@ -1,1 +1,207 @@
-
+// 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:b/Extensions.cs (new)
--- /dev/null
+++ b/Extensions.cs
@@ -1,1 +1,92 @@
+// 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();
+		}
+
+
+	}
+}
+
+

file:b/IAntennaRelay.cs (new)
--- /dev/null
+++ b/IAntennaRelay.cs
@@ -1,1 +1,61 @@
+// 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 KSP;
+using System;
+
+namespace AntennaRange
+{
+	/*
+	 * Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
+	 * */
+	public interface IAntennaRelay
+	{
+		/// <summary>
+		/// Gets the parent Vessel.
+		/// </summary>
+		/// <value>The parent Vessel.</value>
+		Vessel vessel { get; }
+
+		/// <summary>
+		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
+		/// </summary>
+		/// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>
+		double transmitDistance { get; }
+
+		/// <summary>
+		/// The maximum distance at which this relay can operate.
+		/// </summary>
+		/// <value>The max transmit distance.</value>
+		float maxTransmitDistance { get; }
+
+		/// <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>
+		bool relayChecked { get; }
+
+		/// <summary>
+		/// Determines whether this instance can transmit.
+		/// </summary>
+		/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
+		bool CanTransmit();
+	}
+}
+
+

--- /dev/null
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,1 +1,473 @@
-
+// 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;
+using System.Text;
+using KSP;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	/*
+	 * 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
+	 * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
+	 * 
+	 * In general, the scaling functions assume the following relation:
+	 * 
+	 *     D² α P/R,
+	 * 
+	 * where D is the total transmission distance, P is the transmission power, and R is the data rate.
+	 * 
+	 * */
+
+	/*
+	 * Fields
+	 * */
+	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
+	{
+		// Stores the packetResourceCost as defined in the .cfg file.
+		protected float _basepacketResourceCost;
+
+		// Stores the packetSize as defined in the .cfg file.
+		protected float _basepacketSize;
+
+		// Every antenna is a relay.
+		protected AntennaRelay relay;
+
+		// Keep track of vessels with transmitters for relay purposes.
+		protected List<Vessel> _relayVessels;
+
+		// Sometimes we will need to communicate errors; this is how we do it.
+		protected ScreenMessage ErrorMsg;
+
+		// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
+		// and packetSize.
+		[KSPField(isPersistant = false)]
+		public float nominalRange;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
+		public string UItransmitDistance;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
+		public string UImaxTransmitDistance;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
+		public string UIpacketSize;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
+		public string UIpacketCost;
+
+		// The multiplier on packetResourceCost that defines the maximum power output of the antenna.  When the power
+		// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+		[KSPField(isPersistant = false)]
+		public float maxPowerFactor;
+
+		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+		[KSPField(isPersistant = false)]
+		public float maxDataFactor;
+
+		protected bool actionUIUpdate;
+
+		/*
+		 * Properties
+		 * */
+		// Returns the parent vessel housing this antenna.
+		public new Vessel vessel
+		{
+			get
+			{
+				return base.vessel;
+			}
+		}
+
+		// Returns the distance to the nearest relay or Kerbin, whichever is closer.
+		public double transmitDistance
+		{
+			get
+			{
+				return this.relay.transmitDistance;
+			}
+		}
+
+		// Returns the maximum distance this module can transmit
+		public float maxTransmitDistance
+		{
+			get
+			{
+				return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
+			}
+		}
+
+		/*
+		 * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
+		 * in that they both return floats.  Here's some quick justification:
+		 * 
+		 * The stock implementation of GetTransmitterScore (which I cannot override) is:
+		 * 		Score = (1 + DataResourceCost) / DataRate
+		 * 
+		 * The stock DataRate and DataResourceCost are:
+		 * 		DataRate = packetSize / packetInterval
+		 * 		DataResourceCost = packetResourceCost / packetSize
+		 * 
+		 * So, the resulting score is essentially in terms of joules per byte per baud.  Rearranging that a bit, it
+		 * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte.  Either way,
+		 * that metric is not a very reasonable one.
+		 * 
+		 * Two metrics that might make more sense are joules per byte or joules per byte per second.  The latter case
+		 * would look like:
+		 * 		DataRate = packetSize / packetInterval
+		 * 		DataResourceCost = packetResourceCost
+		 * 
+		 * The former case, which I've chosen to implement below, is:
+		 * 		DataRate = packetSize
+		 * 		DataResourceCost = packetResourceCost
+		 * 
+		 * So... hopefully that doesn't screw with anything else.
+		 * */
+		// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
+		// terms of joules/byte
+		public new float DataRate
+		{
+			get
+			{
+				this.PreTransmit_SetPacketSize();
+
+				if (this.CanTransmit())
+				{
+					return this.packetSize;
+				}
+				else
+				{
+					return float.Epsilon;
+				}
+			}
+		}
+
+		// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+		// to be scored in terms of joules/byte
+		public new float DataResourceCost
+		{
+			get
+			{
+				this.PreTransmit_SetPacketResourceCost();
+
+				if (this.CanTransmit())
+				{
+					return this.packetResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
+
+		// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
+		public bool relayChecked
+		{
+			get
+			{
+				return this.relay.relayChecked;
+			}
+		}
+
+		/*
+		 * Methods
+		 * */
+		// Build ALL the objects.
+		public ModuleLimitedDataTransmitter () : base()
+		{
+			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
+		}
+
+		// 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(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._basepacketResourceCost = base.packetResourceCost;
+
+			Tools.PostDebugMessage(string.Format(
+				"{0} loaded:\n" +
+				"packetSize: {1}\n" +
+				"packetResourceCost: {2}\n" +
+				"nominalRange: {3}\n" +
+				"maxPowerFactor: {4}\n" +
+				"maxDataFactor: {5}\n",
+				this.name,
+				base.packetSize,
+				this._basepacketResourceCost,
+				this.nominalRange,
+				this.maxPowerFactor,
+				this.maxDataFactor
+			));
+		}
+
+		// Post an error in the communication messages describing the reason transmission has failed.  Currently there
+		// 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)
+				);
+
+			this.ErrorMsg.message = string.Format(
+				"<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
+				((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
+				ErrorText
+			);
+
+			Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);
+
+			ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
+		}
+
+		// Before transmission, set packetResourceCost.  Per above, packet cost increases with the square of
+		// distance.  packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
+		// transmission fails (see CanTransmit).
+		protected void PreTransmit_SetPacketResourceCost()
+		{
+			if (this.transmitDistance <= this.nominalRange)
+			{
+				base.packetResourceCost = this._basepacketResourceCost;
+			}
+			else
+			{
+				base.packetResourceCost = this._basepacketResourceCost
+					* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
+			}
+		}
+
+		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
+		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
+		protected void PreTransmit_SetPacketSize()
+		{
+			if (this.transmitDistance >= this.nominalRange)
+			{
+				base.packetSize = this._basepacketSize;
+			}
+			else
+			{
+				base.packetSize = Math.Min(
+					this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
+					this._basepacketSize * this.maxDataFactor);
+			}
+		}
+
+		// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
+		public override string GetInfo()
+		{
+			string text = base.GetInfo();
+			text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
+			text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
+			return text;
+		}
+
+		// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
+		public new bool CanTransmit()
+		{
+			PartStates partState = this.part.State;
+			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: {1} on {2} cannot transmit: {3}",
+					this.GetType().Name,
+					this.part.partInfo.title,
+					this.vessel.vesselName,
+					Enum.GetName(typeof(PartStates), partState)
+				));
+				return false;
+			}
+			return this.relay.CanTransmit();
+		}
+
+		// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
+		// returns false.
+		public new void TransmitData(List<ScienceData> dataQueue)
+		{
+			if (this.CanTransmit())
+			{
+				base.TransmitData(dataQueue);
+			}
+			else
+			{
+				this.PostCannotTransmitError ();
+			}
+
+			Tools.PostDebugMessage (
+				"distance: " + this.transmitDistance
+				+ " packetSize: " + this.packetSize
+				+ " packetResourceCost: " + this.packetResourceCost
+			);
+		}
+
+		// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
+		// returns false.
+		public new void StartTransmission()
+		{
+			PreTransmit_SetPacketSize ();
+			PreTransmit_SetPacketResourceCost ();
+
+			Tools.PostDebugMessage (
+				"distance: " + this.transmitDistance
+				+ " packetSize: " + this.packetSize
+				+ " packetResourceCost: " + this.packetResourceCost
+				);
+
+			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.StartTransmission();
+			}
+			else
+			{
+				this.PostCannotTransmitError ();
+			}
+		}
+
+		public void Update()
+		{
+			if (this.actionUIUpdate)
+			{
+				this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+				this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A";
+				this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "N/A";
+			}
+		}
+
+		public void onPartActionUICreate(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = true;
+			}
+		}
+
+		public void onPartActionUIDismiss(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = false;
+			}
+		}
+
+		public override string ToString()
+		{
+			StringBuilder msg = new StringBuilder();
+
+			msg.Append(this.part.partInfo.title);
+
+			if (vessel != null)
+			{
+				msg.Append(" on ");
+				msg.Append(vessel.vesselName);
+			}
+
+			return msg.ToString();
+		}
+
+		// When debugging, it's nice to have a button that just tells you everything.
+		#if DEBUG
+		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
+		public void DebugInfo()
+		{
+			PreTransmit_SetPacketSize ();
+			PreTransmit_SetPacketResourceCost ();
+
+			string msg = string.Format(
+				"'{0}'\n" + 
+				"_basepacketSize: {1}\n" +
+				"packetSize: {2}\n" +
+				"_basepacketResourceCost: {3}\n" +
+				"packetResourceCost: {4}\n" +
+				"maxTransmitDistance: {5}\n" +
+				"transmitDistance: {6}\n" +
+				"nominalRange: {7}\n" +
+				"CanTransmit: {8}\n" +
+				"DataRate: {9}\n" +
+				"DataResourceCost: {10}\n" +
+				"TransmitterScore: {11}\n" +
+				"NearestRelay: {12}",
+				this.name,
+				this._basepacketSize,
+				base.packetSize,
+				this._basepacketResourceCost,
+				base.packetResourceCost,
+				this.maxTransmitDistance,
+				this.transmitDistance,
+				this.nominalRange,
+				this.CanTransmit(),
+				this.DataRate,
+				this.DataResourceCost,
+				ScienceUtil.GetTransmitterScore(this),
+				this.relay.FindNearestRelay()
+				);
+			ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));
+		}
+		#endif
+	}
+}

--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -1,1 +1,34 @@
+// 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.Reflection;
+using System.Runtime.CompilerServices;
 
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle("AntennaRange")]
+[assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
+[assembly: AssemblyCopyright("toadicus")]
+// 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.*")]
+// 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)]
+//[assembly: AssemblyKeyFile("")]
+
+

--- /dev/null
+++ b/Properties/ChangeLog
@@ -1,1 +1,5 @@
+2014-01-14  toadicus  <>
 
+	* AssemblyInfo.cs: New AssemblyInfo file for reason.
+
+

--- /dev/null
+++ b/ProtoAntennaRelay.cs
@@ -1,1 +1,118 @@
+// 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.Linq;
+
+namespace AntennaRange
+{
+	/*
+	 * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
+	 * This is used for finding relays in unloaded Vessels.
+	 * */
+	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;
+
+		/// <summary>
+		/// The maximum distance at which this transmitter can operate.
+		/// </summary>
+		/// <value>The max transmit distance.</value>
+		public override float maxTransmitDistance
+		{
+			get
+			{
+				return relayPrefab.maxTransmitDistance;
+			}
+		}
+
+		/// <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 override bool relayChecked
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
+		/// Gets the underlying part's title.
+		/// </summary>
+		/// <value>The title.</value>
+		public string title
+		{
+			get
+			{
+				return this.protoPart.partInfo.title;
+			}
+		}
+
+		public override bool CanTransmit()
+		{
+			PartStates partState = (PartStates)this.protoPart.state;
+			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: {1} on {2} cannot transmit: {3}",
+					this.GetType().Name,
+					this.title,
+					this.vessel.vesselName,
+					Enum.GetName(typeof(PartStates), partState)
+				));
+				return false;
+			}
+			return base.CanTransmit();
+		}
+
+		public override string ToString()
+		{
+			return string.Format(
+				"{0} on {1}.",
+				this.title,
+				this.protoPart.pVesselRef.vesselName
+			);
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
+		/// </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)
+		{
+			this.relayPrefab = prefabRelay;
+			this.protoPart = pps;
+			this.vessel = pps.pVesselRef.vesselRef;
+		}
+
+		~ProtoAntennaRelay()
+		{
+			Tools.PostDebugMessage(string.Format(
+				"{0}: destroyed",
+				this.ToString()
+			));
+		}
+	}
+}
+
+

file:b/RelayDatabase.cs (new)
--- /dev/null
+++ b/RelayDatabase.cs
@@ -1,1 +1,326 @@
-
+// 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 System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	public class RelayDatabase
+	{
+		/*
+		 * Static members
+		 * */
+		// Singleton storage
+		protected static RelayDatabase _instance;
+		// Gets the singleton
+		public static RelayDatabase Instance
+		{
+			get
+			{	
+				if (_instance == null)
+				{
+					_instance = new RelayDatabase();
+				}
+
+				return _instance;
+			}
+		}
+
+		/*
+		 * Instance members
+		 * */
+
+		/*
+		 * Fields
+		 * */
+		// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
+		protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
+
+		// 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;
+
+		/*
+		 * Properties
+		 * */
+		// Gets the Part-hashed table of relays in a given vessel
+		public Dictionary<int, IAntennaRelay> this [Vessel vessel]
+		{
+			get
+			{
+				// If we don't have an entry for this vessel...
+				if (!this.ContainsKey(vessel.id))
+				{
+					// ...Generate an entry for this vessel.
+					this.AddVessel(vessel);
+				}
+				// If our part count disagrees with the vessel's part count...
+				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+				{
+					// ...Update the our vessel in the cache
+					this.UpdateVessel(vessel);
+				}
+
+				// Return the Part-hashed table of relays for this vessel
+				return relayDatabase[vessel.id];
+			}
+		}
+
+		/* 
+		 * Methods
+		 * */
+		// Adds a vessel to the database
+		// The return for this function isn't used yet, but seems useful for potential future API-uses
+		public bool AddVessel(Vessel vessel)
+		{
+			// If this vessel is already here...
+			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.vesselName,
+					vessel.id
+				));
+
+				// ...and refuse to add
+				return false;
+			}
+			// otherwise, add the vessel to our tables...
+			else
+			{
+				// Build an empty table...
+				this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
+
+				// Update the empty index
+				this.UpdateVessel(vessel);
+
+				// Return success
+				return true;
+			}
+		}
+
+		// Update the vessel's entry in the table
+		public void UpdateVessel(Vessel vessel)
+		{
+			// Squak if the database doesn't have the vessel
+			if (!this.ContainsKey(vessel))
+			{
+				throw new InvalidOperationException(string.Format(
+					"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
+					this.GetType().Name,
+					vessel.vesselName,
+					vessel.id
+				));
+			}
+
+			Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
+
+			// Actually build and assign the table
+			this.getVesselRelays(vessel, ref vesselTable);
+			// Set the part count
+			this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
+		}
+
+		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
+		public bool ContainsKey(Guid key)
+		{
+			return this.relayDatabase.ContainsKey(key);
+		}
+
+		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
+		public bool ContainsKey(Vessel vessel)
+		{
+			return this.ContainsKey(vessel.id);
+		}
+
+		// Runs when a vessel is modified (or when we switch to one, to catch docking events)
+		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 || vessel.loaded)
+				{
+					Tools.PostDebugMessage(string.Format(
+						"{0}: dirtying cache for vessel '{1}' (id: {2}).",
+						this.GetType().Name,
+						vessel.vesselName,
+						vessel.id
+					));
+
+					// Dirty the cache (real vessels will never have negative part counts)
+					this.vesselPartCountTable[vessel.id] = -1;
+				}
+			}
+		}
+
+		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
+		public void onSceneChange(GameScenes scene)
+		{
+			// If the active vessel is a real thing...
+			if (FlightGlobals.ActiveVessel != null)
+			{
+				// ... dirty its cache
+				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
+		protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
+		{
+			// We're going to completely regen this table, so dump the current contents.
+			relays.Clear();
+
+			Tools.PostDebugMessage(string.Format(
+				"{0}: Getting antenna relays from vessel {1}.",
+				"IAntennaRelay",
+				vessel.vesselName
+			));
+
+			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
+			if (vessel.loaded) {
+				Tools.PostDebugMessage(string.Format(
+					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
+					"IAntennaRelay",
+					vessel.vesselName
+				));
+
+				// Loop through the Parts in the Vessel...
+				foreach (Part part in vessel.Parts)
+				{
+					// ...loop through the PartModules in the Part...
+					foreach (PartModule module in part.Modules)
+					{
+						// ...if the module is a relay...
+						if (module is IAntennaRelay)
+						{
+							// ...add the module to the table
+							relays.Add(part.GetHashCode(), module as IAntennaRelay);
+							// ...neglect relay objects after the first in each part.
+							break;
+						}
+					}
+				}
+			}
+			// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
+			else
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
+					this.GetType().Name,
+					vessel.vesselName
+				));
+
+				// Loop through the ProtoPartModuleSnapshots in the Vessel...
+				foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
+				{
+					Tools.PostDebugMessage(string.Format(
+						"{0}: Searching in protopartsnapshot {1}",
+						this.GetType().Name,
+						pps
+					));
+
+					// ...Fetch the prefab, because it's more useful for what we're doing.
+					Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
+
+					Tools.PostDebugMessage(string.Format(
+						"{0}: Got partPrefab {1} in protopartsnapshot {2}",
+						this.GetType().Name,
+						partPrefab,
+						pps
+					));
+
+					// ...loop through the PartModules in the prefab...
+					foreach (PartModule module in partPrefab.Modules)
+					{
+						Tools.PostDebugMessage(string.Format(
+							"{0}: Searching in partmodule {1}",
+							this.GetType().Name,
+							module
+						));
+
+						// ...if the module is a relay...
+						if (module is IAntennaRelay)
+						{
+							Tools.PostDebugMessage(string.Format(
+								"{0}: partmodule {1} is antennarelay",
+								this.GetType().Name,
+								module
+							));
+
+							// ...build a new ProtoAntennaRelay and add it to the table
+							relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
+							// ...neglect relay objects after the first in each part.
+							break;
+						}
+					}
+				}
+			}
+
+			Tools.PostDebugMessage(string.Format(
+				"{0}: vessel '{1}' has {2} transmitters.",
+				"IAntennaRelay",
+				vessel.vesselName,
+				relays.Count
+			));
+		}
+
+		// Construct the singleton
+		protected RelayDatabase()
+		{
+			// Initialize the databases
+			this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+			this.vesselPartCountTable = new Dictionary<Guid, int>();
+			this.CheckedVesselsTable = new Dictionary<Guid, bool>();
+
+			// Subscribe to some events
+			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);
+		}
+
+		~RelayDatabase()
+		{
+			// Unsubscribe from the events
+			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);
+
+			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
+		}
+	}
+}
+
+