A bunch of stuff trying to use prefabs and protos instead of relying on
[AntennaRange.git] / ModuleLimitedDataTransmitter.cs
blob:a/ModuleLimitedDataTransmitter.cs -> blob:b/ModuleLimitedDataTransmitter.cs
--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,24 +1,37 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
 //
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// ModuleLimitedDataTransmitter.cs
 //
-//     Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-//     Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-//     Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014, toadicus
+// All rights reserved.
 //
-// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
-// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
 //
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
 //
-// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
-
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+//    to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+using KSP;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using KSP;
+using System.Text;
+using ToadicusTools;
 using UnityEngine;
 
 namespace AntennaRange
@@ -41,29 +54,44 @@
 	 * */
 	public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
 	{
-		protected System.Diagnostics.Stopwatch searchTimer;
-		protected long millisecondsBetweenSearches;
-
 		// Stores the packetResourceCost as defined in the .cfg file.
 		protected float _basepacketResourceCost;
 
 		// Stores the packetSize as defined in the .cfg file.
 		protected float _basepacketSize;
 
+		// Every antenna is a relay.
+		protected AntennaRelay relay;
+
 		// Keep track of vessels with transmitters for relay purposes.
 		protected List<Vessel> _relayVessels;
 
 		// Sometimes we will need to communicate errors; this is how we do it.
 		protected ScreenMessage ErrorMsg;
-
-		// Let's make the error text pretty!
-		protected UnityEngine.GUIStyle ErrorStyle;
 
 		// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
 		// and packetSize.
 		[KSPField(isPersistant = false)]
 		public float nominalRange;
 
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
+		public string UIrelayStatus;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
+		public string UIrelayTarget;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
+		public string UItransmitDistance;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
+		public string UImaxTransmitDistance;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
+		public string UIpacketSize;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
+		public string UIpacketCost;
+
 		// The multiplier on packetResourceCost that defines the maximum power output of the antenna.  When the power
 		// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
 		[KSPField(isPersistant = false)]
@@ -73,69 +101,92 @@
 		[KSPField(isPersistant = false)]
 		public float maxDataFactor;
 
-		protected CelestialBody _Kerbin;
+		[KSPField(
+			isPersistant = true,
+			guiName = "Packet Throttle",
+			guiUnits = "%",
+			guiActive = true,
+			guiActiveEditor = false
+		)]
+		[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
+		public float packetThrottle;
+
+		protected bool actionUIUpdate;
 
 		/*
 		 * Properties
 		 * */
-		// We don't have a Bard, so we'll hide Kerbin here.
-		protected CelestialBody Kerbin
-		{
-			get
-			{
-				if (this._Kerbin == null)
-				{
-					foreach (CelestialBody cb in FlightGlobals.Bodies)
-					{
-						if (cb.name == "Kerbin")
-						{
-							this._Kerbin = cb;
-							break;
-						}
-					}
-				}
-				return this._Kerbin;
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets the nearest relay.
-		/// </summary>
-		/// <value>The nearest relay</value>
-		public IAntennaRelay nearestRelay
-		{
-			get;
-			protected set;
-		}
-
-
-		// Returns the distance to the nearest relay or Kerbin, whichever is closer.
+		// Returns the parent vessel housing this antenna.
+		public new Vessel vessel
+		{
+			get
+			{
+				if (base.vessel != null)
+				{
+					return base.vessel;
+				}
+				else if (this.part != null)
+				{
+					return this.part.vessel;
+				}
+
+				else
+				{
+					return null;
+				}
+			}
+		}
+
+		public IAntennaRelay targetRelay
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return null;
+				}
+
+				return this.relay.targetRelay;
+			}
+		}
+
+		// Returns the distance to the target relay or Kerbin, whichever is closer.
 		public double transmitDistance
 		{
 			get
 			{
-				this.nearestRelay = this.FindNearestRelay();
-
-				// If there is no available relay nearby...
-				if (nearestRelay == null)
-				{
-					// .. return the distance to Kerbin
-					return this.DistanceTo(this.Kerbin);
-				}
-				else
-				{
-					/// ...otherwise, return the distance to the nearest available relay.
-					return this.DistanceTo(nearestRelay);
-				}
+				if (this.relay == null)
+				{
+					return double.PositiveInfinity;
+				}
+
+				return this.relay.transmitDistance;
+			}
+		}
+
+		public double nominalTransmitDistance
+		{
+			get
+			{
+				return this.nominalRange;
 			}
 		}
 
 		// Returns the maximum distance this module can transmit
-		public float maxTransmitDistance
-		{
-			get
-			{
-				return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
+		public double maxTransmitDistance
+		{
+			get
+			{
+				// TODO: Cache this in a way that doesn't break everything.
+				return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
+			}
+		}
+
+		public CelestialBody firstOccludingBody
+		{
+			get
+			{
+				return this.relay.firstOccludingBody;
 			}
 		}
 
@@ -203,11 +254,30 @@
 			}
 		}
 
-		// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
-		public bool relayChecked
-		{
-			get;
-			protected set;
+		public bool KerbinDirect
+		{
+			get
+			{
+				if (this.relay != null)
+				{
+					return this.relay.KerbinDirect;
+				}
+
+				return false;
+			}
+		}
+
+		public string Title
+		{
+			get
+			{
+				if (this.part != null && this.part.partInfo != null)
+				{
+					return this.part.partInfo.title;
+				}
+
+				return string.Empty;
+			}
 		}
 
 		/*
@@ -216,38 +286,16 @@
 		// Build ALL the objects.
 		public ModuleLimitedDataTransmitter () : base()
 		{
-			// Make the error posting prettier.
-			this.ErrorStyle = new UnityEngine.GUIStyle();
-			this.ErrorStyle.normal.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
-			this.ErrorStyle.active.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
-			this.ErrorStyle.hover.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
-			this.ErrorStyle.fontStyle = UnityEngine.FontStyle.Normal;
-			this.ErrorStyle.padding.top = 32;
-
-			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle);
-		}
-
-		// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
-		public override void OnStart (StartState state)
-		{
-			base.OnStart (state);
-		}
-
-		// When the module loads, fetch the Squad KSPFields from the base.  This is necessary in part because
-		// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
-		// work.
-		public override void OnLoad(ConfigNode node)
-		{
-			this.Fields.Load(node);
-			base.Fields.Load(node);
-
-			base.OnLoad (node);
+			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
+			this.packetThrottle = 100f;
+		}
+
+		public override void OnAwake()
+		{
+			base.OnAwake();
 
 			this._basepacketSize = base.packetSize;
 			this._basepacketResourceCost = base.packetResourceCost;
-
-			this.searchTimer = new System.Diagnostics.Stopwatch();
-			this.millisecondsBetweenSearches = 5000;
 
 			Tools.PostDebugMessage(string.Format(
 				"{0} loaded:\n" +
@@ -265,19 +313,53 @@
 			));
 		}
 
+		// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
+		public override void OnStart (StartState state)
+		{
+			base.OnStart (state);
+
+			if (state >= StartState.PreLaunch)
+			{
+				this.relay = new AntennaRelay(this);
+				this.relay.maxTransmitDistance = this.maxTransmitDistance;
+				this.relay.nominalTransmitDistance = this.nominalRange;
+
+				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
+
+				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
+				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
+			}
+		}
+
+		// When the module loads, fetch the Squad KSPFields from the base.  This is necessary in part because
+		// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
+		// work.
+		public override void OnLoad(ConfigNode node)
+		{
+			this.Fields.Load(node);
+			base.Fields.Load(node);
+
+			base.OnLoad (node);
+		}
+
 		// Post an error in the communication messages describing the reason transmission has failed.  Currently there
 		// is only one reason for this.
 		protected void PostCannotTransmitError()
 		{
-			string ErrorText = string.Format (
-				"Unable to transmit: out of range!  Maximum range = {0}m; Current range = {1}m.",
-				Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2),
-				Tools.MuMech_ToSI((double)this.transmitDistance, 2)
-				);
-
-			this.ErrorMsg.message = ErrorText;
-
-			ScreenMessages.PostScreenMessage(this.ErrorMsg, true);
+			string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
+
+			this.ErrorMsg.message = string.Format(
+				"<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
+				((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
+				((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
+				ErrorText
+			);
+
+			Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);
+
+			ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
 		}
 
 		// Before transmission, set packetResourceCost.  Per above, packet cost increases with the square of
@@ -285,31 +367,53 @@
 		// transmission fails (see CanTransmit).
 		protected void PreTransmit_SetPacketResourceCost()
 		{
-			if (this.transmitDistance <= this.nominalRange)
+			if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
 			{
 				base.packetResourceCost = this._basepacketResourceCost;
 			}
 			else
 			{
+				double rangeFactor = (this.transmitDistance / this.nominalRange);
+				rangeFactor *= rangeFactor;
+
 				base.packetResourceCost = this._basepacketResourceCost
-					* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
-			}
+					* (float)rangeFactor;
+
+				Tools.PostDebugMessage(
+					this,
+					"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
+					base.packetResourceCost,
+					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.
 		protected void PreTransmit_SetPacketSize()
 		{
-			if (this.transmitDistance >= this.nominalRange)
+			if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
 			{
 				base.packetSize = this._basepacketSize;
 			}
 			else
 			{
+				double rangeFactor = (this.nominalRange / this.transmitDistance);
+				rangeFactor *= rangeFactor;
+
 				base.packetSize = Math.Min(
-					this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
+					this._basepacketSize * (float)rangeFactor,
 					this._basepacketSize * this.maxDataFactor);
-			}
+
+				Tools.PostDebugMessage(
+					this,
+					"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
+					base.packetSize,
+					rangeFactor);
+			}
+
+			base.packetSize *= this.packetThrottle / 100f;
 		}
 
 		// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
@@ -324,33 +428,110 @@
 		// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
 		public new bool CanTransmit()
 		{
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Checking if {1} on {2} can transmit.",
-				this.GetType().Name,
-				base.part.name,
-				this.vessel
-			));
-
-			if (this.transmitDistance > this.maxTransmitDistance)
+			if (this.part == null || this.relay == null)
 			{
 				return false;
 			}
-			else
-			{
-				return true;
-			}
+
+			PartStates partState = this.part.State;
+			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: {1} on {2} cannot transmit: {3}",
+					this.GetType().Name,
+					this.part.partInfo.title,
+					this.vessel.vesselName,
+					Enum.GetName(typeof(PartStates), partState)
+				));
+				return false;
+			}
+			return this.relay.CanTransmit();
 		}
 
 		// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
 		// returns false.
 		public new void TransmitData(List<ScienceData> dataQueue)
 		{
+			this.PreTransmit_SetPacketSize();
+			this.PreTransmit_SetPacketResourceCost();
+
 			if (this.CanTransmit())
 			{
+				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
 				base.TransmitData(dataQueue);
 			}
 			else
 			{
+				Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+				var logger = Tools.DebugLogger.New(this);
+
+				foreach (ModuleScienceContainer	scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())
+				{
+					logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
+						scienceContainer.part.partInfo.title);
+
+					if (
+						scienceContainer.capacity != 0 &&
+						scienceContainer.GetScienceCount() >= scienceContainer.capacity
+					)
+					{
+						logger.Append("\tInsufficient capacity, skipping.\n");
+						continue;
+					}
+
+					List<ScienceData> dataStored = new List<ScienceData>();
+
+					foreach (ScienceData data in dataQueue)
+					{
+						if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
+						{
+							logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
+							continue;
+						}
+
+						logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
+						if (scienceContainer.AddData(data))
+						{
+							logger.Append("done, removing from queue.\n");
+
+							dataStored.Add(data);
+						}
+						#if DEBUG
+						else
+						{
+							logger.Append("failed.\n");
+						}
+						#endif
+					}
+
+					dataQueue.RemoveAll(i => dataStored.Contains(i));
+
+					logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
+				}
+
+				logger.Print();
+
+				if (dataQueue.Count > 0)
+				{
+					StringBuilder msg = new StringBuilder();
+
+					msg.Append('[');
+					msg.Append(this.part.partInfo.title);
+					msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
+					msg.Append("Data to be discarded:\n");
+
+					foreach (ScienceData data in dataQueue)
+					{
+						msg.AppendFormat("\n{0}\n", data.title);
+					}
+
+					ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+					Tools.PostDebugMessage(msg.ToString());
+				}
+
 				this.PostCannotTransmitError ();
 			}
 
@@ -361,103 +542,6 @@
 			);
 		}
 
-		/// <summary>
-		/// Finds the nearest relay.
-		/// </summary>
-		/// <returns>The nearest relay or null, if no relays in range.</returns>
-		public IAntennaRelay FindNearestRelay()
-		{
-			if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
-			{
-				return this.nearestRelay;
-			}
-
-			if (this.searchTimer.IsRunning)
-			{
-				this.searchTimer.Stop();
-				this.searchTimer.Reset();
-			}
-
-			this.searchTimer.Start();
-
-			// Set this relay as checked, so that we don't check it again.
-			this.relayChecked = true;
-
-			// Get a list of vessels within transmission range.
-			List<Vessel> nearbyVessels = FlightGlobals.Vessels
-				.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance)
-				.ToList();
-
-			nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Non-debris vessels in range: {1}",
-				this.GetType().Name,
-				nearbyVessels.Count
-			));
-
-			// Remove this vessel.
-			nearbyVessels.RemoveAll(v => v.id == vessel.id);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Vessels in range excluding self: {1}",
-				this.GetType().Name,
-				nearbyVessels.Count
-			));
-
-			// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range.
-			List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList();
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays.",
-				this.GetType().Name,
-				nearbyRelays.Count
-			));
-
-			// Remove all relays already checked this time.
-			nearbyRelays.RemoveAll(r => r.relayChecked);
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays not already checked.",
-				this.GetType().Name,
-				nearbyRelays.Count
-			));
-
-			// Remove all relays that cannot transmit.
-			// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin.
-			nearbyRelays.RemoveAll(r => !r.CanTransmit());
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Found {1} nearby relays not already checked that can transmit.",
-				this.GetType().Name,
-				nearbyRelays.Count
-			));
-
-			// Sort the available relays by distance.
-			nearbyRelays.Sort(new RelayComparer(this.vessel));
-
-			// Get the nearest available relay, or null if there are no available relays nearby.
-			IAntennaRelay _nearestRelay = nearbyRelays.FirstOrDefault();
-
-			// If we have a nearby relay...
-			if (_nearestRelay != null)
-			{
-				// ...but that relay is farther than Kerbin...
-				if (this.DistanceTo(_nearestRelay) > this.DistanceTo(Kerbin))
-				{
-					// ...just use Kerbin.
-					_nearestRelay = null;
-				}
-			}
-
-			// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
-			// used next time.
-			this.relayChecked = false;
-
-			// Return the nearest available relay, or null if there are no available relays nearby.
-			return _nearestRelay;
-		}
-
 		// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
 		// returns false.
 		public new void StartTransmission()
@@ -473,72 +557,119 @@
 
 			if (this.CanTransmit())
 			{
-				string message;
-
-				message = "Beginning transmission ";
-
-				if (this.nearestRelay == null)
-				{
-					message += "directly to Kerbin.";
+				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+				base.StartTransmission();
+			}
+			else
+			{
+				this.PostCannotTransmitError ();
+			}
+		}
+
+		public void Update()
+		{
+			if (this.actionUIUpdate)
+			{
+				if (this.CanTransmit())
+				{
+					this.UIrelayStatus = "Connected";
+					this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+					this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
+					this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
 				}
 				else
 				{
-					message += "via relay " + this.nearestRelay;
-				}
-
-				ScreenMessages.PostScreenMessage(message, 4f, ScreenMessageStyle.UPPER_LEFT);
-
-				base.StartTransmission();
+					if (this.relay.firstOccludingBody == null)
+					{
+						this.UIrelayStatus = "Out of range";
+					}
+					else
+					{
+						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+					}
+					this.UImaxTransmitDistance = "N/A";
+					this.UIpacketSize = "N/A";
+					this.UIpacketCost = "N/A";
+				}
+
+				if (this.KerbinDirect)
+				{
+					this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
+				}
+				else
+				{
+					this.UIrelayTarget = this.targetRelay.ToString();
+				}
+			}
+		}
+
+		public void onPartActionUICreate(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = true;
+			}
+		}
+
+		public void onPartActionUIDismiss(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = false;
+			}
+		}
+
+		public override string ToString()
+		{
+			StringBuilder msg = new StringBuilder();
+
+			msg.Append(this.part.partInfo.title);
+
+			if (vessel != null)
+			{
+				msg.Append(" on ");
+				msg.Append(vessel.vesselName);
+			}
+			else if (
+				this.part != null &&
+				this.part.protoPartSnapshot != null &&
+				this.part.protoPartSnapshot != null &&
+				this.part.protoPartSnapshot.pVesselRef != null
+			)
+			{
+				msg.Append(" on ");
+				msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+			}
+
+			return msg.ToString();
+		}
+
+		private string buildTransmitMessage()
+		{
+			StringBuilder message = new StringBuilder();
+
+			message.Append("[");
+			message.Append(base.part.partInfo.title);
+			message.Append("]: ");
+
+			message.Append("Beginning transmission ");
+
+			if (this.KerbinDirect)
+			{
+				message.Append("directly to Kerbin.");
 			}
 			else
 			{
-				this.PostCannotTransmitError ();
-			}
-		}
-
-
-		/*		
-		 * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
-		 * */
-		internal class RelayComparer : IComparer<IAntennaRelay>
-		{
-			/// <summary>
-			/// The reference Vessel (usually the active vessel).
-			/// </summary>
-			protected Vessel referenceVessel;
-
-			// We don't want no stinking public parameterless constructors.
-			private RelayComparer() {}
-
-			/// <summary>
-			/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use
-			/// in sorting relays by distance.
-			/// </summary>
-			/// <param name="reference">The reference Vessel</param>
-			public RelayComparer(Vessel reference)
-			{
-				this.referenceVessel = reference;
-			}
-
-			/// <summary>
-			/// Compare the <see cref="IAntennaRelay"/>s "one" and "two".
-			/// </summary>
-			/// <param name="one">The first IAntennaRelay in the comparison</param>
-			/// <param name="two">The second IAntennaRelay in the comparison</param>
-			public int Compare(IAntennaRelay one, IAntennaRelay two)
-			{
-				double distanceOne;
-				double distanceTwo;
-
-				distanceOne = one.vessel.DistanceTo(referenceVessel);
-				distanceTwo = two.vessel.DistanceTo(referenceVessel);
-
-				return distanceOne.CompareTo(distanceTwo);
-			}
-		}
-
+				message.Append("via ");
+				message.Append(this.relay.targetRelay);
+			}
+
+			return message.ToString();
+		}
+
+		#if DEBUG
 		// When debugging, it's nice to have a button that just tells you everything.
-		#if DEBUG
 		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
 		public void DebugInfo()
 		{
@@ -558,7 +689,9 @@
 				"DataRate: {9}\n" +
 				"DataResourceCost: {10}\n" +
 				"TransmitterScore: {11}\n" +
-				"NearestRelay: {12}",
+				"targetRelay: {12}\n" +
+				"KerbinDirect: {13}\n" +
+				"Vessel ID: {14}",
 				this.name,
 				this._basepacketSize,
 				base.packetSize,
@@ -571,9 +704,33 @@
 				this.DataRate,
 				this.DataResourceCost,
 				ScienceUtil.GetTransmitterScore(this),
-				this.FindNearestRelay()
+				this.relay.targetRelay == null ? "null" : this.relay.targetRelay.ToString(),
+				this.KerbinDirect,
+				this.vessel.id
 				);
-			ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));
+
+			Tools.PostLogMessage(msg);
+		}
+
+		[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
+		public void PrintAllVessels()
+		{
+			StringBuilder sb = new StringBuilder();
+
+			sb.Append("Dumping FlightGlobals.Vessels:");
+
+			foreach (Vessel vessel in FlightGlobals.Vessels)
+			{
+				sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
+			}
+
+			Tools.PostDebugMessage(sb.ToString());
+		}
+		 
+		[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
+		public void DumpRelayDB()
+		{
+			RelayDatabase.Instance.Dump();
 		}
 		#endif
 	}