Moved all of the vessel parsing code into the new RelayDatabase, which
Moved all of the vessel parsing code into the new RelayDatabase, which
watches some events to dirty the cache when states change. Also modules
will no longer offer to transmit if their part is dead or deactivated.

--- a/AntennaRange.cfg
+++ b/AntennaRange.cfg
@@ -48,8 +48,3 @@
 	}
 }
 
-@PART[*]:HAS[MODULE[ModuleLimitedDataTransmitter]]:Final
-{
-	isAntenna = True
-}
-

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -98,7 +98,7 @@
 		/// Determines whether this instance can transmit.
 		/// </summary>
 		/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
-		public bool CanTransmit()
+		public virtual bool CanTransmit()
 		{
 			if (this.transmitDistance > this.maxTransmitDistance)
 			{
@@ -207,14 +207,6 @@
 			return _nearestRelay;
 		}
 
-		public override string ToString()
-		{
-			return string.Format(
-				"Antenna relay on vessel {0}.",
-				vessel
-			);
-		}
-
 		/// <summary>
 		/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
 		/// </summary>

--- a/Extensions.cs
+++ b/Extensions.cs
@@ -82,60 +82,10 @@
 		/// <param name="vessel">This <see cref="Vessel"/></param>
 		public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
 		{
-			Tools.PostDebugMessage(string.Format(
-				"{0}: Getting antenna relays from vessel {1}.",
-				"IAntennaRelay",
-				vessel.name
-			));
+			return RelayDatabase.Instance[vessel].Values.ToList();
+		}
 
-			List<IAntennaRelay> Transmitters;
 
-			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
-			if (vessel.loaded) {
-				Tools.PostDebugMessage(string.Format(
-					"{0}: vessel {1} is loaded.",
-					"IAntennaRelay",
-					vessel.name
-					));
-
-				// Gets a list of PartModules implementing IAntennaRelay
-				Transmitters = vessel.Parts
-					.SelectMany (p => p.Modules.OfType<IAntennaRelay> ())
-					.ToList();
-			}
-			// If the vessel is not loaded, we need to find ProtoPartModuleSnapshots with a true IsAntenna field.
-			else
-			{
-				Tools.PostDebugMessage(string.Format(
-					"{0}: vessel {1} is not loaded.",
-					"IAntennaRelay",
-					vessel.name
-					));
-
-				Transmitters = new List<IAntennaRelay>();
-
-				// Loop through the ProtoPartModuleSnapshots in this Vessel
-				foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
-				{
-					Transmitters.AddRange(
-						PartLoader.getPartInfoByName(pps.partName)
-						.partPrefab
-						.Modules
-						.OfType<IAntennaRelay>()
-					);
-				}
-			}
-
-			Tools.PostDebugMessage(string.Format(
-				"{0}: vessel {1} has {2} transmitters.",
-				"IAntennaRelay",
-				vessel.name,
-				Transmitters.Count
-				));
-
-			// Return the list of IAntennaRelays
-			return Transmitters;
-		}
 	}
 }
 

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -18,6 +18,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using KSP;
 using UnityEngine;
 
@@ -56,14 +57,23 @@
 		// 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 = "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)]
@@ -72,6 +82,8 @@
 		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 		[KSPField(isPersistant = false)]
 		public float maxDataFactor;
+
+		protected bool actionUIUpdate;
 
 		/*
 		 * Properties
@@ -182,15 +194,7 @@
 		// 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);
+			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
 		}
 
 		// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
@@ -202,6 +206,11 @@
 			{
 				this.relay = new AntennaRelay(vessel);
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
+
+				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
+
+				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
+				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
 			}
 		}
 
@@ -244,9 +253,18 @@
 				Tools.MuMech_ToSI((double)this.transmitDistance, 2)
 				);
 
-			this.ErrorMsg.message = ErrorText;
-
-			ScreenMessages.PostScreenMessage(this.ErrorMsg, true);
+			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
@@ -293,6 +311,18 @@
 		// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
 		public new bool CanTransmit()
 		{
+			PartStates partState = this.part.State;
+			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: {1} on {2} cannot transmit: {3}",
+					this.GetType().Name,
+					this.part.partInfo.title,
+					this.vessel.name,
+					Enum.GetName(typeof(PartStates), partState)
+				));
+				return false;
+			}
 			return this.relay.CanTransmit();
 		}
 
@@ -331,20 +361,25 @@
 
 			if (this.CanTransmit())
 			{
-				string message;
-
-				message = "Beginning transmission ";
+				StringBuilder message = new StringBuilder();
+
+				message.Append("[");
+				message.Append(base.part.partInfo.title);
+				message.Append("] ");
+
+				message.Append("Beginning transmission ");
 
 				if (this.relay.nearestRelay == null)
 				{
-					message += "directly to Kerbin.";
+					message.Append("directly to Kerbin.");
 				}
 				else
 				{
-					message += "via relay " + this.relay.nearestRelay;
-				}
-
-				ScreenMessages.PostScreenMessage(message, 4f, ScreenMessageStyle.UPPER_LEFT);
+					message.Append("via ");
+					message.Append(this.relay.nearestRelay);
+				}
+
+				ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
 				base.StartTransmission();
 			}
@@ -352,6 +387,41 @@
 			{
 				this.PostCannotTransmitError ();
 			}
+		}
+
+		public void Update()
+		{
+			if (this.actionUIUpdate)
+			{
+				this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+				this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A";
+				this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "N/A";
+			}
+		}
+
+		public void onPartActionUICreate(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = true;
+			}
+		}
+
+		public void onPartActionUIDismiss(Part eventPart)
+		{
+			if (eventPart == base.part)
+			{
+				this.actionUIUpdate = false;
+			}
+		}
+
+		public override string ToString()
+		{
+			return string.Format(
+				"{0} on {1}.",
+				this.part.partInfo.title,
+				vessel.name
+			);
 		}
 
 		// When debugging, it's nice to have a button that just tells you everything.

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -26,11 +26,11 @@
 	 * */
 	public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
 	{
-		// Stores the proto module.
-		protected ProtoPartModuleSnapshot protoModule;
+		// Stores the relay prefab
+		protected IAntennaRelay relayPrefab;
 
-		// Stores the proto part, which seems silly because all we need is the name.
-		protected Part partPrefab;
+		// Stores the prototype part so we can make sure we haven't exploded or so.
+		protected ProtoPartSnapshot protoPart;
 
 		/// <summary>
 		/// The maximum distance at which this transmitter can operate.
@@ -40,7 +40,7 @@
 		{
 			get
 			{
-				return this.partPrefab.Modules.OfType<ModuleLimitedDataTransmitter>().First().maxTransmitDistance;
+				return relayPrefab.maxTransmitDistance;
 			}
 		}
 
@@ -51,23 +51,42 @@
 		/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
 		public override bool relayChecked
 		{
+			get;
+			protected set;
+		}
+
+		public string Title
+		{
 			get
 			{
-				bool result;
-				Boolean.TryParse(this.protoModule.moduleValues.GetValue("relayChecked"), out result);
-				return result;
+				return this.protoPart.partInfo.title;
 			}
-			protected set
+		}
+
+		public override bool CanTransmit()
+		{
+			PartStates partState = (PartStates)this.protoPart.state;
+			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
 			{
-				if (this.protoModule.moduleValues.HasValue("relayChecked"))
-				{
-					this.protoModule.moduleValues.SetValue("relayChecked", value.ToString ());
-				}
-				else
-				{
-					this.protoModule.moduleValues.AddValue("relayChecked", value);
-				}
+				Tools.PostDebugMessage(string.Format(
+					"{0}: {1} on {2} cannot transmit: {3}",
+					this.GetType().Name,
+					this.Title,
+					this.vessel.name,
+					Enum.GetName(typeof(PartStates), partState)
+				));
+				return false;
 			}
+			return base.CanTransmit();
+		}
+
+		public override string ToString()
+		{
+			return string.Format(
+				"{0} on {1}.",
+				this.Title,
+				vessel.name
+			);
 		}
 
 		/// <summary>
@@ -75,10 +94,11 @@
 		/// </summary>
 		/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
 		/// <param name="vessel">The parent Vessel</param>
-		public ProtoAntennaRelay(ProtoPartModuleSnapshot ppms, ProtoPartSnapshot pps, Vessel vessel) : base(vessel)
+		public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(pps.pVesselRef.vesselRef)
 		{
-			this.protoModule = ppms;
-			this.partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
+			this.relayPrefab = prefabRelay;
+			this.protoPart = pps;
+			this.vessel = pps.pVesselRef.vesselRef;
 		}
 	}
 }

file:b/RelayDatabase.cs (new)
--- /dev/null
+++ b/RelayDatabase.cs
@@ -1,1 +1,236 @@
-
+// AntennaRange © 2014 toadicus
+//
+// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
+// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
+
+using KSP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace AntennaRange
+{
+	public class RelayDatabase
+	{
+		/*
+		 * Static members
+		 * */
+		protected static RelayDatabase _instance;
+
+		public static RelayDatabase Instance
+		{
+			get
+			{	
+				if (_instance == null)
+				{
+					_instance = new RelayDatabase();
+				}
+
+				return _instance;
+			}
+		}
+
+		/*
+		 * Instance members
+		 * */
+
+		/*
+		 * Fields
+		 * */
+		protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
+
+		protected Dictionary<Guid, int> vesselPartCountDB;
+
+		/*
+		 * Properties
+		 * */
+		public Dictionary<int, IAntennaRelay> this [Vessel vessel]
+		{
+			get
+			{
+				if (!this.ContainsKey(vessel.id))
+				{
+					this.AddVessel(vessel);
+				}
+				if (this.vesselPartCountDB[vessel.id] != vessel.Parts.Count)
+				{
+					this.UpdateVessel(vessel);
+				}
+
+				return relayDatabase[vessel.id];
+			}
+		}
+
+		/* 
+		 * Methods
+		 * */
+		public bool AddVessel(Vessel vessel)
+		{
+			if (relayDatabase.ContainsKey(vessel.id))
+			{
+				Debug.LogWarning(string.Format(
+					"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
+					this.GetType().Name,
+					vessel.name,
+					vessel.id
+				));
+
+				return false;
+			}
+			else
+			{
+				this.UpdateVessel(vessel);
+
+				return true;
+			}
+		}
+
+		public void UpdateVessel(Vessel vessel)
+		{
+			if (!relayDatabase.ContainsKey(vessel.id))
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: Update called vessel '{1}' (id: {2}) not in database: vessel will be added.",
+					this.GetType().Name,
+					vessel.name,
+					vessel.id
+				));
+			}
+			else
+			{
+				// Remove stuff?
+			}
+
+			this.relayDatabase[vessel.id] = this.getVesselRelays(vessel);
+			this.vesselPartCountDB[vessel.id] = vessel.Parts.Count;
+		}
+
+		public bool ContainsKey(Guid key)
+		{
+			return (this.relayDatabase.ContainsKey(key) & this.vesselPartCountDB.ContainsKey(key));
+		}
+
+		public bool ContainsKey(Vessel vessel)
+		{
+			return this.ContainsKey(vessel.id);
+		}
+
+		public void onVesselWasModified(Vessel vessel)
+		{
+			if (this.ContainsKey(vessel))
+			{
+				if (this.vesselPartCountDB[vessel.id] != vessel.Parts.Count)
+				{
+					Tools.PostDebugMessage(string.Format(
+						"{0}: vessel '{1}' (id: {2}) was modified.",
+						this.GetType().Name,
+						vessel.name,
+						vessel.id
+					));
+
+					this.vesselPartCountDB[vessel.id] = -1;
+				}
+			}
+		}
+
+		protected Dictionary<int, IAntennaRelay> getVesselRelays(Vessel vessel)
+		{
+			Dictionary<int, IAntennaRelay> relays;
+
+			if (this.ContainsKey(vessel))
+			{
+				relays = this.relayDatabase[vessel.id];
+				relays.Clear();
+			}
+			else
+			{
+				relays = new Dictionary<int, IAntennaRelay>();
+			}
+
+			Tools.PostDebugMessage(string.Format(
+				"{0}: Getting antenna relays from vessel {1}.",
+				"IAntennaRelay",
+				vessel.name
+			));
+
+			// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
+			if (vessel.loaded) {
+				Tools.PostDebugMessage(string.Format(
+					"{0}: vessel {1} is loaded, searching for modules in loaded parts.",
+					"IAntennaRelay",
+					vessel.name
+				));
+
+				// Gets a list of PartModules implementing IAntennaRelay
+				foreach (Part part in vessel.Parts)
+				{
+					foreach (PartModule module in part.Modules)
+					{
+						if (module is IAntennaRelay)
+						{
+							relays.Add(part.GetHashCode(), module as IAntennaRelay);
+							break;
+						}
+					}
+				}
+			}
+			// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
+			else
+			{
+				Tools.PostDebugMessage(string.Format(
+					"{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
+					"IAntennaRelay",
+					vessel.name
+				));
+
+				// Loop through the ProtoPartModuleSnapshots in this Vessel
+				foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
+				{
+					ProtoAntennaRelay protoRelay;
+					Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
+
+					foreach (PartModule module in partPrefab.Modules)
+					{
+						if (module is IAntennaRelay)
+						{
+							protoRelay =
+								new ProtoAntennaRelay(module as IAntennaRelay, pps);
+							relays.Add(pps.GetHashCode(), protoRelay);
+							break;
+						}
+					}
+				}
+			}
+
+			Tools.PostDebugMessage(string.Format(
+				"{0}: vessel '{1}' has {2} transmitters.",
+				"IAntennaRelay",
+				vessel.name,
+				relays.Count
+			));
+
+			// Return the list of IAntennaRelays
+			return relays;
+		}
+
+		protected RelayDatabase()
+		{
+			relayDatabase =	new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+			vesselPartCountDB = new Dictionary<Guid, int>();
+
+			GameEvents.onVesselWasModified.Add(this.onVesselWasModified);
+			GameEvents.onVesselChange.Add(this.onVesselWasModified);
+		}
+
+		~RelayDatabase()
+		{
+			GameEvents.onVesselWasModified.Remove(this.onVesselWasModified);
+			GameEvents.onVesselChange.Remove(this.onVesselWasModified);
+
+			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
+		}
+	}
+}
+
+