Version 1.11.2 1.11.2
Version 1.11.2

--- a/.gitattributes
+++ b/.gitattributes
@@ -4,8 +4,8 @@
 # These files are text and should be normalized (convert crlf => lf)
 *.cs      text diff=csharp
 *.cfg     text
-*.csproj  text
-*.sln     text
+*.csproj  text eol=crlf
+*.sln     text eol=crlf
 
 # Images should be treated as binary
 # (binary is a macro for -text -diff)

--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -4,8 +4,14 @@
 // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
 
 using KSP;
+using KSP.UI.Screens;
 using System;
-using ToadicusTools;
+using System.Collections.Generic;
+using System.Reflection;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
+using ToadicusTools.GUIUtils;
+using ToadicusTools.Wrappers;
 using UnityEngine;
 
 namespace AntennaRange
@@ -28,6 +34,8 @@
 		private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
 		private const string RANGE_KEY = "range";
 
+		private const string USE_TOOLBAR_KEY = "useToolbarIfAvailable";
+
 		/// <summary>
 		/// Indicates whether connections require line of sight.
 		/// </summary>
@@ -112,7 +120,19 @@
 			}
 		}
 
+		/// <summary>
+		/// Gets a value indicating whether we should use Toolbar if available.
+		/// </summary>
+		/// <value><c>true</c> if we should use Toolbar if available; otherwise, <c>false</c>.</value>
+		public static bool UseToolbarIfAvailable
+		{
+			get;
+			private set;
+		}
+
 #pragma warning disable 1591
+
+		private static MethodInfo partLoader_CompilePartInfo;
 
 		private bool showConfigWindow;
 		private Rect configWindowPos;
@@ -174,6 +194,8 @@
 
 			ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
 			this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+
+			ARConfiguration.UseToolbarIfAvailable = this.LoadConfigValue(USE_TOOLBAR_KEY, true);
 
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
 			GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
@@ -212,6 +234,16 @@
 					this.trackingStationRanges.SPrint());
 			}
 
+			if (partLoader_CompilePartInfo == null)
+			{
+				partLoader_CompilePartInfo = typeof(PartLoader).GetMethod(
+					"CompilePartInfo",
+					BindingFlags.NonPublic | BindingFlags.Instance
+				);
+
+				this.Log("Fetched PartLoader.CompilePartInfo: {0}", partLoader_CompilePartInfo);
+			}
+
 			this.runOnce = true;
 
 			this.LogDebug("Awake.");
@@ -227,17 +259,24 @@
 				this.runOnce = false;
 
 				this.SetKerbinRelayRange();
+				this.updateModuleInfos();
 			}
 		}
 
 		public void OnGUI()
 		{
 			// Only runs once, if the Toolbar is available.
-			if (ToolbarManager.ToolbarAvailable)
+			if (ToolbarManager.ToolbarAvailable && ARConfiguration.UseToolbarIfAvailable)
 			{
 				if (this.toolbarButton == null)
 				{
 					this.LogDebug("Toolbar available; initializing toolbar button.");
+
+					if (this.appLauncherButton != null)
+					{
+						ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
+						this.appLauncherButton = null;
+					}
 
 					this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
 					this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
@@ -252,6 +291,12 @@
 			}
 			else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
 			{
+				if (this.toolbarButton != null)
+				{
+					this.toolbarButton.Destroy();
+					this.toolbarButton = null;
+				}
+
 				this.LogDebug("Toolbar available; initializing AppLauncher button.");
 
 				this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
@@ -275,7 +320,7 @@
 					GUILayout.ExpandWidth(true)
 				);
 
-				configPos = Tools.ClampRectToScreen(configPos, 20);
+				configPos = WindowTools.ClampRectToScreen(configPos, 20);
 
 				if (configPos != this.configWindowPos)
 				{
@@ -291,7 +336,7 @@
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
-			bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
+			bool requireLineOfSight = Layout.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
 			if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
 			{
 				ARConfiguration.RequireLineOfSight = requireLineOfSight;
@@ -303,7 +348,7 @@
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
 
 			bool requireConnectionForControl =
-				GUITools.Toggle(
+				Layout.Toggle(
 					ARConfiguration.RequireConnectionForControl,
 					"Require Connection for Probe Control"
 				);
@@ -317,29 +362,33 @@
 
 			GUILayout.BeginHorizontal();
 
-			bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
+			bool fixedPowerCost = Layout.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
 			if (fixedPowerCost != ARConfiguration.FixedPowerCost)
 			{
 				ARConfiguration.FixedPowerCost = fixedPowerCost;
 				this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
+
+				this.updateModuleInfos();
 			}
 
 			GUILayout.EndHorizontal();
 
 			GUILayout.BeginHorizontal();
 
-			bool useAdditive = GUITools.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
+			bool useAdditive = Layout.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
 			if (useAdditive != ARConfiguration.UseAdditiveRanges)
 			{
 				ARConfiguration.UseAdditiveRanges = useAdditive;
 				this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
+
+				this.updateModuleInfos();
 			}
 
 			GUILayout.EndHorizontal();
 
 			GUILayout.BeginHorizontal();
 
-			bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
+			bool prettyLines = Layout.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
 			if (prettyLines != ARConfiguration.PrettyLines)
 			{
 				ARConfiguration.PrettyLines = prettyLines;
@@ -350,6 +399,17 @@
 
 			GUILayout.BeginHorizontal();
 
+			bool useToolbar = Layout.Toggle(ARConfiguration.UseToolbarIfAvailable, "Use Blizzy's Toolbar, if Available");
+			if (useToolbar != ARConfiguration.UseToolbarIfAvailable)
+			{
+				ARConfiguration.UseToolbarIfAvailable = useToolbar;
+				this.SaveConfigValue(USE_TOOLBAR_KEY, useToolbar);
+			}
+
+			GUILayout.EndHorizontal();
+
+			GUILayout.BeginHorizontal();
+
 			GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
 
 			this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
@@ -427,6 +487,90 @@
 			{
 				this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
 				this.SetKerbinRelayRange();
+
+				this.updateModuleInfos();
+			}
+		}
+
+		private void updateModuleInfos()
+		{
+			if (PartLoader.Instance != null && PartLoader.Instance.parts != null)
+			{
+				this.Log("Updating module infos in PartLoader");
+				this.updateModuleInfos(PartLoader.Instance.parts);
+			}
+
+			if (RDTestSceneLoader.Instance != null && RDTestSceneLoader.Instance.partsList != null)
+			{
+				this.Log("Updating module infos in RDTestSceneLoader");
+				this.updateModuleInfos(RDTestSceneLoader.Instance.partsList);
+			}
+		}
+
+		private void updateModuleInfos(List<AvailablePart> partsList)
+		{
+			if (partLoader_CompilePartInfo == null)
+			{
+				this.LogError("Cannot recompile part info; partLoader_CompilePartInfo not found.");
+				return;
+			}
+
+			if (PartLoader.Instance == null)
+			{
+				this.LogError("Cannot recompile part info; PartLoader.Instance is null.");
+				return;
+			}
+
+			// We need to go find all of the prefabs and update them, because Squad broke IModuleInfo.
+			AvailablePart availablePart;
+			Part partPrefab;
+			PartModule modulePrefab;
+			object[] compileArgs = new object[2];
+
+			this.Log("Updating module infos...");
+
+			for (int apIdx = 0; apIdx < partsList.Count; apIdx++)
+			{
+				availablePart = partsList[apIdx];
+
+				if (availablePart == null)
+				{
+					continue;
+				}
+
+				partPrefab = availablePart.partPrefab;
+
+				if (partPrefab == null || partPrefab.Modules == null)
+				{
+					continue;
+				}
+
+				for (int pmIdx = 0; pmIdx < partPrefab.Modules.Count; pmIdx++)
+				{
+					modulePrefab = partPrefab.Modules[pmIdx];
+
+					if (modulePrefab == null)
+					{
+						continue;
+					}
+
+					if (modulePrefab is IAntennaRelay)
+					{
+						this.Log("Found prefab IAntennaRelay {0}", modulePrefab);
+
+						this.Log("Recompiling part and module info for {0}", availablePart.name);
+
+						availablePart.moduleInfos.Clear();
+						availablePart.resourceInfos.Clear();
+
+						compileArgs[0] = availablePart;
+						compileArgs[1] = partPrefab;
+
+						partLoader_CompilePartInfo.Invoke(PartLoader.Instance, compileArgs);
+
+						break;
+					}
+				}
 			}
 		}
 

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -29,9 +29,13 @@
 #pragma warning disable 1591
 
 using KSP;
+using KSP.UI.Screens;
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Wrappers;
 using UnityEngine;
 
 namespace AntennaRange
@@ -53,7 +57,7 @@
 		private IButton toolbarButton;
 
 		private ApplicationLauncherButton appLauncherButton;
-		private Tools.DebugLogger log;
+		private PooledDebugLogger log;
 
 		private System.Diagnostics.Stopwatch updateTimer;
 		#endregion
@@ -127,7 +131,7 @@
 		{
 			this.lockID = "ARConnectionRequired";
 
-			this.log = Tools.DebugLogger.New(this);
+			this.log = PooledDebugLogger.New(this);
 
 			this.updateTimer = new System.Diagnostics.Stopwatch();
 
@@ -146,7 +150,7 @@
 			this.appLauncherTextures[ConnectionStatus.Optimal] =
 				GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
 
-			if (ToolbarManager.ToolbarAvailable)
+			if (ToolbarManager.ToolbarAvailable && ARConfiguration.UseToolbarIfAvailable)
 			{
 				this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
@@ -255,7 +259,7 @@
 			{
 				Vessel vessel;
 				IAntennaRelay relay;
-				IAntennaRelay bestActiveRelay;
+				IAntennaRelay bestActiveRelay = null;
 				IList<IAntennaRelay> activeVesselRelays;
 
 				usefulRelays.Clear();
@@ -269,6 +273,14 @@
 						continue;
 					}
 
+					switch (vessel.vesselType)
+					{
+						case VesselType.Debris:
+						case VesselType.Flag:
+						case VesselType.Unknown:
+							continue;
+					}
+
 					log.AppendFormat("\nFetching best relay for vessel {0}", vessel);
 
 					relay = vessel.GetBestRelay();
@@ -287,21 +299,6 @@
 				{
 					bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);
 
-					for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
-					{
-						relay = activeVesselRelays[rIdx];
-
-						// The best active relay will get checked with the other useful relays later.
-						if (relay == null || relay == bestActiveRelay)
-						{
-							continue;
-						}
-
-						log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
-
-						relay.FindNearestRelay();
-					}
-
 					log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay);
 
 					usefulRelays.Add(bestActiveRelay);
@@ -320,6 +317,24 @@
 
 					log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
 
+					relay.FindNearestRelay();
+					relay.RecalculateTransmissionRates();
+				}
+
+				// Very last, find routes for the non-best relays on the active vessel.
+				for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
+				{
+					relay = activeVesselRelays[rIdx];
+
+					// The best active relay will get checked with the other useful relays later.
+					if (relay == null || relay == bestActiveRelay)
+					{
+						continue;
+					}
+
+					log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
+
+					relay.RecalculateTransmissionRates();
 					relay.FindNearestRelay();
 				}
 

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -31,7 +31,8 @@
 using KSP;
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.DebugTools;
 using UnityEngine;
 
 namespace AntennaRange
@@ -51,7 +52,7 @@
 		// Debug Stuff
 		#pragma warning disable 649
 		private System.Diagnostics.Stopwatch timer;
-		private Tools.DebugLogger log;
+		private PooledDebugLogger log;
 		private long relayStart;
 		private long start;
 		#pragma warning restore 649
@@ -99,7 +100,7 @@
 			this.timer = new System.Diagnostics.Stopwatch();
 			#endif
 			#if DEBUG
-			this.log = Tools.DebugLogger.New(this);
+			this.log = PooledDebugLogger.Get();
 			#endif
 		}
 
@@ -121,7 +122,7 @@
 				log.Clear();
 
 				log.AppendFormat("OnPreCull.\n");
-
+/* @ TODO: Fix
 				log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
 					"\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
 					"\tMapView.MapCamera.Distance: {2}\n",
@@ -129,7 +130,7 @@
 					MapView.MapCamera.camera.fieldOfView,
 					MapView.MapCamera.Distance
 				);
-
+*/
 				log.AppendLine("FlightGlobals ready and Vessels list not null.");
 
 				IAntennaRelay relay;
@@ -212,20 +213,22 @@
 			}
 
 			LineRenderer renderer = this[relay.vessel.id];
-			Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
+			Vector3 start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
 
 			float lineWidth;
 			float d = Screen.height / 2f + 0.01f;
 
 			if (MapView.Draw3DLines)
 			{
-				lineWidth = 0.005859375f * MapView.MapCamera.Distance;
+				lineWidth = 0.00833333333f * MapView.MapCamera.Distance;
 			}
 			else
 			{
-				lineWidth = 2f;
-
-				start = MapView.MapCamera.camera.WorldToScreenPoint(start);
+				lineWidth = 3f;
+
+				// TODO: No idea if this substitution is right.
+				// start = MapView.MapCamera.camera.WorldToScreenPoint(start);
+				start = PlanetariumCamera.Camera.WorldToScreenPoint(start);
 
 				start.z = start.z >= 0f ? d : -d;
 			}
@@ -240,7 +243,7 @@
 			relayStart = timer.ElapsedMilliseconds;
 			#endif
 
-			Vector3d nextPoint;
+			Vector3 nextPoint;
 
 			renderer.enabled = true;
 
@@ -277,6 +280,17 @@
 					return;
 				}
 
+				switch (relay.targetRelay.vessel.vesselType)
+				{
+					case VesselType.Debris:
+					case VesselType.Flag:
+					case VesselType.Unknown:
+						renderer.enabled = false;
+						return;
+					default:
+						break;
+				}
+
 				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
 			}
 
@@ -284,7 +298,9 @@
 
 			if (!MapView.Draw3DLines)
 			{
-				nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+				// TODO: No idea if this substitution is right.
+				// nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+				nextPoint = PlanetariumCamera.Camera.WorldToScreenPoint(nextPoint);
 				nextPoint.z = nextPoint.z >= 0f ? d : -d;
 			}
 
@@ -306,6 +322,12 @@
 				while (enumerator.MoveNext())
 				{
 					lineRenderer = enumerator.Current;
+
+					if (lineRenderer == null)
+					{
+						continue;
+					}
+
 					lineRenderer.enabled = false;
 
 					if (freeObjects)

--- a/AntennaRange.csproj
+++ b/AntennaRange.csproj
@@ -80,6 +80,7 @@
     <Compile Include="ARConfiguration.cs" />
     <Compile Include="ARFlightController.cs" />
     <Compile Include="ARMapRenderer.cs" />
+    <Compile Include="RelayDataCost.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
@@ -93,7 +94,12 @@
     </Reference>
     <Reference Include="UnityEngine">
       <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
-      <Private>False</Private>
+    </Reference>
+    <Reference Include="KSPUtil">
+      <HintPath>..\_KSPAssemblies\KSPUtil.dll</HintPath>
+    </Reference>
+    <Reference Include="UnityEngine.UI">
+      <HintPath>..\_KSPAssemblies\UnityEngine.UI.dll</HintPath>
     </Reference>
   </ItemGroup>
   <ItemGroup>

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -28,7 +28,9 @@
 
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Extensions;
+using UnityEngine;
 
 namespace AntennaRange
 {
@@ -156,6 +158,66 @@
 		}
 
 		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		public virtual RelayDataCost CurrentLinkCost
+		{
+			get
+			{
+				return this.moduleRef?.CurrentLinkCost ?? RelayDataCost.Infinity;
+			}
+			set
+			{
+				throw new NotImplementedException(
+					string.Format(
+						"{0} must not assign CurrentLinkCost.  This is probably a bug.",
+						this.GetType().FullName
+					)
+				);
+			}
+		}
+
+		/// <summary>
+		/// Gets the current network link cost back to Kerbin, in EC/MiT.
+		/// </summary>
+		/// <value>The current network link cost back to Kerbin, in EC/MiT.</value>
+		public virtual RelayDataCost CurrentNetworkLinkCost
+		{
+			get
+			{
+				RelayDataCost cost = new RelayDataCost();
+
+				IAntennaRelay relay = this.moduleRef;
+
+				ushort iters = 0;
+				while (relay != null)
+				{
+					cost += relay.CurrentLinkCost;
+
+					if (relay.KerbinDirect)
+					{
+						break;
+					}
+
+					iters++;
+
+					if (iters > 255)
+					{
+						this.LogError("Bailing out of AntennaRelay.CurrentNetworkLinkCost because it looks like " +
+							"we're stuck in an infinite loop.  This is probably a bug.");
+						
+						break;
+					}
+
+					relay = relay.targetRelay;
+				}
+
+				return cost;
+			}
+		}
+
+		/// <summary>
 		/// Gets or sets the link status.
 		/// </summary>
 		public virtual ConnectionStatus LinkStatus
@@ -193,6 +255,131 @@
 		}
 
 		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
+		public void RecalculateTransmissionRates()
+		{
+			if (!this.canTransmit) {
+				this.moduleRef.CurrentLinkCost = RelayDataCost.Infinity;
+				return;
+			}
+
+			RelayDataCost cost = this.GetPotentialLinkCost(this.CurrentLinkSqrDistance, this.NominalLinkSqrDistance);
+
+			this.moduleRef.CurrentLinkCost = cost;
+		}
+
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="currentSqrDistance">Square of the current distance to the target</param>
+		/// <param name="nominalSqrDistance">Square of the nominal range to the target.</param>
+		public RelayDataCost GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance)
+		{
+			RelayDataCost linkCost = new RelayDataCost();
+
+			float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance);
+
+			RelayDataCost baseCost = this.moduleRef?.BaseLinkCost ?? RelayDataCost.Infinity;
+
+			if (ARConfiguration.FixedPowerCost)
+			{
+				linkCost.PacketResourceCost = baseCost.PacketResourceCost;
+
+				linkCost.PacketSize = Mathf.Min(
+					baseCost.PacketSize * rangeFactor,
+					baseCost.PacketSize * this.moduleRef.MaxDataFactor
+				);
+			}
+			else
+			{
+				if (currentSqrDistance > nominalSqrDistance)
+				{
+					linkCost.PacketSize = baseCost.PacketSize;
+					linkCost.PacketResourceCost = baseCost.PacketResourceCost / rangeFactor;
+				}
+				else
+				{
+					linkCost.PacketSize = Mathf.Min(
+						baseCost.PacketSize * rangeFactor,
+						baseCost.PacketSize * this.moduleRef.MaxDataFactor
+					);
+					linkCost.PacketResourceCost = baseCost.PacketResourceCost;
+				}
+			}
+
+			linkCost.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f;
+			linkCost.PacketSize *= this.moduleRef.PacketThrottle / 100f;
+
+			return linkCost;
+		}
+
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="potentialTarget">Potential target relay.</param>
+		public RelayDataCost GetPotentialLinkCost(IAntennaRelay potentialTarget)
+		{
+			if (potentialTarget == null)
+			{
+				return RelayDataCost.Infinity;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(potentialTarget);
+
+			if (currentSqrDistance > this.MaxLinkSqrDistanceTo(potentialTarget))
+			{
+				return RelayDataCost.Infinity;
+			}
+
+			double nominalSqrDistance;
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * potentialTarget.nominalTransmitDistance;
+			}
+			else
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
+			}
+
+			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
+		}
+
+		/// <summary>
+		/// Gets the potential link cost, in EC/MiT.
+		/// </summary>
+		/// <returns>The potential link cost, in EC/MiT.</returns>
+		/// <param name="body">Potential target Body</param>
+		public RelayDataCost GetPotentialLinkCost(CelestialBody body)
+		{
+			if (body == null || body != Kerbin)
+			{
+				return RelayDataCost.Infinity;
+			}
+
+			double currentSqrDistance = this.SqrDistanceTo(body);
+
+			if (currentSqrDistance > this.MaxLinkSqrDistanceTo(body))
+			{
+				return RelayDataCost.Infinity;
+			}
+
+			double nominalSqrDistance;
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange;
+			}
+			else
+			{
+				nominalSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance;
+			}
+
+			return GetPotentialLinkCost(currentSqrDistance, nominalSqrDistance);
+		}
+
+		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
 		/// <returns>The nearest relay or null, if no relays in range.</returns>
@@ -203,9 +390,9 @@
 				return;
 			}
 
-			Tools.DebugLogger log;
+			PooledDebugLogger log;
 			#if DEBUG
-			log = Tools.DebugLogger.New(this);
+			log = PooledDebugLogger.New(this);
 			#endif
 
 			#if BENCH
@@ -241,15 +428,12 @@
 			CelestialBody bodyOccludingBestOccludedRelay = null;
 			IAntennaRelay needle;
 
-			double nearestRelaySqrQuotient = double.PositiveInfinity;
-			double bestOccludedSqrQuotient = double.PositiveInfinity;
-
-			double potentialSqrDistance;
-			double maxLinkSqrDistance;
-			double potentialSqrQuotient;
-
-			double kerbinSqrDistance;
-			double kerbinSqrQuotient;
+			RelayDataCost cheapestRelayRate = RelayDataCost.Infinity;
+			RelayDataCost cheapestOccludedRelayRate = RelayDataCost.Infinity;
+
+			RelayDataCost potentialRelayRate;
+
+			RelayDataCost kerbinRelayRate = this.GetPotentialLinkCost(Kerbin);
 
 			bool isCircular;
 			int iterCount;
@@ -304,24 +488,16 @@
 				#endif
 
 				// Find the distance from here to the vessel...
-				log.Append("\n\tgetting distance to potential vessel");
-				potentialSqrDistance = this.SqrDistanceTo(potentialBestRelay);
-				log.Append("\n\tgetting best vessel relay");
-
-				log.Append("\n\tgetting max link distance to potential relay");
-
-				if (ARConfiguration.UseAdditiveRanges)
-				{
-					maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
-				}
-				else
-				{
-					maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
-				}
-
-				log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance);
-
-				potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
+				log.Append("\n\tgetting cost to potential vessel");
+				potentialRelayRate = potentialBestRelay.CurrentNetworkLinkCost +
+					this.GetPotentialLinkCost(potentialBestRelay);
+
+				log.AppendFormat(
+					"\n\tpotentialRelayRate = {0} ({1} + {2})",
+					potentialRelayRate,
+					potentialBestRelay.CurrentNetworkLinkCost,
+					this.GetPotentialLinkCost(potentialBestRelay)
+				);
 
 				#if BENCH
 				startLOSVesselTicks = performanceTimer.ElapsedTicks;
@@ -349,22 +525,21 @@
 					log.AppendFormat("\n\t\t\t{0}: Relay {1} not in line of sight.",
 						this.ToString(), potentialBestRelay);
 					
-					log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
-					log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
-					log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
+					log.AppendFormat("\n\t\t\tpotentialRelayRate: {0}", potentialRelayRate);
+					log.AppendFormat("\n\t\t\tcheapestOccludedRelayRate: {0}", cheapestOccludedRelayRate);
 
 					if (
-						(potentialSqrQuotient < bestOccludedSqrQuotient) &&
-						(potentialSqrQuotient <= 1d) &&
+						(potentialRelayRate < cheapestRelayRate) &&
+						this.IsInRangeOf(potentialBestRelay) &&
 						potentialBestRelay.CanTransmit()
 					)
 					{
-						log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
+						log.Append("\n\t\t...vessel is cheapest and in range and potentialBestRelay can transmit");
 						log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
 
 						this.bestOccludedRelay = potentialBestRelay;
 						bodyOccludingBestOccludedRelay = fob;
-						bestOccludedSqrQuotient = potentialSqrQuotient;
+						cheapestOccludedRelayRate = potentialRelayRate;
 					}
 					else
 					{
@@ -391,12 +566,16 @@
 				/*
 				 * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
 				 * */
-				if (potentialSqrQuotient > nearestRelaySqrQuotient)
+				if (potentialRelayRate > cheapestRelayRate)
 				{
 					
-					log.AppendFormat("\n\t{0}: Relay {1} discarded because it is farther than another the nearest relay.",
+					log.AppendFormat(
+							"\n\t{0}: Relay {1} discarded because it is more expensive than the cheapest relay." +
+							"\n\t\t({2}, {3} > {4})",
 						this.ToString(),
-						potentialBestRelay
+						potentialBestRelay,
+						this.nearestRelay == null ? "NULL" : this.nearestRelay.ToString(),
+						potentialRelayRate, cheapestRelayRate
 					);
 					continue;
 				}
@@ -460,13 +639,13 @@
 
 					if (!isCircular)
 					{
-						nearestRelaySqrQuotient = potentialSqrQuotient;
+						cheapestRelayRate = potentialRelayRate;
 						this.nearestRelay = potentialBestRelay;
 
-						log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
+						log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)",
 							this.ToString(),
 							this.nearestRelay.ToString(),
-							Math.Sqrt(nearestRelaySqrQuotient)
+							cheapestRelayRate
 						);
 					}
 					else
@@ -495,29 +674,15 @@
 
 			CelestialBody bodyOccludingKerbin = null;
 
-			kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
-			kerbinSqrDistance *= kerbinSqrDistance;
-
-			if (ARConfiguration.UseAdditiveRanges)
-			{
-				kerbinSqrQuotient = kerbinSqrDistance /
-					(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
-			}
-			else
-			{
-				kerbinSqrQuotient = kerbinSqrDistance /
-					(this.maxTransmitDistance * this.maxTransmitDistance);
-			}
-
 			log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
 			log.AppendFormat(
-				"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinSqrDistance={5}m²)",
+					"\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinRelayRate={5} EC/MiT)",
 				this,
 				this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-				nearestRelaySqrQuotient,
+				cheapestRelayRate,
 				this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
-				bestOccludedSqrQuotient,
-				kerbinSqrDistance
+				cheapestOccludedRelayRate,
+				kerbinRelayRate
 			);
 			
 			#if BENCH
@@ -535,13 +700,12 @@
 				#endif
 				log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
 
-				// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
-				// Therefore, this will only be true if a valid relay is in range.
-				if (nearestRelaySqrQuotient <= 1d)
-				{
-					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+				// If we're in range of the "nearest" (actually cheapest) relay, use it.
+				if (this.IsInRangeOf(this.nearestRelay))
+				{
+					log.AppendFormat("\n\t\tCan transmit to nearby relay {0}).",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					this.KerbinDirect = false;
 					this.canTransmit = true;
@@ -550,27 +714,27 @@
 				// If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
 				else
 				{
-					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0}.",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					this.canTransmit = false;
 
-					// If the best occluded relay is closer than Kerbin, check it against the nearest relay.
-					// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
-					if (bestOccludedSqrQuotient < kerbinSqrQuotient)
-					{
-						log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
-							bestOccludedRelay, kerbinSqrDistance);
+					// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
+					// Since cheapestOccludedRelayRate is infinity if there are no occluded relays, this is safe
+					if (cheapestOccludedRelayRate < kerbinRelayRate)
+					{
+						log.AppendFormat("\n\t\t\tBest occluded relay is cheaper than Kerbin ({0} < {1})",
+							cheapestOccludedRelayRate, kerbinRelayRate);
 						
 						this.KerbinDirect = false;
 
-						// If the nearest relay is closer than the best occluded relay, pick it.
-						// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-						if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+						// If the nearest relay is cheaper than the best occluded relay, pick it.
+						// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+						if (cheapestRelayRate < cheapestOccludedRelayRate)
 						{
-							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
+								cheapestRelayRate, cheapestOccludedRelayRate);
 							
 							this.targetRelay = this.nearestRelay;
 							this.firstOccludingBody = null;
@@ -578,36 +742,37 @@
 						// Otherwise, target the best occluded relay.
 						else
 						{
-							log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-								nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+								cheapestRelayRate, cheapestOccludedRelayRate);
 							
 							this.targetRelay = bestOccludedRelay;
 							this.firstOccludingBody = bodyOccludingBestOccludedRelay;
 						}
 					}
-					// Otherwise, check Kerbin against the nearest relay.
-					// Since we have LOS, blank the first occluding body.
+					// Otherwise, check Kerbin against the "nearest" (cheapest) relay.
 					else
 					{
-						log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
-							bestOccludedRelay, kerbinSqrDistance);
+						log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
+							cheapestOccludedRelayRate, kerbinRelayRate);
 						
-						// If the nearest relay is closer than Kerbin, pick it.
-						// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-						if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+						// If the "nearest" (cheapest) relay is cheaper than Kerbin, pick it.
+						// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+						if (cheapestRelayRate < kerbinRelayRate)
 						{
-							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-								nearestRelaySqrQuotient, kerbinSqrQuotient);
-							
+							log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+								cheapestRelayRate, kerbinRelayRate);
+
+							// Since we have LOS, blank the first occluding body.
+							this.firstOccludingBody = null;
+
 							this.KerbinDirect = false;
-							this.firstOccludingBody = null;
 							this.targetRelay = this.nearestRelay;
 						}
 						// Otherwise, pick Kerbin.
 						else
 						{
-							log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-								nearestRelaySqrQuotient, kerbinSqrQuotient);
+							log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+								cheapestRelayRate, kerbinRelayRate);
 							
 							this.KerbinDirect = true;
 							this.firstOccludingBody = bodyOccludingKerbin;
@@ -625,21 +790,21 @@
 
 				log.AppendFormat("\n\tKerbin is in LOS.");
 
-				// If the nearest relay is closer than Kerbin and in range, transmit to it.
-				if (nearestRelaySqrQuotient <= 1d)
-				{
-					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-						nearestRelaySqrQuotient, 1d);
+				// If the nearest relay is in range, we can transmit.
+				if (this.IsInRangeOf(this.nearestRelay))
+				{
+					log.AppendFormat("\n\t\tCan transmit to nearby relay {0} (in range).",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					this.canTransmit = true;
 
 					// If the nearestRelay is closer than Kerbin, use it.
-					if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+					if (cheapestRelayRate < kerbinRelayRate)
 					{
 						log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, kerbinSqrQuotient);
+							cheapestRelayRate, kerbinRelayRate);
 
 						this.KerbinDirect = false;
 						this.targetRelay = this.nearestRelay;
@@ -649,7 +814,7 @@
 					{
 						log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
 							this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, kerbinSqrQuotient);
+							cheapestRelayRate, kerbinRelayRate);
 
 						this.KerbinDirect = true;
 						this.targetRelay = null;
@@ -658,15 +823,14 @@
 				// If the nearest relay is out of range, we still need to check on Kerbin.
 				else
 				{
-					log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
-						this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
-							nearestRelaySqrQuotient, 1d);
+					log.AppendFormat("\n\t\tCheapest relay {0} is out of range.",
+						this.nearestRelay == null ? "null" : this.nearestRelay.ToString()
+					);
 
 					// If Kerbin is in range, use it.
-					if (kerbinSqrQuotient <= 1d)
-					{
-						log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
-							kerbinSqrQuotient, 1d);
+					if (this.IsInRangeOf(Kerbin))
+					{
+						log.AppendFormat("\n\t\t\tCan transmit to Kerbin (in range).");
 
 						this.canTransmit = true;
 						this.KerbinDirect = true;
@@ -676,26 +840,25 @@
 					// Kerbin and bestOccludedRelay
 					else
 					{
-						log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
-								kerbinSqrQuotient, 1d);
+						log.AppendFormat("\n\t\t\tCan't transmit to Kerbin (out of range).");
 
 						this.canTransmit = false;
 
-						// If the best occluded relay is closer than Kerbin, check it against the nearest relay.
+						// If the best occluded relay is cheaper than Kerbin, check it against the nearest relay.
 						// Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
-						if (bestOccludedSqrQuotient < kerbinSqrQuotient)
+						if (cheapestOccludedRelayRate < kerbinRelayRate)
 						{
 							log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
-								bestOccludedRelay, kerbinSqrDistance);
+								cheapestOccludedRelayRate, kerbinRelayRate);
 							
 							this.KerbinDirect = false;
 
 							// If the nearest relay is closer than the best occluded relay, pick it.
-							// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-							if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+							// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+							if (cheapestRelayRate < cheapestOccludedRelayRate)
 							{
-								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.",
+									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = this.nearestRelay;
 								this.firstOccludingBody = null;
@@ -703,8 +866,8 @@
 							// Otherwise, target the best occluded relay.
 							else
 							{
-								log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-									nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...and cheaper than the cheapest relay ({0} >= {1}), so picking it.",
+									cheapestRelayRate, cheapestOccludedRelayRate);
 								
 								this.targetRelay = bestOccludedRelay;
 								this.firstOccludingBody = bodyOccludingBestOccludedRelay;
@@ -714,17 +877,17 @@
 						// Since we have LOS, blank the first occluding body.
 						else
 						{
-							log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
-								bestOccludedRelay, kerbinSqrDistance);
+							log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})",
+								cheapestOccludedRelayRate, kerbinRelayRate);
 							
 							this.firstOccludingBody = null;
 
-							// If the nearest relay is closer than Kerbin, pick it.
-							// Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
-							if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+							// If the nearest relay is cheaper than Kerbin, pick it.
+							// Since cheapestRelayRate is infinity if there are no nearby relays, this is safe.
+							if (cheapestRelayRate < kerbinRelayRate)
 							{
-								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
-									nearestRelaySqrQuotient, kerbinSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.",
+									cheapestRelayRate, kerbinRelayRate);
 								
 								this.KerbinDirect = false;
 								this.targetRelay = this.nearestRelay;
@@ -732,8 +895,8 @@
 							// Otherwise, pick Kerbin.
 							else
 							{
-								log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
-									nearestRelaySqrQuotient, kerbinSqrQuotient);
+								log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.",
+									cheapestRelayRate, kerbinRelayRate);
 								
 								this.KerbinDirect = true;
 								this.targetRelay = null;
@@ -881,6 +1044,7 @@
 		/// as an <see cref="AntennaRange.IAntennaRelay"/></param>
 		public AntennaRelay(IAntennaRelay module)
 		{
+			this.KerbinDirect = true;
 			this.moduleRef = module;
 
 			#if BENCH

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -37,8 +37,10 @@
 // maxDataFactor:	The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 // 
 
-@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
+	@TechRequired = start
+
 	@MODULE[ModuleDataTransmitter]
 	{
 		@name = ModuleLimitedDataTransmitter
@@ -59,7 +61,7 @@
 	}
 }
 
-@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
@@ -81,7 +83,7 @@
 	}
 }
 
-@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech]
 {
 	@MODULE[ModuleDataTransmitter]
 	{
@@ -91,6 +93,31 @@
 		simpleRange = 56250000000
 		maxPowerFactor = 16
 		maxDataFactor = 2
+	}
+
+	MODULE
+	{
+		name = ModuleScienceContainer
+
+		dataIsCollectable = true
+		dataIsStorable = false
+
+		storageRange = 2
+	}
+}
+
+@PART[HighGainAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech]
+{
+	@TechRequired = electronics
+	@description = Repurposed for medium range probes, the HG-55 provdes high speed directional data transmission.
+
+	@MODULE[ModuleDataTransmitter]
+	{
+		@name = ModuleLimitedDataTransmitter
+		nominalRange = 7774867578
+		simpleRange = 25030376544
+		maxPowerFactor = 2.6180339887498948
+		maxDataFactor = 9
 	}
 
 	MODULE
@@ -130,7 +157,7 @@
 EVA_RESOURCE
 {
 	name = ElectricCharge
-	amount = 100
+	amount = 0
 	maxAmount = 100
 }
 

 Binary files a/GameData/AntennaRange/Textures/appLauncherIcon.png and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIcon.png and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconNoConnection.png and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -47,6 +47,42 @@
 		IAntennaRelay targetRelay { get; }
 
 		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		RelayDataCost CurrentLinkCost { get; set; }
+
+		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		RelayDataCost BaseLinkCost { get; }
+
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
+		float PacketThrottle { get; }
+
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
+		float MaxDataFactor { get; }
+
+		/// <summary>
+		/// Gets the data resource cost in EC/MiT.
+		/// </summary>
+		/// <value>The data resource cost in EC/MiT.</value>
+		double DataResourceCost { get; }
+
+		/// <summary>
+		/// Gets the current network resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current network resource rate in EC/MiT.</value>
+		RelayDataCost CurrentNetworkLinkCost { get; }
+
+		/// <summary>
 		/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
 		/// directly with Kerbin.
 		/// </summary>
@@ -56,7 +92,6 @@
 		/// The link distance, in meters, at which this relay behaves nominally.
 		/// </summary>
 		double NominalLinkSqrDistance { get; }
-
 
 		/// <summary>
 		/// The link distance, in meters, beyond which this relay cannot operate.
@@ -100,9 +135,19 @@
 		bool CanTransmit();
 
 		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
+		void RecalculateTransmissionRates();
+
+		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
 		void FindNearestRelay();
+
+		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
+		void RecalculateMaxRange();
 
 		/// <summary>
 		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -30,7 +30,9 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
-using ToadicusTools;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
 using UnityEngine;
 
 namespace AntennaRange
@@ -49,20 +51,13 @@
 	public class ModuleLimitedDataTransmitter
 		: ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
 	{
-		// Stores the packetResourceCost as defined in the .cfg file.
-		private float _basepacketResourceCost;
-
-		// Stores the packetSize as defined in the .cfg file.
-		private float _basepacketSize;
+		private const string tooltipSkinName = "PartTooltipSkin";
 
 		// Every antenna is a relay.
 		private AntennaRelay relay;
 
 		// Sometimes we will need to communicate errors; this is how we do it.
 		private ScreenMessage ErrorMsg;
-
-		// Used in module info panes for part tooltips in the editor and R&D
-		private GUIContent moduleInfoContent;
 
 		/// <summary>
 		/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
@@ -177,9 +172,62 @@
 				else
 				{
 					this.LogError("Vessel and/or part reference are null, returning null vessel.");
+					#if DEBUG && VERBOSE
 					this.LogError(new System.Diagnostics.StackTrace().ToString());
+					#endif
 					return null;
 				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the current link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current link resource rate in EC/MiT.</value>
+		public RelayDataCost CurrentLinkCost
+		{
+			get
+			{
+				return new RelayDataCost(this.packetResourceCost, this.packetSize);
+			}
+			set
+			{
+				this.packetResourceCost = value.PacketResourceCost;
+				this.packetSize = value.PacketSize;
+			}
+		}
+
+		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		public RelayDataCost BaseLinkCost
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
+		public float PacketThrottle
+		{
+			get
+			{
+				return this.packetThrottle;
+			}
+		}
+
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
+		public float MaxDataFactor
+		{
+			get
+			{
+				return this.maxDataFactor;
 			}
 		}
 
@@ -352,8 +400,6 @@
 		{
 			get
 			{
-				this.PreTransmit_SetPacketSize();
-
 				if (this.CanTransmit())
 				{
 					return this.packetSize;
@@ -373,8 +419,6 @@
 		{
 			get
 			{
-				this.PreTransmit_SetPacketResourceCost();
-
 				if (this.CanTransmit())
 				{
 					return this.packetResourceCost;
@@ -383,6 +427,23 @@
 				{
 					return float.PositiveInfinity;
 				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the current network resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The current network resource rate in EC/MiT.</value>
+		public RelayDataCost CurrentNetworkLinkCost
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return RelayDataCost.Infinity;
+				}
+
+				return this.relay.CurrentNetworkLinkCost;
 			}
 		}
 
@@ -408,7 +469,7 @@
 		// Build ALL the objects.
 		public ModuleLimitedDataTransmitter () : base()
 		{
-			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
+			this.ErrorMsg = new ScreenMessage("", 4f, ScreenMessageStyle.UPPER_LEFT);
 			this.packetThrottle = 100f;
 		}
 
@@ -419,24 +480,19 @@
 		{
 			base.OnAwake();
 
-			this._basepacketSize = base.packetSize;
-			this._basepacketResourceCost = base.packetResourceCost;
-			this.moduleInfoContent = new GUIContent();
-
-			Tools.PostDebugMessage(string.Format(
-				"{0} loaded:\n" +
+			this.LogDebug("{0} loaded:\n" +
 				"packetSize: {1}\n" +
 				"packetResourceCost: {2}\n" +
 				"nominalTransmitDistance: {3}\n" +
 				"maxPowerFactor: {4}\n" +
 				"maxDataFactor: {5}\n",
-				this.name,
+				this,
 				base.packetSize,
-				this._basepacketResourceCost,
+				this.packetResourceCost,
 				this.nominalTransmitDistance,
 				this.maxPowerFactor,
 				this.maxDataFactor
-			));
+			);
 		}
 
 		/// <summary>
@@ -447,7 +503,7 @@
 		{
 			base.OnStart (state);
 
-			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+			this.RecalculateMaxRange();
 
 			if (state >= StartState.PreLaunch)
 			{
@@ -455,7 +511,7 @@
 				this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
 
-				this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance);
+				this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance);
 
 				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
 				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
@@ -475,7 +531,8 @@
 
 			base.OnLoad (node);
 
-			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+			this.BaseLinkCost = new RelayDataCost(base.packetResourceCost, base.packetSize);
+			this.RecalculateMaxRange();
 		}
 
 		/// <summary>
@@ -498,24 +555,51 @@
 		// HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
 		private void drawTooltipWidget(Rect rect)
 		{
+			/*
+			 * Removed all this because Squad doesn't even call it anymore.
+			 *
 			this.moduleInfoContent.text = this.GetInfo();
 
-			GUIStyle style0 = PartListTooltips.fetch.tooltipSkin.customStyles[0];
-			GUIStyle style1 = PartListTooltips.fetch.tooltipSkin.customStyles[1];
+			if (partTooltipSkin == null)
+			{
+				UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin));
+				GUISkin skin;
+				for (int sIdx = 0; sIdx < skins.Length; sIdx++)
+				{
+					skin = (GUISkin)skins[sIdx];
+
+					if (skin.name == tooltipSkinName)
+					{
+						partTooltipSkin = skin;
+						partTooltipBodyStyle = partTooltipSkin.customStyles[0];
+						partTooltipHeaderStyle = partTooltipSkin.customStyles[1];
+					}
+				}
+
+				if (partTooltipSkin == null)
+				{
+					this.LogError("Could not find GUISkin {0}?  Please report this!", tooltipSkinName);
+					return;
+				}
+				else
+				{
+					this.Log("Loaded GUISkin {0}", tooltipSkinName);
+				}
+			}
 
 			float width = rect.width;
 			float orgHeight = rect.height;
-			float height = style0.CalcHeight(this.moduleInfoContent, width);
+			float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width);
 
 			rect.height = height;
 
-			GUI.Box(rect, this.moduleInfoContent, style0);
-			GUI.Label(rect, this.GetModuleTitle(), style1);
+			GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle);
+			GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle);
 
 			GUILayout.Space(height - orgHeight
-				- style0.padding.bottom - style0.padding.top
-				- 2f * (style0.margin.bottom + style0.margin.top)
-			);
+				- partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
+				- 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top)
+			);*/
 		}
 
 		/// <summary>
@@ -531,34 +615,86 @@
 		/// </summary>
 		public override string GetInfo()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string text;
-
-			sb.Append(base.GetInfo());
-
-			if (ARConfiguration.UseAdditiveRanges)
-			{
-				sb.AppendFormat(Tools.SIFormatter, "Nominal Range to Kerbin: {0:S3}m\n",
-					Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				if (ARConfiguration.UseAdditiveRanges)
+				{
+					sb.AppendFormat("<b>Nominal Range to Kerbin: </b>{0:S3}m\n",
+						Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
+					);
+					sb.AppendFormat("<b>Maximum Range to Kerbin: </b>{0:S3}m\n",
+						Math.Sqrt(
+							this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
+							ARConfiguration.KerbinRelayRange
+						)
+					);
+				}
+				else
+				{
+					sb.AppendFormat("<b>Nominal Range: </b>{0:S3}m\n", this.nominalTransmitDistance);
+					sb.AppendFormat("<b>Maximum Range: </b>{0:S3}m\n", this.maxTransmitDistance);
+				}
+
+				sb.AppendLine();
+
+				sb.AppendFormat("<b>Nominal Packet Size: </b>{0:S2}iT\n", this.BaseLinkCost.PacketSize * 1000000f);
+				sb.AppendFormat(
+					"<b>Nominal Data Rate: </b>{0:S2}iT/sec\n",
+					this.BaseLinkCost.PacketSize / this.packetInterval * 1000000f
 				);
-				sb.AppendFormat(Tools.SIFormatter, "Maximum Range to Kerbin: {0:S3}m",
-					Math.Sqrt(
-						this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
-						ARConfiguration.KerbinRelayRange
-					)
-				);
-			}
-			else
-			{
-				sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
-				sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m", this.maxTransmitDistance);
-			}
-
-			text = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return text;
+
+				sb.AppendLine();
+
+				sb.AppendFormat("<b>Within Nominal Range...\n...Maximum Speedup:</b> {0:P0}\n", this.maxDataFactor);
+
+				if (ARConfiguration.FixedPowerCost)
+				{
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Outside Nominal Range...\n...Maximum Slowdown:</b> {0:P1}\n",
+						1f / this.maxPowerFactor
+					);
+
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Packet Cost:</b> {0:0.0#} {1}\n",
+						this.BaseLinkCost.PacketResourceCost,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+					sb.AppendFormat(
+						"<b>Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+				}
+				else
+				{
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Nominal Packet Cost:</b> {0:0.0#} {1}\n",
+						this.BaseLinkCost.PacketResourceCost,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+					sb.AppendFormat(
+						"<b>Nominal Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+
+					sb.AppendLine();
+
+					sb.AppendFormat(
+						"<b>Outside Nominal Range...\n...Maximum Power Drain:</b> {0:0.0#} {1}/s\n",
+						this.BaseLinkCost.PacketResourceCost / this.packetInterval * this.maxPowerFactor,
+						this.requiredResource == "ElectricCharge" ? "EC" : this.requiredResource
+					);
+				}
+
+				return sb.ToString();
+			}
 		}
 
 		/// <summary>
@@ -576,13 +712,13 @@
 			{
 				case PartStates.DEAD:
 				case PartStates.DEACTIVATED:
-					Tools.PostDebugMessage(string.Format(
+					this.LogDebug(
 						"{0}: {1} on {2} cannot transmit: {3}",
 						this.GetType().Name,
 						this.part.partInfo.title,
 						this.vessel.vesselName,
 						Enum.GetName(typeof(PartStates), this.part.State)
-					));
+					);
 					return false;
 				default:
 					break;
@@ -592,6 +728,17 @@
 		}
 
 		/// <summary>
+		/// Recalculates the transmission rates.
+		/// </summary>
+		public void RecalculateTransmissionRates()
+		{
+			if (this.relay != null)
+			{
+				this.relay.RecalculateTransmissionRates();
+			}
+		}
+
+		/// <summary>
 		/// Finds the nearest relay.
 		/// </summary>
 		public void FindNearestRelay()
@@ -607,43 +754,29 @@
 		/// returns false.
 		/// </summary>
 		/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
-		/// <param name="callback">Callback function</param>
-		public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
+		public new void TransmitData(List<ScienceData> dataQueue)
 		{
 			this.LogDebug(
 				"TransmitData(List<ScienceData> dataQueue, Callback callback) called.  dataQueue.Count={0}",
 				dataQueue.Count
 			);
 
-			this.FindNearestRelay();
-
-			this.PreTransmit_SetPacketSize();
-			this.PreTransmit_SetPacketResourceCost();
-
 			if (this.CanTransmit())
 			{
 				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
 				this.LogDebug(
 					"CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
-					dataQueue.SPrint(),
-					callback == null ? "null" : callback.ToString()
+					dataQueue.SPrint()
 				);
 
-				if (callback == null)
-				{
-					base.TransmitData(dataQueue);
-				}
-				else
-				{
-					base.TransmitData(dataQueue, callback);
-				}
+				base.TransmitData(dataQueue);
 			}
 			else
 			{
-				Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
-
-				var logger = Tools.DebugLogger.New(this);
+				this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+				var logger = PooledDebugLogger.New(this);
 
 				IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
 				ModuleScienceContainer scienceContainer;
@@ -699,31 +832,30 @@
 
 				if (dataQueue.Count > 0)
 				{
-					StringBuilder sb = Tools.GetStringBuilder();
-
-					sb.Append('[');
-					sb.Append(this.part.partInfo.title);
-					sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
-					sb.Append("Data to be discarded:\n");
-
-					ScienceData data;
-					for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
+					using (PooledStringBuilder sb = PooledStringBuilder.Get())
 					{
-						data = dataQueue[dIdx];
-						sb.AppendFormat("\t{0}\n", data.title);
+						sb.Append('[');
+						sb.Append(this.part.partInfo.title);
+						sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
+						sb.Append("Data to be discarded:\n");
+
+						ScienceData data;
+						for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
+						{
+							data = dataQueue[dIdx];
+							sb.AppendFormat("\t{0}\n", data.title);
+						}
+
+						ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+						this.LogDebug(sb.ToString());
 					}
-
-					ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
-					Tools.PostDebugMessage(sb.ToString());
-
-					Tools.PutStringBuilder(sb);
 				}
 
 				this.PostCannotTransmitError();
 			}
 
-			Tools.PostDebugMessage (
+			this.LogDebug(
 				"distance: " + this.CurrentLinkSqrDistance
 				+ " packetSize: " + this.packetSize
 				+ " packetResourceCost: " + this.packetResourceCost
@@ -731,32 +863,12 @@
 		}
 
 		/// <summary>
-		/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
-		/// returns false.
-		/// </summary>
-		/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
-		public new void TransmitData(List<ScienceData> dataQueue)
-		{
-			this.LogDebug(
-				"TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
-				dataQueue.Count
-			);
-
-			this.TransmitData(dataQueue, null);
-		}
-
-		/// <summary>
 		/// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
 		/// returns false.
 		/// </summary>
 		public new void StartTransmission()
 		{
-			this.FindNearestRelay();
-
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
-
-			Tools.PostDebugMessage (
+			this.LogDebug(
 				"distance: " + this.CurrentLinkSqrDistance
 				+ " packetSize: " + this.packetSize
 				+ " packetResourceCost: " + this.packetResourceCost
@@ -781,31 +893,31 @@
 		{
 			if (this.actionUIUpdate)
 			{
-				this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
+				this.UImaxTransmitDistance = TextTools.Format("{0:S3}m",
 					Math.Sqrt(this.MaximumLinkSqrDistance));
-				this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
+				this.UInominalLinkDistance = TextTools.Format("{0:S3}m",
 					Math.Sqrt(this.NominalLinkSqrDistance));
 				
 				if (this.CanTransmit())
 				{
 					this.UIrelayStatus = this.LinkStatus.ToString();
-					this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
+					this.UItransmitDistance = TextTools.Format("{0:S3}m",
 						Math.Sqrt(this.CurrentLinkSqrDistance));
-					this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate);
-					this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost);
+					this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
+					this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost);
 				}
 				else
 				{
 					if (this.relay.firstOccludingBody == null)
 					{
-						this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
+						this.UItransmitDistance = TextTools.Format("{0:S3}m",
 							Math.Sqrt(this.CurrentLinkSqrDistance));
 						this.UIrelayStatus = "Out of range";
 					}
 					else
 					{
 						this.UItransmitDistance = "N/A";
-						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+						this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
 					}
 					this.UIpacketSize = "N/A";
 					this.UIpacketCost = "N/A";
@@ -817,9 +929,29 @@
 				}
 				else
 				{
-					this.UIrelayTarget = this.targetRelay.ToString();
-				}
-			}
+					if (this.targetRelay != null)
+					{
+						this.UIrelayTarget = this.targetRelay.ToString();
+					}
+					else
+					{
+						this.UIrelayTarget = "A mysterious null entity";
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
+		public void RecalculateMaxRange()
+		{
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+
+			#if DEBUG
+			this.Log("Recalculated max range: sqrt({0}) * {1} = {2}",
+				this.maxPowerFactor, this.nominalTransmitDistance, this.maxTransmitDistance);
+			#endif
 		}
 
 		/// <summary>
@@ -828,32 +960,38 @@
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
 		public override string ToString()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string msg;
-
-			sb.Append(this.part.partInfo.title);
-
-			if (vessel != null)
-			{
-				sb.Append(" on ");
-				sb.Append(vessel.vesselName);
-			}
-			else if (
-				this.part != null &&
-				this.part.protoPartSnapshot != null &&
-				this.part.protoPartSnapshot != null &&
-				this.part.protoPartSnapshot.pVesselRef != null
-			)
-			{
-				sb.Append(" on ");
-				sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
-			}
-
-			msg = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return msg;
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				string msg;
+
+				if (this.part != null && this.part.partInfo != null)
+				{
+					sb.Append(this.part.partInfo.title);
+				}
+				else
+				{
+					sb.Append(this.GetType().Name);
+				}
+
+				if (vessel != null)
+				{
+					sb.Append(" on ");
+					sb.Append(vessel.vesselName);
+				}
+				else if (
+					this.part != null &&
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot != null &&
+					this.part.protoPartSnapshot.pVesselRef != null)
+				{
+					sb.Append(" on ");
+					sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+				}
+
+				msg = sb.ToString();
+
+				return msg;
+			}
 		}
 
 		// When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
@@ -889,77 +1027,37 @@
 				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).
-		private void PreTransmit_SetPacketResourceCost()
-		{
-			if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance)
-			{
-				base.packetResourceCost = this._basepacketResourceCost;
-			}
-			else
-			{
-				float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance);
-
-				base.packetResourceCost = this._basepacketResourceCost * rangeFactor;
-			}
-
-			base.packetResourceCost *= this.packetThrottle / 100f;
-		}
-
-		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
-		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
-		private void PreTransmit_SetPacketSize()
-		{
-			if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance)
-			{
-				base.packetSize = this._basepacketSize;
-			}
-			else
-			{
-				float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
-
-				base.packetSize = Mathf.Min(
-					this._basepacketSize * rangeFactor,
-					this._basepacketSize * this.maxDataFactor
-				);
-			}
-
-			base.packetSize *= this.packetThrottle / 100f;
+			this.LogDebug(this.ErrorMsg.message);
+
+			ScreenMessages.PostScreenMessage(this.ErrorMsg);
 		}
 
 		private string buildTransmitMessage()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			string msg;
-
-			sb.Append("[");
-			sb.Append(base.part.partInfo.title);
-			sb.Append("]: ");
-
-			sb.Append("Beginning transmission ");
-
-			if (this.KerbinDirect)
-			{
-				sb.Append("directly to Kerbin.");
-			}
-			else
-			{
-				sb.Append("via ");
-				sb.Append(this.relay.targetRelay);
-			}
-
-			msg = sb.ToString();
-
-			Tools.PutStringBuilder(sb);
-
-			return msg;
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				string msg;
+
+				sb.Append("[");
+				sb.Append(base.part.partInfo.title);
+				sb.Append("]: ");
+
+				sb.Append("Beginning transmission ");
+
+				if (this.KerbinDirect)
+				{
+					sb.Append("directly to Kerbin.");
+				}
+				else
+				{
+					sb.Append("via ");
+					sb.Append(this.relay.targetRelay);
+				}
+
+				msg = sb.ToString();
+
+				return msg;
+			}
 		}
 
 		#if DEBUG
@@ -967,8 +1065,8 @@
 		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
 		public void DebugInfo()
 		{
-			PreTransmit_SetPacketSize ();
-			PreTransmit_SetPacketResourceCost ();
+			if (this.relay != null)
+				this.relay.RecalculateTransmissionRates();
 
 			DebugPartModule.DumpClassObject(this);
 		}
@@ -976,27 +1074,26 @@
 		[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
 		public void PrintAllVessels()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-			
-			sb.Append("Dumping FlightGlobals.Vessels:");
-
-			Vessel vessel;
-			for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
-			{
-				vessel = FlightGlobals.Vessels[i];
-				sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
-			}
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				sb.Append("Dumping FlightGlobals.Vessels:");
+
+				Vessel vessel;
+				for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+				{
+					vessel = FlightGlobals.Vessels[i];
+					sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
+				}
 		    
-			Tools.PostDebugMessage(sb.ToString());
-
-			Tools.PutStringBuilder(sb);
+				ToadicusTools.Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 		 
-		[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
+		/*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
 		public void DumpRelayDB()
 		{
 			RelayDatabase.Instance.Dump();
-		}
+		}*/
 		#endif
 	}
 }

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("1.10.1.*")]
+[assembly: AssemblyVersion("1.11.2.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -29,6 +29,7 @@
 using KSP;
 using System;
 using ToadicusTools;
+using ToadicusTools.Text;
 
 namespace AntennaRange
 {
@@ -72,6 +73,69 @@
 		}
 
 		/// <summary>
+		/// Gets the base link resource rate in EC/MiT.
+		/// </summary>
+		/// <value>The base link resource rate in EC/MiT.</value>
+		public RelayDataCost BaseLinkCost
+		{
+			get;
+			private set;
+		}
+
+		/// <summary>
+		/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+		/// to be scored in terms of joules/byte
+		/// </summary>
+		public double DataResourceCost
+		{
+			get
+			{
+				if (this.CanTransmit())
+				{
+					return this.moduleRef.DataResourceCost;
+				}
+				else
+				{
+					return float.PositiveInfinity;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets the packet throttle.
+		/// </summary>
+		/// <value>The packet throttle in range [0..100].</value>
+		public float PacketThrottle
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.PacketThrottle;
+			}
+		}
+
+		/// <summary>
+		/// Gets the max data factor.
+		/// </summary>
+		/// <value>The max data factor.</value>
+		public float MaxDataFactor
+		{
+			get
+			{
+				if (this.moduleRef == null)
+				{
+					return float.NaN;
+				}
+
+				return this.moduleRef.MaxDataFactor;
+			}
+		}
+
+		/// <summary>
 		/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
 		/// </summary>
 		public override double nominalTransmitDistance
@@ -119,7 +183,7 @@
 			PartStates partState = (PartStates)this.protoPart.state;
 			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
 			{
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: {1} on {2} cannot transmit: {3}",
 					this.GetType().Name,
 					this.Title,
@@ -132,23 +196,33 @@
 		}
 
 		/// <summary>
+		/// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+		/// </summary>
+		public void RecalculateMaxRange()
+		{
+			if (this.moduleRef != null)
+			{
+				this.moduleRef.RecalculateMaxRange();
+			}
+		}
+
+		/// <summary>
 		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
 		/// </summary>
 		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
 		public override string ToString()
 		{
-			System.Text.StringBuilder sb = Tools.GetStringBuilder();
-
-			sb.Append(this.Title);
-
-			if (this.protoPart != null && this.protoPart.pVesselRef != null)
-			{
-				sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
-			}
-
-			Tools.PutStringBuilder(sb);
-
-			return sb.ToString();
+			using (PooledStringBuilder sb = PooledStringBuilder.Get())
+			{
+				sb.Append(this.Title);
+
+				if (this.protoPart != null && this.protoPart.pVesselRef != null)
+				{
+					sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
+				}
+
+				return sb.ToString();
+			}
 		}
 
 		/// <summary>
@@ -162,6 +236,8 @@
 			this.protoPart = pps;
 
 			this.Log("constructed ({0})", this.GetType().Name);
+
+			this.RecalculateMaxRange();
 		}
 	}
 }

file:b/RelayDataCost.cs (new)
--- /dev/null
+++ b/RelayDataCost.cs
@@ -1,1 +1,259 @@
-
+// AntennaRange
+//
+// RelayLinkCost.cs
+//
+// Copyright © 2016, 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 UnityEngine;
+
+namespace AntennaRange
+{
+	/// <summary>
+	/// A struct representing the cost of sending data through a relay.
+	/// </summary>
+	public struct RelayDataCost : IComparable, IComparable<RelayDataCost>
+	{
+		/// <summary>
+		/// A RelayDataCost object representing infinitely high cost.
+		/// </summary>
+		public static readonly RelayDataCost Infinity = new RelayDataCost(float.PositiveInfinity, 0f);
+
+		/// <param name="one">Left</param>
+		/// <param name="two">Right</param>
+		public static RelayDataCost operator+ (RelayDataCost one, RelayDataCost two)
+		{
+			RelayDataCost gcd, lcd;
+
+			if (one.PacketSize > two.PacketSize) {
+				gcd = one;
+				lcd = two;
+			}
+			else
+			{
+				gcd = two;
+				lcd = one;
+			}
+
+			if (lcd.PacketSize != 0f)
+			{
+				float mul = gcd.PacketSize / lcd.PacketSize;
+
+				lcd.PacketSize *= mul;
+				lcd.PacketResourceCost *= mul;
+			}
+
+			return new RelayDataCost(gcd.PacketResourceCost + lcd.PacketResourceCost, gcd.PacketSize + lcd.PacketSize);
+		}
+
+		/// <param name="only">RelayDataCost to be negated</param>
+		public static RelayDataCost operator- (RelayDataCost only)
+		{
+			return new RelayDataCost(-only.PacketResourceCost, only.PacketSize);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static RelayDataCost operator- (RelayDataCost left, RelayDataCost right)
+		{
+			return left + -right;
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator> (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) > 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator>= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) >= 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator== (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) == 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator<= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) <= 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator< (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) < 0);
+		}
+
+		/// <param name="left">Left.</param>
+		/// <param name="right">Right.</param>
+		public static bool operator!= (RelayDataCost left, RelayDataCost right)
+		{
+			return (left.CompareTo(right) != 0);
+		}
+
+		/// <summary>
+		/// The resource cost of a packet, in EC/packet
+		/// </summary>
+		public float PacketResourceCost;
+
+		/// <summary>
+		/// The data capacity of a packet, MiT/packet
+		/// </summary>
+		public float PacketSize;
+
+		/// <summary>
+		/// Gets the resource cost per unit data, in EC/MiT
+		/// </summary>
+		/// <value>The resource cost per unit data, in EC/MiT</value>
+		public double ResourceCostPerData
+		{
+			get
+			{
+				if (this.PacketSize == 0f || float.IsInfinity(this.PacketResourceCost))
+				{
+					return double.PositiveInfinity;
+				}
+
+				return (double)this.PacketResourceCost / (double)this.PacketSize;
+			}
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="AntennaRange.RelayDataCost"/> struct.
+		/// </summary>
+		/// <param name="cost">resource cost of a packet, in EC/packet</param>
+		/// <param name="size">data capacity of a packet, MiT/packet</param>
+		public RelayDataCost(float cost, float size)
+		{
+			this.PacketResourceCost = cost;
+			this.PacketSize = size;
+		}
+
+		/// <summary>
+		/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.RelayDataCost"/>.
+		/// </summary>
+		/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.RelayDataCost"/>.</returns>
+		public override string ToString()
+		{
+			return string.Format("{0} EC/MiT", this.ResourceCostPerData);
+		}
+
+		/// <summary>
+		/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="AntennaRange.RelayDataCost"/>.
+		/// </summary>
+		/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="AntennaRange.RelayDataCost"/>.</param>
+		/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current
+		/// <see cref="AntennaRange.RelayDataCost"/>; otherwise, <c>false</c>.</returns>
+		public override bool Equals(object obj)
+		{
+			if (obj is RelayDataCost)
+			{
+				return ((RelayDataCost)obj == this);
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Serves as a hash function for a <see cref="AntennaRange.RelayDataCost"/> object.
+		/// </summary>
+		/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table.</returns>
+		public override int GetHashCode()
+		{
+			int hash = 137;
+
+			hash = (hash * 61) + this.PacketResourceCost.GetHashCode();
+			hash = (hash * 61) + this.PacketSize.GetHashCode();
+
+			return hash;
+		}
+
+		/// <summary>
+		/// Compares this RelayDataCost to another object.  Throws NotImplementedException for objects
+		/// that are not RelayDataCost objects
+		/// </summary>
+		/// <returns>-1 if this is less than o, 0 if this equals o, 1 if this is greater than o</returns>
+		/// <param name="o">Another object</param>
+		public int CompareTo(object o)
+		{
+			if (o is RelayDataCost)
+			{
+				return this.CompareTo((RelayDataCost)o);
+			}
+
+			throw new NotImplementedException(
+				string.Format(
+					"Cannot compare {0} to foreign type {1}",
+					this.GetType().Name,
+					o.GetType().Name
+				)
+			);
+		}
+
+		/// <summary>
+		/// Compares this RelayDataCost to another object.  Throws NotImplementedException for objects
+		/// that are not RelayDataCost objects
+		/// </summary>
+		/// <returns>-1 if this is less than o, 0 if this equals o, 1 if this is greater than o</returns>
+		/// <param name="o">Another RelayDataCost</param>
+		public int CompareTo(RelayDataCost o)
+		{
+			int val;
+
+			if (this.ResourceCostPerData > o.ResourceCostPerData)
+			{
+				val = 1;
+			}
+			else if (this.ResourceCostPerData < o.ResourceCostPerData)
+			{
+				val = -1;
+			}
+			else
+			{
+				val = 0;
+			}
+
+			#if DEBUG
+			Debug.LogErrorFormat("RelayLinkCost comparing {0} to {1}, returning {2}", this, o, val);
+			#endif
+
+			return val;
+		}
+	}
+}
+
+

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -96,10 +96,10 @@
 		public void DirtyVessel(Vessel vessel)
 		{
 			#if DEBUG
-			Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
+			Logging.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
 				vessel, new System.Diagnostics.StackTrace().ToString());
 			#else
-			Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
+			Logging.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
 			#endif
 
 			this.relayDatabase.Remove(vessel.id);
@@ -109,7 +109,7 @@
 
 		public void ClearCache()
 		{
-			Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
+			Logging.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
 
 			this.relayDatabase.Clear();
 			this.bestRelayTable.Clear();
@@ -206,7 +206,7 @@
 				// or if we've just docked or undocked)...
 				if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
 				{
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: dirtying cache for vessel '{1}' ({2}).",
 						this.GetType().Name,
 						vessel.vesselName,
@@ -222,7 +222,7 @@
 		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
 		private void onSceneChange(GameScenes scene)
 		{
-			Tools.PostDebugMessage(
+			Logging.PostDebugMessage(
 				"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}.  ActiveVessel is {2}",
 				HighLogic.LoadedScene,
 				scene,
@@ -235,7 +235,7 @@
 				{
 					if (FlightGlobals.ActiveVessel != null)
 					{
-						Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
+						Logging.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
 							FlightGlobals.ActiveVessel.vesselName);
 
 						this.onVesselEvent(FlightGlobals.ActiveVessel);
@@ -275,7 +275,7 @@
 			// We're going to completely regen this table, so dump the current contents.
 			relays.Clear();
 
-			Tools.PostDebugMessage(string.Format(
+			Logging.PostDebugMessage(string.Format(
 				"{0}: Getting antenna relays from vessel {1}.",
 				"IAntennaRelay",
 				vessel.vesselName
@@ -287,7 +287,7 @@
 
 			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
 			if (vessel.loaded) {
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
 					"IAntennaRelay",
 					vessel.vesselName
@@ -327,7 +327,7 @@
 			// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
 			else
 			{
-				Tools.PostDebugMessage(string.Format(
+				Logging.PostDebugMessage(string.Format(
 					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
 					this.GetType().Name,
 					vessel.vesselName
@@ -339,7 +339,7 @@
 				{
 					pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
 
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: Searching in protopartsnapshot {1}",
 						this.GetType().Name,
 						pps
@@ -348,7 +348,7 @@
 					// ...Fetch the prefab, because it's more useful for what we're doing.
 					Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
 
-					Tools.PostDebugMessage(string.Format(
+					Logging.PostDebugMessage(string.Format(
 						"{0}: Got partPrefab {1} in protopartsnapshot {2}",
 						this.GetType().Name,
 						partPrefab,
@@ -361,7 +361,7 @@
 					{
 						module = partPrefab.Modules[modIdx];
 
-						Tools.PostDebugMessage(string.Format(
+						Logging.PostDebugMessage(string.Format(
 							"{0}: Searching in partmodule {1}",
 							this.GetType().Name,
 							module
@@ -370,7 +370,7 @@
 						// ...if the module is a relay...
 						if (module is IAntennaRelay)
 						{
-							Tools.PostDebugMessage(string.Format(
+							Logging.PostDebugMessage(string.Format(
 								"{0}: partmodule {1} is antennarelay",
 								this.GetType().Name,
 								module
@@ -395,7 +395,7 @@
 
 			this.bestRelayTable[vessel.id] = bestRelay;
 
-			Tools.PostDebugMessage(string.Format(
+			Logging.PostDebugMessage(string.Format(
 				"{0}: vessel '{1}' ({2}) has {3} transmitters.",
 				"IAntennaRelay",
 				vessel.vesselName,
@@ -436,7 +436,7 @@
 			GameEvents.onPartUndock.Remove(this.onPartEvent);
 			GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
 
-			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
+			Logging.PostDebugMessage(this.GetType().Name + " destroyed.");
 
 			KSPLog.print(string.Format(
 				"{0} destructed.  Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
@@ -450,28 +450,27 @@
 		#if DEBUG
 		public void Dump()
 		{
-			StringBuilder sb = Tools.GetStringBuilder();
-
-			sb.Append("Dumping RelayDatabase:");
-
-			var dbEnum = this.relayDatabase.GetEnumerator();
-			IList<IAntennaRelay> vesselRelays;
-			while (dbEnum.MoveNext())
-			{
-				sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
-
-				vesselRelays = dbEnum.Current.Value;
-				IAntennaRelay relay;
-				for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
-				{
-					relay = vesselRelays[rIdx];
-					sb.AppendFormat("\n\t{0}", relay.ToString());
-				}
-			}
-
-			Tools.PostDebugMessage(sb.ToString());
-
-			Tools.PutStringBuilder(sb);
+			using (ToadicusTools.Text.PooledStringBuilder sb = ToadicusTools.Text.PooledStringBuilder.Get())
+			{
+				sb.Append("Dumping RelayDatabase:");
+
+				var dbEnum = this.relayDatabase.GetEnumerator();
+				IList<IAntennaRelay> vesselRelays;
+				while (dbEnum.MoveNext())
+				{
+					sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
+
+					vesselRelays = dbEnum.Current.Value;
+					IAntennaRelay relay;
+					for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
+					{
+						relay = vesselRelays[rIdx];
+						sb.AppendFormat("\n\t{0}", relay.ToString());
+					}
+				}
+
+				Logging.PostDebugMessage(sb.ToString());
+			}
 		}
 		#endif
 	}

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -28,7 +28,7 @@
 
 using System;
 using System.Collections.Generic;
-using ToadicusTools;
+using ToadicusTools.Extensions;
 
 namespace AntennaRange
 {
@@ -144,6 +144,79 @@
 		}
 
 		/// <summary>
+		/// Returns the square of the maximum link range between two relays.
+		/// </summary>
+		/// <returns>The maximum link range between two relays.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
+		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				return relayOne.maxTransmitDistance * relayTwo.maxTransmitDistance;
+			}
+			else
+			{
+				return relayOne.maxTransmitDistance * relayOne.maxTransmitDistance;
+			}
+		}
+
+		/// <summary>
+		/// Returns the square of the maximum link range between a relay and Kerbin.
+		/// </summary>
+		/// <returns>The maximum link range between a relay and Kerbin.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="body">A CelestialBody (must be Kerbin).</param>
+		public static double MaxLinkSqrDistanceTo(this AntennaRelay relayOne, CelestialBody body)
+		{
+			if (body != AntennaRelay.Kerbin)
+			{
+				return 0d;
+			}
+
+			if (ARConfiguration.UseAdditiveRanges)
+			{
+				return relayOne.maxTransmitDistance * ARConfiguration.KerbinRelayRange;
+			}
+			else
+			{
+				return relayOne.maxTransmitDistance * relayOne.maxTransmitDistance;
+			}
+		}
+
+		/// <summary>
+		/// Determines if relayOne is in range of the specified relayTwo.
+		/// </summary>
+		/// <returns><c>true</c> if relayOne is in range of the specifie relayTwo; otherwise, <c>false</c>.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="relayTwo">Relay two.</param>
+		public static bool IsInRangeOf(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+		{
+			if (relayOne == null || relayTwo == null)
+			{
+				return false;
+			}
+
+			return relayOne.SqrDistanceTo(relayTwo) <= relayOne.MaxLinkSqrDistanceTo(relayTwo);
+		}
+
+		/// <summary>
+		/// Determines if relayOne is in range of the specified body.
+		/// </summary>
+		/// <returns><c>true</c> if relayOne is in range of the specified body; otherwise, <c>false</c>.</returns>
+		/// <param name="relayOne">Relay one.</param>
+		/// <param name="body">Body.</param>
+		public static bool IsInRangeOf(this AntennaRelay relayOne, CelestialBody body)
+		{
+			if (relayOne == null || body == null)
+			{
+				return false;
+			}
+
+			return relayOne.SqrDistanceTo(body) <= relayOne.MaxLinkSqrDistanceTo(body);
+		}
+
+		/// <summary>
 		/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
 		/// </summary>
 		/// <param name="vessel">This <see cref="Vessel"/></param>
@@ -221,7 +294,7 @@
 		/// </summary>
 		public static void Log(this AntennaRelay relay, string format, params object[] args)
 		{
-			Tools.PostLogMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+			ToadicusTools.Logging.PostLogMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
 		}
 
 		/// <summary>
@@ -229,7 +302,7 @@
 		/// </summary>
 		public static void Log(this AntennaRelay relay, string msg)
 		{
-			Tools.PostLogMessage("[{0}] {1}", relay.ToString(), msg);
+			ToadicusTools.Logging.PostLogMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 
 		/// <summary>
@@ -237,7 +310,7 @@
 		/// </summary>
 		public static void LogWarning(this AntennaRelay relay, string format, params object[] args)
 		{
-			Tools.PostWarningMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+			ToadicusTools.Logging.PostWarningMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
 		}
 
 		/// <summary>
@@ -245,7 +318,7 @@
 		/// </summary>
 		public static void LogWarning(this AntennaRelay relay, string msg)
 		{
-			Tools.PostWarningMessage("[{0}] {1}", relay.ToString(), msg);
+			ToadicusTools.Logging.PostWarningMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 
 		/// <summary>
@@ -253,7 +326,7 @@
 		/// </summary>
 		public static void LogError(this AntennaRelay relay, string format, params object[] args)
 		{
-			Tools.PostErrorMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+			ToadicusTools.Logging.PostErrorMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
 		}
 
 		/// <summary>
@@ -261,7 +334,7 @@
 		/// </summary>
 		public static void LogError(this AntennaRelay relay, string msg)
 		{
-			Tools.PostErrorMessage("[{0}] {1}", relay.ToString(), msg);
+			ToadicusTools.Logging.PostErrorMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 
 		/// <summary>
@@ -270,7 +343,7 @@
 		[System.Diagnostics.Conditional("DEBUG")]
 		public static void LogDebug(this AntennaRelay relay, string format, params object[] args)
 		{
-			Tools.PostDebugMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
+			ToadicusTools.Logging.PostDebugMessage(string.Format("[{0}] {1}", relay.ToString(), format), args);
 		}
 
 		/// <summary>
@@ -279,7 +352,7 @@
 		[System.Diagnostics.Conditional("DEBUG")]
 		public static void LogDebug(this AntennaRelay relay, string msg)
 		{
-			Tools.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
+			ToadicusTools.Logging.PostDebugMessage("[{0}] {1}", relay.ToString(), msg);
 		}
 	}