--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,105 +1,399 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
//
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// ModuleLimitedDataTransmitter.cs
//
-// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-// Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014-2015, toadicus
+// All rights reserved.
//
-// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
-// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
//
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
//
-// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
-
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+using KSP;
using System;
using System.Collections.Generic;
-using System.Linq;
-using KSP;
+using System.Text;
+using ToadicusTools.DebugTools;
+using ToadicusTools.Extensions;
+using ToadicusTools.Text;
using UnityEngine;
namespace AntennaRange
{
- /*
- * ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-
- * finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with
- * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
- *
- * In general, the scaling functions assume the following relation:
- *
- * D² α P/R,
- *
- * where D is the total transmission distance, P is the transmission power, and R is the data rate.
- *
- * */
-
- /*
- * Fields
- * */
- public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
+ ///
+ /// 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
+ /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
+ ///
+ /// In general, the scaling functions assume the following relation:
+ ///
+ /// D² α P/R,
+ ///
+ /// where D is the total transmission distance, P is the transmission power, and R is the data rate.
+ ///
+ public class ModuleLimitedDataTransmitter
+ : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay, IModuleInfo
{
- // Stores the packetResourceCost as defined in the .cfg file.
- protected float _basepacketResourceCost;
-
- // Stores the packetSize as defined in the .cfg file.
- protected float _basepacketSize;
+ private const string tooltipSkinName = "PartTooltipSkin";
+ private static GUISkin partTooltipSkin;
+ private static GUIStyle partTooltipBodyStyle;
+ private static GUIStyle partTooltipHeaderStyle;
// Every antenna is a relay.
- protected AntennaRelay relay;
-
- // Keep track of vessels with transmitters for relay purposes.
- protected List _relayVessels;
+ private AntennaRelay relay;
// Sometimes we will need to communicate errors; this is how we do it.
- protected ScreenMessage ErrorMsg;
-
- // Let's make the error text pretty!
- protected UnityEngine.GUIStyle ErrorStyle;
-
- // The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
- // and packetSize.
+ private ScreenMessage ErrorMsg;
+
+ // Used in module info panes for part tooltips in the editor and R&D
+ private GUIContent moduleInfoContent;
+
+ ///
+ /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
+ /// prescribed by packetResourceCost and packetSize.
+ ///
[KSPField(isPersistant = false)]
- public float nominalRange;
-
- // The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
- // cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+ public double nominalRange;
+
+ ///
+ /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
+ /// prescribed by packetResourceCost and packetSize.
+ ///
+ [KSPField(isPersistant = false)]
+ public double simpleRange;
+
+ ///
+ /// Relay status string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
+ public string UIrelayStatus;
+
+ ///
+ /// Relay target string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
+ public string UIrelayTarget;
+
+ ///
+ /// Transmit distance string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
+ public string UItransmitDistance;
+
+ ///
+ /// The nominal range string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
+ public string UInominalLinkDistance;
+
+ ///
+ /// Maximum distance string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
+ public string UImaxTransmitDistance;
+
+ ///
+ /// Packet size string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
+ public string UIpacketSize;
+
+ ///
+ /// Packet cost string for use in action menus.
+ ///
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
+ public string UIpacketCost;
+
+ ///
+ /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
+ /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+ ///
[KSPField(isPersistant = false)]
public float maxPowerFactor;
- // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+ ///
+ /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+ ///
[KSPField(isPersistant = false)]
public float maxDataFactor;
+
+ ///
+ /// The packet throttle.
+ ///
+ [KSPField(
+ isPersistant = true,
+ guiName = "Packet Throttle",
+ guiUnits = "%",
+ guiActive = true,
+ guiActiveEditor = false
+ )]
+ [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
+ public float packetThrottle;
+
+ private bool actionUIUpdate;
/*
* Properties
* */
- // Returns the parent vessel housing this antenna.
+ ///
+ /// Gets the parent Vessel.
+ ///
public new Vessel vessel
{
get
{
- return base.vessel;
- }
- }
-
- // Returns the distance to the nearest relay or Kerbin, whichever is closer.
- public double transmitDistance
- {
- get
- {
- return this.relay.transmitDistance;
- }
- }
-
- // Returns the maximum distance this module can transmit
- public float maxTransmitDistance
- {
- get
- {
- return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
+ if (base.vessel != null)
+ {
+ return base.vessel;
+ }
+ else if (this.part != null && this.part.vessel != null)
+ {
+ return this.part.vessel;
+ }
+ else if (
+ this.part.protoPartSnapshot != null &&
+ this.part.protoPartSnapshot.pVesselRef != null &&
+ this.part.protoPartSnapshot.pVesselRef.vesselRef != null
+ )
+ {
+ return this.part.protoPartSnapshot.pVesselRef.vesselRef;
+ }
+ else
+ {
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the data capacity of a packet, in MiT/packet
+ ///
+ /// The data capacity of a packet, in MiT/packet
+ public float PacketSize
+ {
+ get
+ {
+ return this.packetSize;
+ }
+ set
+ {
+ this.packetSize = value;
+ }
+ }
+
+ ///
+ /// Gets the base data capacity of a packet, in MiT/packet
+ ///
+ /// The base data capacity of a packet, in MiT/packet
+ public float BasePacketSize
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets or sets the resource cost of a packet, in EC/packet
+ ///
+ /// The resource cost of a packet, in EC/packet
+ public float PacketResourceCost
+ {
+ get
+ {
+ return this.packetResourceCost;
+ }
+ set
+ {
+ this.packetResourceCost = value;
+ }
+ }
+
+ ///
+ /// Gets the base resource cost of a packet, in EC/packet
+ ///
+ /// The base resource cost of a packet, in EC/packet
+ public float BasePacketResourceCost
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the packet throttle.
+ ///
+ /// The packet throttle in range [0..100].
+ public float PacketThrottle
+ {
+ get
+ {
+ return this.packetThrottle;
+ }
+ }
+
+ ///
+ /// Gets the max data factor.
+ ///
+ /// The max data factor.
+ public float MaxDataFactor
+ {
+ get
+ {
+ return this.maxDataFactor;
+ }
+ }
+
+ ///
+ /// Gets the target relay.
+ ///
+ public IAntennaRelay targetRelay
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return null;
+ }
+
+ return this.relay.targetRelay;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether this Relay is communicating
+ /// directly with Kerbin.
+ ///
+ public bool KerbinDirect
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.KerbinDirect;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// Gets or sets the nominal link distance, in meters.
+ ///
+ public double NominalLinkSqrDistance
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.NominalLinkSqrDistance;
+ }
+
+ return 0d;
+ }
+ }
+
+ ///
+ /// Gets or sets the maximum link distance, in meters.
+ ///
+ public double MaximumLinkSqrDistance
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.MaximumLinkSqrDistance;
+ }
+
+ return 0d;
+ }
+ }
+
+ ///
+ /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
+ ///
+ public double CurrentLinkSqrDistance
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return double.PositiveInfinity;
+ }
+
+ return this.relay.CurrentLinkSqrDistance;
+ }
+ }
+
+ ///
+ /// Gets the link status.
+ ///
+ public ConnectionStatus LinkStatus
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return ConnectionStatus.None;
+ }
+
+ return this.relay.LinkStatus;
+ }
+ }
+
+ ///
+ /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
+ ///
+ public double nominalTransmitDistance
+ {
+ get
+ {
+ if (ARConfiguration.UseAdditiveRanges)
+ {
+ return this.nominalRange;
+ }
+ else
+ {
+ return this.simpleRange;
+ }
+ }
+ }
+
+ ///
+ /// The maximum distance at which this relay can operate.
+ ///
+ public double maxTransmitDistance
+ {
+ get;
+ protected set;
+ }
+
+ ///
+ /// The first CelestialBody blocking line of sight to a
+ ///
+ public CelestialBody firstOccludingBody
+ {
+ get
+ {
+ return this.relay.firstOccludingBody;
}
}
@@ -129,50 +423,70 @@
*
* So... hopefully that doesn't screw with anything else.
* */
- // Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
- // terms of joules/byte
+ ///
+ /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
+ /// terms of joules/byte
+ ///
public new float DataRate
{
get
{
- this.PreTransmit_SetPacketSize();
-
- if (this.CanTransmit())
- {
- return this.packetSize;
- }
- else
- {
- return float.Epsilon;
- }
- }
- }
-
- // Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
- // to be scored in terms of joules/byte
- public new float DataResourceCost
- {
- get
- {
- this.PreTransmit_SetPacketResourceCost();
-
- if (this.CanTransmit())
- {
- return this.packetResourceCost;
- }
- else
+ if (this.relay == null)
{
return float.PositiveInfinity;
}
- }
- }
-
- // Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
- public bool relayChecked
- {
- get
- {
- return this.relay.relayChecked;
+
+ return this.relay.DataRate;
+ }
+ }
+
+ ///
+ /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+ /// to be scored in terms of joules/byte
+ ///
+ public new double DataResourceCost
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return double.PositiveInfinity;
+ }
+
+ return this.relay.DataResourceCost;
+ }
+ }
+
+ ///
+ /// Gets the current network resource rate in EC/MiT.
+ ///
+ /// The current network resource rate in EC/MiT.
+ public RelayDataCost CurrentNetworkLinkCost
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return RelayDataCost.Infinity;
+ }
+
+ return this.relay.CurrentNetworkLinkCost;
+ }
+ }
+
+ ///
+ /// Gets the Part title.
+ ///
+ public string Title
+ {
+ get
+ {
+ if (this.part != null && this.part.partInfo != null)
+ {
+ return this.part.partInfo.title;
+ }
+
+ return string.Empty;
}
}
@@ -182,32 +496,65 @@
// Build ALL the objects.
public ModuleLimitedDataTransmitter () : base()
{
- // Make the error posting prettier.
- this.ErrorStyle = new UnityEngine.GUIStyle();
- this.ErrorStyle.normal.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
- this.ErrorStyle.active.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
- this.ErrorStyle.hover.textColor = (UnityEngine.Color)XKCDColors.OrangeRed;
- this.ErrorStyle.fontStyle = UnityEngine.FontStyle.Normal;
- this.ErrorStyle.padding.top = 32;
-
- this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT, this.ErrorStyle);
- }
-
- // At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
+ this.ErrorMsg = new ScreenMessage("", 4f, ScreenMessageStyle.UPPER_LEFT);
+ this.packetThrottle = 100f;
+ }
+
+ ///
+ /// PartModule OnAwake override; runs at Unity Awake.
+ ///
+ public override void OnAwake()
+ {
+ base.OnAwake();
+
+ this.BasePacketSize = base.packetSize;
+ this.BasePacketResourceCost = base.packetResourceCost;
+ this.moduleInfoContent = new GUIContent();
+
+ this.LogDebug("{0} loaded:\n" +
+ "packetSize: {1}\n" +
+ "packetResourceCost: {2}\n" +
+ "nominalTransmitDistance: {3}\n" +
+ "maxPowerFactor: {4}\n" +
+ "maxDataFactor: {5}\n",
+ this,
+ base.packetSize,
+ this.BasePacketResourceCost,
+ this.nominalTransmitDistance,
+ this.maxPowerFactor,
+ this.maxDataFactor
+ );
+ }
+
+ ///
+ /// PartModule OnStart override; runs at Unity Start.
+ ///
+ /// State.
public override void OnStart (StartState state)
{
base.OnStart (state);
+ this.RecalculateMaxRange();
+
if (state >= StartState.PreLaunch)
{
- this.relay = new AntennaRelay(vessel);
+ this.relay = new AntennaRelay(this);
+ this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
this.relay.maxTransmitDistance = this.maxTransmitDistance;
- }
- }
-
- // When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
- // overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
- // work.
+
+ this.UImaxTransmitDistance = TextTools.Format("{0:S3}m", this.maxTransmitDistance);
+
+ GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
+ GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
+ }
+ }
+
+ ///
+ /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
+ /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
+ /// work.
+ ///
+ /// with data for this module.
public override void OnLoad(ConfigNode node)
{
this.Fields.Load(node);
@@ -215,183 +562,513 @@
base.OnLoad (node);
- this._basepacketSize = base.packetSize;
- this._basepacketResourceCost = base.packetResourceCost;
-
- Tools.PostDebugMessage(string.Format(
- "{0} loaded:\n" +
- "packetSize: {1}\n" +
- "packetResourceCost: {2}\n" +
- "nominalRange: {3}\n" +
- "maxPowerFactor: {4}\n" +
- "maxDataFactor: {5}\n",
- this.name,
- base.packetSize,
- this._basepacketResourceCost,
- this.nominalRange,
- this.maxPowerFactor,
- this.maxDataFactor
- ));
- }
-
- // Post an error in the communication messages describing the reason transmission has failed. Currently there
- // is only one reason for this.
- protected void PostCannotTransmitError()
- {
- string ErrorText = string.Format (
- "Unable to transmit: out of range! Maximum range = {0}m; Current range = {1}m.",
- Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2),
- Tools.MuMech_ToSI((double)this.transmitDistance, 2)
+ this.RecalculateMaxRange();
+ }
+
+ ///
+ /// Gets the human-friendly module title.
+ ///
+ public string GetModuleTitle()
+ {
+ return "Comms Transceiver";
+ }
+
+ ///
+ /// Returns drawTooltipWidget as a callback for part tooltips.
+ ///
+ public Callback 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)
+ );
+ }
+
+ ///
+ /// Returns an empty string, because we don't really have a "primary field" like some modules do.
+ ///
+ public string GetPrimaryField()
+ {
+ return string.Empty;
+ }
+
+ ///
+ /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Determines whether this instance can transmit.
+ /// true if this instance can transmit; otherwise, false.
+ ///
+ 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();
+ }
+
+ ///
+ /// Recalculates the transmission rates.
+ ///
+ public void RecalculateTransmissionRates()
+ {
+ if (this.relay != null)
+ {
+ this.relay.RecalculateTransmissionRates();
+ }
+ }
+
+ ///
+ /// Finds the nearest relay.
+ ///
+ public void FindNearestRelay()
+ {
+ if (this.relay != null)
+ {
+ this.relay.FindNearestRelay();
+ }
+ }
+
+ ///
+ /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
+ /// returns false.
+ ///
+ /// List of to transmit.
+ public new void TransmitData(List dataQueue)
+ {
+ this.LogDebug(
+ "TransmitData(List 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()
);
- this.ErrorMsg.message = ErrorText;
-
- ScreenMessages.PostScreenMessage(this.ErrorMsg, true);
- }
-
- // Before transmission, set packetResourceCost. Per above, packet cost increases with the square of
- // distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
- // transmission fails (see CanTransmit).
- protected void PreTransmit_SetPacketResourceCost()
- {
- if (this.transmitDistance <= this.nominalRange)
- {
- base.packetResourceCost = this._basepacketResourceCost;
+ base.TransmitData(dataQueue);
}
else
{
- base.packetResourceCost = this._basepacketResourceCost
- * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
- }
- }
-
- // Before transmission, set packetSize. Per above, packet size increases with the inverse square of
- // distance. packetSize maxes out at _basepacketSize * maxDataFactor.
- protected void PreTransmit_SetPacketSize()
- {
- if (this.transmitDistance >= this.nominalRange)
- {
- base.packetSize = this._basepacketSize;
- }
- else
- {
- base.packetSize = Math.Min(
- this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
- this._basepacketSize * this.maxDataFactor);
- }
- }
-
- // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
- public override string GetInfo()
- {
- string text = base.GetInfo();
- text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
- text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
- return text;
- }
-
- // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
- public new bool CanTransmit()
- {
- return this.relay.CanTransmit();
- }
-
- // Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
- // returns false.
- public new void TransmitData(List dataQueue)
- {
- if (this.CanTransmit())
- {
- base.TransmitData(dataQueue);
- }
- else
- {
- this.PostCannotTransmitError ();
- }
-
- Tools.PostDebugMessage (
- "distance: " + this.transmitDistance
+ this.LogDebug("{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+ var logger = PooledDebugLogger.New(this);
+
+ IList vesselContainers = this.vessel.getModulesOfType();
+ ModuleScienceContainer scienceContainer;
+ for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
+ {
+ scienceContainer = vesselContainers[cIdx];
+
+ logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
+ scienceContainer.part.partInfo.title);
+
+ if (
+ scienceContainer.capacity != 0 &&
+ scienceContainer.GetScienceCount() >= scienceContainer.capacity
+ )
+ {
+ logger.Append("\tInsufficient capacity, skipping.\n");
+ continue;
+ }
+
+ List dataStored = new List();
+
+ ScienceData data;
+ for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
+ {
+ data = dataQueue[dIdx];
+ if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
+ {
+ logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
+ continue;
+ }
+
+ logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
+ if (scienceContainer.AddData(data))
+ {
+ logger.Append("done, removing from queue.\n");
+
+ dataStored.Add(data);
+ }
+ #if DEBUG
+ else
+ {
+ logger.Append("failed.\n");
+ }
+ #endif
+ }
+
+ dataQueue.RemoveAll(i => dataStored.Contains(i));
+
+ logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
+ }
+
+ logger.Print();
+
+ if (dataQueue.Count > 0)
+ {
+ using (PooledStringBuilder sb = PooledStringBuilder.Get())
+ {
+ 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());
+ }
+ }
+
+ this.PostCannotTransmitError();
+ }
+
+ this.LogDebug(
+ "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost
);
}
- // Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
- // returns false.
+ ///
+ /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
+ /// returns false.
+ ///
public new void StartTransmission()
{
- PreTransmit_SetPacketSize ();
- PreTransmit_SetPacketResourceCost ();
-
- Tools.PostDebugMessage (
- "distance: " + this.transmitDistance
+ this.LogDebug(
+ "distance: " + this.CurrentLinkSqrDistance
+ " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost
);
if (this.CanTransmit())
{
- string message;
-
- message = "Beginning transmission ";
-
- if (this.relay.nearestRelay == null)
- {
- message += "directly to Kerbin.";
+ ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+ base.StartTransmission();
+ }
+ else
+ {
+ this.PostCannotTransmitError ();
+ }
+ }
+
+ ///
+ /// MonoBehaviour Update
+ ///
+ public void Update()
+ {
+ 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())
+ {
+ this.UIrelayStatus = this.LinkStatus.ToString();
+ this.UItransmitDistance = TextTools.Format("{0:S3}m",
+ Math.Sqrt(this.CurrentLinkSqrDistance));
+ this.UIpacketSize = TextTools.Format("{0:S3}MiT", this.DataRate);
+ this.UIpacketCost = TextTools.Format("{0:S3}EC", this.DataResourceCost);
}
else
{
- message += "via relay " + this.relay.nearestRelay;
- }
-
- ScreenMessages.PostScreenMessage(message, 4f, ScreenMessageStyle.UPPER_LEFT);
-
- base.StartTransmission();
- }
- else
- {
- this.PostCannotTransmitError ();
- }
- }
-
+ if (this.relay.firstOccludingBody == null)
+ {
+ this.UItransmitDistance = TextTools.Format("{0:S3}m",
+ Math.Sqrt(this.CurrentLinkSqrDistance));
+ this.UIrelayStatus = "Out of range";
+ }
+ else
+ {
+ this.UItransmitDistance = "N/A";
+ this.UIrelayStatus = TextTools.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+ }
+ this.UIpacketSize = "N/A";
+ this.UIpacketCost = "N/A";
+ }
+
+ if (this.KerbinDirect)
+ {
+ this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
+ }
+ else
+ {
+ if (this.targetRelay != null)
+ {
+ this.UIrelayTarget = this.targetRelay.ToString();
+ }
+ else
+ {
+ this.UIrelayTarget = "A mysterious null entity";
+ }
+ }
+ }
+ }
+
+ ///
+ /// Recalculates the max range; useful for making sure we're using additive ranges when enabled.
+ ///
+ 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
+ }
+
+ ///
+ /// Returns a that represents the current .
+ ///
+ /// A that represents the current .
+ 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
+ {
+ sb.Append(this.GetType().Name);
+ }
+
+ if (vessel != null)
+ {
+ 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)
+ {
+ this.actionUIUpdate = true;
+ }
+ }
+
+ // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
+ private void onPartActionUIDismiss(Part eventPart)
+ {
+ if (eventPart == base.part)
+ {
+ this.actionUIUpdate = false;
+ }
+ }
+
+ // Post an error in the communication messages describing the reason transmission has failed. Currently there
+ // is only one reason for this.
+ private void PostCannotTransmitError()
+ {
+ string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
+
+ this.ErrorMsg.message = string.Format(
+ "{4}",
+ ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"),
+ ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"),
+ ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"),
+ ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"),
+ ErrorText
+ );
+
+ 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.
- #if DEBUG
[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
public void DebugInfo()
{
- PreTransmit_SetPacketSize ();
- PreTransmit_SetPacketResourceCost ();
-
- string msg = string.Format(
- "'{0}'\n" +
- "_basepacketSize: {1}\n" +
- "packetSize: {2}\n" +
- "_basepacketResourceCost: {3}\n" +
- "packetResourceCost: {4}\n" +
- "maxTransmitDistance: {5}\n" +
- "transmitDistance: {6}\n" +
- "nominalRange: {7}\n" +
- "CanTransmit: {8}\n" +
- "DataRate: {9}\n" +
- "DataResourceCost: {10}\n" +
- "TransmitterScore: {11}\n" +
- "NearestRelay: {12}",
- this.name,
- this._basepacketSize,
- base.packetSize,
- this._basepacketResourceCost,
- base.packetResourceCost,
- this.maxTransmitDistance,
- this.transmitDistance,
- this.nominalRange,
- this.CanTransmit(),
- this.DataRate,
- this.DataResourceCost,
- ScienceUtil.GetTransmitterScore(this),
- this.relay.FindNearestRelay()
- );
- ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT));
- }
+ if (this.relay != null)
+ this.relay.RecalculateTransmissionRates();
+
+ DebugPartModule.DumpClassObject(this);
+ }
+
+ [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
+ public void PrintAllVessels()
+ {
+ using (PooledStringBuilder sb = PooledStringBuilder.Get())
+ {
+ sb.Append("Dumping FlightGlobals.Vessels:");
+
+ Vessel vessel;
+ for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+ {
+ vessel = FlightGlobals.Vessels[i];
+ sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
+ }
+
+ ToadicusTools.Logging.PostDebugMessage(sb.ToString());
+ }
+ }
+
+ /*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
+ public void DumpRelayDB()
+ {
+ RelayDatabase.Instance.Dump();
+ }*/
#endif
}
}