First pass at storing bestOccludedRelay. BadRelayTracking
[AntennaRange.git] / ModuleLimitedDataTransmitter.cs
blob:a/ModuleLimitedDataTransmitter.cs -> blob:b/ModuleLimitedDataTransmitter.cs
// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014-2015, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 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 // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 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. // 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, // 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 // 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, // 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 // 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, // 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 // 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. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;  
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools.DebugTools;
  using ToadicusTools.Extensions;
  using ToadicusTools.Text;
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>
* */ public class ModuleLimitedDataTransmitter
  : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay  
{ {
// Stores the packetResourceCost as defined in the .cfg file. private const string tooltipSkinName = "PartTooltipSkin";
protected float _basepacketResourceCost; private static GUISkin partTooltipSkin;
  private static GUIStyle partTooltipBodyStyle;
// Stores the packetSize as defined in the .cfg file. private static GUIStyle partTooltipHeaderStyle;
protected float _basepacketSize;  
   
// Every antenna is a relay. // Every antenna is a relay.
protected AntennaRelay relay; private 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. // Sometimes we will need to communicate errors; this is how we do it.
protected ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost // Used in module info panes for part tooltips in the editor and R&D
// and packetSize. private GUIContent moduleInfoContent;
   
  /// <summary>
  /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
  /// prescribed by packetResourceCost and packetSize.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float nominalRange; public double nominalRange;
   
  /// <summary>
  /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
  /// prescribed by packetResourceCost and packetSize.
  /// </summary>
  [KSPField(isPersistant = false)]
  public double simpleRange;
   
  /// <summary>
  /// Relay status string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Status")] [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
public string UIrelayStatus; public string UIrelayStatus;
   
  /// <summary>
  /// Relay target string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
public string UIrelayTarget; public string UIrelayTarget;
   
  /// <summary>
  /// Transmit distance string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")] /// <summary>
  /// The nominal range string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
  public string UInominalLinkDistance;
   
  /// <summary>
  /// Maximum distance string for use in action menus.
  /// </summary>
  [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
  /// <summary>
  /// Packet size string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
  /// <summary>
  /// Packet cost string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// <summary>
// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// 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;
   
  /// <summary>
  /// The packet throttle.
  /// </summary>
[KSPField( [KSPField(
isPersistant = true, isPersistant = true,
guiName = "Packet Throttle", guiName = "Packet Throttle",
guiUnits = "%", guiUnits = "%",
guiActive = true, guiActive = true,
guiActiveEditor = false guiActiveEditor = false
)] )]
[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)] [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
public float packetThrottle; public float packetThrottle;
   
protected bool actionUIUpdate; private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
// Returns the parent vessel housing this antenna. /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
public new Vessel vessel public new Vessel vessel
{ {
get get
{ {
return base.vessel; if (base.vessel != null)
} {
} return base.vessel;
  }
// Returns the distance to the nearest relay or Kerbin, whichever is closer. else if (this.part != null && this.part.vessel != null)
public double transmitDistance {
{ return this.part.vessel;
get }
{ else if (
return this.relay.transmitDistance; this.part.protoPartSnapshot != null &&
} this.part.protoPartSnapshot.pVesselRef != null &&
} this.part.protoPartSnapshot.pVesselRef.vesselRef != null
  )
// Returns the maximum distance this module can transmit {
public float maxTransmitDistance return this.part.protoPartSnapshot.pVesselRef.vesselRef;
{ }
get else
{ {
return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange; this.LogError("Vessel and/or part reference are null, returning null vessel.");
} #if DEBUG && VERBOSE
} this.LogError(new System.Diagnostics.StackTrace().ToString());
  #endif
  return null;
  }
  }
  }
   
  /// <summary>
  /// Gets or sets the data capacity of a packet, in MiT/packet
  /// </summary>
  /// <value>The data capacity of a packet, in MiT/packet</value>
  public float PacketSize
  {
  get
  {
  return this.packetSize;
  }
  set
  {
  this.packetSize = value;
  }
  }
   
  /// <summary>
  /// Gets the base data capacity of a packet, in MiT/packet
  /// </summary>
  /// <value>The base data capacity of a packet, in MiT/packet</value>
  public float BasePacketSize
  {
  get;
  private set;
  }
   
  /// <summary>
  /// Gets or sets the resource cost of a packet, in EC/packet
  /// </summary>
  /// <value>The resource cost of a packet, in EC/packet</value>
  public float PacketResourceCost
  {
  get
  {
  return this.packetResourceCost;
  }
  set
  {
  this.packetResourceCost = value;
  }
  }
   
  /// <summary>
  /// Gets the base resource cost of a packet, in EC/packet
  /// </summary>
  /// <value>The base resource cost of a packet, in EC/packet</value>
  public float BasePacketResourceCost
  {
  get;
  private set;
  }
   
  /// <summary>
  /// Gets the packet throttle.
  /// </summary>
  /// <value>The packet throttle in range [0..100].</value>
  public float PacketThrottle
  {
  get
  {
  return this.packetThrottle;
  }
  }
   
  /// <summary>
  /// Gets the max data factor.
  /// </summary>
  /// <value>The max data factor.</value>
  public float MaxDataFactor
  {
  get
  {
  return this.maxDataFactor;
  }
  }
   
  /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
  {
  get
  {
  if (this.relay == null)
  {
  return null;
  }
   
  return this.relay.targetRelay;
  }
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public bool KerbinDirect
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.KerbinDirect;
  }
   
  return false;
  }
  }
   
  /// <summary>
  /// Gets or sets the nominal link distance, in meters.
  /// </summary>
  public double NominalLinkSqrDistance
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.NominalLinkSqrDistance;
  }
   
  return 0d;
  }
  }
   
  /// <summary>
  /// Gets or sets the maximum link distance, in meters.
  /// </summary>
  public double MaximumLinkSqrDistance
  {
  get
  {
  if (this.relay != null)
  {
  return this.relay.MaximumLinkSqrDistance;
  }
   
  return 0d;
  }
  }
   
  /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  public double CurrentLinkSqrDistance
  {
  get
  {
  if (this.relay == null)
  {
  return double.PositiveInfinity;
  }
   
  return this.relay.CurrentLinkSqrDistance;
  }
  }
   
  /// <summary>
  /// Gets the link status.
  /// </summary>
  public ConnectionStatus LinkStatus
  {
  get
  {
  if (this.relay == null)
  {
  return ConnectionStatus.None;
  }
   
  return this.relay.LinkStatus;
  }
  }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public double nominalTransmitDistance
  {
  get
  {
  if (ARConfiguration.UseAdditiveRanges)
  {
  return this.nominalRange;
  }
  else
  {
  return this.simpleRange;
  }
  }
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  public double maxTransmitDistance
  {
  get;
  protected set;
  }
   
  /// <summary>
  /// The first CelestialBody blocking line of sight to a
  /// </summary>
public CelestialBody firstOccludingBody public CelestialBody firstOccludingBody
{ {
get get
{ {
return this.relay.firstOccludingBody; 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(); if (this.relay == null)
   
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; return float.PositiveInfinity;
} }
}  
} return this.relay.DataRate;
  }
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay. }
public bool relayChecked  
{ /// <summary>
get /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
{ /// to be scored in terms of joules/byte
if (this.relay != null) /// </summary>
{ public new double DataResourceCost
return this.relay.relayChecked; {
} get
  {
// If our relay is null, always return null so we're never checked. if (this.relay == null)
return true; {
} return double.PositiveInfinity;
} }
   
public bool KerbinDirect return this.relay.DataResourceCost;
{ }
get }
{  
if (this.relay != null) /// <summary>
{ /// Gets the current network resource rate in EC/MiT.
return this.relay.KerbinDirect; /// </summary>
} /// <value>The current network resource rate in EC/MiT.</value>
  public RelayDataCost CurrentNetworkLinkCost
return false; {
  get
  {
  if (this.relay == null)
  {
  return RelayDataCost.Infinity;
  }
   
  return this.relay.CurrentNetworkLinkCost;
  }
  }
   
  /// <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()
{ {
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT); this.ErrorMsg = new ScreenMessage("", 4f, ScreenMessageStyle.UPPER_LEFT);
this.packetThrottle = 100f; this.packetThrottle = 100f;
} }
   
  /// <summary>
  /// PartModule OnAwake override; runs at Unity Awake.
  /// </summary>
public override void OnAwake() public override void OnAwake()
{ {
base.OnAwake(); base.OnAwake();
   
this._basepacketSize = base.packetSize; this.BasePacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this.BasePacketResourceCost = base.packetResourceCost;
  this.moduleInfoContent = new GUIContent();
Tools.PostDebugMessage(string.Format(  
"{0} loaded:\n" + this.LogDebug("{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalTransmitDistance: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this.BasePacketResourceCost,
this.nominalRange, this.nominalTransmitDistance,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); );
} }
   
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin. /// <summary>
  /// PartModule OnStart override; runs at Unity Start.
  /// </summary>
  /// <param name="state">State.</param>
public override void OnStart (StartState state) public override void OnStart (StartState state)
{ {
base.OnStart (state); base.OnStart (state);
   
  this.RecalculateMaxRange();
   
if (state >= StartState.PreLaunch) if (state >= StartState.PreLaunch)
{ {
this.relay = new AntennaRelay(this); this.relay = new AntennaRelay(this);
  this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
   
this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m"; this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance);
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
} }
} }
   
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because /// <summary>
// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
// work. /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
  /// work.
  /// </summary>
  /// <param name="node"><see cref="ConfigNode"/> with data for this module.</param>
public override void OnLoad(ConfigNode node) public override void OnLoad(ConfigNode node)
{ {
this.Fields.Load(node); this.Fields.Load(node);
base.Fields.Load(node); base.Fields.Load(node);
   
base.OnLoad (node); base.OnLoad (node);
}  
  this.RecalculateMaxRange();
// Post an error in the communication messages describing the reason transmission has failed. Currently there }
// is only one reason for this.  
protected void PostCannotTransmitError() /// <summary>
{ /// Gets the human-friendly module title.
string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!"); /// </summary>
  public string GetModuleTitle()
this.ErrorMsg.message = string.Format( {
"<color='#{0}{1}{2}{3}'><b>{4}</b></color>", return "Comms Transceiver";
((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"), }
((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),  
((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"), /// <summary>
((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"), /// Returns drawTooltipWidget as a callback for part tooltips.
ErrorText /// </summary>
  public Callback<Rect> GetDrawModulePanelCallback()
  {
  return this.drawTooltipWidget;
  }
   
  // Called by Squad's part tooltip system when drawing tooltips.
  // HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
  private void drawTooltipWidget(Rect rect)
  {
  this.moduleInfoContent.text = this.GetInfo();
   
  if (partTooltipSkin == null)
  {
  UnityEngine.Object[] skins = Resources.FindObjectsOfTypeAll(typeof(GUISkin));
  GUISkin skin;
  for (int sIdx = 0; sIdx < skins.Length; sIdx++)
  {
  skin = (GUISkin)skins[sIdx];
   
  if (skin.name == tooltipSkinName)
  {
  partTooltipSkin = skin;
  partTooltipBodyStyle = partTooltipSkin.customStyles[0];
  partTooltipHeaderStyle = partTooltipSkin.customStyles[1];
  }
  }
   
  if (partTooltipSkin == null)
  {
  this.LogError("Could not find GUISkin {0}? Please report this!", tooltipSkinName);
  return;
  }
  else
  {
  this.Log("Loaded GUISkin {0}", tooltipSkinName);
  }
  }
   
  float width = rect.width;
  float orgHeight = rect.height;
  float height = partTooltipBodyStyle.CalcHeight(this.moduleInfoContent, width);
   
  rect.height = height;
   
  GUI.Box(rect, this.moduleInfoContent, partTooltipBodyStyle);
  GUI.Label(rect, this.GetModuleTitle(), partTooltipHeaderStyle);
   
  GUILayout.Space(height - orgHeight
  - partTooltipBodyStyle.padding.bottom - partTooltipBodyStyle.padding.top
  - 2f * (partTooltipBodyStyle.margin.bottom + partTooltipBodyStyle.margin.top)
); );
  }
Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);  
  /// <summary>
ScreenMessages.PostScreenMessage(this.ErrorMsg, false); /// Returns an empty string, because we don't really have a "primary field" like some modules do.
} /// </summary>
  public string GetPrimaryField()
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of {
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point return string.Empty;
// transmission fails (see CanTransmit). }
protected void PreTransmit_SetPacketResourceCost()  
{ /// <summary>
if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange) /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
{ /// </summary>
base.packetResourceCost = this._basepacketResourceCost; public override string GetInfo()
  {
  using (PooledStringBuilder sb = PooledStringBuilder.Get())
  {
  string text;
   
  sb.Append(base.GetInfo());
   
  if (ARConfiguration.UseAdditiveRanges)
  {
  sb.AppendFormat("Nominal Range to Kerbin: {0:S3}m\n",
  Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
  );
  sb.AppendFormat("Maximum Range to Kerbin: {0:S3}m",
  Math.Sqrt(
  this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
  ARConfiguration.KerbinRelayRange
  )
  );
  }
  else
  {
  sb.AppendFormat("Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
  sb.AppendFormat("Maximum Range: {0:S3}m", this.maxTransmitDistance);
  }
   
  text = sb.ToString();
   
  return text;
  }
  }
   
  /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
  public new bool CanTransmit()
  {
  if (this.part == null || this.relay == null)
  {
  return false;
  }
   
  switch (this.part.State)
  {
  case PartStates.DEAD:
  case PartStates.DEACTIVATED:
  this.LogDebug(
  "{0}: {1} on {2} cannot transmit: {3}",
  this.GetType().Name,
  this.part.partInfo.title,
  this.vessel.vesselName,
  Enum.GetName(typeof(PartStates), this.part.State)
  );
  return false;
  default:
  break;
  }
   
  return this.relay.CanTransmit();
  }
   
  /// <summary>
  /// Recalculates the transmission rates.
  /// </summary>
  public void RecalculateTransmissionRates()
  {
  if (this.relay != null)
  {
  this.relay.RecalculateTransmissionRates();
  }
  }
   
  /// <summary>
  /// Finds the nearest relay.
  /// </summary>
  public void FindNearestRelay()
  {
  if (this.relay != null)
  {
  this.relay.FindNearestRelay();
  }
  }
   
  /// <summary>
  /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  public new void TransmitData(List<ScienceData> dataQueue)
  {
  this.LogDebug(
  "TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}",
  dataQueue.Count
  );
   
  if (this.CanTransmit())
  {
  ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  this.LogDebug(
  "CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
  dataQueue.SPrint()
  );
   
  base.TransmitData(dataQueue);
} }
else else
{ {
double rangeFactor = (this.transmitDistance / this.nominalRange); this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
rangeFactor *= rangeFactor;  
  var logger = PooledDebugLogger.New(this);
base.packetResourceCost = this._basepacketResourceCost  
* (float)rangeFactor; IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
  ModuleScienceContainer scienceContainer;
Tools.PostDebugMessage( for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
this, {
"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).", scienceContainer = vesselContainers[cIdx];
base.packetResourceCost,  
rangeFactor);  
}  
   
base.packetResourceCost *= this.packetThrottle / 100f;  
}  
   
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of  
// distance. packetSize maxes out at _basepacketSize * maxDataFactor.  
protected void PreTransmit_SetPacketSize()  
{  
if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)  
{  
base.packetSize = this._basepacketSize;  
}  
else  
{  
double rangeFactor = (this.nominalRange / this.transmitDistance);  
rangeFactor *= rangeFactor;  
   
base.packetSize = Math.Min(  
this._basepacketSize * (float)rangeFactor,  
this._basepacketSize * this.maxDataFactor);  
   
Tools.PostDebugMessage(  
this,  
"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",  
base.packetSize,  
rangeFactor);  
}  
   
base.packetSize *= this.packetThrottle / 100f;  
}  
   
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.  
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()  
{  
if (this.part == null || this.relay == null)  
{  
return false;  
}  
   
PartStates partState = this.part.State;  
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)  
{  
Tools.PostDebugMessage(string.Format(  
"{0}: {1} on {2} cannot transmit: {3}",  
this.GetType().Name,  
this.part.partInfo.title,  
this.vessel.vesselName,  
Enum.GetName(typeof(PartStates), partState)  
));  
return false;  
}  
return this.relay.CanTransmit();  
}  
   
// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit  
// returns false.  
public new void TransmitData(List<ScienceData> dataQueue)  
{  
this.PreTransmit_SetPacketSize();  
this.PreTransmit_SetPacketResourceCost();  
   
if (this.CanTransmit())  
{  
StringBuilder message = new StringBuilder();  
   
message.Append("[");  
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
message.Append("directly to Kerbin.");  
}  
else  
{  
message.Append("via ");  
message.Append(this.relay.nearestRelay);  
}  
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
base.TransmitData(dataQueue);  
}  
else  
{  
Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);  
   
var logger = Tools.DebugLogger.New(this);  
   
foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())  
{  
logger.AppendFormat("Checking ModuleScienceContainer in {0}\n", logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
scienceContainer.part.partInfo.title); scienceContainer.part.partInfo.title);
   
if ( if (
scienceContainer.capacity != 0 && scienceContainer.capacity != 0 &&
scienceContainer.GetScienceCount() >= scienceContainer.capacity scienceContainer.GetScienceCount() >= scienceContainer.capacity
) )
{ {
logger.Append("\tInsufficient capacity, skipping.\n"); logger.Append("\tInsufficient capacity, skipping.\n");
continue; continue;
} }
   
List<ScienceData> dataStored = new List<ScienceData>(); List<ScienceData> dataStored = new List<ScienceData>();
   
foreach (ScienceData data in dataQueue) ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
  data = dataQueue[dIdx];
if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data)) if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
{ {
logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n"); logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
continue; continue;
} }
   
logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID); logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
if (scienceContainer.AddData(data)) if (scienceContainer.AddData(data))
{ {
logger.Append("done, removing from queue.\n"); logger.Append("done, removing from queue.\n");
   
dataStored.Add(data); dataStored.Add(data);
} }
#if DEBUG #if DEBUG
else else
{ {
logger.Append("failed.\n"); logger.Append("failed.\n");
} }
#endif #endif
} }
   
dataQueue.RemoveAll(i => dataStored.Contains(i)); dataQueue.RemoveAll(i => dataStored.Contains(i));
   
logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count); logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
} }
   
logger.Print(); logger.Print();
   
if (dataQueue.Count > 0) if (dataQueue.Count > 0)
{ {
StringBuilder msg = new StringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
   
msg.Append('[');  
msg.Append(this.part.partInfo.title);  
msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");  
msg.Append("Data to be discarded:\n");  
   
foreach (ScienceData data in dataQueue)  
{ {
msg.AppendFormat("\n{0}\n", data.title); sb.Append('[');
  sb.Append(this.part.partInfo.title);
  sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
  sb.Append("Data to be discarded:\n");
   
  ScienceData data;
  for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
  {
  data = dataQueue[dIdx];
  sb.AppendFormat("\t{0}\n", data.title);
  }
   
  ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
  this.LogDebug(sb.ToString());
} }
  }
ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
  this.PostCannotTransmitError();
Tools.PostDebugMessage(msg.ToString()); }
}  
  this.LogDebug(
this.PostCannotTransmitError (); "distance: " + this.CurrentLinkSqrDistance
}  
   
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit /// <summary>
// returns false. /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
public new void StartTransmission() public new void StartTransmission()
{ {
PreTransmit_SetPacketSize (); this.LogDebug(
PreTransmit_SetPacketResourceCost (); "distance: " + this.CurrentLinkSqrDistance
   
Tools.PostDebugMessage (  
"distance: " + this.transmitDistance  
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message.Append("[");  
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
message.Append("directly to Kerbin.");  
}  
else  
{  
message.Append("via ");  
message.Append(this.relay.nearestRelay);  
}  
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
  /// <summary>
  /// MonoBehaviour Update
  /// </summary>
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
  this.UImaxTransmitDistance = TextTools.Format("{0:S3}m",
  Math.Sqrt(this.MaximumLinkSqrDistance));
  this.UInominalLinkDistance = TextTools.Format("{0:S3}m",
  Math.Sqrt(this.NominalLinkSqrDistance));
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = "Connected"; this.UIrelayStatus = this.LinkStatus.ToString();
this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; this.UItransmitDistance = TextTools.Format("{0:S3}m",
this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
  this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost);
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
  this.UItransmitDistance = TextTools.Format("{0:S3}m",
  Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UItransmitDistance = "N/A";
  this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
this.UImaxTransmitDistance = "N/A";  
this.UIpacketSize = "N/A"; this.UIpacketSize = "N/A";
this.UIpacketCost = "N/A"; this.UIpacketCost = "N/A";
} }
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
if (this.relay.bestOccludedRelay != null) this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
  }
  else
  {
  if (this.targetRelay != null)
{ {
this.UIrelayTarget = this.relay.bestOccludedRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
else else
{ {
this.UIrelayTarget = "Kerbin"; this.UIrelayTarget = "A mysterious null entity";
} }
} }
  }
  }
   
  /// <summary>
  /// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
  /// </summary>
  public void RecalculateMaxRange()
  {
  this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
   
  #if DEBUG
  this.Log("Recalculated max range: sqrt({0}) * {1} = {2}",
  this.maxPowerFactor, this.nominalTransmitDistance, this.maxTransmitDistance);
  #endif
  }
   
  /// <summary>
  /// 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()
  {
  using (PooledStringBuilder sb = PooledStringBuilder.Get())
  {
  string msg;
   
  if (this.part != null && this.part.partInfo != null)
  {
  sb.Append(this.part.partInfo.title);
  }
else else
{ {
this.UIrelayTarget = this.relay.nearestRelay.ToString(); sb.Append(this.GetType().Name);
} }
}  
} if (vessel != null)
  {
public void onPartActionUICreate(Part eventPart) sb.Append(" on ");
  sb.Append(vessel.vesselName);
  }
  else if (
  this.part != null &&
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot != null &&
  this.part.protoPartSnapshot.pVesselRef != null)
  {
  sb.Append(" on ");
  sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
  }
   
  msg = sb.ToString();
   
  return msg;
  }
  }
   
  // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
  private void onPartActionUICreate(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = true; this.actionUIUpdate = true;
} }
} }
   
public void onPartActionUIDismiss(Part eventPart) // 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) if (eventPart == base.part)
{ {
this.actionUIUpdate = false; this.actionUIUpdate = false;
} }
} }
   
public override string ToString() // Post an error in the communication messages describing the reason transmission has failed. Currently there
{ // is only one reason for this.
StringBuilder msg = new StringBuilder(); private void PostCannotTransmitError()
  {
msg.Append(this.part.partInfo.title); string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
   
if (vessel != null) this.ErrorMsg.message = string.Format(
{ "<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
msg.Append(" on "); ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
msg.Append(vessel.vesselName); ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
} ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
  ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
return msg.ToString(); ErrorText
} );
   
  this.LogDebug(this.ErrorMsg.message);
   
  ScreenMessages.PostScreenMessage(this.ErrorMsg);
  }
   
  private string buildTransmitMessage()
  {
  using (PooledStringBuilder sb = PooledStringBuilder.Get())
  {
  string msg;
   
  sb.Append("[");
  sb.Append(base.part.partInfo.title);
  sb.Append("]: ");
   
  sb.Append("Beginning transmission ");
   
  if (this.KerbinDirect)
  {
  sb.Append("directly to Kerbin.");
  }
  else
  {
  sb.Append("via ");
  sb.Append(this.relay.targetRelay);
  }
   
  msg = sb.ToString();
   
  return msg;
  }
  }
   
  #if DEBUG
// 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 (); if (this.relay != null)
PreTransmit_SetPacketResourceCost (); this.relay.RecalculateTransmissionRates();
   
string msg = string.Format( DebugPartModule.DumpClassObject(this);
"'{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}\n" +  
"Vessel ID: {13}",  
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(),  
this.vessel.id  
);  
Tools.PostDebugMessage(msg);  
} }
   
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels() public void PrintAllVessels()
{ {
StringBuilder sb = new StringBuilder(); using (PooledStringBuilder sb = PooledStringBuilder.Get())
  {
sb.Append("Dumping FlightGlobals.Vessels:"); sb.Append("Dumping FlightGlobals.Vessels:");
   
foreach (Vessel vessel in FlightGlobals.Vessels) Vessel vessel;
{ for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id); {
} vessel = FlightGlobals.Vessels[i];
  sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
Tools.PostDebugMessage(sb.ToString()); }
}  
  ToadicusTools.Logging.PostDebugMessage(sb.ToString());
[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)] }
  }
   
  /*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
public void DumpRelayDB() public void DumpRelayDB()
{ {
RelayDatabase.Instance.Dump(); RelayDatabase.Instance.Dump();
} }*/
#endif #endif
} }
} }