ModuleLimitedDataTransmitter: Changed ToString to not include the vessel name if the vessel is null.
ModuleLimitedDataTransmitter: Changed ToString to not include the vessel name if the vessel is null.

file:a/ARTools.cs -> file:b/ARTools.cs
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using System; using System;
   
namespace AntennaRange namespace AntennaRange
{ {
public static class Tools public static class Tools
{ {
private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT); private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT);
  // Function that posts messages to the screen and the log when DEBUG is defined.
[System.Diagnostics.Conditional("DEBUG")] [System.Diagnostics.Conditional("DEBUG")]
public static void PostDebugMessage(string Msg) public static void PostDebugMessage(string Msg)
{ {
if (HighLogic.LoadedScene > GameScenes.SPACECENTER) if (HighLogic.LoadedScene > GameScenes.SPACECENTER)
{ {
debugmsg.message = Msg; debugmsg.message = Msg;
ScreenMessages.PostScreenMessage(debugmsg, true); ScreenMessages.PostScreenMessage(debugmsg, true);
} }
   
KSPLog.print(Msg); KSPLog.print(Msg);
} }
   
/* /*
* MuMech_ToSI is a part of the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. * MuMech_ToSI is a part of the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
* */ * */
public static string MuMech_ToSI(double d, int digits = 3, int MinMagnitude = 0, int MaxMagnitude = int.MaxValue) public static string MuMech_ToSI(double d, int digits = 3, int MinMagnitude = 0, int MaxMagnitude = int.MaxValue)
{ {
float exponent = (float)Math.Log10(Math.Abs(d)); float exponent = (float)Math.Log10(Math.Abs(d));
exponent = UnityEngine.Mathf.Clamp(exponent, (float)MinMagnitude, (float)MaxMagnitude); exponent = UnityEngine.Mathf.Clamp(exponent, (float)MinMagnitude, (float)MaxMagnitude);
   
if (exponent >= 0) if (exponent >= 0)
{ {
switch ((int)Math.Floor(exponent)) switch ((int)Math.Floor(exponent))
{ {
case 0: case 0:
case 1: case 1:
case 2: case 2:
return d.ToString("F" + digits); return d.ToString("F" + digits);
case 3: case 3:
case 4: case 4:
case 5: case 5:
return (d / 1e3).ToString("F" + digits) + "k"; return (d / 1e3).ToString("F" + digits) + "k";
case 6: case 6:
case 7: case 7:
case 8: case 8:
return (d / 1e6).ToString("F" + digits) + "M"; return (d / 1e6).ToString("F" + digits) + "M";
case 9: case 9:
case 10: case 10:
case 11: case 11:
return (d / 1e9).ToString("F" + digits) + "G"; return (d / 1e9).ToString("F" + digits) + "G";
case 12: case 12:
case 13: case 13:
case 14: case 14:
return (d / 1e12).ToString("F" + digits) + "T"; return (d / 1e12).ToString("F" + digits) + "T";
case 15: case 15:
case 16: case 16:
case 17: case 17:
return (d / 1e15).ToString("F" + digits) + "P"; return (d / 1e15).ToString("F" + digits) + "P";
case 18: case 18:
case 19: case 19:
case 20: case 20:
return (d / 1e18).ToString("F" + digits) + "E"; return (d / 1e18).ToString("F" + digits) + "E";
case 21: case 21:
case 22: case 22:
case 23: case 23:
return (d / 1e21).ToString("F" + digits) + "Z"; return (d / 1e21).ToString("F" + digits) + "Z";
default: default:
return (d / 1e24).ToString("F" + digits) + "Y"; return (d / 1e24).ToString("F" + digits) + "Y";
} }
} }
else if (exponent < 0) else if (exponent < 0)
{ {
switch ((int)Math.Floor(exponent)) switch ((int)Math.Floor(exponent))
{ {
case -1: case -1:
case -2: case -2:
case -3: case -3:
return (d * 1e3).ToString("F" + digits) + "m"; return (d * 1e3).ToString("F" + digits) + "m";
case -4: case -4:
case -5: case -5:
case -6: case -6:
return (d * 1e6).ToString("F" + digits) + "μ"; return (d * 1e6).ToString("F" + digits) + "μ";
case -7: case -7:
case -8: case -8:
case -9: case -9:
return (d * 1e9).ToString("F" + digits) + "n"; return (d * 1e9).ToString("F" + digits) + "n";
case -10: case -10:
case -11: case -11:
case -12: case -12:
return (d * 1e12).ToString("F" + digits) + "p"; return (d * 1e12).ToString("F" + digits) + "p";
case -13: case -13:
case -14: case -14:
case -15: case -15:
return (d * 1e15).ToString("F" + digits) + "f"; return (d * 1e15).ToString("F" + digits) + "f";
case -16: case -16:
case -17: case -17:
case -18: case -18:
return (d * 1e18).ToString("F" + digits) + "a"; return (d * 1e18).ToString("F" + digits) + "a";
case -19: case -19:
case -20: case -20:
case -21: case -21:
return (d * 1e21).ToString("F" + digits) + "z"; return (d * 1e21).ToString("F" + digits) + "z";
default: default:
return (d * 1e24).ToString("F" + digits) + "y"; return (d * 1e24).ToString("F" + digits) + "y";
} }
} }
else else
{ {
return "0"; return "0";
} }
} }
} }
} }
   
   
// //
// AntennaRange © 2013 toadicus // AntennaRange © 2013 toadicus
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // 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/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
// 3.0 Uported License. // 3.0 Uported License.
// //
// Specifications: // Specifications:
// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost // nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
// and packetSize. // and packetSize.
// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the // maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. // power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna. // maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
// //
   
@PART[longAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 1500000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
   
@PART[mediumDishAntenna]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 30000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
   
@PART[commDish]  
{  
@MODULE[ModuleDataTransmitter]  
{  
@name = ModuleLimitedDataTransmitter  
nominalRange = 80000000000  
maxPowerFactor = 8  
maxDataFactor = 4  
}  
}  
   
  @PART[longAntenna]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 1500000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
  }
   
  @PART[mediumDishAntenna]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 30000000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
  }
   
  @PART[commDish]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 80000000000
  maxPowerFactor = 8
  maxDataFactor = 4
  }
  }
   
file:a/AntennaRange.cs (deleted)
/*  
* AntennaRange © 2013 toadicus  
*  
* AntennaRange provides incentive and requirements for the use of the various antenna parts.  
* Nominally, the breakdown is as follows:  
*  
* Communotron 16 - Suitable up to Kerbalsynchronous Orbit  
* Comms DTS-M1 - Suitable throughout the Kerbin subsystem  
* Communotron 88-88 - Suitable throughout the Kerbol system.  
*  
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a  
* copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/  
*  
* This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike  
* 3.0 Uported License.  
*  
* This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.  
*  
*/  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using KSP;  
using UnityEngine;  
 
namespace AntennaRange  
{  
/*  
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-  
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with  
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.  
*  
* In general, the scaling functions assume the following relation:  
*  
* D² α P/R,  
*  
* where D is the total transmission distance, P is the transmission power, and R is the data rate.  
*  
* */  
 
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay  
{  
[KSPField(isPersistant = true)]  
protected bool IsAntenna = true;  
 
// 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;  
 
// 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)]  
public float maxPowerFactor;  
 
// The multipler on packetSize that defines the maximum data bandwidth of the antenna.  
[KSPField(isPersistant = false)]  
public float maxDataFactor;  
 
[KSPField(isPersistant = true)]  
protected float ARmaxTransmitDistance;  
 
/*  
* Properties  
* */  
// Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes.  
public new Vessel vessel  
{  
get  
{  
return base.vessel;  
}  
}  
 
public double transmitDistance  
{  
get  
{  
return this.relay.transmitDistance;  
}  
}  
 
// Returns the maximum distance this module can transmit  
public float maxTransmitDistance  
{  
get  
{  
return this.ARmaxTransmitDistance;  
}  
}  
 
/*  
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except  
* in that they both return floats. Here's some quick justification:  
*  
* The stock implementation of GetTransmitterScore (which I cannot override) is:  
* Score = (1 + DataResourceCost) / DataRate  
*  
* The stock DataRate and DataResourceCost are:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost / packetSize  
*  
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it  
* could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,  
* that metric is not a very reasonable one.  
*  
* Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case  
* would look like:  
* DataRate = packetSize / packetInterval  
* DataResourceCost = packetResourceCost  
*  
* The former case, which I've chosen to implement below, is:  
* DataRate = packetSize  
* DataResourceCost = packetResourceCost  
*  
* So... hopefully that doesn't screw with anything else.  
* */  
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in  
// terms of joules/byte  
public new float DataRate  
{  
get  
{  
this.PreTransmit_SetPacketSize();  
return this.packetSize;  
}  
}  
 
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas  
// to be scored in terms of joules/byte  
public new float DataResourceCost  
{  
get  
{  
this.PreTransmit_SetPacketResourceCost();  
 
if (this.CanTransmit())  
{  
return this.packetResourceCost;  
}  
else  
{  
return float.PositiveInfinity;  
}  
}  
}  
 
public bool relayChecked  
{  
get  
{  
return this.relay.relayChecked;  
}  
}  
 
/*  
* Methods  
* */  
// 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.Bold;  
this.ErrorStyle.padding.top = 32;  
 
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle);  
}  
 
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.  
public override void OnStart (StartState state)  
{  
base.OnStart (state);  
 
if (state >= StartState.PreLaunch)  
{  
this.relay = new AntennaRelay(vessel);  
this.relay.maxTransmitDistance = this.maxTransmitDistance;  
}  
 
// Pre-set the transmit cost and packet size when loading.  
this.PreTransmit_SetPacketResourceCost();  
this.PreTransmit_SetPacketSize();  
}  
 
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because  
// 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);  
 
this.ARmaxTransmitDistance = Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;  
 
base.OnLoad (node);  
 
this._basepacketSize = base.packetSize;  
this._basepacketResourceCost = base.packetResourceCost;  
 
Tools.PostDebugMessage(string.Format(  
"{0} loaded:\n" +  
"packetSize: {1}\n" +  
"packetResourceCost: {2}\n" +  
"nominalRange: {3}\n" +  
"maxPowerFactor: {4}\n" +  
"maxDataFactor: {5}\n",  
this.name,  
base.packetSize,  
this._basepacketResourceCost,  
this.nominalRange,  
this.maxPowerFactor,  
this.maxDataFactor  
));  
}  
 
// 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.ARmaxTransmitDistance, 2),  
Tools.MuMech_ToSI((double)this.transmitDistance, 2)  
);  
 
this.ErrorMsg.message = ErrorText;  
 
ScreenMessages.PostScreenMessage(this.ErrorMsg, true);  
}  
 
// 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).  
protected void PreTransmit_SetPacketResourceCost()  
{  
if (this.transmitDistance <= this.nominalRange)  
{  
base.packetResourceCost = this._basepacketResourceCost;  
}  
else  
{  
base.packetResourceCost = this._basepacketResourceCost  
* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);  
}  
}  
 
// 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)  
{  
base.packetSize = this._basepacketSize;  
}  
else  
{  
base.packetSize = Math.Min(  
this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),  
this._basepacketSize * this.maxDataFactor);  
}  
}  
 
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.  
public override string GetInfo()  
{  
string text = base.GetInfo();  
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";  
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.ARmaxTransmitDistance, 2) + "m\n";  
return text;  
}  
 
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.  
public new bool CanTransmit()  
{  
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)  
{  
if (this.CanTransmit())  
{  
base.TransmitData(dataQueue);  
}  
else  
{  
this.PostCannotTransmitError ();  
}  
 
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize  
+ " packetResourceCost: " + this.packetResourceCost  
);  
}  
 
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit  
// returns false.  
public new void StartTransmission()  
{  
PreTransmit_SetPacketSize ();  
PreTransmit_SetPacketResourceCost ();  
 
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize  
+ " packetResourceCost: " + this.packetResourceCost  
);  
if (this.CanTransmit())  
{  
base.StartTransmission();  
}  
else  
{  
this.PostCannotTransmitError ();  
}  
}  
 
// 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()  
{  
PreTransmit_SetPacketSize ();  
PreTransmit_SetPacketResourceCost ();  
 
string msg = string.Format(  
"'{0}'\n" +  
"_basepacketSize: {1}\n" +  
"packetSize: {2}\n" +  
"_basepacketResourceCost: {3}\n" +  
"packetResourceCost: {4}\n" +  
"maxTransmitDistance: {5}\n" +  
"transmitDistance: {6}\n" +  
"nominalRange: {7}\n" +  
"CanTransmit: {8}\n" +  
"DataRate: {9}\n" +  
"DataResourceCost: {10}\n" +  
"TransmitterScore: {11}",  
this.name,  
this._basepacketSize,  
base.packetSize,  
this._basepacketResourceCost,  
base.packetResourceCost,  
this.ARmaxTransmitDistance,  
this.transmitDistance,  
this.nominalRange,  
this.CanTransmit(),  
this.DataRate,  
this.DataResourceCost,  
ScienceUtil.GetTransmitterScore(this)  
);  
ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));  
}  
#endif  
}  
}  
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
   
namespace AntennaRange namespace AntennaRange
{ {
public class AntennaRelay : IAntennaRelay public class AntennaRelay
{ {
  // We don't have a Bard, so we'll hide Kerbin here.
protected CelestialBody Kerbin; protected CelestialBody Kerbin;
   
  protected System.Diagnostics.Stopwatch searchTimer;
  protected long millisecondsBetweenSearches;
   
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
  /// <value>The parent Vessel.</value>
public Vessel vessel public Vessel vessel
{ {
get; get;
protected set; protected set;
} }
   
// Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes. /// <summary>
  /// Gets or sets the nearest relay.
  /// </summary>
  /// <value>The nearest relay</value>
  public IAntennaRelay nearestRelay
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// Gets the transmit distance.
  /// </summary>
  /// <value>The transmit distance.</value>
public double transmitDistance public double transmitDistance
{ {
get get
{ {
IAntennaRelay nearestRelay = this.FindNearestRelay(); this.nearestRelay = this.FindNearestRelay();
   
  // If there is no available relay nearby...
if (nearestRelay == null) if (nearestRelay == null)
{ {
  // .. return the distance to Kerbin
return this.DistanceTo(this.Kerbin); return this.DistanceTo(this.Kerbin);
} }
else else
{ {
  /// ...otherwise, return the distance to the nearest available relay.
return this.DistanceTo(nearestRelay); return this.DistanceTo(nearestRelay);
} }
} }
} }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
public virtual float maxTransmitDistance public virtual float maxTransmitDistance
{ {
get; get;
set; set;
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
/// the current relay attempt. /// the current relay attempt.
/// </summary> /// </summary>
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value> /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
public virtual bool relayChecked public virtual bool relayChecked
{ {
get; get;
protected set; protected set;
} }
   
public bool CanTransmit() /// <summary>
  /// Determines whether this instance can transmit.
  /// </summary>
  /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
  public virtual bool CanTransmit()
{ {
if (this.transmitDistance > this.maxTransmitDistance) if (this.transmitDistance > this.maxTransmitDistance)
{ {
return false; return false;
} }
else else
{ {
return true; return true;
} }
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
/// <returns>The nearest relay.</returns> /// <returns>The nearest relay or null, if no relays in range.</returns>
public IAntennaRelay FindNearestRelay() 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; this.relayChecked = true;
   
  // Get a list of vessels within transmission range.
List<Vessel> nearbyVessels = FlightGlobals.Vessels List<Vessel> nearbyVessels = FlightGlobals.Vessels
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance) .Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance)
.ToList(); .ToList();
   
Tools.PostDebugMessage(string.Format( nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris);
"{0}: Vessels in range: {1}", nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Flag);
   
  Tools.PostDebugMessage(string.Format(
  "{0}: Non-debris, non-flag vessels in range: {1}",
this.GetType().Name, this.GetType().Name,
nearbyVessels.Count nearbyVessels.Count
)); ));
   
  // Remove this vessel.
nearbyVessels.RemoveAll(v => v.id == vessel.id); nearbyVessels.RemoveAll(v => v.id == vessel.id);
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Vessels in range excluding self: {1}", "{0}: Vessels in range excluding self: {1}",
this.GetType().Name, this.GetType().Name,
nearbyVessels.Count nearbyVessels.Count
)); ));
   
  // Get a flattened list of all IAntennaRelay modules and protomodules in transmission range.
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Found {1} nearby relays.", "{0}: Found {1} nearby relays.",
this.GetType().Name, this.GetType().Name,
nearbyRelays.Count nearbyRelays.Count
)); ));
   
  // Remove all relays already checked this time.
nearbyRelays.RemoveAll(r => r.relayChecked); nearbyRelays.RemoveAll(r => r.relayChecked);
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Found {1} nearby relays not already checked.", "{0}: Found {1} nearby relays not already checked.",
this.GetType().Name, this.GetType().Name,
nearbyRelays.Count 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()); nearbyRelays.RemoveAll(r => !r.CanTransmit());
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Found {1} nearby relays not already checked that can transmit.", "{0}: Found {1} nearby relays not already checked that can transmit.",
this.GetType().Name, this.GetType().Name,
nearbyRelays.Count nearbyRelays.Count
)); ));
   
  // Sort the available relays by distance.
nearbyRelays.Sort(new RelayComparer(this.vessel)); nearbyRelays.Sort(new RelayComparer(this.vessel));
   
IAntennaRelay nearestRelay = nearbyRelays.FirstOrDefault(); // 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; this.relayChecked = false;
   
return nearestRelay; // Return the nearest available relay, or null if there are no available relays nearby.
  return _nearestRelay;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
public AntennaRelay(Vessel v) public AntennaRelay(Vessel v)
{ {
this.vessel = v; this.vessel = v;
   
  this.searchTimer = new System.Diagnostics.Stopwatch();
  this.millisecondsBetweenSearches = 5000;
   
  // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
  // we hope it is safe enough.
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
} }
   
  /*
  * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
  * */
internal class RelayComparer : IComparer<IAntennaRelay> internal class RelayComparer : IComparer<IAntennaRelay>
{ {
  /// <summary>
  /// The reference Vessel (usually the active vessel).
  /// </summary>
protected Vessel referenceVessel; protected Vessel referenceVessel;
   
  // We don't want no stinking public parameterless constructors.
private RelayComparer() {} 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) public RelayComparer(Vessel reference)
{ {
this.referenceVessel = 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) public int Compare(IAntennaRelay one, IAntennaRelay two)
{ {
double distanceOne; double distanceOne;
double distanceTwo; double distanceTwo;
   
distanceOne = one.vessel.DistanceTo(referenceVessel); distanceOne = one.vessel.DistanceTo(referenceVessel);
distanceTwo = two.vessel.DistanceTo(referenceVessel); distanceTwo = two.vessel.DistanceTo(referenceVessel);
   
return distanceOne.CompareTo(distanceTwo); return distanceOne.CompareTo(distanceTwo);
} }
} }
} }
} }
   
   
file:b/ChangeLog (new)
  2014-01-14 toadicus <>
 
  * ModuleLimitedDataTransmitter.cs: Added a ":" to the
  transmission communications for consistency with stock
  behavior.
 
 
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
   
namespace AntennaRange namespace AntennaRange
{ {
  /*
  * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
  * */
public static class Extensions public static class Extensions
{ {
  /// <summary>
  /// Returns the distance between this Vessel and another Vessel.
  /// </summary>
  /// <param name="vesselOne">This <see cref="Vessel"/><see ></param>
  /// <param name="vesselTwo">Another <see cref="Vessel"/></param>
public static double DistanceTo(this Vessel vesselOne, Vessel vesselTwo) public static double DistanceTo(this Vessel vesselOne, Vessel vesselTwo)
{ {
return (vesselOne.GetWorldPos3D() - vesselTwo.GetWorldPos3D()).magnitude; return (vesselOne.GetWorldPos3D() - vesselTwo.GetWorldPos3D()).magnitude;
} }
   
  /// <summary>
  /// Returns the distance between this Vessel and a CelestialBody
  /// </summary>
  /// <param name="vessel">This Vessel</param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
public static double DistanceTo(this Vessel vessel, CelestialBody body) public static double DistanceTo(this Vessel vessel, CelestialBody body)
{ {
return (vessel.GetWorldPos3D() - body.position).magnitude; return (vessel.GetWorldPos3D() - body.position).magnitude;
} }
   
public static double DistanceTo(this IAntennaRelay relay, Vessel Vessel) /// <summary>
  /// Returns the distance between this IAntennaRelay and a Vessel
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="Vessel">A <see cref="Vessel"/></param>
  public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
{ {
return relay.vessel.DistanceTo(Vessel); return relay.vessel.DistanceTo(Vessel);
} }
   
public static double DistanceTo(this IAntennaRelay relay, CelestialBody body) /// <summary>
  /// Returns the distance between this IAntennaRelay and a CelestialBody
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
  public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.DistanceTo(body); return relay.vessel.DistanceTo(body);
} }
   
public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo) /// <summary>
  /// Returns the distance between this IAntennaRelay and another IAntennaRelay
  /// </summary>
  /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
  /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
  public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.DistanceTo(relayTwo.vessel); return relayOne.DistanceTo(relayTwo.vessel);
} }
   
  /// <summary>
  /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel) public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
{ {
Tools.PostDebugMessage(string.Format( return RelayDatabase.Instance[vessel].Values.ToList();
"{0}: Getting antenna relays from vessel {1}.",  
"IAntennaRelay",  
vessel.name  
));  
   
List<IAntennaRelay> Transmitters;  
   
if (vessel.loaded) {  
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} is loaded.",  
"IAntennaRelay",  
vessel.name  
));  
   
Transmitters = vessel.Parts  
.SelectMany (p => p.Modules.OfType<IAntennaRelay> ())  
.ToList();  
} else {  
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} is not loaded.",  
"IAntennaRelay",  
vessel.name  
));  
   
Transmitters = new List<IAntennaRelay>();  
   
foreach (ProtoPartModuleSnapshot ms in vessel.protoVessel.protoPartSnapshots.SelectMany(ps => ps.modules))  
{  
if (ms.IsAntenna())  
{  
Transmitters.Add(new ProtoAntennaRelay(ms, vessel));  
}  
}  
}  
   
Tools.PostDebugMessage(string.Format(  
"{0}: vessel {1} has {2} transmitters.",  
"IAntennaRelay",  
vessel.name,  
Transmitters.Count  
));  
   
return Transmitters;  
} }
   
public static bool IsAntenna (this PartModule module)  
{  
return module.Fields.GetValue<bool> ("IsAntenna");  
}  
   
public static bool IsAntenna(this ProtoPartModuleSnapshot protomodule)  
{  
bool result;  
   
return Boolean.TryParse (protomodule.moduleValues.GetValue ("IsAntenna") ?? "False", out result)  
? result : false;  
}  
} }
} }
   
   
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using KSP; using KSP;
using System; using System;
   
namespace AntennaRange namespace AntennaRange
{ {
  /*
  * Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
  * */
public interface IAntennaRelay public interface IAntennaRelay
{ {
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
  /// <value>The parent Vessel.</value>
Vessel vessel { get; } Vessel vessel { get; }
   
  /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  /// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>
  double transmitDistance { get; }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
float maxTransmitDistance { get; } float maxTransmitDistance { get; }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
  /// the current relay attempt.
  /// </summary>
  /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
bool relayChecked { get; } bool relayChecked { get; }
   
  /// <summary>
  /// Determines whether this instance can transmit.
  /// </summary>
  /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
bool CanTransmit(); bool CanTransmit();
} }
} }
   
   
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
 
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using KSP;
  using UnityEngine;
 
  namespace AntennaRange
  {
  /*
  * ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-
  * finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with
  * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
  *
  * In general, the scaling functions assume the following relation:
  *
  * D² α P/R,
  *
  * where D is the total transmission distance, P is the transmission power, and R is the data rate.
  *
  * */
 
  /*
  * Fields
  * */
  public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
  {
  // 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;
 
  // 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)]
  public float maxPowerFactor;
 
  // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  [KSPField(isPersistant = false)]
  public float maxDataFactor;
 
  protected bool actionUIUpdate;
 
  /*
  * Properties
  * */
  // Returns the parent vessel housing this antenna.
  public new Vessel vessel
  {
  get
  {
  return base.vessel;
  }
  }
 
  // Returns the distance to the nearest relay or Kerbin, whichever is closer.
  public double transmitDistance
  {
  get
  {
  return this.relay.transmitDistance;
  }
  }
 
  // Returns the maximum distance this module can transmit
  public float maxTransmitDistance
  {
  get
  {
  return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
  }
  }
 
  /*
  * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
  * in that they both return floats. Here's some quick justification:
  *
  * The stock implementation of GetTransmitterScore (which I cannot override) is:
  * Score = (1 + DataResourceCost) / DataRate
  *
  * The stock DataRate and DataResourceCost are:
  * DataRate = packetSize / packetInterval
  * DataResourceCost = packetResourceCost / packetSize
  *
  * So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it
  * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,
  * that metric is not a very reasonable one.
  *
  * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
  * would look like:
  * DataRate = packetSize / packetInterval
  * DataResourceCost = packetResourceCost
  *
  * The former case, which I've chosen to implement below, is:
  * DataRate = packetSize
  * DataResourceCost = packetResourceCost
  *
  * So... hopefully that doesn't screw with anything else.
  * */
  // Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
  // terms of joules/byte
  public new float DataRate
  {
  get
  {
  this.PreTransmit_SetPacketSize();
 
  if (this.CanTransmit())
  {
  return this.packetSize;
  }
  else
  {
  return float.Epsilon;
  }
  }
  }
 
  // Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
  // to be scored in terms of joules/byte
  public new float DataResourceCost
  {
  get
  {
  this.PreTransmit_SetPacketResourceCost();
 
  if (this.CanTransmit())
  {
  return this.packetResourceCost;
  }
  else
  {
  return float.PositiveInfinity;
  }
  }
  }
 
  // Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
  public bool relayChecked
  {
  get
  {
  return this.relay.relayChecked;
  }
  }
 
  /*
  * Methods
  * */
  // Build ALL the objects.
  public ModuleLimitedDataTransmitter () : base()
  {
  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.
  public override void OnStart (StartState state)
  {
  base.OnStart (state);
 
  if (state >= StartState.PreLaunch)
  {
  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);
  }
  }
 
  // 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._basepacketSize = base.packetSize;
  this._basepacketResourceCost = base.packetResourceCost;
 
  Tools.PostDebugMessage(string.Format(
  "{0} loaded:\n" +
  "packetSize: {1}\n" +
  "packetResourceCost: {2}\n" +
  "nominalRange: {3}\n" +
  "maxPowerFactor: {4}\n" +
  "maxDataFactor: {5}\n",
  this.name,
  base.packetSize,
  this._basepacketResourceCost,
  this.nominalRange,
  this.maxPowerFactor,
  this.maxDataFactor
  ));
  }
 
  // 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 = 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
  // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
  // transmission fails (see CanTransmit).
  protected void PreTransmit_SetPacketResourceCost()
  {
  if (this.transmitDistance <= this.nominalRange)
  {
  base.packetResourceCost = this._basepacketResourceCost;
  }
  else
  {
  base.packetResourceCost = this._basepacketResourceCost
  * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
  }
  }
 
  // 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)
  {
  base.packetSize = this._basepacketSize;
  }
  else
  {
  base.packetSize = Math.Min(
  this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
  this._basepacketSize * this.maxDataFactor);
  }
  }
 
  // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
  public override string GetInfo()
  {
  string text = base.GetInfo();
  text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
  text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
  return text;
  }
 
  // 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();
  }
 
  // Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  // returns false.
  public new void TransmitData(List<ScienceData> dataQueue)
  {
  if (this.CanTransmit())
  {
  base.TransmitData(dataQueue);
  }
  else
  {
  this.PostCannotTransmitError ();
  }
 
  Tools.PostDebugMessage (
  "distance: " + this.transmitDistance
  + " packetSize: " + this.packetSize
  + " packetResourceCost: " + this.packetResourceCost
  );
  }
 
  // Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
  // returns false.
  public new void StartTransmission()
  {
  PreTransmit_SetPacketSize ();
  PreTransmit_SetPacketResourceCost ();
 
  Tools.PostDebugMessage (
  "distance: " + this.transmitDistance
  + " packetSize: " + this.packetSize
  + " packetResourceCost: " + this.packetResourceCost
  );
 
  if (this.CanTransmit())
  {
  StringBuilder message = new StringBuilder();
 
  message.Append("[");
  message.Append(base.part.partInfo.title);
  message.Append("]: ");
 
  message.Append("Beginning transmission ");
 
  if (this.relay.nearestRelay == null)
  {
  message.Append("directly to Kerbin.");
  }
  else
  {
  message.Append("via ");
  message.Append(this.relay.nearestRelay);
  }
 
  ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
  base.StartTransmission();
  }
  else
  {
  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()
  {
  StringBuilder msg = new StringBuilder();
 
  msg.Append(this.part.partInfo.title);
 
  if (vessel != null)
  {
  msg.Append(" on ");
  msg.Append(vessel.name);
  }
 
  return msg.ToString();
  }
 
  // 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()
  {
  PreTransmit_SetPacketSize ();
  PreTransmit_SetPacketResourceCost ();
 
  string msg = string.Format(
  "'{0}'\n" +
  "_basepacketSize: {1}\n" +
  "packetSize: {2}\n" +
  "_basepacketResourceCost: {3}\n" +
  "packetResourceCost: {4}\n" +
  "maxTransmitDistance: {5}\n" +
  "transmitDistance: {6}\n" +
  "nominalRange: {7}\n" +
  "CanTransmit: {8}\n" +
  "DataRate: {9}\n" +
  "DataResourceCost: {10}\n" +
  "TransmitterScore: {11}\n" +
  "NearestRelay: {12}",
  this.name,
  this._basepacketSize,
  base.packetSize,
  this._basepacketResourceCost,
  base.packetResourceCost,
  this.maxTransmitDistance,
  this.transmitDistance,
  this.nominalRange,
  this.CanTransmit(),
  this.DataRate,
  this.DataResourceCost,
  ScienceUtil.GetTransmitterScore(this),
  this.relay.FindNearestRelay()
  );
  ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));
  }
  #endif
  }
  }
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
  using System.Reflection;
  using System.Runtime.CompilerServices;
 
  // Information about this assembly is defined by the following attributes.
  // Change them to the values specific to your project.
  [assembly: AssemblyTitle("AntennaRange")]
  [assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
  [assembly: AssemblyCopyright("toadicus")]
  // 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("0.6.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)]
  //[assembly: AssemblyKeyFile("")]
 
 
  2014-01-14 toadicus <>
 
  * AssemblyInfo.cs: New AssemblyInfo file for reason.
 
 
  // AntennaRange © 2014 toadicus
  //
  // AntennaRange provides incentive and requirements for the use of the various antenna parts.
  // Nominally, the breakdown is as follows:
  //
  // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
  // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
  // Communotron 88-88 - Suitable throughout the Kerbol system.
  //
  // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
  // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
  //
  // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
  // 3.0 Uported License.
  //
  // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using System; using System;
  using System.Linq;
   
namespace AntennaRange namespace AntennaRange
{ {
public class ProtoAntennaRelay : AntennaRelay /*
  * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
  * This is used for finding relays in unloaded Vessels.
  * */
  public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
{ {
protected ProtoPartModuleSnapshot snapshot; // Stores the relay prefab
  protected IAntennaRelay relayPrefab;
   
  // Stores the prototype part so we can make sure we haven't exploded or so.
  protected ProtoPartSnapshot protoPart;
   
/// <summary> /// <summary>
/// The maximum distance at which this transmitter can operate. /// The maximum distance at which this transmitter can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> /// <value>The max transmit distance.</value>
public override float maxTransmitDistance public override float maxTransmitDistance
{ {
get get
{ {
double result; return relayPrefab.maxTransmitDistance;
Double.TryParse(snapshot.moduleValues.GetValue ("ARmaxTransmitDistance") ?? "0", out result);  
return (float)result;  
} }
} }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
  /// the current relay attempt.
  /// </summary>
  /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
public override bool relayChecked public override bool relayChecked
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// Gets the underlying part's title.
  /// </summary>
  /// <value>The title.</value>
  public string title
{ {
get get
{ {
bool result; return this.protoPart.partInfo.title;
Boolean.TryParse(this.snapshot.moduleValues.GetValue("relayChecked"), out result);  
return result;  
}  
protected set  
{  
this.snapshot.moduleValues.SetValue("relayChecked", value.ToString());  
} }
} }
   
public ProtoAntennaRelay(ProtoPartModuleSnapshot ms, Vessel vessel) : base(vessel) public override bool CanTransmit()
{ {
this.snapshot = ms; PartStates partState = (PartStates)this.protoPart.state;
  if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
  {
  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,
  this.protoPart.pVesselRef.vesselName
  );
  }
   
  /// <summary>
  /// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
  /// </summary>
  /// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
  /// <param name="vessel">The parent Vessel</param>
  public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(pps.pVesselRef.vesselRef)
  {
  this.relayPrefab = prefabRelay;
  this.protoPart = pps;
  this.vessel = pps.pVesselRef.vesselRef;
} }
} }
} }
   
   
file:b/RelayDatabase.cs (new)
  // 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
  * */
  // Singleton storage
  protected static RelayDatabase _instance;
  // Gets the singleton
  public static RelayDatabase Instance
  {
  get
  {
  if (_instance == null)
  {
  _instance = new RelayDatabase();
  }
 
  return _instance;
  }
  }
 
  /*
  * Instance members
  * */
 
  /*
  * Fields
  * */
  // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
  protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
 
  // Vessel.id-keyed hash table of part counts, used for caching
  protected Dictionary<Guid, int> vesselPartCountTable;
 
  /*
  * Properties
  * */
  // Gets the Part-hashed table of relays in a given vessel
  public Dictionary<int, IAntennaRelay> this [Vessel vessel]
  {
  get
  {
  // If we don't have an entry for this vessel...
  if (!this.ContainsKey(vessel.id))
  {
  // ...Generate an entry for this vessel.
  this.AddVessel(vessel);
  }
  // If our part count disagrees with the vessel's part count...
  if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
  {
  // ...Update the our vessel in the cache
  this.UpdateVessel(vessel);
  }
 
  // Return the Part-hashed table of relays for this vessel
  return relayDatabase[vessel.id];
  }
  }
 
  /*
  * Methods
  * */
  // Adds a vessel to the database
  // The return for this function isn't used yet, but seems useful for potential future API-uses
  public bool AddVessel(Vessel vessel)
  {
  // If this vessel is already here...
  if (relayDatabase.ContainsKey(vessel.id))
  {
  // ...post an error
  Debug.LogWarning(string.Format(
  "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
  this.GetType().Name,
  vessel.name,
  vessel.id
  ));
 
  // ...and refuse to add
  return false;
  }
  // otherwise, add the vessel to our tables...
  else
  {
  // Build an empty table...
  this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
 
  // Update the empty index
  this.UpdateVessel(vessel);
 
  // Return success
  return true;
  }
  }
 
  // Update the vessel's entry in the table
  public void UpdateVessel(Vessel vessel)
  {
  // Squak if the database doesn't have the vessel
  if (!relayDatabase.ContainsKey(vessel.id))
  {
  throw new InvalidOperationException(string.Format(
  "{0}: Update called vessel '{1}' (id: {2}) not in database: vessel will be added.",
  this.GetType().Name,
  vessel.name,
  vessel.id
  ));
  }
 
  Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
 
  // Actually build and assign the table
  this.getVesselRelays(vessel, ref vesselTable);
  // Set the part count
  this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
  }
 
  // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
  public bool ContainsKey(Guid key)
  {
  return (this.relayDatabase.ContainsKey(key) & this.vesselPartCountTable.ContainsKey(key));
  }
 
  // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
  public bool ContainsKey(Vessel vessel)
  {
  return this.ContainsKey(vessel.id);
  }
 
  // Runs when a vessel is modified (or when we switch to one, to catch docking events)
  public void onVesselWasModified(Vessel vessel)
  {
  // If we have this vessel in our cache...
  if (this.ContainsKey(vessel))
  {
  // If our part counts disagree (such as if a part has been added or broken off,
  // or if we've just docked or undocked)...
  if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
  {
  Tools.PostDebugMessage(string.Format(
  "{0}: dirtying cache for vessel '{1}' (id: {2}).",
  this.GetType().Name,
  vessel.name,
  vessel.id
  ));
 
  // Dirty the cache (real vessels will never have negative part counts)
  this.vesselPartCountTable[vessel.id] = -1;
  }
  }
  }
 
  // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
  public void onSceneChange(GameScenes scene)
  {
  // If the active vessel is a real thing...
  if (FlightGlobals.ActiveVessel != null)
  {
  // ... dirty its cache
  this.onVesselWasModified(FlightGlobals.ActiveVessel);
  }
  }
 
  // Produce a Part-hashed table of relays for the given vessel
  protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
  {
  // We're going to completely regen this table, so dump the current contents.
  relays.Clear();
 
  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
  ));
 
  // Loop through the Parts in the Vessel...
  foreach (Part part in vessel.Parts)
  {
  // ...loop through the PartModules in the Part...
  foreach (PartModule module in part.Modules)
  {
  // ...if the module is a relay...
  if (module is IAntennaRelay)
  {
  // ...add the module to the table
  relays.Add(part.GetHashCode(), module as IAntennaRelay);
  // ...neglect relay objects after the first in each part.
  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 the Vessel...
  foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
  {
  // ...Fetch the prefab, because it's more useful for what we're doing.
  Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
 
  // ...loop through the PartModules in the prefab...
  foreach (PartModule module in partPrefab.Modules)
  {
  // ...if the module is a relay...
  if (module is IAntennaRelay)
  {
  // ...build a new ProtoAntennaRelay and add it to the table
  relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
  // ...neglect relay objects after the first in each part.
  break;
  }
  }
  }
  }
 
  Tools.PostDebugMessage(string.Format(
  "{0}: vessel '{1}' has {2} transmitters.",
  "IAntennaRelay",
  vessel.name,
  relays.Count
  ));
  }
 
  // Construct the singleton
  protected RelayDatabase()
  {
  // Initialize the databases
  relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
  vesselPartCountTable = new Dictionary<Guid, int>();
 
  // Subscribe to some events
  GameEvents.onVesselWasModified.Add(this.onVesselWasModified);
  GameEvents.onVesselChange.Add(this.onVesselWasModified);
  GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
  }
 
  ~RelayDatabase()
  {
  // Unsubscribe from the events
  GameEvents.onVesselWasModified.Remove(this.onVesselWasModified);
  GameEvents.onVesselChange.Remove(this.onVesselWasModified);
  GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
 
  Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
  }
  }
  }