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
// AntennaRange © 2014 toadicus // AntennaRange
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // ModuleLimitedDataTransmitter.cs
// Nominally, the breakdown is as follows:  
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Copyright © 2014, toadicus
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // All rights reserved.
// 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 // Redistribution and use in source and binary forms, with or without modification,
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // are permitted provided that the following conditions are met:
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // 1. Redistributions of source code must retain the above copyright notice,
// 3.0 Uported License. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using KSP; using System.Text;
  using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range- /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files. /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
* ///
* In general, the scaling functions assume the following relation: /// <para>In general, the scaling functions assume the following relation:</para>
* ///
* D² α P/R, /// <para> D² α P/R,</para>
* ///
* where D is the total transmission distance, P is the transmission power, and R is the data rate. /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
* /// </summary>
* */  
   
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{ {
protected System.Diagnostics.Stopwatch searchTimer;  
protected long millisecondsBetweenSearches;  
   
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
protected float _basepacketResourceCost; private float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
protected float _basepacketSize; private float _basepacketSize;
   
// Keep track of vessels with transmitters for relay purposes. // Every antenna is a relay.
protected List<Vessel> _relayVessels; private AntennaRelay relay;
   
// Sometimes we will need to communicate errors; this is how we do it. // Sometimes we will need to communicate errors; this is how we do it.
protected ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// Let's make the error text pretty! /// <summary>
protected UnityEngine.GUIStyle ErrorStyle; /// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
  /// and packetSize.
// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost /// </summary>
// and packetSize.  
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float nominalRange; public double nominalRange;
   
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// <summary>
// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// Relay status string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
  public string UIrelayStatus;
   
  /// <summary>
  /// Relay target string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
  public string UIrelayTarget;
   
  /// <summary>
  /// Transmit distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
  public string UItransmitDistance;
   
  /// <summary>
  /// Maximum distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
  public string UImaxTransmitDistance;
   
  /// <summary>
  /// Packet size string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
  public string UIpacketSize;
   
  /// <summary>
  /// Packet cost string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
  public string UIpacketCost;
   
  /// <summary>
  /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
  /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
// The multipler on packetSize that defines the maximum data bandwidth of the antenna. /// <summary>
  /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
protected CelestialBody _Kerbin; /// <summary>
  /// The packet throttle.
  /// </summary>
  [KSPField(
  isPersistant = true,
  guiName = "Packet Throttle",
  guiUnits = "%",
  guiActive = true,
  guiActiveEditor = false
  )]
  [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
  public float packetThrottle;
   
  private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
// We don't have a Bard, so we'll hide Kerbin here. /// <summary>
protected CelestialBody Kerbin /// Gets the parent Vessel.
{ /// </summary>
get public new Vessel vessel
{ {
if (this._Kerbin == null) get
{ {
foreach (CelestialBody cb in FlightGlobals.Bodies) if (base.vessel != null)
{ {
if (cb.name == "Kerbin") return base.vessel;
{ }
this._Kerbin = cb; else if (this.part != null)
break; {
} return this.part.vessel;
} }
}  
return this._Kerbin; else
} {
} return null;
  }
/// <summary> }
/// Gets or sets the nearest relay. }
/// </summary>  
/// <value>The nearest relay</value> /// <summary>
public IAntennaRelay nearestRelay /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
{ /// </summary>
get; public IAntennaRelay targetRelay
protected set; {
} get
  {
  if (this.relay == null)
// Returns the distance to the nearest relay or Kerbin, whichever is closer. {
  return null;
  }
   
  return this.relay.targetRelay;
  }
  }
   
  /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
public double transmitDistance public double transmitDistance
{ {
get get
{ {
this.nearestRelay = this.FindNearestRelay(); if (this.relay == null)
  {
// If there is no available relay nearby... return double.PositiveInfinity;
if (nearestRelay == null) }
{  
// .. return the distance to Kerbin return this.relay.transmitDistance;
return this.DistanceTo(this.Kerbin); }
} }
else  
{ /// <summary>
/// ...otherwise, return the distance to the nearest available relay. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
return this.DistanceTo(nearestRelay); /// </summary>
} public double nominalTransmitDistance
} {
} get
  {
// Returns the maximum distance this module can transmit return this.nominalRange;
public float maxTransmitDistance }
{ }
get  
{ /// <summary>
return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange; /// The maximum distance at which this relay can operate.
  /// </summary>
  public double maxTransmitDistance
  {
  get
  {
  // TODO: Cache this in a way that doesn't break everything.
  return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
  }
  }
   
  /// <summary>
  /// The first CelestialBody blocking line of sight to a
  /// </summary>
  public CelestialBody firstOccludingBody
  {
  get
  {
  return this.relay.firstOccludingBody;
} }
} }
   
/* /*
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except * 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: * in that they both return floats. Here's some quick justification:
* *
* The stock implementation of GetTransmitterScore (which I cannot override) is: * The stock implementation of GetTransmitterScore (which I cannot override) is:
* Score = (1 + DataResourceCost) / DataRate * Score = (1 + DataResourceCost) / DataRate
* *
* The stock DataRate and DataResourceCost are: * The stock DataRate and DataResourceCost are:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost / packetSize * DataResourceCost = packetResourceCost / packetSize
* *
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it * 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, * 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. * 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 * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
* would look like: * would look like:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* The former case, which I've chosen to implement below, is: * The former case, which I've chosen to implement below, is:
* DataRate = packetSize * DataRate = packetSize
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* So... hopefully that doesn't screw with anything else. * So... hopefully that doesn't screw with anything else.
* */ * */
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in /// <summary>
// terms of joules/byte /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
  /// terms of joules/byte
  /// </summary>
public new float DataRate public new float DataRate
{ {
get get
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetSize; return this.packetSize;
} }
else else
{ {
return float.Epsilon; return float.Epsilon;
} }
} }
} }
   
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas /// <summary>
// to be scored in terms of joules/byte /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
public new float DataResourceCost /// to be scored in terms of joules/byte
  /// </summary>
  public new double DataResourceCost
{ {
get get
{ {
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetResourceCost; return this.packetResourceCost;
} }
else else
{ {
return float.PositiveInfinity; return float.PositiveInfinity;
} }
} }
} }
   
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay. /// <summary>
public bool relayChecked /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
{ /// directly with Kerbin.
get; /// </summary>
protected set; public bool KerbinDirect
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.KerbinDirect;
  }
   
  return false;
  }
  }
   
  /// <summary>
  /// Gets the Part title.
  /// </summary>
  public string Title
  {
  get
  {
  if (this.part != null && this.part.partInfo != null)
  {
  return this.part.partInfo.title;
  }
   
  return string.Empty;
  }
} }
   
/* /*
* Methods * Methods
* */ * */
// Build ALL the objects. // Build ALL the objects.
public ModuleLimitedDataTransmitter () : base() public ModuleLimitedDataTransmitter () : base()
{ {
// Make the error posting prettier. this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
this.ErrorStyle = new UnityEngine.GUIStyle(); this.packetThrottle = 100f;
this.ErrorStyle.normal.textColor = (UnityEngine.Color)XKCDColors.OrangeRed; }
this.ErrorStyle.active.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;  
this.ErrorStyle.hover.textColor = (UnityEngine.Color)XKCDColors.OrangeRed; /// <summary>
this.ErrorStyle.fontStyle = UnityEngine.FontStyle.Normal; /// PartModule OnAwake override; runs at Unity Awake.
this.ErrorStyle.padding.top = 32; /// </summary>
  public override void OnAwake()
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle); {
} base.OnAwake();
   
// 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._basepacketSize = base.packetSize; this._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this._basepacketResourceCost = base.packetResourceCost;
   
this.searchTimer = new System.Diagnostics.Stopwatch();  
this.millisecondsBetweenSearches = 5000;  
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0} loaded:\n" + "{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalRange: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this.name,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
this.nominalRange, this.nominalRange,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
// Post an error in the communication messages describing the reason transmission has failed. Currently there /// <summary>
// is only one reason for this. /// PartModule OnStart override; runs at Unity Start.
protected void PostCannotTransmitError() /// </summary>
{ /// <param name="state">State.</param>
string ErrorText = string.Format ( public override void OnStart (StartState state)
"Unable to transmit: out of range! Maximum range = {0}m; Current range = {1}m.", {
Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2), base.OnStart (state);
Tools.MuMech_ToSI((double)this.transmitDistance, 2)  
); if (state >= StartState.PreLaunch)
  {
this.ErrorMsg.message = ErrorText; this.relay = new AntennaRelay(this);
  this.relay.maxTransmitDistance = this.maxTransmitDistance;
ScreenMessages.PostScreenMessage(this.ErrorMsg, true); this.relay.nominalTransmitDistance = this.nominalRange;
}  
  this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of  
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
// transmission fails (see CanTransmit). GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
protected void PreTransmit_SetPacketResourceCost() }
{ }
if (this.transmitDistance <= this.nominalRange)  
{ /// <summary>
base.packetResourceCost = this._basepacketResourceCost; /// 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
else /// work.
{ /// </summary>
base.packetResourceCost = this._basepacketResourceCost /// <param name="node"><see cref="ConfigNode"/> with data for this module.</param>
* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2); public override void OnLoad(ConfigNode node)
} {
} this.Fields.Load(node);
  base.Fields.Load(node);
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of  
// distance. packetSize maxes out at _basepacketSize * maxDataFactor. base.OnLoad (node);
protected void PreTransmit_SetPacketSize() }
{  
if (this.transmitDistance >= this.nominalRange) /// <summary>
{ /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
base.packetSize = this._basepacketSize; /// </summary>
}  
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() public override string GetInfo()
{ {
string text = base.GetInfo(); string text = base.GetInfo();
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n"; text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n"; text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
return text; return text;
} }
   
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
public new bool CanTransmit() public new bool CanTransmit()
{ {
Tools.PostDebugMessage(string.Format( if (this.part == null || this.relay == null)
"{0}: Checking if {1} on {2} can transmit.",  
this.GetType().Name,  
base.part.name,  
this.vessel  
));  
   
if (this.transmitDistance > this.maxTransmitDistance)  
{ {
return false; return false;
} }
   
  switch (this.part.State)
  {
  case PartStates.DEAD:
  case 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), this.part.State)
  ));
  return false;
  default:
  break;
  }
   
  return this.relay.CanTransmit();
  }
   
  /// <summary>
  /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  /// <param name="callback">Callback function</param>
  public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
  {
  this.PreTransmit_SetPacketSize();
  this.PreTransmit_SetPacketResourceCost();
   
  if (this.CanTransmit())
  {
  ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  base.TransmitData(dataQueue, callback);
  }
else else
{ {
return true; Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
}  
} var logger = Tools.DebugLogger.New(this);
   
// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())
// returns false. {
public new void TransmitData(List<ScienceData> dataQueue) logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
{ scienceContainer.part.partInfo.title);
if (this.CanTransmit())  
{ if (
base.TransmitData(dataQueue); scienceContainer.capacity != 0 &&
} scienceContainer.GetScienceCount() >= scienceContainer.capacity
else )
{ {
this.PostCannotTransmitError (); 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("\t{0}\n", data.title);
  }
   
  ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  Tools.PostDebugMessage(msg.ToString());
  }
   
  this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
/// </summary> /// returns false.
/// <returns>The nearest relay or null, if no relays in range.</returns> /// </summary>
public IAntennaRelay FindNearestRelay() /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
{ public new void TransmitData(List<ScienceData> dataQueue)
if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches) {
{ this.TransmitData(dataQueue, null);
return this.nearestRelay; }
}  
  /// <summary>
if (this.searchTimer.IsRunning) /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
{ /// returns false.
this.searchTimer.Stop(); /// </summary>
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() public new void StartTransmission()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
string message; ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message = "Beginning transmission "; base.StartTransmission();
  }
if (this.nearestRelay == null) else
{ {
message += "directly to Kerbin."; this.PostCannotTransmitError ();
  }
  }
   
  /// <summary>
  /// MonoBehaviour Update
  /// </summary>
  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 else
{ {
message += "via relay " + this.nearestRelay; if (this.relay.firstOccludingBody == null)
} {
  this.UIrelayStatus = "Out of range";
ScreenMessages.PostScreenMessage(message, 4f, ScreenMessageStyle.UPPER_LEFT); }
  else
base.StartTransmission(); {
  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();
  }
  }
  }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
  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();
  }
   
  // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
  private void onPartActionUICreate(Part eventPart)
  {
  if (eventPart == base.part)
  {
  this.actionUIUpdate = true;
  }
  }
   
  // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
  private void onPartActionUIDismiss(Part eventPart)
  {
  if (eventPart == base.part)
  {
  this.actionUIUpdate = false;
  }
  }
   
  // Post an error in the communication messages describing the reason transmission has failed. Currently there
  // is only one reason for this.
  private void PostCannotTransmitError()
  {
  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
  // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
  // transmission fails (see CanTransmit).
  private void PreTransmit_SetPacketResourceCost()
  {
  if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
  {
  base.packetResourceCost = this._basepacketResourceCost;
} }
else else
{ {
this.PostCannotTransmitError (); float rangeFactor = (float)(this.transmitDistance / this.nominalRange);
} rangeFactor *= rangeFactor;
}  
  base.packetResourceCost = this._basepacketResourceCost
  * rangeFactor;
/*  
* Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance. Tools.PostDebugMessage(
* */ this,
internal class RelayComparer : IComparer<IAntennaRelay> "Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
{ base.packetResourceCost,
/// <summary> rangeFactor);
/// The reference Vessel (usually the active vessel). }
/// </summary>  
protected Vessel referenceVessel; base.packetResourceCost *= this.packetThrottle / 100f;
  }
// We don't want no stinking public parameterless constructors.  
private RelayComparer() {} // Before transmission, set packetSize. Per above, packet size increases with the inverse square of
  // distance. packetSize maxes out at _basepacketSize * maxDataFactor.
/// <summary> private void PreTransmit_SetPacketSize()
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use {
/// in sorting relays by distance. if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
/// </summary> {
/// <param name="reference">The reference Vessel</param> base.packetSize = this._basepacketSize;
public RelayComparer(Vessel reference) }
{ else
this.referenceVessel = reference; {
} float rangeFactor = (float)(this.nominalRange / this.transmitDistance);
  rangeFactor *= rangeFactor;
/// <summary>  
/// Compare the <see cref="IAntennaRelay"/>s "one" and "two". base.packetSize = Mathf.Min(
/// </summary> this._basepacketSize * rangeFactor,
/// <param name="one">The first IAntennaRelay in the comparison</param> this._basepacketSize * this.maxDataFactor);
/// <param name="two">The second IAntennaRelay in the comparison</param>  
public int Compare(IAntennaRelay one, IAntennaRelay two) Tools.PostDebugMessage(
{ this,
double distanceOne; "Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
double distanceTwo; base.packetSize,
  rangeFactor);
distanceOne = one.vessel.DistanceTo(referenceVessel); }
distanceTwo = two.vessel.DistanceTo(referenceVessel);  
  base.packetSize *= this.packetThrottle / 100f;
return distanceOne.CompareTo(distanceTwo); }
}  
} 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
  {
  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. // 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)] [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
public void DebugInfo() public void DebugInfo()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
string msg = string.Format( string msg = string.Format(
"'{0}'\n" + "'{0}'\n" +
"_basepacketSize: {1}\n" + "_basepacketSize: {1}\n" +
"packetSize: {2}\n" + "packetSize: {2}\n" +
"_basepacketResourceCost: {3}\n" + "_basepacketResourceCost: {3}\n" +
"packetResourceCost: {4}\n" + "packetResourceCost: {4}\n" +
"maxTransmitDistance: {5}\n" + "maxTransmitDistance: {5}\n" +
"transmitDistance: {6}\n" + "transmitDistance: {6}\n" +
"nominalRange: {7}\n" + "nominalRange: {7}\n" +
"CanTransmit: {8}\n" + "CanTransmit: {8}\n" +
"DataRate: {9}\n" + "DataRate: {9}\n" +
"DataResourceCost: {10}\n" + "DataResourceCost: {10}\n" +
"TransmitterScore: {11}\n" + "TransmitterScore: {11}\n" +
"NearestRelay: {12}", "targetRelay: {12}\n" +
  "KerbinDirect: {13}\n" +
  "Vessel ID: {14}",
this.name, this.name,
this._basepacketSize, this._basepacketSize,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
base.packetResourceCost, base.packetResourceCost,
this.maxTransmitDistance, this.maxTransmitDistance,
this.transmitDistance, this.transmitDistance,
this.nominalRange, this.nominalRange,
this.CanTransmit(), this.CanTransmit(),
this.DataRate, this.DataRate,
this.DataResourceCost, this.DataResourceCost,
ScienceUtil.GetTransmitterScore(this), 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 #endif
} }
} }