Megacommit towards making relays that work for CelestialBodies. I think I'm overcomplicating this. BodyRelayDev
Megacommit towards making relays that work for CelestialBodies. I think I'm overcomplicating this.

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -16,6 +16,16 @@
 	[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
 	public class ARConfiguration : MonoBehaviour
 	{
+		private const string WINDOW_POS_KEY = "configWindowPos";
+		private const string REQUIRE_LOS_KEY = "requireLineOfSight";
+		private const string GRACE_RATIO_KEY = "graceRatio";
+		private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
+		private const string FIXED_POWER_KEY = "fixedPowerCost";
+		private const string PRETTY_LINES_KEY = "drawPrettyLines";
+		private const string UPDATE_DELAY_KEY = "updateDelay";
+
+		private bool runOnce;
+
 		/// <summary>
 		/// Indicates whether connections require line of sight.
 		/// </summary>
@@ -59,6 +69,12 @@
 		public static bool PrettyLines
 		{
 			get;
+			set;
+		}
+
+		public static long UpdateDelay
+		{
+			get;
 			private set;
 		}
 
@@ -66,6 +82,9 @@
 
 		private bool showConfigWindow;
 		private Rect configWindowPos;
+
+		private string updateDelayStr;
+		private long updateDelay;
 
 		private IButton toolbarButton;
 		private ApplicationLauncherButton appLauncherButton;
@@ -96,25 +115,43 @@
 			this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
 
 
-			this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
-
-			ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
-
-			ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d));
+			this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
+
+			ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
+
+			ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
 			ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
 
 			ARConfiguration.RequireConnectionForControl =
-				this.LoadConfigValue("requireConnectionForControl", false);
-
-			ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false);
-
-			ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true);
+				this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
+
+			ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
+
+			ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
+
+			ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
+
+			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 
+			this.runOnce = true;
+
 			Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
 
 			Tools.PostDebugMessage(this, "Awake.");
+		}
+
+		public void Update()
+		{
+			if (runOnce)
+			{
+				KerbinRelay.TrackingStationMaxLevel =
+					ScenarioUpgradeableFacilities.protoUpgradeables[SpaceCenterFacility.TrackingStation.ToString()]
+						.facilityRefs[0].MaxLevel;
+				
+				runOnce = false;
+			}
 		}
 
 		public void OnGUI()
@@ -167,7 +204,7 @@
 				if (configPos != this.configWindowPos)
 				{
 					this.configWindowPos = configPos;
-					this.SaveConfigValue("configWindowPos", this.configWindowPos);
+					this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos);
 				}
 			}
 		}
@@ -182,7 +219,7 @@
 			if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
 			{
 				ARConfiguration.RequireLineOfSight = requireLineOfSight;
-				this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
+				this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
 			}
 
 			GUILayout.EndHorizontal();
@@ -197,7 +234,7 @@
 			if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
 			{
 				ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
-				this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
+				this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
 			}
 
 			GUILayout.EndHorizontal();
@@ -208,7 +245,7 @@
 			if (fixedPowerCost != ARConfiguration.FixedPowerCost)
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
-				this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
+				this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
 			}
 
 			GUILayout.EndHorizontal();
@@ -219,10 +256,26 @@
 			if (prettyLines != ARConfiguration.PrettyLines)
 			{
 				ARConfiguration.PrettyLines = prettyLines;
-				this.SaveConfigValue("drawPrettyLines", prettyLines);
-			}
-
-			GUILayout.EndHorizontal();
+				this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal();
+
+			GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
+
+			this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
+
+			GUILayout.Label("ms", GUILayout.ExpandWidth(false));
+
+			GUILayout.EndHorizontal();
+
+			if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
+			{
+				ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
+				this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+			}
 
 			if (requireLineOfSight)
 			{
@@ -243,7 +296,7 @@
 				if (newRatio != graceRatio)
 				{
 					ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
-					this.SaveConfigValue("graceRatio", newRatio);
+					this.SaveConfigValue(GRACE_RATIO_KEY, newRatio);
 				}
 
 				GUILayout.EndHorizontal();
@@ -281,6 +334,7 @@
 		private void toggleConfigWindow()
 		{
 			this.showConfigWindow = !this.showConfigWindow;
+			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
 		}
 
 		private T LoadConfigValue<T>(string key, T defaultValue)

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -148,7 +148,7 @@
 				this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
 				this.toolbarButton.Text = "AntennaRange";
 				this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
-				this.toolbarButton.Enabled = false;
+				this.toolbarButton.OnClick += (e) => (this.buttonToggle());
 			}
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
@@ -162,14 +162,6 @@
 
 		private void FixedUpdate()
 		{
-			if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
-			{
-				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
-					ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
-					this.appLauncherTextures[ConnectionStatus.None]
-				);
-			}
-
 			this.log.Clear();
 
 			VesselCommand availableCommand;
@@ -224,7 +216,21 @@
 
 		private void Update()
 		{
-			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 16L)
+			if (this.toolbarButton != null)
+			{
+				this.toolbarButton.Enabled = MapView.MapIsEnabled;
+			}
+
+			if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
+			{
+				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
+					this.buttonToggle, this.buttonToggle,
+					ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
+					this.appLauncherTextures[ConnectionStatus.None]
+				);
+			}
+
+			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
 			{
 				this.updateTimer.Restart();
 			}
@@ -329,6 +335,14 @@
 		}
 		#endregion
 
+		private void buttonToggle()
+		{
+			if (MapView.MapIsEnabled)
+			{
+				ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
+			}
+		}
+
 		#region Event Handlers
 		private void onSceneChangeRequested(GameScenes scene)
 		{

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -39,7 +39,7 @@
 	public class ARMapRenderer : MonoBehaviour
 	{
 		#region Fields
-		private Dictionary<Guid, LineRenderer> vesselLineRenderers;
+		private Dictionary<IPositionedObject, LineRenderer> vesselLineRenderers;
 
 		// Debug Stuff
 		#pragma warning disable 649
@@ -55,13 +55,13 @@
 		#endregion
 
 		#region Properties
-		public LineRenderer this[Guid idx]
+		public LineRenderer this[IPositionedObject idx]
 		{
 			get
 			{
 				if (this.vesselLineRenderers == null)
 				{
-					this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
+					this.vesselLineRenderers = new Dictionary<IPositionedObject, LineRenderer>();
 				}
 
 				LineRenderer lr;
@@ -94,7 +94,7 @@
 		{
 			if (ARConfiguration.PrettyLines)
 			{
-				this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
+				this.vesselLineRenderers = new Dictionary<IPositionedObject, LineRenderer>();
 			}
 
 			#if DEBUG
@@ -216,14 +216,14 @@
 		{
 			log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay);
 
-			if (relay.vessel == null)
+			if (relay.Host == null)
 			{
 				log.Append("\n\t\tvessel is null, bailing out");
 				return;
 			}
 
-			LineRenderer renderer = this[relay.vessel.id];
-			Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
+			LineRenderer renderer = this[relay.Host];
+			Vector3d start = ScaledSpace.LocalToScaledSpace(relay.Host.WorldPos);
 
 			float lineWidth;
 			float d = Screen.height / 2f + 0.01f;
@@ -273,11 +273,11 @@
 
 			if (relay.KerbinDirect)
 			{
-				nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
+				nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.WorldPos);
 			}
 			else
 			{
-				if (relay.targetRelay == null || relay.targetRelay.vessel == null)
+				if (relay.targetRelay == null || relay.targetRelay.Host == null)
 				{
 					this.LogError(
 						"SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
@@ -288,7 +288,7 @@
 					return;
 				}
 
-				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
+				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.Host.WorldPos);
 			}
 
 			renderer.SetColors(thisColor, thisColor);

--- a/AntennaRange.csproj
+++ b/AntennaRange.csproj
@@ -80,6 +80,8 @@
     <Compile Include="ARConfiguration.cs" />
     <Compile Include="ARFlightController.cs" />
     <Compile Include="ARMapRenderer.cs" />
+    <Compile Include="PositionedObject.cs" />
+    <Compile Include="KerbinRelay.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -35,21 +35,21 @@
 	/// <summary>
 	/// Relay code at the heart of AntennaRange
 	/// </summary>
-	public class AntennaRelay
+	public class AntennaRelay : IAntennaRelay
 	{
 		// We don't have a Bard, so we'll hide Kerbin here.
-		private static CelestialBody _Kerbin;
+		private static BodyWrapper _Kerbin;
 
 		/// <summary>
 		/// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
 		/// </summary>
-		public static CelestialBody Kerbin
+		public static BodyWrapper Kerbin
 		{
 			get
 			{
 				if (_Kerbin == null && FlightGlobals.ready)
 				{
-					_Kerbin = FlightGlobals.GetHomeBody();
+					_Kerbin = (BodyWrapper)FlightGlobals.GetHomeBody();
 				}
 
 				return _Kerbin;
@@ -72,11 +72,11 @@
 		/// Gets the parent Vessel.
 		/// </summary>
 		/// <value>The parent Vessel.</value>
-		public virtual Vessel vessel
+		public virtual IPositionedObject Host
 		{
 			get
 			{
-				return this.moduleRef.vessel;
+				return this.moduleRef.Host;
 			}
 		}
 
@@ -144,6 +144,14 @@
 		{
 			get;
 			protected set;
+		}
+
+		public virtual string Title
+		{
+			get
+			{
+				return this.moduleRef.Title;
+			}
 		}
 
 		/// <summary>
@@ -251,7 +259,7 @@
 				
 				log.Append("\n\tchecking if vessel is this vessel");
 				// Skip vessels with the wrong ID
-				if (potentialVessel.id == vessel.id)
+				if (OBJ.ReferenceEquals(potentialVessel, Host.HostObject))
 				{
 					log.Append("\n\tSkipping because vessel is this vessel.");
 					continue;
@@ -276,7 +284,7 @@
 				// Skip vessels to which we do not have line of sight.
 				if (
 					ARConfiguration.RequireLineOfSight &&
-					!this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
+					!this.Host.hasLineOfSightTo((VesselWrapper)potentialVessel, out fob, ARConfiguration.RadiusRatio)
 				)
 				{
 					log.Append("\n\t\t...failed LOS check");
@@ -331,8 +339,11 @@
 					needle = potentialBestRelay;
 					bool isCircular = false;
 
+					int iterCount = 0;
 					while (needle != null)
 					{
+						iterCount++;
+
 						if (needle.KerbinDirect)
 						{
 							break;
@@ -343,8 +354,31 @@
 							break;
 						}
 
-						if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
-						{
+						if (needle.targetRelay.Host == this.Host || needle == this.moduleRef)
+						{
+							isCircular = true;
+							break;
+						}
+
+						// Avoid infinite loops when we're not catching things right.
+						if (iterCount > FlightGlobals.Vessels.Count)
+						{
+							Tools.PostErrorMessage(
+								"[{0}] iterCount exceeded while checking for circular network; assuming it is circular" +
+								"\n\tneedle={1}" +
+								"\n\tthis.moduleRef={2}",
+								this,
+								needle == null ? "null" : string.Format(
+									"{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
+									needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
+										"{0}\n\tneedle.targetRelay.Host={1}",
+										needle.targetRelay,
+										needle.targetRelay.Host == null ?
+											"null" : needle.targetRelay.Host.ToString()
+									)
+								),
+								this.moduleRef == null ? "null" : this.moduleRef.ToString()
+							);
 							isCircular = true;
 							break;
 						}
@@ -374,8 +408,7 @@
 
 			CelestialBody bodyOccludingKerbin = null;
 
-			double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
-			kerbinSqrDistance *= kerbinSqrDistance;
+			double kerbinSqrDistance = this.sqrDistanceTo(Kerbin);
 
 			log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
 			log.AppendFormat(
@@ -389,7 +422,10 @@
 			);
 
 			// If we don't have LOS to Kerbin, focus on relays
-			if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
+			if (
+				ARConfiguration.RequireLineOfSight &&
+				!this.Host.hasLineOfSightTo((BodyWrapper)Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
+			)
 			{
 				log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
 

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -37,12 +37,13 @@
 // maxDataFactor:	The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 // 
 
+// Maximum distance 51696km, about 10% past Minmus
 @PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 1500000
+		nominalRange = 18277500
 		maxPowerFactor = 8
 		maxDataFactor = 4
 	}
@@ -58,14 +59,16 @@
 	}
 }
 
+// Maximum distance 37152180km, about 5% past Duna
+// Bonus data transmission when at short range
 @PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 30000000
-		maxPowerFactor = 8
-		maxDataFactor = 4
+		nominalRange = 18576090000
+		maxPowerFactor = 4
+		maxDataFactor = 8
 	}
 
 	MODULE
@@ -79,14 +82,16 @@
 	}
 }
 
+// Maximum distance 224770770km, about 75% past Eeloo.
 @PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
-		nominalRange = 80000000000
-		maxPowerFactor = 8
-		maxDataFactor = 4
+		@packetResourceCost /= 1.414213
+		nominalRange = 56192692500
+		maxPowerFactor = 16
+		maxDataFactor = 2
 	}
 
 	MODULE

--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -39,7 +39,7 @@
 		/// <summary>
 		/// Gets the parent Vessel.
 		/// </summary>
-		Vessel vessel { get; }
+		IPositionedObject Host { get; }
 
 		/// <summary>
 		/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.

file:b/KerbinRelay.cs (new)
--- /dev/null
+++ b/KerbinRelay.cs
@@ -1,1 +1,241 @@
-
+// AntennaRange
+//
+// BodyRelay.cs
+//
+// Copyright © 2015, 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.
+
+#pragma warning disable 1591
+#define DEBUG
+
+using KSP;
+using System;
+using System.Collections.Generic;
+using ToadicusTools;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	[KSPAddon(KSPAddon.Startup.Flight, false)]
+	public class KerbinRelay : MonoBehaviour, IAntennaRelay
+	{
+		public static KerbinRelay Kerbin;
+		public static int TrackingStationMaxLevel = 2;
+
+		private List<double> stationLevelRanges;
+
+		public IList<double> StationLevelRange
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Gets the parent Vessel.
+		/// </summary>
+		public BodyWrapper Host
+		{
+			get;
+			private set;
+		}
+		IPositionedObject IAntennaRelay.Host
+		{
+			get
+			{
+				return (IPositionedObject)this.Host;
+			}
+		}
+
+		/// <summary>
+		/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
+		/// </summary>
+		public IAntennaRelay targetRelay
+		{
+			get
+			{
+				return null;
+			}
+		}
+
+		/// <summary>
+		/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
+		/// </summary>
+		public double transmitDistance
+		{
+			get
+			{
+				return double.PositiveInfinity;
+			}
+		}
+
+		/// <summary>
+		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
+		/// </summary>
+		public double nominalTransmitDistance
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// The maximum distance at which this relay can operate.
+		/// </summary>
+		public double maxTransmitDistance
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// The first CelestialBody blocking line of sight to a 
+		/// </summary>
+		public CelestialBody firstOccludingBody
+		{
+			get
+			{
+				return null;
+			}
+		}
+
+		/// <summary>
+		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+		/// directly with Kerbin.
+		/// </summary>
+		public bool KerbinDirect
+		{
+			get
+			{
+				return true;
+			}
+		}
+
+		/// <summary>
+		/// Gets the Part title.
+		/// </summary>
+		public string Title
+		{
+			get
+			{
+				if (this.Host != null)
+				{
+					return string.Format("Tracking Station on {0}", this.Host.HostObject.bodyName);
+				}
+				else
+				{
+					return "Tracking Station on an unknown world";
+				}
+			}
+		}
+
+		/// <summary>
+		/// Determines whether this instance can transmit.
+		/// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
+		/// </summary>
+		public bool CanTransmit()
+		{
+			return true;
+		}
+
+		/// <summary>
+		/// Finds the nearest relay.
+		/// </summary>
+		public void FindNearestRelay()
+		{
+			return;
+		}
+
+		/// <summary>
+		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
+		/// </summary>
+		public override string ToString()
+		{
+			return this.Title;
+		}
+
+		private void Awake()
+		{
+			this.stationLevelRanges = new List<double>();
+			this.StationLevelRange = this.stationLevelRanges.AsReadOnly();
+
+			this.stationLevelRanges.Add(51696576d);
+			this.stationLevelRanges.Add(37152180000d);
+			this.stationLevelRanges.Add(224770770000d);
+
+			if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
+			{
+				GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
+			}
+
+		}
+
+		private void Update()
+		{
+			if (FlightGlobals.ready && Kerbin == null)
+			{
+				this.Host = (BodyWrapper)FlightGlobals.GetHomeBody();
+
+				if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
+				{
+					this.maxTransmitDistance = this.stationLevelRanges[this.TrackingStationLevel()];
+				}
+				else
+				{
+					this.maxTransmitDistance = this.stationLevelRanges[2];
+				}
+
+				this.nominalTransmitDistance = this.maxTransmitDistance;
+
+				Kerbin = this;
+			}
+		}
+
+		private void OnDestroy()
+		{
+			GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded);
+		}
+
+		public int TrackingStationLevel()
+		{
+			this.LogDebug("Tracking station level: {0} ({1} * {2})",(int)(
+				ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) *
+				(float)TrackingStationMaxLevel),
+				ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation),
+				TrackingStationMaxLevel
+			);
+
+			return (int)(
+				ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) *
+				(float)TrackingStationMaxLevel);
+		}
+
+		private void onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int level)
+		{
+			// fac.FacilityLevel
+			this.maxTransmitDistance = this.stationLevelRanges[this.TrackingStationLevel()];
+			this.nominalTransmitDistance = this.maxTransmitDistance;
+		}
+	}
+}
+
+

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -137,23 +137,38 @@
 		/// <summary>
 		/// Gets the parent Vessel.
 		/// </summary>
-		public new Vessel vessel
+		public VesselWrapper Host
 		{
 			get
 			{
 				if (base.vessel != null)
 				{
-					return base.vessel;
+					return (VesselWrapper)base.vessel;
 				}
 				else if (this.part != null && this.part.vessel != null)
 				{
-					return this.part.vessel;
+					return (VesselWrapper)this.part.vessel;
+				}
+				else if (
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot.pVesselRef != null &&
+					this.part.protoPartSnapshot.pVesselRef.vesselRef != null
+				)
+				{
+					return (VesselWrapper)this.part.protoPartSnapshot.pVesselRef.vesselRef;
 				}
 				else
 				{
 					this.LogError("Vessel and/or part reference are null, returning null vessel.");
 					return null;
 				}
+			}
+		}
+		IPositionedObject IAntennaRelay.Host
+		{
+			get
+			{
+				return this.Host;
 			}
 		}
 
@@ -426,7 +441,7 @@
 						"{0}: {1} on {2} cannot transmit: {3}",
 						this.GetType().Name,
 						this.part.partInfo.title,
-						this.vessel.vesselName,
+						this.Host.HostObject.vesselName,
 						Enum.GetName(typeof(PartStates), this.part.State)
 					));
 					return false;
@@ -491,7 +506,7 @@
 
 				var logger = Tools.DebugLogger.New(this);
 
-				IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
+				IList<ModuleScienceContainer> vesselContainers = this.Host.HostObject.getModulesOfType<ModuleScienceContainer>();
 				ModuleScienceContainer scienceContainer;
 				for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
 				{
@@ -650,7 +665,7 @@
 
 				if (this.KerbinDirect)
 				{
-					this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
+					this.UIrelayTarget = AntennaRelay.Kerbin.HostObject.bodyName;
 				}
 				else
 				{
@@ -670,10 +685,10 @@
 
 			sb.Append(this.part.partInfo.title);
 
-			if (vessel != null)
+			if (Host != null)
 			{
 				sb.Append(" on ");
-				sb.Append(vessel.vesselName);
+				sb.Append(Host.HostObject.vesselName);
 			}
 			else if (
 				this.part != null &&

--- /dev/null
+++ b/PositionedObject.cs
@@ -1,1 +1,158 @@
+// AntennaRange
+//
+// PositionedObject.cs
+//
+// Copyright © 2015, 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;
+
+#pragma warning disable 1591
+
+namespace AntennaRange
+{
+	public interface IPositionedObject
+	{
+		object HostObject { get; }
+		Vector3d WorldPos { get; }
+	}
+
+	public interface IPositionedObject<T>
+	{
+		T HostObject { get; }
+	}
+
+	public abstract class PositionedObject<T> : IPositionedObject<T>, IPositionedObject
+	{
+		public abstract T HostObject { get; protected set; }
+
+		object IPositionedObject.HostObject
+		{
+			get
+			{
+				return (object)this.HostObject;
+			}
+		}
+
+		public abstract Vector3d WorldPos { get; }
+
+
+		protected static Dictionary<T, PositionedObject<T>> bin = new Dictionary<T, PositionedObject<T>>(); 
+	}
+
+	public class VesselWrapper : PositionedObject<Vessel>
+	{
+		public override Vessel HostObject
+		{
+			get;
+			protected set;
+		}
+
+		public override Vector3d WorldPos
+		{
+			get
+			{
+				if (this.HostObject != null)
+				{
+					return this.HostObject.GetWorldPos3D();
+				}
+				else
+				{
+					return Vector3d.zero;
+				}
+			}
+		}
+
+		public VesselWrapper(Vessel host)
+		{
+			this.HostObject = host;
+		}
+
+		public static explicit operator VesselWrapper(Vessel vessel)
+		{
+			PositionedObject<Vessel> wrapper;
+			if (!bin.TryGetValue(vessel, out wrapper))
+			{
+				wrapper = new VesselWrapper(vessel);
+				bin[vessel] = wrapper;
+			}
+
+			return (VesselWrapper)wrapper;
+		}
+
+		public static implicit operator Vessel(VesselWrapper wrapper)
+		{
+			return wrapper.HostObject;
+		}
+	}
+
+	public class BodyWrapper : PositionedObject<CelestialBody>
+	{
+		public override CelestialBody HostObject
+		{
+			get;
+			protected set;
+		}
+
+		public override Vector3d WorldPos
+		{
+			get
+			{
+				if (this.HostObject != null)
+				{
+					return this.HostObject.position;
+				}
+				else
+				{
+					return Vector3d.zero;
+				}
+			}
+		}
+
+		public BodyWrapper(CelestialBody host)
+		{
+			this.HostObject = host;
+		}
+
+		public static explicit operator BodyWrapper(CelestialBody body)
+		{
+			PositionedObject<CelestialBody> wrapper;
+			if (!bin.TryGetValue(body, out wrapper))
+			{
+				wrapper = new BodyWrapper(body);
+				bin[body] = wrapper;
+			}
+
+			return (BodyWrapper)wrapper;
+		}
+
+		public static implicit operator CelestialBody(BodyWrapper wrapper)
+		{
+			return wrapper.HostObject;
+		}
+	}
+}
+
+

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -44,7 +44,7 @@
 		/// <summary>
 		/// Gets the parent Vessel.
 		/// </summary>
-		public override Vessel vessel
+		public override IPositionedObject Host
 		{
 			get
 			{
@@ -54,7 +54,7 @@
 					this.protoPart.pVesselRef.vesselRef != null
 				)
 				{
-					return this.protoPart.pVesselRef.vesselRef;
+					return (VesselWrapper)this.protoPart.pVesselRef.vesselRef;
 				}
 				else
 				{
@@ -124,7 +124,7 @@
 					"{0}: {1} on {2} cannot transmit: {3}",
 					this.GetType().Name,
 					this.Title,
-					this.vessel.vesselName,
+					this.Host.ToString(),
 					Enum.GetName(typeof(PartStates), partState)
 				));
 				return false;

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -38,63 +38,115 @@
 	public static class RelayExtensions
 	{
 		/// <summary>
-		/// Returns the distance between this IAntennaRelay and a Vessel
-		/// </summary>
-		/// <param name="relay">This <see cref="IAntennaRelay"/></param>
-		/// <param name="Vessel">A <see cref="Vessel"/></param>
-		public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
-		{
-			return relay.vessel.DistanceTo(Vessel);
-		}
-
-		/// <summary>
-		/// Returns the distance between this IAntennaRelay and a CelestialBody
-		/// </summary>
-		/// <param name="relay">This <see cref="IAntennaRelay"/></param>
-		/// <param name="body">A <see cref="CelestialBody"/></param>
-		public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
-		{
-			return relay.vessel.DistanceTo(body) - body.Radius;
-		}
-
-		/// <summary>
-		/// Returns the distance between this IAntennaRelay and another IAntennaRelay
-		/// </summary>
-		/// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
-		/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
-		public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
-		{
-			return relayOne.DistanceTo(relayTwo.vessel);
-		}
-
-		/// <summary>
-		/// Returns the square of 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 sqrDistanceTo(this AntennaRelay relay, Vessel vessel)
-		{
-			return relay.vessel.sqrDistanceTo(vessel);
-		}
-
-		/// <summary>
-		/// Returns the square of 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 sqrDistanceTo(this AntennaRelay relay, CelestialBody body)
-		{
-			return relay.vessel.sqrDistanceTo(body);
-		}
-
-		/// <summary>
-		/// Returns the square of 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 sqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
-		{
-			return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
+		/// Returns the world distance between two <see cref="AntennaRange.IPositionedObject"/> objects.
+		/// </summary>
+		public static double DistanceTo(this IPositionedObject object1, IPositionedObject object2)
+		{
+			double dist = (object1.WorldPos - object2.WorldPos).magnitude;
+
+			if (object1 is BodyWrapper)
+			{
+				dist -= ((BodyWrapper)object1).HostObject.Radius;
+			}
+
+			if (object2 is BodyWrapper)
+			{
+				dist -= ((BodyWrapper)object2).HostObject.Radius;
+			}
+
+			return dist;
+		}
+
+		/// <summary>
+		/// Returns the world distance between an <see cref="AntennaRange.IAntennaRelay"/> and
+		/// an <see cref="AntennaRange.IPositionedObject"/>.
+		/// </summary>
+		public static double DistanceTo(this IAntennaRelay relay, IPositionedObject obj)
+		{
+			return relay.Host.DistanceTo(obj);
+		}
+
+		/// <summary>
+		/// Returns the world distance between two <see cref="AntennaRange.IAntennaRelay"/> objects.
+		/// </summary>
+		public static double DistanceTo(this IAntennaRelay relay1, IAntennaRelay relay2)
+		{
+			return relay1.Host.DistanceTo(relay2.Host);
+		}
+
+		/// <summary>
+		/// Returns the world distance between an <see cref="AntennaRange.IAntennaRelay"/> and a <see cref="Vessel"/>.
+		/// </summary>
+		public static double DistanceTo(this IAntennaRelay relay, Vessel vessel)
+		{
+			return relay.Host.DistanceTo((VesselWrapper)vessel);
+		}
+
+		/// <summary>
+		/// Returns the square of the world distance between two <see cref="AntennaRange.IPositionedObject"/> objects.
+		/// </summary>
+		public static double sqrDistanceTo(this IPositionedObject object1, IPositionedObject object2)
+		{
+			if (object1 is BodyWrapper || object2 is BodyWrapper)
+			{
+				double dist = object1.DistanceTo(object2);
+				dist *= dist;
+				return dist;
+			}
+			else
+			{
+				return (object1.WorldPos - object2.WorldPos).sqrMagnitude;
+			}
+		}
+
+		/// <summary>
+		/// Returns the square of the world distance between an <see cref="AntennaRange.IAntennaRelay"/>
+		/// and a <see cref="Vessel"/>.
+		/// </summary>
+		public static double sqrDistanceTo(this IAntennaRelay relay, Vessel vessel)
+		{
+			return relay.Host.sqrDistanceTo((VesselWrapper)vessel);
+		}
+
+		/// <summary>
+		/// Returns the square of the world distance between an <see cref="AntennaRange.IAntennaRelay"/>
+		/// and a <see cref="CelestialBody"/>.
+		/// </summary>
+		public static double sqrDistanceTo(this IAntennaRelay relay, CelestialBody body)
+		{
+			return relay.Host.sqrDistanceTo((BodyWrapper)body);
+		}
+
+		/// <summary>
+		/// Returns <c>true</c> if the origin <see cref="AntennaRange.IPositionedObject"/> has line of sight to the
+		/// target <see cref="AntennaRange.IPositionedObject"/>, <c>false</c> otherwise.  If not, firstOccludingBody
+		/// outputs the first <see cref="CelestialBody"/> blocking line of sight.
+		/// </summary>
+		public static bool hasLineOfSightTo(
+			this IPositionedObject origin,
+			IPositionedObject target,
+			out CelestialBody firstOccludingBody,
+			double sqrRatio = 1d
+		)
+		{
+			CelestialBody[] excludedBodies;
+
+			if (target is BodyWrapper)
+			{
+				excludedBodies = new CelestialBody[] { (target as BodyWrapper).HostObject };
+			}
+			else
+			{
+				excludedBodies = null;
+			}
+
+			return VectorTools.IsLineOfSightBetween(
+				origin.WorldPos,
+				target.WorldPos,
+				out firstOccludingBody,
+				excludedBodies,
+				sqrRatio
+			);
 		}
 
 		/// <summary>