--- a/ModuleLimitedDataTransmitter.cs +++ b/ModuleLimitedDataTransmitter.cs @@ -1,24 +1,37 @@ -// 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, 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; using UnityEngine; namespace AntennaRange @@ -41,9 +54,9 @@ * */ public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay { - // Call this an antenna so that you don't have to. - [KSPField(isPersistant = true)] - protected bool IsAntenna; + // If true, use a fixed power cost at the configured value and degrade data rates instead of increasing power + // requirements. + public static bool fixedPowerCost; // Stores the packetResourceCost as defined in the .cfg file. protected float _basepacketResourceCost; @@ -59,15 +72,27 @@ // 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. [KSPField(isPersistant = false)] public float nominalRange; + [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] + public string UIrelayStatus; + + [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] + public string UItransmitDistance; + + [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")] + public string UImaxTransmitDistance; + + [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] + public string UIpacketSize; + + [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)] @@ -76,6 +101,18 @@ // The multipler on packetSize that defines the maximum data bandwidth of the antenna. [KSPField(isPersistant = false)] public float maxDataFactor; + + [KSPField( + isPersistant = true, + guiName = "Packet Throttle", + guiUnits = "%", + guiActive = true, + guiActiveEditor = false + )] + [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)] + public float packetThrottle; + + protected bool actionUIUpdate; /* * Properties @@ -140,7 +177,15 @@ get { this.PreTransmit_SetPacketSize(); - return this.packetSize; + + if (this.CanTransmit()) + { + return this.packetSize; + } + else + { + return float.Epsilon; + } } } @@ -178,15 +223,8 @@ // 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); + this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT); + this.packetThrottle = 100f; } // At least once, when the module starts with a state on the launch pad or later, go find Kerbin. @@ -196,8 +234,13 @@ if (state >= StartState.PreLaunch) { - this.relay = new AntennaRelay(vessel); + this.relay = new AntennaRelay(this); this.relay.maxTransmitDistance = this.maxTransmitDistance; + + this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m"; + + GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); + GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); } } @@ -208,8 +251,6 @@ { this.Fields.Load(node); base.Fields.Load(node); - - this.IsAntenna = true; base.OnLoad (node); @@ -236,15 +277,20 @@ // 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.ErrorMsg.message = ErrorText; - - ScreenMessages.PostScreenMessage(this.ErrorMsg, true); + string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!"); + + this.ErrorMsg.message = string.Format( + "<color='#{0}{1}{2}{3}'><b>{4}</b></color>", + ((int)(XKCDColors.OrangeRed.r * 255f)).ToString("x2"), + ((int)(XKCDColors.OrangeRed.g * 255f)).ToString("x2"), + ((int)(XKCDColors.OrangeRed.b * 255f)).ToString("x2"), + ((int)(XKCDColors.OrangeRed.a * 255f)).ToString("x2"), + ErrorText + ); + + Tools.PostDebugMessage(this.GetType().Name + ": " + this.ErrorMsg.message); + + ScreenMessages.PostScreenMessage(this.ErrorMsg, false); } // Before transmission, set packetResourceCost. Per above, packet cost increases with the square of @@ -252,7 +298,7 @@ // transmission fails (see CanTransmit). protected void PreTransmit_SetPacketResourceCost() { - if (this.transmitDistance <= this.nominalRange) + if (fixedPowerCost || this.transmitDistance <= this.nominalRange) { base.packetResourceCost = this._basepacketResourceCost; } @@ -261,13 +307,15 @@ base.packetResourceCost = this._basepacketResourceCost * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2); } + + base.packetResourceCost *= this.packetThrottle / 100f; } // Before transmission, set packetSize. Per above, packet size increases with the inverse square of // distance. packetSize maxes out at _basepacketSize * maxDataFactor. protected void PreTransmit_SetPacketSize() { - if (this.transmitDistance >= this.nominalRange) + if (!fixedPowerCost && this.transmitDistance >= this.nominalRange) { base.packetSize = this._basepacketSize; } @@ -277,6 +325,8 @@ this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2), this._basepacketSize * this.maxDataFactor); } + + base.packetSize *= this.packetThrottle / 100f; } // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description. @@ -291,6 +341,23 @@ // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. public new bool CanTransmit() { + if (this.part == null || this.relay == null) + { + return false; + } + + PartStates partState = this.part.State; + if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) + { + Tools.PostDebugMessage(string.Format( + "{0}: {1} on {2} cannot transmit: {3}", + this.GetType().Name, + this.part.partInfo.title, + this.vessel.vesselName, + Enum.GetName(typeof(PartStates), partState) + )); + return false; + } return this.relay.CanTransmit(); } @@ -298,12 +365,104 @@ // returns false. public new void TransmitData(List<ScienceData> dataQueue) { + this.PreTransmit_SetPacketSize(); + this.PreTransmit_SetPacketResourceCost(); + if (this.CanTransmit()) { + StringBuilder message = new StringBuilder(); + + message.Append("["); + message.Append(base.part.partInfo.title); + message.Append("]: "); + + message.Append("Beginning transmission "); + + if (this.relay.nearestRelay == null) + { + message.Append("directly to Kerbin."); + } + else + { + message.Append("via "); + message.Append(this.relay.nearestRelay); + } + + ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); + base.TransmitData(dataQueue); } else { + Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); + + var logger = Tools.DebugLogger.New(this); + + foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>()) + { + logger.AppendFormat("Checking ModuleScienceContainer in {0}\n", + scienceContainer.part.partInfo.title); + + if ( + scienceContainer.capacity != 0 && + scienceContainer.GetScienceCount() >= scienceContainer.capacity + ) + { + logger.Append("\tInsufficient capacity, skipping.\n"); + continue; + } + + List<ScienceData> dataStored = new List<ScienceData>(); + + foreach (ScienceData data in dataQueue) + { + if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data)) + { + logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n"); + continue; + } + + logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID); + if (scienceContainer.AddData(data)) + { + logger.Append("done, removing from queue.\n"); + + dataStored.Add(data); + } + #if DEBUG + else + { + logger.Append("failed.\n"); + } + #endif + } + + dataQueue.RemoveAll(i => dataStored.Contains(i)); + + logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count); + } + + logger.Print(); + + if (dataQueue.Count > 0) + { + StringBuilder msg = new StringBuilder(); + + msg.Append('['); + msg.Append(this.part.partInfo.title); + msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); + msg.Append("Data to be discarded:\n"); + + foreach (ScienceData data in dataQueue) + { + msg.AppendFormat("\n{0}\n", data.title); + } + + ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); + + Tools.PostDebugMessage(msg.ToString()); + } + this.PostCannotTransmitError (); } @@ -329,18 +488,25 @@ if (this.CanTransmit()) { - this.ErrorMsg.message = "Beginning transmission "; + StringBuilder message = new StringBuilder(); + + message.Append("["); + message.Append(base.part.partInfo.title); + message.Append("]: "); + + message.Append("Beginning transmission "); if (this.relay.nearestRelay == null) { - this.ErrorMsg.message += "directly to Kerbin."; + message.Append("directly to Kerbin."); } else { - this.ErrorMsg.message += "via relay " + this.relay.nearestRelay; - } - - ScreenMessages.PostScreenMessage(this.ErrorMsg); + message.Append("via "); + message.Append(this.relay.nearestRelay); + } + + ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); base.StartTransmission(); } @@ -348,6 +514,74 @@ { this.PostCannotTransmitError (); } + } + + public void Update() + { + if (this.actionUIUpdate) + { + if (this.CanTransmit()) + { + this.UIrelayStatus = string.Format("Connected via {0}", this.relay); + this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; + this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; + this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; + } + else + { + if (this.relay.firstOccludingBody == null) + { + this.UIrelayStatus = string.Intern("Out of range"); + } + else + { + if (this.relay.losStatus == LineOfSightStatus.Blocked) + { + this.UIrelayStatus = + string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); + } + else if (this.relay.losStatus == LineOfSightStatus.Marginal) + { + this.UIrelayStatus = + string.Format("Almost blocked by {0}", this.relay.firstOccludingBody.bodyName); + } + } + this.UImaxTransmitDistance = "N/A"; + this.UIpacketSize = "N/A"; + this.UIpacketCost = "N/A"; + } + } + } + + public void onPartActionUICreate(Part eventPart) + { + if (eventPart == base.part) + { + this.actionUIUpdate = true; + } + } + + public void onPartActionUIDismiss(Part eventPart) + { + if (eventPart == base.part) + { + this.actionUIUpdate = false; + } + } + + public override string ToString() + { + StringBuilder msg = new StringBuilder(); + + msg.Append(this.part.partInfo.title); + + if (vessel != null) + { + msg.Append(" on "); + msg.Append(vessel.vesselName); + } + + return msg.ToString(); } // When debugging, it's nice to have a button that just tells you everything. @@ -371,7 +605,8 @@ "DataRate: {9}\n" + "DataResourceCost: {10}\n" + "TransmitterScore: {11}\n" + - "NearestRelay: {12}", + "NearestRelay: {12}\n" + + "Vessel ID: {13}", this.name, this._basepacketSize, base.packetSize, @@ -384,9 +619,31 @@ this.DataRate, this.DataResourceCost, ScienceUtil.GetTransmitterScore(this), - this.relay.FindNearestRelay() + this.relay.FindNearestRelay(), + this.vessel.id ); - ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 4f, ScreenMessageStyle.UPPER_RIGHT)); + Tools.PostDebugMessage(msg); + } + + [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] + public void PrintAllVessels() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("Dumping FlightGlobals.Vessels:"); + + foreach (Vessel vessel in FlightGlobals.Vessels) + { + sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id); + } + + Tools.PostDebugMessage(sb.ToString()); + } + + [KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)] + public void DumpRelayDB() + { + RelayDatabase.Instance.Dump(); } #endif }