--- 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 } }