Version 0.6.3
--- /dev/null
+++ b/ARTools.cs
@@ -1,1 +1,130 @@
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+using System;
+
+namespace AntennaRange
+{
+ public static class Tools
+ {
+ private static ScreenMessage debugmsg = new ScreenMessage("", 2f, ScreenMessageStyle.UPPER_RIGHT);
+ // Function that posts messages to the screen and the log when DEBUG is defined.
+ [System.Diagnostics.Conditional("DEBUG")]
+ public static void PostDebugMessage(string Msg)
+ {
+ if (HighLogic.LoadedScene > GameScenes.SPACECENTER)
+ {
+ debugmsg.message = Msg;
+ ScreenMessages.PostScreenMessage(debugmsg, true);
+ }
+
+ KSPLog.print(Msg);
+ }
+
+ /*
+ * MuMech_ToSI is a part of the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+ * */
+ public static string MuMech_ToSI(double d, int digits = 3, int MinMagnitude = 0, int MaxMagnitude = int.MaxValue)
+ {
+ float exponent = (float)Math.Log10(Math.Abs(d));
+ exponent = UnityEngine.Mathf.Clamp(exponent, (float)MinMagnitude, (float)MaxMagnitude);
+
+ if (exponent >= 0)
+ {
+ switch ((int)Math.Floor(exponent))
+ {
+ case 0:
+ case 1:
+ case 2:
+ return d.ToString("F" + digits);
+ case 3:
+ case 4:
+ case 5:
+ return (d / 1e3).ToString("F" + digits) + "k";
+ case 6:
+ case 7:
+ case 8:
+ return (d / 1e6).ToString("F" + digits) + "M";
+ case 9:
+ case 10:
+ case 11:
+ return (d / 1e9).ToString("F" + digits) + "G";
+ case 12:
+ case 13:
+ case 14:
+ return (d / 1e12).ToString("F" + digits) + "T";
+ case 15:
+ case 16:
+ case 17:
+ return (d / 1e15).ToString("F" + digits) + "P";
+ case 18:
+ case 19:
+ case 20:
+ return (d / 1e18).ToString("F" + digits) + "E";
+ case 21:
+ case 22:
+ case 23:
+ return (d / 1e21).ToString("F" + digits) + "Z";
+ default:
+ return (d / 1e24).ToString("F" + digits) + "Y";
+ }
+ }
+ else if (exponent < 0)
+ {
+ switch ((int)Math.Floor(exponent))
+ {
+ case -1:
+ case -2:
+ case -3:
+ return (d * 1e3).ToString("F" + digits) + "m";
+ case -4:
+ case -5:
+ case -6:
+ return (d * 1e6).ToString("F" + digits) + "μ";
+ case -7:
+ case -8:
+ case -9:
+ return (d * 1e9).ToString("F" + digits) + "n";
+ case -10:
+ case -11:
+ case -12:
+ return (d * 1e12).ToString("F" + digits) + "p";
+ case -13:
+ case -14:
+ case -15:
+ return (d * 1e15).ToString("F" + digits) + "f";
+ case -16:
+ case -17:
+ case -18:
+ return (d * 1e18).ToString("F" + digits) + "a";
+ case -19:
+ case -20:
+ case -21:
+ return (d * 1e21).ToString("F" + digits) + "z";
+ default:
+ return (d * 1e24).ToString("F" + digits) + "y";
+ }
+ }
+ else
+ {
+ return "0";
+ }
+ }
+ }
+}
+
+
--- a/AntennaRange.cfg
+++ b/AntennaRange.cfg
@@ -1,50 +1,50 @@
-//
-// AntennaRange © 2013 toadicus
-//
-// 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/
-//
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
-//
-// Specifications:
-// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
-// and packetSize.
-// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
-// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
-// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
-//
-
-@PART[longAntenna]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 1500000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
-
-@PART[mediumDishAntenna]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 30000000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
-
-@PART[commDish]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 80000000000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
+//
+// AntennaRange © 2013 toadicus
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// Specifications:
+// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
+// and packetSize.
+// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
+// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+//
+@PART[longAntenna]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ nominalRange = 1500000
+ maxPowerFactor = 8
+ maxDataFactor = 4
+ }
+}
+
+@PART[mediumDishAntenna]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ nominalRange = 30000000
+ maxPowerFactor = 8
+ maxDataFactor = 4
+ }
+}
+
+@PART[commDish]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ nominalRange = 80000000000
+ maxPowerFactor = 8
+ maxDataFactor = 4
+ }
+}
+
--- a/AntennaRange.cs
+++ /dev/null
@@ -1,256 +1,1 @@
-/*
- * AntennaRange © 2013 toadicus
- *
- * AntennaRange provides incentive and requirements for the use of the various antenna parts.
- * Nominally, the breakdown is as follows:
- *
- * Communotron 16 - Suitable up to Kerbalsynchronous Orbit
- * Comms DTS-M1 - Suitable throughout the Kerbin subsystem
- * Communotron 88-88 - Suitable throughout the Kerbol system.
- *
- * 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/
- *
- * This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike 3.0 Uported License.
- *
- */
-
-using System;
-using System.Collections.Generic;
-using KSP;
-
-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.
- *
- * */
- public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter
- {
- // Stores the packetResourceCost as defined in the .cfg file.
- protected float _basepacketResourceCost;
-
- // Stores the packetSize as defined in the .cfg file.
- protected float _basepacketSize;
-
- // We don't have a Bard, so we're hiding Kerbin here.
- protected CelestialBody _Kerbin;
-
- // Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes.
- protected double transmitDistance
- {
- get
- {
- Vector3d KerbinPos = this._Kerbin.position;
- Vector3d ActivePos = base.vessel.GetWorldPos3D();
-
- return (ActivePos - KerbinPos).magnitude;
- }
- }
-
- // Returns the maximum distance this module can transmit
- public double maxTransmitDistance
- {
- get
- {
- return Math.Sqrt (this.maxPowerFactor) * this.nominalRange;
- }
- }
-
- // The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
- // and packetSize.
- [KSPField(isPersistant = false)]
- public double nominalRange = 1500000d;
-
- // 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 = 8f;
-
- // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
- [KSPField(isPersistant = false)]
- public float maxDataFactor = 4f;
-
- // Build ALL the objects.
- public ModuleLimitedDataTransmitter () : base() { }
-
- public override void OnStart (StartState state)
- {
- base.OnStart (state);
-
- if (state >= StartState.PreLaunch && this._Kerbin == null)
- {
- // Go fetch Kerbin, because it is tricksy and hides from us.
- List<CelestialBody> bodies = FlightGlobals.Bodies;
-
- foreach (CelestialBody body in bodies)
- {
- if (body.name == "Kerbin")
- {
- this._Kerbin = body;
- break;
- }
- }
- }
- }
-
- public override void OnLoad(ConfigNode node)
- {
- base.OnLoad (node);
- this._basepacketSize = base.packetSize;
- this._basepacketResourceCost = base.packetResourceCost;
- }
-
- // 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}; Current range = {1}.",
- this.maxTransmitDistance,
- this.transmitDistance);
- ScreenMessages.PostScreenMessage (new ScreenMessage (ErrorText, 4f, ScreenMessageStyle.UPPER_LEFT));
- }
-
- protected void PreTransmit_SetPacketResourceCost()
- {
- if (this.transmitDistance <= this.nominalRange)
- {
- base.packetResourceCost = this._basepacketResourceCost;
- }
- else
- {
- base.packetResourceCost = this._basepacketResourceCost
- * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
- }
- }
-
- 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: " + this.nominalRange.ToString() + "\n";
- text += "Maximum Range: " + this.maxTransmitDistance.ToString() + "\n";
- return text;
- }
-
- // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
- public new bool CanTransmit()
- {
- if (this.transmitDistance > this.maxTransmitDistance)
- {
- return false;
- }
- return true;
- }
-
- // Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
- // returns false.
- public new void TransmitData(List<ScienceData> dataQueue)
- {
- PreTransmit_SetPacketSize ();
- PreTransmit_SetPacketResourceCost ();
-
- Tools.PostDebugMessage (
- "distance: " + this.transmitDistance
- + " packetSize: " + this.packetSize
- + " packetResourceCost: " + this.packetResourceCost
- );
- if (this.CanTransmit())
- {
- base.TransmitData(dataQueue);
- }
- else
- {
- this.PostCannotTransmitError ();
- }
- }
-
- // 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
- + " packetSize: " + this.packetSize
- + " packetResourceCost: " + this.packetResourceCost
- );
- if (this.CanTransmit())
- {
- base.StartTransmission();
- }
- else
- {
- this.PostCannotTransmitError ();
- }
- }
-
- #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}",
- this.name,
- this._basepacketSize,
- base.packetSize,
- this._basepacketResourceCost,
- base.packetResourceCost,
- this.maxTransmitDistance,
- this.transmitDistance,
- this.nominalRange,
- this.CanTransmit()
- );
- ScreenMessages.PostScreenMessage (new ScreenMessage (msg, 30f, ScreenMessageStyle.UPPER_RIGHT));
- }
- #endif
- }
-
- public static class Tools
- {
- [System.Diagnostics.Conditional("DEBUG")]
- public static void PostDebugMessage(string Str)
- {
- ScreenMessage Message = new ScreenMessage (Str, 4f, ScreenMessageStyle.UPPER_RIGHT);
- ScreenMessages.PostScreenMessage (Message);
- }
- }
-}
-
--- /dev/null
+++ b/AntennaRelay.cs
@@ -1,1 +1,269 @@
-
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AntennaRange
+{
+ public class AntennaRelay
+ {
+ // We don't have a Bard, so we'll hide Kerbin here.
+ protected CelestialBody Kerbin;
+
+ protected System.Diagnostics.Stopwatch searchTimer;
+ protected long millisecondsBetweenSearches;
+
+ /// <summary>
+ /// Gets the parent Vessel.
+ /// </summary>
+ /// <value>The parent Vessel.</value>
+ public Vessel vessel
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets or sets the nearest relay.
+ /// </summary>
+ /// <value>The nearest relay</value>
+ public IAntennaRelay nearestRelay
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets the transmit distance.
+ /// </summary>
+ /// <value>The transmit distance.</value>
+ public double transmitDistance
+ {
+ get
+ {
+ this.nearestRelay = this.FindNearestRelay();
+
+ // If there is no available relay nearby...
+ if (nearestRelay == null)
+ {
+ // .. return the distance to Kerbin
+ return this.DistanceTo(this.Kerbin);
+ }
+ else
+ {
+ /// ...otherwise, return the distance to the nearest available relay.
+ return this.DistanceTo(nearestRelay);
+ }
+ }
+ }
+
+ /// <summary>
+ /// The maximum distance at which this relay can operate.
+ /// </summary>
+ /// <value>The max transmit distance.</value>
+ public virtual float maxTransmitDistance
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
+ /// the current relay attempt.
+ /// </summary>
+ /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
+ public virtual bool relayChecked
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Determines whether this instance can transmit.
+ /// </summary>
+ /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
+ public virtual bool CanTransmit()
+ {
+ if (this.transmitDistance > this.maxTransmitDistance)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Finds the nearest relay.
+ /// </summary>
+ /// <returns>The nearest relay or null, if no relays in range.</returns>
+ public IAntennaRelay FindNearestRelay()
+ {
+ if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
+ {
+ return this.nearestRelay;
+ }
+
+ if (this.searchTimer.IsRunning)
+ {
+ this.searchTimer.Stop();
+ this.searchTimer.Reset();
+ }
+
+ this.searchTimer.Start();
+
+ // Set this relay as checked, so that we don't check it again.
+ this.relayChecked = true;
+
+ // Get a list of vessels within transmission range.
+ List<Vessel> nearbyVessels = FlightGlobals.Vessels
+ .Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance)
+ .ToList();
+
+ nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris);
+ nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Flag);
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Non-debris, non-flag vessels in range: {1}",
+ this.GetType().Name,
+ nearbyVessels.Count
+ ));
+
+ // Remove this vessel.
+ nearbyVessels.RemoveAll(v => v.id == vessel.id);
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Vessels in range excluding self: {1}",
+ this.GetType().Name,
+ nearbyVessels.Count
+ ));
+
+ // Get a flattened list of all IAntennaRelay modules and protomodules in transmission range.
+ List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList();
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Found {1} nearby relays.",
+ this.GetType().Name,
+ nearbyRelays.Count
+ ));
+
+ // Remove all relays already checked this time.
+ nearbyRelays.RemoveAll(r => r.relayChecked);
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Found {1} nearby relays not already checked.",
+ this.GetType().Name,
+ nearbyRelays.Count
+ ));
+
+ // Remove all relays that cannot transmit.
+ // This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin.
+ nearbyRelays.RemoveAll(r => !r.CanTransmit());
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Found {1} nearby relays not already checked that can transmit.",
+ this.GetType().Name,
+ nearbyRelays.Count
+ ));
+
+ // Sort the available relays by distance.
+ nearbyRelays.Sort(new RelayComparer(this.vessel));
+
+ // Get the nearest available relay, or null if there are no available relays nearby.
+ IAntennaRelay _nearestRelay = nearbyRelays.FirstOrDefault();
+
+ // If we have a nearby relay...
+ if (_nearestRelay != null)
+ {
+ // ...but that relay is farther than Kerbin...
+ if (this.DistanceTo(_nearestRelay) > this.DistanceTo(Kerbin))
+ {
+ // ...just use Kerbin.
+ _nearestRelay = null;
+ }
+ }
+
+ // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
+ // used next time.
+ this.relayChecked = false;
+
+ // Return the nearest available relay, or null if there are no available relays nearby.
+ return _nearestRelay;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
+ /// </summary>
+ /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
+ public AntennaRelay(Vessel v)
+ {
+ this.vessel = v;
+
+ this.searchTimer = new System.Diagnostics.Stopwatch();
+ this.millisecondsBetweenSearches = 5000;
+
+ // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
+ // we hope it is safe enough.
+ this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
+ }
+
+ /*
+ * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
+ * */
+ internal class RelayComparer : IComparer<IAntennaRelay>
+ {
+ /// <summary>
+ /// The reference Vessel (usually the active vessel).
+ /// </summary>
+ protected Vessel referenceVessel;
+
+ // We don't want no stinking public parameterless constructors.
+ private RelayComparer() {}
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use
+ /// in sorting relays by distance.
+ /// </summary>
+ /// <param name="reference">The reference Vessel</param>
+ public RelayComparer(Vessel reference)
+ {
+ this.referenceVessel = reference;
+ }
+
+ /// <summary>
+ /// Compare the <see cref="IAntennaRelay"/>s "one" and "two".
+ /// </summary>
+ /// <param name="one">The first IAntennaRelay in the comparison</param>
+ /// <param name="two">The second IAntennaRelay in the comparison</param>
+ public int Compare(IAntennaRelay one, IAntennaRelay two)
+ {
+ double distanceOne;
+ double distanceTwo;
+
+ distanceOne = one.vessel.DistanceTo(referenceVessel);
+ distanceTwo = two.vessel.DistanceTo(referenceVessel);
+
+ return distanceOne.CompareTo(distanceTwo);
+ }
+ }
+ }
+}
+
+
--- /dev/null
+++ b/ChangeLog
@@ -1,1 +1,7 @@
+2014-01-14 toadicus <>
+ * ModuleLimitedDataTransmitter.cs: Added a ":" to the
+ transmission communications for consistency with stock
+ behavior.
+
+
--- /dev/null
+++ b/Extensions.cs
@@ -1,1 +1,92 @@
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AntennaRange
+{
+ /*
+ * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
+ * */
+ public static class Extensions
+ {
+ /// <summary>
+ /// Returns the distance between this Vessel and another Vessel.
+ /// </summary>
+ /// <param name="vesselOne">This <see cref="Vessel"/><see ></param>
+ /// <param name="vesselTwo">Another <see cref="Vessel"/></param>
+ public static double DistanceTo(this Vessel vesselOne, Vessel vesselTwo)
+ {
+ return (vesselOne.GetWorldPos3D() - vesselTwo.GetWorldPos3D()).magnitude;
+ }
+
+ /// <summary>
+ /// Returns the distance between this Vessel and a CelestialBody
+ /// </summary>
+ /// <param name="vessel">This Vessel</param>
+ /// <param name="body">A <see cref="CelestialBody"/></param>
+ public static double DistanceTo(this Vessel vessel, CelestialBody body)
+ {
+ return (vessel.GetWorldPos3D() - body.position).magnitude;
+ }
+
+ /// <summary>
+ /// Returns the distance between this IAntennaRelay and a Vessel
+ /// </summary>
+ /// <param name="relay">This <see cref="IAntennaRelay"/></param>
+ /// <param name="Vessel">A <see cref="Vessel"/></param>
+ public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
+ {
+ return relay.vessel.DistanceTo(Vessel);
+ }
+
+ /// <summary>
+ /// Returns the distance between this IAntennaRelay and a CelestialBody
+ /// </summary>
+ /// <param name="relay">This <see cref="IAntennaRelay"/></param>
+ /// <param name="body">A <see cref="CelestialBody"/></param>
+ public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
+ {
+ return relay.vessel.DistanceTo(body);
+ }
+
+ /// <summary>
+ /// Returns the distance between this IAntennaRelay and another IAntennaRelay
+ /// </summary>
+ /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
+ /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
+ public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
+ {
+ return relayOne.DistanceTo(relayTwo.vessel);
+ }
+
+ /// <summary>
+ /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
+ /// </summary>
+ /// <param name="vessel">This <see cref="Vessel"/></param>
+ public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
+ {
+ return RelayDatabase.Instance[vessel].Values.ToList();
+ }
+
+
+ }
+}
+
+
--- /dev/null
+++ b/IAntennaRelay.cs
@@ -1,1 +1,61 @@
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+using KSP;
+using System;
+
+namespace AntennaRange
+{
+ /*
+ * Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
+ * */
+ public interface IAntennaRelay
+ {
+ /// <summary>
+ /// Gets the parent Vessel.
+ /// </summary>
+ /// <value>The parent Vessel.</value>
+ Vessel vessel { get; }
+
+ /// <summary>
+ /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
+ /// </summary>
+ /// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>
+ double transmitDistance { get; }
+
+ /// <summary>
+ /// The maximum distance at which this relay can operate.
+ /// </summary>
+ /// <value>The max transmit distance.</value>
+ float maxTransmitDistance { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
+ /// the current relay attempt.
+ /// </summary>
+ /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
+ bool relayChecked { get; }
+
+ /// <summary>
+ /// Determines whether this instance can transmit.
+ /// </summary>
+ /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
+ bool CanTransmit();
+ }
+}
+
+
--- /dev/null
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,1 +1,473 @@
-
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using KSP;
+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
+ {
+ // Stores the packetResourceCost as defined in the .cfg file.
+ protected float _basepacketResourceCost;
+
+ // Stores the packetSize as defined in the .cfg file.
+ protected float _basepacketSize;
+
+ // Every antenna is a relay.
+ protected AntennaRelay relay;
+
+ // Keep track of vessels with transmitters for relay purposes.
+ protected List<Vessel> _relayVessels;
+
+ // Sometimes we will need to communicate errors; this is how we do it.
+ protected ScreenMessage ErrorMsg;
+
+ // 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 = "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)]
+ public float maxPowerFactor;
+
+ // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+ [KSPField(isPersistant = false)]
+ public float maxDataFactor;
+
+ protected bool actionUIUpdate;
+
+ /*
+ * Properties
+ * */
+ // Returns the parent vessel housing this antenna.
+ 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;
+ }
+ }
+
+ /*
+ * 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:
+ *
+ * The stock implementation of GetTransmitterScore (which I cannot override) is:
+ * Score = (1 + DataResourceCost) / DataRate
+ *
+ * The stock DataRate and DataResourceCost are:
+ * DataRate = packetSize / packetInterval
+ * DataResourceCost = packetResourceCost / packetSize
+ *
+ * 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,
+ * 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
+ * would look like:
+ * DataRate = packetSize / packetInterval
+ * DataResourceCost = packetResourceCost
+ *
+ * The former case, which I've chosen to implement below, is:
+ * DataRate = packetSize
+ * DataResourceCost = packetResourceCost
+ *
+ * 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
+ 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
+ {
+ 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;
+ }
+ }
+
+ /*
+ * Methods
+ * */
+ // Build ALL the objects.
+ public ModuleLimitedDataTransmitter () : base()
+ {
+ this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
+ }
+
+ // At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
+ public override void OnStart (StartState state)
+ {
+ base.OnStart (state);
+
+ if (state >= StartState.PreLaunch)
+ {
+ this.relay = new AntennaRelay(vessel);
+ this.relay.maxTransmitDistance = this.maxTransmitDistance;
+
+ this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
+
+ 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.
+ public override void OnLoad(ConfigNode node)
+ {
+ this.Fields.Load(node);
+ base.Fields.Load(node);
+
+ 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.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
+ // 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;
+ }
+ 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()
+ {
+ 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.name,
+ Enum.GetName(typeof(PartStates), partState)
+ ));
+ return false;
+ }
+ return this.relay.CanTransmit();
+ }
+
+ // Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
+ // returns false.
+ public new void TransmitData(List<ScienceData> dataQueue)
+ {
+ if (this.CanTransmit())
+ {
+ base.TransmitData(dataQueue);
+ }
+ else
+ {
+ this.PostCannotTransmitError ();
+ }
+
+ Tools.PostDebugMessage (
+ "distance: " + this.transmitDistance
+ + " packetSize: " + this.packetSize
+ + " packetResourceCost: " + this.packetResourceCost
+ );
+ }
+
+ // 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
+ + " packetSize: " + this.packetSize
+ + " packetResourceCost: " + this.packetResourceCost
+ );
+
+ 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.StartTransmission();
+ }
+ else
+ {
+ this.PostCannotTransmitError ();
+ }
+ }
+
+ public void Update()
+ {
+ if (this.actionUIUpdate)
+ {
+ this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+ this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A";
+ this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "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.name);
+ }
+
+ return msg.ToString();
+ }
+
+ // 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));
+ }
+ #endif
+ }
+}
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -1,1 +1,34 @@
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+using System.Reflection;
+using System.Runtime.CompilerServices;
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle("AntennaRange")]
+[assembly: AssemblyDescription("Enforce and Encourage Antenna Diversity")]
+[assembly: AssemblyCopyright("toadicus")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion("0.6.3.*")]
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+
--- /dev/null
+++ b/Properties/ChangeLog
@@ -1,1 +1,5 @@
+2014-01-14 toadicus <>
+ * AssemblyInfo.cs: New AssemblyInfo file for reason.
+
+
--- /dev/null
+++ b/ProtoAntennaRelay.cs
@@ -1,1 +1,110 @@
+// AntennaRange © 2014 toadicus
+//
+// AntennaRange provides incentive and requirements for the use of the various antenna parts.
+// Nominally, the breakdown is as follows:
+//
+// Communotron 16 - Suitable up to Kerbalsynchronous Orbit
+// Comms DTS-M1 - Suitable throughout the Kerbin subsystem
+// Communotron 88-88 - Suitable throughout the Kerbol system.
+//
+// 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/
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
+using System;
+using System.Linq;
+
+namespace AntennaRange
+{
+ /*
+ * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
+ * This is used for finding relays in unloaded Vessels.
+ * */
+ public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
+ {
+ // Stores the relay prefab
+ protected IAntennaRelay relayPrefab;
+
+ // Stores the prototype part so we can make sure we haven't exploded or so.
+ protected ProtoPartSnapshot protoPart;
+
+ /// <summary>
+ /// The maximum distance at which this transmitter can operate.
+ /// </summary>
+ /// <value>The max transmit distance.</value>
+ public override float maxTransmitDistance
+ {
+ get
+ {
+ return relayPrefab.maxTransmitDistance;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
+ /// the current relay attempt.
+ /// </summary>
+ /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
+ public override bool relayChecked
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets the underlying part's title.
+ /// </summary>
+ /// <value>The title.</value>
+ public string title
+ {
+ get
+ {
+ return this.protoPart.partInfo.title;
+ }
+ }
+
+ public override bool CanTransmit()
+ {
+ PartStates partState = (PartStates)this.protoPart.state;
+ if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: {1} on {2} cannot transmit: {3}",
+ this.GetType().Name,
+ this.title,
+ this.vessel.name,
+ Enum.GetName(typeof(PartStates), partState)
+ ));
+ return false;
+ }
+ return base.CanTransmit();
+ }
+
+ public override string ToString()
+ {
+ return string.Format(
+ "{0} on {1}.",
+ this.title,
+ this.protoPart.pVesselRef.vesselName
+ );
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
+ /// </summary>
+ /// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
+ /// <param name="vessel">The parent Vessel</param>
+ public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(pps.pVesselRef.vesselRef)
+ {
+ this.relayPrefab = prefabRelay;
+ this.protoPart = pps;
+ this.vessel = pps.pVesselRef.vesselRef;
+ }
+ }
+}
+
+
--- /dev/null
+++ b/RelayDatabase.cs
@@ -1,1 +1,302 @@
-
+// AntennaRange © 2014 toadicus
+//
+// 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/
+
+using KSP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace AntennaRange
+{
+ public class RelayDatabase
+ {
+ /*
+ * Static members
+ * */
+ // Singleton storage
+ protected static RelayDatabase _instance;
+ // Gets the singleton
+ public static RelayDatabase Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new RelayDatabase();
+ }
+
+ return _instance;
+ }
+ }
+
+ /*
+ * Instance members
+ * */
+
+ /*
+ * Fields
+ * */
+ // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
+ protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
+
+ // Vessel.id-keyed hash table of part counts, used for caching
+ protected Dictionary<Guid, int> vesselPartCountTable;
+
+ /*
+ * Properties
+ * */
+ // Gets the Part-hashed table of relays in a given vessel
+ public Dictionary<int, IAntennaRelay> this [Vessel vessel]
+ {
+ get
+ {
+ // If we don't have an entry for this vessel...
+ if (!this.ContainsKey(vessel.id))
+ {
+ // ...Generate an entry for this vessel.
+ this.AddVessel(vessel);
+ }
+ // If our part count disagrees with the vessel's part count...
+ if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+ {
+ // ...Update the our vessel in the cache
+ this.UpdateVessel(vessel);
+ }
+
+ // Return the Part-hashed table of relays for this vessel
+ return relayDatabase[vessel.id];
+ }
+ }
+
+ /*
+ * Methods
+ * */
+ // Adds a vessel to the database
+ // The return for this function isn't used yet, but seems useful for potential future API-uses
+ public bool AddVessel(Vessel vessel)
+ {
+ // If this vessel is already here...
+ if (relayDatabase.ContainsKey(vessel.id))
+ {
+ // ...post an error
+ Debug.LogWarning(string.Format(
+ "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
+ this.GetType().Name,
+ vessel.name,
+ vessel.id
+ ));
+
+ // ...and refuse to add
+ return false;
+ }
+ // otherwise, add the vessel to our tables...
+ else
+ {
+ // Build an empty table...
+ this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
+
+ // Update the empty index
+ this.UpdateVessel(vessel);
+
+ // Return success
+ return true;
+ }
+ }
+
+ // Update the vessel's entry in the table
+ public void UpdateVessel(Vessel vessel)
+ {
+ // Squak if the database doesn't have the vessel
+ if (!relayDatabase.ContainsKey(vessel.id))
+ {
+ throw new InvalidOperationException(string.Format(
+ "{0}: Update called vessel '{1}' (id: {2}) not in database: vessel will be added.",
+ this.GetType().Name,
+ vessel.name,
+ vessel.id
+ ));
+ }
+
+ Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
+
+ // Actually build and assign the table
+ this.getVesselRelays(vessel, ref vesselTable);
+ // Set the part count
+ this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
+ }
+
+ // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
+ public bool ContainsKey(Guid key)
+ {
+ return this.relayDatabase.ContainsKey(key);
+ }
+
+ // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
+ public bool ContainsKey(Vessel vessel)
+ {
+ return this.ContainsKey(vessel.id);
+ }
+
+ // Runs when a vessel is modified (or when we switch to one, to catch docking events)
+ public void onVesselWasModified(Vessel vessel)
+ {
+ // If we have this vessel in our cache...
+ if (this.ContainsKey(vessel))
+ {
+ // If our part counts disagree (such as if a part has been added or broken off,
+ // or if we've just docked or undocked)...
+ if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: dirtying cache for vessel '{1}' (id: {2}).",
+ this.GetType().Name,
+ vessel.name,
+ vessel.id
+ ));
+
+ // Dirty the cache (real vessels will never have negative part counts)
+ this.vesselPartCountTable[vessel.id] = -1;
+ }
+ }
+ }
+
+ // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
+ public void onSceneChange(GameScenes scene)
+ {
+ // If the active vessel is a real thing...
+ if (FlightGlobals.ActiveVessel != null)
+ {
+ // ... dirty its cache
+ this.onVesselWasModified(FlightGlobals.ActiveVessel);
+ }
+ }
+
+ // Produce a Part-hashed table of relays for the given vessel
+ protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
+ {
+ // We're going to completely regen this table, so dump the current contents.
+ relays.Clear();
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Getting antenna relays from vessel {1}.",
+ "IAntennaRelay",
+ vessel.name
+ ));
+
+ // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
+ if (vessel.loaded) {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
+ "IAntennaRelay",
+ vessel.name
+ ));
+
+ // Loop through the Parts in the Vessel...
+ foreach (Part part in vessel.Parts)
+ {
+ // ...loop through the PartModules in the Part...
+ foreach (PartModule module in part.Modules)
+ {
+ // ...if the module is a relay...
+ if (module is IAntennaRelay)
+ {
+ // ...add the module to the table
+ relays.Add(part.GetHashCode(), module as IAntennaRelay);
+ // ...neglect relay objects after the first in each part.
+ break;
+ }
+ }
+ }
+ }
+ // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
+ else
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
+ this.GetType().Name,
+ vessel.name
+ ));
+
+ // Loop through the ProtoPartModuleSnapshots in the Vessel...
+ foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Searching in protopartsnapshot {1}",
+ this.GetType().Name,
+ pps
+ ));
+
+ // ...Fetch the prefab, because it's more useful for what we're doing.
+ Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Got partPrefab {1} in protopartsnapshot {2}",
+ this.GetType().Name,
+ partPrefab,
+ pps
+ ));
+
+ // ...loop through the PartModules in the prefab...
+ foreach (PartModule module in partPrefab.Modules)
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: Searching in partmodule {1}",
+ this.GetType().Name,
+ module
+ ));
+
+ // ...if the module is a relay...
+ if (module is IAntennaRelay)
+ {
+ Tools.PostDebugMessage(string.Format(
+ "{0}: partmodule {1} is antennarelay",
+ this.GetType().Name,
+ module
+ ));
+
+ // ...build a new ProtoAntennaRelay and add it to the table
+ relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
+ // ...neglect relay objects after the first in each part.
+ break;
+ }
+ }
+ }
+ }
+
+ Tools.PostDebugMessage(string.Format(
+ "{0}: vessel '{1}' has {2} transmitters.",
+ "IAntennaRelay",
+ vessel.name,
+ relays.Count
+ ));
+ }
+
+ // Construct the singleton
+ protected RelayDatabase()
+ {
+ // Initialize the databases
+ relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+ vesselPartCountTable = new Dictionary<Guid, int>();
+
+ // Subscribe to some events
+ GameEvents.onVesselWasModified.Add(this.onVesselWasModified);
+ GameEvents.onVesselChange.Add(this.onVesselWasModified);
+ GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
+ }
+
+ ~RelayDatabase()
+ {
+ // Unsubscribe from the events
+ GameEvents.onVesselWasModified.Remove(this.onVesselWasModified);
+ GameEvents.onVesselChange.Remove(this.onVesselWasModified);
+ GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
+
+ Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
+ }
+ }
+}
+
+