A whole bunch of changes to start making simple relays possible.
A whole bunch of changes to start making simple relays possible.

--- a/AntennaRange.cs
+++ b/AntennaRange.cs
@@ -11,15 +11,17 @@
  * 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 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 KSP;
+using UnityEngine;
 
 namespace AntennaRange
 {
@@ -39,7 +41,7 @@
 	/*
 	 * Fields
 	 * */
-	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter
+	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, ILimitedScienceDataTransmitter
 	{
 		// Stores the packetResourceCost as defined in the .cfg file.
 		protected float _basepacketResourceCost;
@@ -49,6 +51,12 @@
 
 		// We don't have a Bard, so we're hiding Kerbin here.
 		protected CelestialBody _Kerbin;
+
+		// 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;
 
 		// Let's make the error text pretty!
 		protected UnityEngine.GUIStyle ErrorStyle;
@@ -66,6 +74,13 @@
 		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 		[KSPField(isPersistant = false)]
 		public float maxDataFactor;
+
+		[KSPField(isPersistant = true)]
+		protected float ARmaxTransmitDistance;
+
+		// Call this an antenna, to make things easier on us.  This behavior is borrowed from RemoteTech by Cilph.
+		[KSPField(isPersistant = true)]
+		public bool IsAntenna = true;
 
 		/*
 		 * Properties
@@ -83,11 +98,12 @@
 		}
 
 		// Returns the maximum distance this module can transmit
-		public double maxTransmitDistance
+		[KSPField(isPersistant = true)]
+		public float maxTransmitDistance
 		{
 			get
 			{
-				return Math.Sqrt (this.maxPowerFactor) * this.nominalRange;
+				return this.ARmaxTransmitDistance;
 			}
 		}
 
@@ -135,8 +151,22 @@
 			get
 			{
 				this.PreTransmit_SetPacketResourceCost();
-				return this.packetResourceCost;
-			}
+
+				if (this.CanTransmit())
+				{
+					return this.packetResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
+
+		public bool relayChecked
+		{
+			get;
+			protected set;
 		}
 
 		/*
@@ -152,27 +182,29 @@
 			this.ErrorStyle.hover.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
 			this.ErrorStyle.fontStyle = UnityEngine.FontStyle.Bold;
 			this.ErrorStyle.padding.top = 32;
+
+			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle);
 		}
 
 		// 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)
 		{
+			this.relayChecked = false;
+
 			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;
-					}
-				}
-			}
+			if (state >= StartState.PreLaunch)
+			{
+				if (this._Kerbin == null)
+				{
+					// Go fetch Kerbin, because it is tricksy and hides from us.
+					this._Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
+				}
+			}
+
+			// Pre-set the transmit cost and packet size when loading.
+			this.PreTransmit_SetPacketResourceCost();
+			this.PreTransmit_SetPacketSize();
 		}
 
 		// When the module loads, fetch the Squad KSPFields from the base.  This is necessary in part because
@@ -182,6 +214,8 @@
 		{
 			this.Fields.Load(node);
 			base.Fields.Load(node);
+
+			this.ARmaxTransmitDistance = Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
 
 			base.OnLoad (node);
 
@@ -210,16 +244,13 @@
 		{
 			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.ARmaxTransmitDistance, 2),
 				Tools.MuMech_ToSI((double)this.transmitDistance, 2)
 				);
-			ScreenMessages.PostScreenMessage(
-				new ScreenMessage(
-				ErrorText,
-				4f,
-				ScreenMessageStyle.UPPER_LEFT,
-				this.ErrorStyle
-				));
+
+			this.ErrorMsg.message = ErrorText;
+
+			ScreenMessages.PostScreenMessage(this.ErrorMsg, true);
 		}
 
 		// Before transmission, set packetResourceCost.  Per above, packet cost increases with the square of
@@ -259,40 +290,99 @@
 		{
 			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";
+			text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.ARmaxTransmitDistance, 2) + "m\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;
+			this.PreTransmit_SetPacketResourceCost();
+			this.PreTransmit_SetPacketSize();
+
+			if (this.transmitDistance < this.ARmaxTransmitDistance)
+			{
+				return true;
+			}
+			else
+			{
+				this.relayChecked = true;
+
+				List<Vessel> nearbyVessels = FlightGlobals.Vessels
+					.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.transmitDistance)
+					.ToList();
+
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Vessels in range: {1}",
+					this.GetType().Name,
+					nearbyVessels.Count
+					));
+
+				nearbyVessels = nearbyVessels.Where(v => v.id != vessel.id).ToList();
+
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Vessels in range excluding self: {1}",
+					this.GetType().Name,
+					nearbyVessels.Count
+					));
+
+				List<ILimitedScienceDataTransmitter> nearbyTransmitters = nearbyVessels
+					.SelectMany (v => v.GetTransmitters ())
+					.ToList();
+
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Transmitters in nearby parts: {1}",
+					this.GetType().Name,
+					nearbyTransmitters.Count
+					));
+
+				nearbyTransmitters = nearbyTransmitters.Where(m => !m.relayChecked).ToList();
+
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Transmitters in nearby parts not already checked: {1}",
+					this.GetType().Name,
+					nearbyTransmitters.Count
+					));
+
+				nearbyTransmitters = nearbyTransmitters.Where(m => m.CanTransmit()).ToList();
+
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Transmitters in nearby parts not already checked that can transmit: {1}",
+					this.GetType().Name,
+					nearbyTransmitters.Count
+					));
+
+				this.relayChecked = false;
+
+				if (nearbyTransmitters.Count == 0)
+				{
+					return false;
+				}
+				else
+				{
+					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 ();
+			if (this.CanTransmit())
+			{
+				base.TransmitData(dataQueue);
+			}
+			else
+			{
+				this.PostCannotTransmitError ();
+			}
 
 			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
@@ -343,7 +433,7 @@
 				base.packetSize,
 				this._basepacketResourceCost,
 				base.packetResourceCost,
-				this.maxTransmitDistance,
+				this.ARmaxTransmitDistance,
 				this.transmitDistance,
 				this.nominalRange,
 				this.CanTransmit(),
@@ -358,19 +448,18 @@
 
 	public static class Tools
 	{
-		// When debugging, be verbose.  The Conditional attribute prevents this from firing when not DEBUGging.
+		private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT);
+
 		[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);
-			}
+				debugmsg.message = Msg;
+				ScreenMessages.PostScreenMessage(debugmsg, true);
+			}
+
+			KSPLog.print(Msg);
 		}
 
 		/*
@@ -464,4 +553,3 @@
 		}
 	}
 }
-

file:b/Extensions.cs (new)
--- /dev/null
+++ b/Extensions.cs
@@ -1,1 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 
+namespace AntennaRange
+{
+	public static class Extensions
+	{
+		public static IEnumerable<ILimitedScienceDataTransmitter> GetTransmitters (this Vessel vessel)
+		{
+			List<ILimitedScienceDataTransmitter> Transmitters;
+
+			if (vessel.loaded) {
+				Transmitters = vessel.Parts
+					.SelectMany (p => p.Modules.OfType<ILimitedScienceDataTransmitter> ())
+					.ToList();
+			} else {
+				Transmitters = new List<ILimitedScienceDataTransmitter>();
+
+				foreach (ProtoPartModuleSnapshot ms in vessel.protoVessel.protoPartSnapshots.SelectMany(ps => ps.modules))
+				{
+					if (ms.IsAntenna())
+					{
+						Transmitters.Add(new ProtoDataTransmitter(ms, vessel));
+					}
+				}
+			}
+
+			return Transmitters;
+		}
+
+		public static bool IsAntenna (this PartModule module)
+		{
+			return module.Fields.GetValue<bool> ("IsAntenna");
+		}
+
+		public static bool IsAntenna(this ProtoPartModuleSnapshot protomodule)
+		{
+			bool result;
+
+			return Boolean.TryParse (protomodule.moduleValues.GetValue ("IsAntenna") ?? "False", out result)
+				? result : false;
+		}
+	}
+}
+
+

--- /dev/null
+++ b/ILimitedScienceDataTransmitter.cs
@@ -1,1 +1,13 @@
+using System;
 
+namespace AntennaRange
+{
+	public interface ILimitedScienceDataTransmitter : IScienceDataTransmitter
+	{
+		float maxTransmitDistance { get; }
+
+		bool relayChecked { get; }
+	}
+}
+
+

--- /dev/null
+++ b/ProtoDataTransmitter.cs
@@ -1,1 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 
+namespace AntennaRange
+{
+	public class ProtoDataTransmitter : ILimitedScienceDataTransmitter
+	{
+		protected ProtoPartModuleSnapshot snapshot;
+		protected Vessel vessel;
+
+		// Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes.
+		protected double transmitDistance
+		{
+			get
+			{
+				Vector3d KerbinPos = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin").position;
+				Vector3d ActivePos = this.vessel.GetWorldPos3D();
+
+				return (ActivePos - KerbinPos).magnitude;
+			}
+		}
+
+		/// <summary>
+		/// The maximum distance at which this transmitter can operate.
+		/// </summary>
+		/// <value>The max transmit distance.</value>
+		public float maxTransmitDistance
+		{
+			get
+			{
+				double result;
+				Double.TryParse(snapshot.moduleValues.GetValue ("ARmaxTransmitDistance") ?? "0", out result);
+				return (float)result;
+			}
+		}
+
+		/// <summary>
+		/// Gets the data rate in MiT per second.
+		/// </summary>
+		/// <value>The data rate.</value>
+		public float DataRate
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
+		/// Gets the data resource cost in units of ElectricCharge.
+		/// </summary>
+		/// <value>The data resource cost.</value>
+		public double DataResourceCost
+		{
+			get;
+			protected 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 bool relayChecked
+		{
+			get;
+			protected set;
+		}
+
+		/// <summary>
+		/// Determines whether this module can transmit.
+		/// </summary>
+		/// <returns><c>true</c> if this module can transmit; otherwise, <c>false</c>.</returns>
+		public bool CanTransmit()
+		{
+			Tools.PostDebugMessage (string.Format (
+				"{0}: transmitDistance: {1}, maxDistance: {2}",
+				this.GetType().Name,
+				this.transmitDistance,
+				this.maxTransmitDistance
+			));
+
+			if (this.transmitDistance < this.maxTransmitDistance)
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Determines whether this module is busy.
+		/// </summary>
+		/// <returns><c>true</c> if this module is busy; otherwise, <c>false</c>.</returns>
+		public bool IsBusy()
+		{
+			return false;
+		}
+
+		/// <summary>
+		/// Transmits the data in a queue.
+		/// </summary>
+		/// <param name="dataQueue">Data queue to be transmitted.</param>
+		public void TransmitData(List<ScienceData> dataQueue) {}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
+		/// </summary>
+		/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
+		public ProtoDataTransmitter(ProtoPartModuleSnapshot ms, Vessel v)
+		{
+			this.snapshot = ms;
+			this.vessel = v;
+
+			this.relayChecked = false;
+		}
+	}
+}
+
+