Version 1.10 1.10
Version 1.10

// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014-2015, 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.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
/// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
/// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para> /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
/// ///
/// <para>In general, the scaling functions assume the following relation:</para> /// <para>In general, the scaling functions assume the following relation:</para>
/// ///
/// <para> D² α P/R,</para> /// <para> D² α P/R,</para>
/// ///
/// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para> /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
/// </summary> /// </summary>
public class ModuleLimitedDataTransmitter public class ModuleLimitedDataTransmitter
: ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
{ {
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
private float _basepacketResourceCost; private float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
private float _basepacketSize; private float _basepacketSize;
   
// Every antenna is a relay. // Every antenna is a relay.
private AntennaRelay relay; 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.
private ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// Used in module info panes for part tooltips in the editor and R&D // Used in module info panes for part tooltips in the editor and R&D
private GUIContent moduleInfoContent; private GUIContent moduleInfoContent;
   
/// <summary> /// <summary>
/// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
/// prescribed by packetResourceCost and packetSize. /// prescribed by packetResourceCost and packetSize.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public double nominalRange; public double nominalRange;
   
/// <summary> /// <summary>
/// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
/// prescribed by packetResourceCost and packetSize. /// prescribed by packetResourceCost and packetSize.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public double simpleRange; public double simpleRange;
   
/// <summary> /// <summary>
/// Relay status string for use in action menus. /// Relay status string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Status")] [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
public string UIrelayStatus; public string UIrelayStatus;
   
/// <summary> /// <summary>
/// Relay target string for use in action menus. /// Relay target string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
public string UIrelayTarget; public string UIrelayTarget;
   
/// <summary> /// <summary>
/// Transmit distance string for use in action menus. /// Transmit distance string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
/// <summary> /// <summary>
/// The nominal range string for use in action menus. /// The nominal range string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")] [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
public string UInominalLinkDistance; public string UInominalLinkDistance;
   
/// <summary> /// <summary>
/// Maximum distance string for use in action menus. /// Maximum distance string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
/// <summary> /// <summary>
/// Packet size string for use in action menus. /// Packet size string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
/// <summary> /// <summary>
/// Packet cost string for use in action menus. /// Packet cost string for use in action menus.
/// </summary> /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
/// <summary> /// <summary>
/// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
/// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
/// <summary> /// <summary>
/// The multipler on packetSize that defines the maximum data bandwidth of the antenna. /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
/// </summary> /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
/// <summary> /// <summary>
/// The packet throttle. /// The packet throttle.
/// </summary> /// </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;
   
private bool actionUIUpdate; private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
public new Vessel vessel public new Vessel vessel
{ {
get get
{ {
if (base.vessel != null) if (base.vessel != null)
{ {
return base.vessel; return base.vessel;
} }
else if (this.part != null && this.part.vessel != null) else if (this.part != null && this.part.vessel != null)
{ {
return this.part.vessel; return this.part.vessel;
} }
else if ( else if (
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null && this.part.protoPartSnapshot.pVesselRef != null &&
this.part.protoPartSnapshot.pVesselRef.vesselRef != null this.part.protoPartSnapshot.pVesselRef.vesselRef != null
) )
{ {
return this.part.protoPartSnapshot.pVesselRef.vesselRef; return this.part.protoPartSnapshot.pVesselRef.vesselRef;
} }
else else
{ {
this.LogError("Vessel and/or part reference are null, returning null vessel."); this.LogError("Vessel and/or part reference are null, returning null vessel.");
this.LogError(new System.Diagnostics.StackTrace().ToString()); this.LogError(new System.Diagnostics.StackTrace().ToString());
return null; return null;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
public IAntennaRelay targetRelay public IAntennaRelay targetRelay
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null; return null;
} }
   
return this.relay.targetRelay; return this.relay.targetRelay;
} }
} }
   
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
/// directly with Kerbin. /// directly with Kerbin.
/// </summary> /// </summary>
public bool KerbinDirect public bool KerbinDirect
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.KerbinDirect; return this.relay.KerbinDirect;
} }
   
return false; return false;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the nominal link distance, in meters. /// Gets or sets the nominal link distance, in meters.
/// </summary> /// </summary>
public double NominalLinkSqrDistance public double NominalLinkSqrDistance
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.NominalLinkSqrDistance; return this.relay.NominalLinkSqrDistance;
} }
   
return 0d; return 0d;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the maximum link distance, in meters. /// Gets or sets the maximum link distance, in meters.
/// </summary> /// </summary>
public double MaximumLinkSqrDistance public double MaximumLinkSqrDistance
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.MaximumLinkSqrDistance; return this.relay.MaximumLinkSqrDistance;
} }
   
return 0d; return 0d;
} }
} }
   
/// <summary> /// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer. /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary> /// </summary>
public double CurrentLinkSqrDistance public double CurrentLinkSqrDistance
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return double.PositiveInfinity; return double.PositiveInfinity;
} }
   
return this.relay.CurrentLinkSqrDistance; return this.relay.CurrentLinkSqrDistance;
} }
} }
   
/// <summary> /// <summary>
/// Gets the link status. /// Gets the link status.
/// </summary> /// </summary>
public ConnectionStatus LinkStatus public ConnectionStatus LinkStatus
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return ConnectionStatus.None; return ConnectionStatus.None;
} }
   
return this.relay.LinkStatus; return this.relay.LinkStatus;
} }
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </summary>
public double nominalTransmitDistance public double nominalTransmitDistance
{ {
get get
{ {
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
return this.nominalRange; return this.nominalRange;
} }
else else
{ {
return this.simpleRange; return this.simpleRange;
} }
} }
} }
   
/// <summary> /// <summary>
/// The maximum distance at which this relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
public double maxTransmitDistance public double maxTransmitDistance
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// The first CelestialBody blocking line of sight to a /// The first CelestialBody blocking line of sight to a
/// </summary> /// </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.
* */ * */
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
/// terms of joules/byte /// terms of joules/byte
/// </summary> /// </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;
} }
} }
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
/// to be scored in terms of joules/byte /// to be scored in terms of joules/byte
/// </summary> /// </summary>
public new double DataResourceCost 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;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the Part title. /// Gets the Part title.
/// </summary> /// </summary>
public string Title public string Title
{ {
get get
{ {
if (this.part != null && this.part.partInfo != null) if (this.part != null && this.part.partInfo != null)
{ {
return this.part.partInfo.title; return this.part.partInfo.title;
} }
   
return string.Empty; 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, false, ScreenMessageStyle.UPPER_LEFT);
this.packetThrottle = 100f; this.packetThrottle = 100f;
} }
   
/// <summary> /// <summary>
/// PartModule OnAwake override; runs at Unity Awake. /// PartModule OnAwake override; runs at Unity Awake.
/// </summary> /// </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(); this.moduleInfoContent = new GUIContent();
   
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" +
"nominalTransmitDistance: {3}\n" + "nominalTransmitDistance: {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.nominalTransmitDistance, this.nominalTransmitDistance,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
/// <summary> /// <summary>
/// PartModule OnStart override; runs at Unity Start. /// PartModule OnStart override; runs at Unity Start.
/// </summary> /// </summary>
/// <param name="state">State.</param> /// <param name="state">State.</param>
public override void OnStart (StartState state) public override void OnStart (StartState state)
{ {
base.OnStart (state); base.OnStart (state);
   
this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
   
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.nominalTransmitDistance = this.nominalTransmitDistance;
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
   
this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance); this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{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);
} }
} }
   
/// <summary> /// <summary>
/// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because /// 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 /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
/// work. /// work.
/// </summary> /// </summary>
/// <param name="node"><see cref="ConfigNode"/> with data for this module.</param> /// <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.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance; this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
} }
   
/// <summary> /// <summary>
/// Gets the human-friendly module title. /// Gets the human-friendly module title.
/// </summary> /// </summary>
public string GetModuleTitle() public string GetModuleTitle()
{ {
return "Comms Transceiver"; return "Comms Transceiver";
} }
   
/// <summary> /// <summary>
/// Returns drawTooltipWidget as a callback for part tooltips. /// Returns drawTooltipWidget as a callback for part tooltips.
/// </summary> /// </summary>
public Callback<Rect> GetDrawModulePanelCallback() public Callback<Rect> GetDrawModulePanelCallback()
{ {
return this.drawTooltipWidget; return this.drawTooltipWidget;
} }
   
// Called by Squad's part tooltip system when drawing tooltips. // Called by Squad's part tooltip system when drawing tooltips.
// HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118 // HACK: Currently hacks around Squad's extraneous layout box, see KSPModders issue #5118
private void drawTooltipWidget(Rect rect) private void drawTooltipWidget(Rect rect)
{ {
this.moduleInfoContent.text = this.GetInfo(); this.moduleInfoContent.text = this.GetInfo();
   
GUIStyle style0 = PartListTooltips.fetch.tooltipSkin.customStyles[0]; GUIStyle style0 = PartListTooltips.fetch.tooltipSkin.customStyles[0];
GUIStyle style1 = PartListTooltips.fetch.tooltipSkin.customStyles[1]; GUIStyle style1 = PartListTooltips.fetch.tooltipSkin.customStyles[1];
   
float width = rect.width; float width = rect.width;
float orgHeight = rect.height; float orgHeight = rect.height;
float height = style0.CalcHeight(this.moduleInfoContent, width); float height = style0.CalcHeight(this.moduleInfoContent, width);
   
rect.height = height; rect.height = height;
   
GUI.Box(rect, this.moduleInfoContent, style0); GUI.Box(rect, this.moduleInfoContent, style0);
GUI.Label(rect, this.GetModuleTitle(), style1); GUI.Label(rect, this.GetModuleTitle(), style1);
   
GUILayout.Space(height - orgHeight GUILayout.Space(height - orgHeight
- style0.padding.bottom - style0.padding.top - style0.padding.bottom - style0.padding.top
- 2f * (style0.margin.bottom + style0.margin.top) - 2f * (style0.margin.bottom + style0.margin.top)
); );
} }
   
/// <summary> /// <summary>
/// Returns an empty string, because we don't really have a "primary field" like some modules do. /// Returns an empty string, because we don't really have a "primary field" like some modules do.
/// </summary> /// </summary>
public string GetPrimaryField() public string GetPrimaryField()
{ {
return string.Empty; return string.Empty;
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description. /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
/// </summary> /// </summary>
public override string GetInfo() public override string GetInfo()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
string text; string text;
   
sb.Append(base.GetInfo()); sb.Append(base.GetInfo());
   
if (ARConfiguration.UseAdditiveRanges) if (ARConfiguration.UseAdditiveRanges)
{ {
sb.AppendFormat(Tools.SIFormatter, "Nominal Range to Kerbin: {0:S3}m\n", sb.AppendFormat(Tools.SIFormatter, "Nominal Range to Kerbin: {0:S3}m\n",
Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange) Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange)
); );
sb.AppendFormat(Tools.SIFormatter, "Maximum Range to Kerbin: {0:S3}m", sb.AppendFormat(Tools.SIFormatter, "Maximum Range to Kerbin: {0:S3}m",
Math.Sqrt(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange) Math.Sqrt(
  this.nominalTransmitDistance * Math.Sqrt(this.maxPowerFactor) *
  ARConfiguration.KerbinRelayRange
  )
); );
} }
else else
{ {
sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance); sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m", this.maxTransmitDistance); sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m", this.maxTransmitDistance);
} }
   
text = sb.ToString(); text = sb.ToString();
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
   
return text; return text;
} }
   
/// <summary> /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// <c>true</c> if this instance can transmit; otherwise, <c>false</c>. /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </summary>
public new bool CanTransmit() public new bool CanTransmit()
{ {
if (this.part == null || this.relay == null) if (this.part == null || this.relay == null)
{ {
return false; return false;
} }
   
switch (this.part.State) switch (this.part.State)
{ {
case PartStates.DEAD: case PartStates.DEAD:
case PartStates.DEACTIVATED: case PartStates.DEACTIVATED:
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.part.partInfo.title, this.part.partInfo.title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), this.part.State) Enum.GetName(typeof(PartStates), this.part.State)
)); ));
return false; return false;
default: default:
break; break;
} }
   
return this.relay.CanTransmit(); return this.relay.CanTransmit();
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
public void FindNearestRelay() public void FindNearestRelay()
{ {
if (this.relay != null) if (this.relay != null)
{ {
this.relay.FindNearestRelay(); this.relay.FindNearestRelay();
} }
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
/// returns false. /// returns false.
/// </summary> /// </summary>
/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param> /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
/// <param name="callback">Callback function</param> /// <param name="callback">Callback function</param>
public new void TransmitData(List<ScienceData> dataQueue, Callback callback) public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
{ {
this.LogDebug( this.LogDebug(
"TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}", "TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}",
dataQueue.Count dataQueue.Count
); );
   
this.FindNearestRelay(); this.FindNearestRelay();
   
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
this.LogDebug( this.LogDebug(
"CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}", "CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
dataQueue.SPrint(), dataQueue.SPrint(),
callback == null ? "null" : callback.ToString() callback == null ? "null" : callback.ToString()
); );
   
if (callback == null) if (callback == null)
{ {
base.TransmitData(dataQueue); base.TransmitData(dataQueue);
} }
else else
{ {
base.TransmitData(dataQueue, callback); base.TransmitData(dataQueue, callback);
} }
} }
else else
{ {
Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = Tools.DebugLogger.New(this); var logger = Tools.DebugLogger.New(this);
   
IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>(); IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
ModuleScienceContainer scienceContainer; ModuleScienceContainer scienceContainer;
for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++) for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
{ {
scienceContainer = vesselContainers[cIdx]; scienceContainer = vesselContainers[cIdx];
   
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>();
   
ScienceData data; ScienceData data;
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++) for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
data = dataQueue[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 sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
   
sb.Append('['); sb.Append('[');
sb.Append(this.part.partInfo.title); sb.Append(this.part.partInfo.title);
sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
sb.Append("Data to be discarded:\n"); sb.Append("Data to be discarded:\n");
   
ScienceData data; ScienceData data;
for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++) for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
{ {
data = dataQueue[dIdx]; data = dataQueue[dIdx];
sb.AppendFormat("\t{0}\n", data.title); sb.AppendFormat("\t{0}\n", data.title);
} }
   
ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
   
this.PostCannotTransmitError(); this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.CurrentLinkSqrDistance "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
/// returns false. /// returns false.
/// </summary> /// </summary>
/// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param> /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
public new void TransmitData(List<ScienceData> dataQueue) public new void TransmitData(List<ScienceData> dataQueue)
{ {
this.LogDebug( this.LogDebug(
"TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}", "TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
dataQueue.Count dataQueue.Count
); );
   
this.TransmitData(dataQueue, null); this.TransmitData(dataQueue, null);
} }
   
/// <summary> /// <summary>
/// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
/// returns false. /// returns false.
/// </summary> /// </summary>
public new void StartTransmission() public new void StartTransmission()
{ {
this.FindNearestRelay(); this.FindNearestRelay();
   
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.CurrentLinkSqrDistance "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
/// <summary> /// <summary>
/// MonoBehaviour Update /// MonoBehaviour Update
/// </summary> /// </summary>
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
Math.Sqrt(this.MaximumLinkSqrDistance)); Math.Sqrt(this.MaximumLinkSqrDistance));
this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
Math.Sqrt(this.NominalLinkSqrDistance)); Math.Sqrt(this.NominalLinkSqrDistance));
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = this.LinkStatus.ToString(); this.UIrelayStatus = this.LinkStatus.ToString();
this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
Math.Sqrt(this.CurrentLinkSqrDistance)); Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate); this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate);
this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost); this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost);
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m",
Math.Sqrt(this.CurrentLinkSqrDistance)); Math.Sqrt(this.CurrentLinkSqrDistance));
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UItransmitDistance = "N/A"; this.UItransmitDistance = "N/A";
this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
this.UIpacketSize = "N/A"; this.UIpacketSize = "N/A";
this.UIpacketCost = "N/A"; this.UIpacketCost = "N/A";
} }
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
this.UIrelayTarget = AntennaRelay.Kerbin.bodyName; this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
} }
else else
{ {
this.UIrelayTarget = this.targetRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
} }
} }
   
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
public override string ToString() public override string ToString()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
string msg; string msg;
   
sb.Append(this.part.partInfo.title); sb.Append(this.part.partInfo.title);
   
if (vessel != null) if (vessel != null)
{ {
sb.Append(" on "); sb.Append(" on ");
sb.Append(vessel.vesselName); sb.Append(vessel.vesselName);
} }
else if ( else if (
this.part != null && this.part != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null this.part.protoPartSnapshot.pVesselRef != null
) )
{ {
sb.Append(" on "); sb.Append(" on ");
sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName); sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
   
return msg; return msg;
} }
   
// When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty. // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
private void onPartActionUICreate(Part eventPart) private void onPartActionUICreate(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = true; this.actionUIUpdate = true;
} }
} }
   
// When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty. // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
private void onPartActionUIDismiss(Part eventPart) private void onPartActionUIDismiss(Part eventPart)
{ {
if (eventPart == base.part) if (eventPart == base.part)
{ {
this.actionUIUpdate = false; this.actionUIUpdate = false;
} }
} }
   
// Post an error in the communication messages describing the reason transmission has failed. Currently there // Post an error in the communication messages describing the reason transmission has failed. Currently there
// is only one reason for this. // is only one reason for this.
private void PostCannotTransmitError() private void PostCannotTransmitError()
{ {
string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!"); string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
   
this.ErrorMsg.message = string.Format( this.ErrorMsg.message = string.Format(
"<color='#{0}{1}{2}{3}'><b>{4}</b></color>", "<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"), ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
ErrorText ErrorText
); );
   
Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message); Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message);
   
ScreenMessages.PostScreenMessage(this.ErrorMsg, false); ScreenMessages.PostScreenMessage(this.ErrorMsg, false);
} }
   
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of // Before transmission, set packetResourceCost. Per above, packet cost increases with the square of
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
// transmission fails (see CanTransmit). // transmission fails (see CanTransmit).
private void PreTransmit_SetPacketResourceCost() private void PreTransmit_SetPacketResourceCost()
{ {
if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance) if (ARConfiguration.FixedPowerCost || this.CurrentLinkSqrDistance <= this.NominalLinkSqrDistance)
{ {
base.packetResourceCost = this._basepacketResourceCost; base.packetResourceCost = this._basepacketResourceCost;
} }
else else
{ {
float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance); float rangeFactor = (float)(this.CurrentLinkSqrDistance / this.NominalLinkSqrDistance);
   
base.packetResourceCost = this._basepacketResourceCost * rangeFactor; base.packetResourceCost = this._basepacketResourceCost * rangeFactor;
} }
   
base.packetResourceCost *= this.packetThrottle / 100f; base.packetResourceCost *= this.packetThrottle / 100f;
} }
   
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of // Before transmission, set packetSize. Per above, packet size increases with the inverse square of
// distance. packetSize maxes out at _basepacketSize * maxDataFactor. // distance. packetSize maxes out at _basepacketSize * maxDataFactor.
private void PreTransmit_SetPacketSize() private void PreTransmit_SetPacketSize()
{ {
if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance) if (!ARConfiguration.FixedPowerCost && this.CurrentLinkSqrDistance >= this.NominalLinkSqrDistance)
{ {
base.packetSize = this._basepacketSize; base.packetSize = this._basepacketSize;
} }
else else
{ {
float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance); float rangeFactor = (float)(this.NominalLinkSqrDistance / this.CurrentLinkSqrDistance);
   
base.packetSize = Mathf.Min( base.packetSize = Mathf.Min(
this._basepacketSize * rangeFactor, this._basepacketSize * rangeFactor,
this._basepacketSize * this.maxDataFactor this._basepacketSize * this.maxDataFactor
); );
} }
   
base.packetSize *= this.packetThrottle / 100f; base.packetSize *= this.packetThrottle / 100f;
} }
   
private string buildTransmitMessage() private string buildTransmitMessage()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
string msg; string msg;
   
sb.Append("["); sb.Append("[");
sb.Append(base.part.partInfo.title); sb.Append(base.part.partInfo.title);
sb.Append("]: "); sb.Append("]: ");
   
sb.Append("Beginning transmission "); sb.Append("Beginning transmission ");
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
sb.Append("directly to Kerbin."); sb.Append("directly to Kerbin.");
} }
else else
{ {
sb.Append("via "); sb.Append("via ");
sb.Append(this.relay.targetRelay); sb.Append(this.relay.targetRelay);
} }
   
msg = sb.ToString(); msg = sb.ToString();
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
   
return msg; return msg;
} }
   
#if DEBUG #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.
[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 ();
   
DebugPartModule.DumpClassObject(this); DebugPartModule.DumpClassObject(this);
} }
   
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels() public void PrintAllVessels()
{ {
StringBuilder sb = Tools.GetStringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
sb.Append("Dumping FlightGlobals.Vessels:"); sb.Append("Dumping FlightGlobals.Vessels:");
   
Vessel vessel; Vessel vessel;
for (int i = 0; i < FlightGlobals.Vessels.Count; i++) for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
{ {
vessel = FlightGlobals.Vessels[i]; vessel = FlightGlobals.Vessels[i];
sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id); sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
} }
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
   
Tools.PutStringBuilder(sb); Tools.PutStringBuilder(sb);
} }
[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
} }
} }
// AntennaRange // AntennaRange
// //
// AssemblyInfo.cs // AssemblyInfo.cs
// //
// Copyright © 2014-2015, 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 System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
   
[assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)] [assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
   
// Information about this assembly is defined by the following attributes. // Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project. // Change them to the values specific to your project.
[assembly: AssemblyTitle("AntennaRange")] [assembly: AssemblyTitle("AntennaRange")]
[assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")] [assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
[assembly: AssemblyCopyright("toadicus")] [assembly: AssemblyCopyright("toadicus")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision, // The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision. // and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.9.1.*")] [assembly: AssemblyVersion("1.10.*")]
// The following attributes are used to specify the signing key for the assembly, // The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing. // if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)] //[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")] //[assembly: AssemblyKeyFile("")]
   
file:b/README.md (new)
  # AntennaRange
  A KSP mod that enforces and encourages the use of the bigger antennas.
 
  # For Part Developers
  ## The Fields
  AntennaRange extends and augments the functionality of the stock ModuleDataTransmitter through the new `ModuleLimitedDataTransmitter` class. This class uses four additional configurable fields to define the part's behavior.
 
  `nominalRange` is the range, in meters, at which the part should function identically to the stock module, i.e. without any modification to the power cost or packet size. This is used along with maxPowerFactor to calculate the maximum range of the part.
  `simpleRange` is the same as nominalRange, but is used when the mod is in "simple" mode. In general it will probably need to be a much larger number than nominalRange.
  `maxPowerFactor` effectively sets the maximum range of the antenna by essentially capping how much the power may be "turned up" to get better range. I originally used 8 for this number, because it felt right. I've since used 4 (for my DTS) and 16 (for my Comm. 88-88). You don't want this number to be too high, or small probes will go uncontrollable a lot when transmitting.
  `maxDataFactor` defines the maximum "speed up" bonus that comes from using antennas at less their nominal range. I originally used 4 for this number for all parts; the DTS has a higher bonus now.
 
  Note that all of the fields needed for Squad's `ModuleDataTransmitter` still need to be filled out. Depending on how you're defining your parts, they might need to go in your AntennaRange patch, or they might already be defined on the base part.
 
  ## The Mechanic
  In general, the scaling functions assume the relation `D² α P/R,` where D is the total transmission distance, P is the transmission power, and R is the data rate. Data rate increases as range decreases below nominalRange: `R α nominalRange² / D²`. By default, power use increases as range increases above `nominalRange`: `P α D² / nominalRange²`. Optionally, power use may remain fixed, and data rate instead decreases as range increases above `nominalRange`: `R α nominalRange² / D²`.
 
  ## Patch Conventions
  To maximize cross-compatibility, please consider the following conventions for ModuleManager patches regarding AntennaRange:
 
  When providing new definitions for your own parts, always specify a `:FOR[YourModHere]` pass name.
  Whenever changing default AntennaRange definitions (e.g. if you were going to rebalance my antennas to suit your mod), please do so in the `:AFTER[AntennaRange]` pass.
  I recommend providing all optional functionality (e.g. enabling RemoteTech vs. AntennaRange modules) in separate patches using `:NEEDS[]` blocks.
 
  A sample AntennaRange configuration for an all-new mod part might look like this:
  ```
  @PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
  {
  MODULE
  {
  // ### Module Definition ###
  name = ModuleLimitedDataTransmitter
 
  // ### Squad Definitions ###
  // Delay between transmission packets, in seconds
  packetInterval = 0.10
 
  // Data capacity of nominal transmission packets, in MiT
  packetSize = 2
 
  // Resource cost of nominal transmission packets, in units
  packetResourceCost = 20.0
 
  // Resource name to be consumed by transmission
  requiredResource = ElectricCharge
 
  // Animation module index, 0-based, of the antenna extend/retract animation
  DeployFxModules = 0
 
  // ### AntennaRange Defintions ###
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "additive" ranges.
  nominalRange = 10000000000
 
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "simple" ranges.
  simpleRange = 56250000000
 
  // The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
  // transmitter. Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
  maxPowerFactor = 16
 
  // The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
  // transmitter.
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
  ```
 
  This example assumes that the base part definition does not include a `ModuleDataTransmitter` module, or any RT modules. If the base part definition includes a `ModuleDataTransmitter` module, a sample AntennaRange patch could look like this:
  ```
  @PART[modPartName]:FOR[YourModName]:NEEDS[AntennaRange,!RemoteTech]
  {
  @MODULE[ModuleDataTransmitter]
  {
  @name = ModuleLimitedDataTransmitter
  nominalRange = 10000000000
  simpleRange = 56250000000
  maxPowerFactor = 16
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
  ```
 
  IIRC, RemoteTech parts should not have `ModuleDataTransmitter` definitions. In that case, to facilitate RT, AR, and Stock compatibility, a suite of patches like this might be appropriate:
 
  ```
  // If we don't have RemoteTech, add a stock ModuleDataTransmitter first.
  @PART[modPartName]:NEEDS[!RemoteTech]:BEFORE[YourModName]
  {
  MODULE
  {
  // ### Module Definition ###
  name = ModuleDataTransmitter
 
  // ### Squad Definitions ###
  // Delay between transmission packets, in seconds
  packetInterval = 0.10
 
  // Data capacity of nominal transmission packets, in MiT
  packetSize = 2
 
  // Resource cost of nominal transmission packets, in units
  packetResourceCost = 20.0
 
  // Resource name to be consumed by transmission
  requiredResource = ElectricCharge
 
  // Animation module index, 0-based, of the antenna extend/retract animation
  DeployFxModules = 0
  }
  }
 
  // If AntennaRange is installed, convert that to a ModuleLimitedDataTransmitter
  @PART[modPartName]:NEEDS[AntennaRange,!RemoteTech]:FOR[YourModName]
  {
  @MODULE[ModuleDataTransmitter]
  {
  // ### Module Redefinition ###
  @name = ModuleLimitedDataTransmitter
 
  // ### AntennaRange Defintions ###
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "additive" ranges.
  nominalRange = 10000000000
 
  // Range, in meters, at which the antenna behaves per the "nominal" characteristics above
  // Used with "simple" ranges.
  simpleRange = 56250000000
 
  // The maxmimum multiplier on packetResourceCost, essentially defining the maximum power output of the
  // transmitter. Maximum range is defined as: maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)
  maxPowerFactor = 16
 
  // The maximum multiplier on packetSize, essentially defining the maximum data throughput of the
  // transmitter.
  maxDataFactor = 2
  }
 
  // We add this ModuleScienceContainer so that when transmission fails the antennas can try to stash the data instead of dumping it to the void.
  MODULE
  {
  name = ModuleScienceContainer
 
  dataIsCollectable = true
  dataIsStorable = false
 
  storageRange = 2
  }
  }
 
  // If RemoteTech is installed, do their module(s) instead
  @PART[modPartName]:NEEDS[RemoteTech]:FOR[YourModName]
  {
  // RemoteTech module(s) here
  }
  ```
 
  ## Useful Formulas
 
  ### Per Antenna
  `nominalRange` is a given, and is never calculated
  `maxPowerFactor` is a given, and is never calculated
  `maxTransmitDistance = nominalRange * sqrt(maxPowerFactor)`
 
  ### Per Link
  A "link" is any connected pair of antennas.
  `NominalLinkDistance = sqrt(nominalRange1 * nominalRange2)`
  `MaxLinkDistance = sqrt(maxTransmitDistance1 * maxTransmitDistance2)`
 
  Therefore, to find the `MaxLinkDistance` from two sets of `nominalRange` and `maxPowerFactor`:
  `MaxLinkDistance = sqrt(nominalRange1 * sqrt(maxPowerFactor1) * nominalRange2 * sqrt(maxPowerFactor2))`
 
  To find a single antenna's `nominalRange` from a desired `maxTransmitDistance` given its `maxPowerFactor`:
  `nominalRange = maxTransmitDistance / sqrt(maxPowerFactor)`
 
  To find a single antenna's desired maximum range given the desired maximum link distance and another set `maxTransmitDistance`:
  `maxTransmitDistance1 = MaxLinkDistance * MaxLinkDistance / maxTransmitDistance2`
 
  Remember that `maxPowerFactor` may differ between antennas (and does, in my lastest configs: longAntenna is 8, mediumDish is 4, commDish is 16).
 
  Currently Kerbin's `maxPowerFactor` is hard-coded as 8.
 
  Feel free to use this spreadsheet for balancing antennas if it's useful to you: https://goo.gl/ChsbfL
 
  ## On Balance
  In my configs I've balanced the three stock antennas to cover all of the stock solar system. Since you're introducing five more antennas and working with OPM, you will probably want to change the behavior of the stock parts and diversify the range to gradually cover the whole OPM system. Since you have some parts specifically designed for use in planetary subsystems, their balance when transmitting to other parts is probably more important than their balance when transmitting to Kerbin. For longer range parts designed to make the whole interplanetary leap, the inverse is probably true.
 
  Feel free to ask questions! If anything's unclear or you just want to bounce balance ideas off of me, don't be shy. I'm always happy to help.