AntennaRelay: Change update delay to use a single Stopwatch.
AntennaRelay: Change update delay to use a single Stopwatch.

// AntennaRange // AntennaRange
// //
// ARMapRenderer.cs // ARMapRenderer.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class ARMapRenderer : MonoBehaviour public class ARMapRenderer : MonoBehaviour
{ {
#region Fields #region Fields
private Dictionary<Guid, LineRenderer> vesselLineRenderers; private Dictionary<Guid, LineRenderer> vesselLineRenderers;
  private Dictionary<Guid, bool> vesselFrameCache;
  private bool dumpBool;
#endregion #endregion
   
#region Properties #region Properties
public LineRenderer this[Guid idx] public LineRenderer this[Guid idx]
{ {
get get
{ {
if (this.vesselLineRenderers == null) if (this.vesselLineRenderers == null)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
} }
   
if (!this.vesselLineRenderers.ContainsKey(idx)) LineRenderer lr;
   
  if (this.vesselLineRenderers.TryGetValue(idx, out lr))
  {
  return lr;
  }
  else
{ {
GameObject obj = new GameObject(); GameObject obj = new GameObject();
obj.layer = 31; obj.layer = 31;
   
LineRenderer lr = obj.AddComponent<LineRenderer>(); lr = obj.AddComponent<LineRenderer>();
   
lr.SetColors(Color.green, Color.green); // lr.SetColors(Color.green, Color.green);
lr.material = MapView.OrbitLinesMaterial; lr.material = MapView.OrbitLinesMaterial;
lr.SetVertexCount(2); // lr.SetVertexCount(2);
   
this.vesselLineRenderers[idx] = lr; this.vesselLineRenderers[idx] = lr;
}  
  return lr;
return this.vesselLineRenderers[idx]; }
} }
} }
#endregion #endregion
   
#region MonoBehaviour Lifecycle #region MonoBehaviour Lifecycle
private void Awake() private void Awake()
{ {
if (ARConfiguration.PrettyLines) if (ARConfiguration.PrettyLines)
{ {
this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>(); this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
  this.vesselFrameCache = new Dictionary<Guid, bool>();
} }
} }
   
private void OnPreCull() private void OnPreCull()
{ {
if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines) if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
{ {
this.Cleanup(); this.Cleanup();
   
return; return;
} }
   
Tools.DebugLogger log = Tools.DebugLogger.New(this); Tools.DebugLogger log = Tools.DebugLogger.New(this);
   
try try
{ {
log.AppendFormat("OnPreCull.\n"); log.AppendFormat("OnPreCull.\n");
   
log.AppendFormat("\tMapView: Draw3DLines: {0}\n" + log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
"\tMapView.MapCamera.camera.fieldOfView: {1}\n" + "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
"\tMapView.MapCamera.Distance: {2}\n", "\tMapView.MapCamera.Distance: {2}\n",
MapView.Draw3DLines, MapView.Draw3DLines,
MapView.MapCamera.camera.fieldOfView, MapView.MapCamera.camera.fieldOfView,
MapView.MapCamera.Distance MapView.MapCamera.Distance
); );
   
  this.vesselFrameCache.Clear();
   
log.AppendLine("vesselFrameCache cleared."); log.AppendLine("vesselFrameCache cleared.");
   
if (FlightGlobals.ready && FlightGlobals.Vessels != null) if (FlightGlobals.ready && FlightGlobals.Vessels != null)
{ {
log.AppendLine("FlightGlobals ready and Vessels list not null."); log.AppendLine("FlightGlobals ready and Vessels list not null.");
   
foreach (Vessel vessel in FlightGlobals.Vessels) foreach (Vessel vessel in FlightGlobals.Vessels)
{ {
if (vessel == null) if (vessel == null)
{ {
log.AppendFormat("Skipping vessel {0} altogether because it is null.\n"); log.AppendFormat("Skipping vessel {0} altogether because it is null.\n");
  continue;
  }
   
  if (this.vesselFrameCache.TryGetValue(vessel.id, out dumpBool))
  {
  log.AppendFormat("Skipping vessel {0} because it's already been processed this frame.");
continue; continue;
} }
   
log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName); log.AppendFormat("Checking vessel {0}.\n", vessel.vesselName);
   
switch (vessel.vesselType) switch (vessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.EVA: case VesselType.EVA:
case VesselType.Unknown: case VesselType.Unknown:
case VesselType.SpaceObject: case VesselType.SpaceObject:
log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n", log.AppendFormat("\tDiscarded because vessel is of invalid type {0}\n",
vessel.vesselType); vessel.vesselType);
continue; continue;
} }
   
log.Append("\tChecking connection status...\n"); IAntennaRelay vesselRelay = vessel.GetBestRelay();
   
/*if (vessel.HasConnectedRelay()) if (vesselRelay != null)
{ {
log.AppendLine("\tHas a connection, checking for the best relay to use for the line.");*/ this.SetRelayVertices(vesselRelay);
  }
IAntennaRelay vesselRelay = null;  
float bestScore = float.PositiveInfinity;  
float relayScore = float.NaN;  
   
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)  
{  
relayScore = (float)relay.transmitDistance / relay.maxTransmitDistance;  
   
if (relayScore < bestScore)  
{  
bestScore = relayScore;  
vesselRelay = relay as IAntennaRelay;  
}  
}  
   
if (vesselRelay != null)  
{  
log.AppendFormat("\t...picked relay {0} with a score of {1}",  
vesselRelay, relayScore  
);  
   
this.SetRelayVertices(vesselRelay);  
}  
/*}  
else if (this.vesselLineRenderers.ContainsKey(vessel.id))  
{  
log.AppendLine("\tDisabling line because vessel has no connection.");  
this[vessel.id].enabled = false;  
}*/  
} }
} }
} }
catch (Exception) catch (Exception)
{ {
this.Cleanup(); this.Cleanup();
} }
#if DEBUG #if DEBUG
finally finally
{ {
log.Print(); log.Print();
} }
#endif #endif
} }
   
private void OnDestroy() private void OnDestroy()
{ {
this.Cleanup(); this.Cleanup();
   
print("ARMapRenderer: Destroyed."); print("ARMapRenderer: Destroyed.");
} }
#endregion #endregion
   
private void SetRelayVertices(IAntennaRelay relay) private void SetRelayVertices(IAntennaRelay relay)
{ {
if (relay == null) Color lastColor = default(Color);
{ Color thisColor;
return;  
}  
   
LineRenderer renderer = this[relay.vessel.id]; LineRenderer renderer = this[relay.vessel.id];
  Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
Vector3d start;  
Vector3d end; float lineWidth;
  float d = Screen.height / 2f + 0.01f;
renderer.enabled = true;  
  if (MapView.Draw3DLines)
if (!relay.CanTransmit()) {
{ lineWidth = 0.005859375f * MapView.MapCamera.Distance;
renderer.SetColors(Color.red, Color.red);  
} }
else else
{ {
if (relay.transmitDistance < relay.nominalTransmitDistance) lineWidth = 2f;
{  
renderer.SetColors(Color.green, Color.green); start = MapView.MapCamera.camera.WorldToScreenPoint(start);
   
  start.z = start.z >= 0f ? d : -d;
  }
   
  renderer.SetWidth(lineWidth, lineWidth);
   
  renderer.SetPosition(0, start);
   
  int idx = 0;
   
  while (relay != null)
  {
  Vector3d nextPoint;
   
  renderer.enabled = true;
   
  if (!relay.CanTransmit())
  {
  thisColor = Color.red;
} }
else else
{ {
renderer.SetColors(Color.yellow, Color.yellow); if (relay.transmitDistance < relay.nominalTransmitDistance)
} {
} thisColor = Color.green;
  }
start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D()); else
  {
if (relay.KerbinDirect) thisColor = Color.yellow;
{ }
end = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position); }
}  
else if (lastColor != default(Color) && thisColor != lastColor)
{ {
if (relay.targetRelay == null) break;
{ }
return;  
} lastColor = thisColor;
end = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D()); renderer.SetColors(thisColor, thisColor);
}  
  this.vesselFrameCache[relay.vessel.id] = true;
float lineWidth;  
  if (relay.KerbinDirect)
if (MapView.Draw3DLines) {
{ nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
lineWidth = 0.005859375f * MapView.MapCamera.Distance; relay = null;
} }
else else
{ {
lineWidth = 2f; if (relay.targetRelay == null)
  {
start = MapView.MapCamera.camera.WorldToScreenPoint(start); return;
end = MapView.MapCamera.camera.WorldToScreenPoint(end); }
   
float d = Screen.height / 2f + 0.01f; nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
start.z = start.z >= 0f ? d : -d; relay = relay.targetRelay;
end.z = end.z >= 0f ? d : -d; }
}  
  if (!MapView.Draw3DLines)
renderer.SetWidth(lineWidth, lineWidth); {
  nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
renderer.SetPosition(0, start); nextPoint.z = nextPoint.z >= 0f ? d : -d;
renderer.SetPosition(1, end); }
   
  renderer.SetPosition(++idx, nextPoint);
  }
} }
   
public void Cleanup() public void Cleanup()
{ {
if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0) if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
{ {
foreach (LineRenderer lineRenderer in this.vesselLineRenderers.Values) foreach (LineRenderer lineRenderer in this.vesselLineRenderers.Values)
{ {
lineRenderer.enabled = false; lineRenderer.enabled = false;
GameObject.Destroy(lineRenderer.gameObject); GameObject.Destroy(lineRenderer.gameObject);
} }
this.vesselLineRenderers.Clear(); this.vesselLineRenderers.Clear();
} }
   
  if (this.vesselFrameCache != null && this.vesselFrameCache.Count > 0)
  {
  this.vesselFrameCache.Clear();
  }
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// AntennaRelay.cs // AntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ToadicusTools; using ToadicusTools;
   
// @DONE TODO: Retool nearestRelay to always contain the nearest relay, even if out of range.  
// @DONE TODO: Retool CanTransmit to not rely on nearestRelay == null.  
// TODO: Track occluded vessels somehow.  
   
namespace AntennaRange namespace AntennaRange
{ {
  /// <summary>
  /// Relay code at the heart of AntennaRange
  /// </summary>
public class AntennaRelay public class AntennaRelay
{ {
  private static readonly System.Diagnostics.Stopwatch searchTimer = new System.Diagnostics.Stopwatch();
  private const long millisecondsBetweenSearches = 125L;
   
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
private static CelestialBody _Kerbin; private static CelestialBody _Kerbin;
   
  /// <summary>
  /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
  /// </summary>
public static CelestialBody Kerbin public static CelestialBody Kerbin
{ {
get get
{ {
if (_Kerbin == null && FlightGlobals.ready) if (_Kerbin == null && FlightGlobals.ready)
{ {
_Kerbin = FlightGlobals.GetHomeBody(); _Kerbin = FlightGlobals.GetHomeBody();
} }
   
return _Kerbin; return _Kerbin;
} }
} }
   
protected bool canTransmit; private long lastSearch;
   
  private bool canTransmit;
   
  private IAntennaRelay nearestRelay;
  private IAntennaRelay bestOccludedRelay;
   
  /// <summary>
  /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
  /// <see cref="AntennaRange.IAntennaRelay"/>
  /// </summary>
protected IAntennaRelay moduleRef; protected IAntennaRelay moduleRef;
   
protected System.Diagnostics.Stopwatch searchTimer;  
protected long millisecondsBetweenSearches;  
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public virtual Vessel vessel public virtual Vessel vessel
{ {
get get
{ {
return this.moduleRef.vessel; return this.moduleRef.vessel;
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the nearest relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
/// <value>The nearest relay</value> public IAntennaRelay targetRelay
public IAntennaRelay nearestRelay  
{ {
get; get;
protected set; protected set;
} }
   
public IAntennaRelay bestOccludedRelay /// <summary>
  /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
  /// </summary>
  public virtual CelestialBody firstOccludingBody
{ {
get; get;
protected set; protected set;
} }
   
public IAntennaRelay targetRelay /// <summary>
  /// Gets the transmit distance.
  /// </summary>
  /// <value>The transmit distance.</value>
  public double transmitDistance
  {
  get
  {
  this.FindNearestRelay();
   
  if (this.KerbinDirect || this.targetRelay == null)
  {
  return this.DistanceTo(Kerbin);
  }
  else
  {
  return this.DistanceTo(this.targetRelay);
  }
  }
  }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
  public virtual double nominalTransmitDistance
  {
  get;
  set;
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
  public virtual double maxTransmitDistance
  {
  get;
  set;
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public virtual bool KerbinDirect
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.  
/// </summary>  
public virtual CelestialBody firstOccludingBody  
{  
get;  
protected set;  
}  
   
/// <summary>  
/// Gets the transmit distance.  
/// </summary>  
/// <value>The transmit distance.</value>  
public double transmitDistance  
{  
get  
{  
this.FindNearestRelay();  
   
if (this.KerbinDirect || this.targetRelay == null)  
{  
return this.DistanceTo(Kerbin);  
}  
else  
{  
return this.DistanceTo(this.targetRelay);  
}  
}  
}  
   
public virtual double nominalTransmitDistance  
{  
get;  
set;  
}  
   
/// <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;  
}  
   
public virtual bool KerbinDirect  
{  
get;  
protected set;  
}  
   
/// <summary>  
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit() public virtual bool CanTransmit()
{ {
this.FindNearestRelay(); this.FindNearestRelay();
return this.canTransmit; return this.canTransmit;
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
/// <returns>The nearest relay or null, if no relays in range.</returns> /// <returns>The nearest relay or null, if no relays in range.</returns>
private void FindNearestRelay() private void FindNearestRelay()
{ {
if (!this.searchTimer.IsRunning || this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches) if (!searchTimer.IsRunning)
{ {
this.searchTimer.Reset(); searchTimer.Start();
} }
else  
  long searchTime = searchTimer.ElapsedMilliseconds;
  long timeSinceLast = searchTime - this.lastSearch;
   
  if (timeSinceLast < Math.Max(millisecondsBetweenSearches, UnityEngine.Time.smoothDeltaTime))
{ {
return; return;
} }
   
  Tools.PostDebugMessage("{0}: Updating at {1}ms, {2}ms since last search.",
  this.ToString(), searchTime, timeSinceLast);
   
  this.lastSearch = searchTime;
   
// Skip vessels that have already been checked for a nearest relay this pass. // Skip vessels that have already been checked for a nearest relay this pass.
if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(this.vessel.id)) if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(this.vessel.id))
{ {
return; return;
} }
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: finding nearest relay for {1}", "{0}: finding nearest relay for {1}",
this.GetType().Name, this.GetType().Name,
this.ToString() this.ToString()
)); ));
} }
   
// Set this vessel as checked, so that we don't check it again. // Set this vessel as checked, so that we don't check it again.
RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true; RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
   
// Blank everything we're trying to find before the search. // Blank everything we're trying to find before the search.
this.firstOccludingBody = null; this.firstOccludingBody = null;
this.bestOccludedRelay = null; this.bestOccludedRelay = null;
this.targetRelay = null; this.targetRelay = null;
this.nearestRelay = null; this.nearestRelay = null;
   
CelestialBody bodyOccludingBestOccludedRelay = null; CelestialBody bodyOccludingBestOccludedRelay = null;
   
double nearestRelaySqrDistance = double.PositiveInfinity; double nearestRelaySqrDistance = double.PositiveInfinity;
double bestOccludedSqrDistance = double.PositiveInfinity; double bestOccludedSqrDistance = double.PositiveInfinity;
double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; double maxTransmitSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
   
/* /*
* Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
* far away. When we find a candidate, get through its antennae for relays which have not been checked yet * far away. When we find a candidate, get through its antennae for relays which have not been checked yet
* and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison * and that can transmit. Once we find a suitable candidate, assign it to nearestRelay for comparison
* against future finds. * against future finds.
* */ * */
foreach (Vessel potentialVessel in FlightGlobals.Vessels) foreach (Vessel potentialVessel in FlightGlobals.Vessels)
{ {
// Skip vessels of the wrong type. // Skip vessels of the wrong type.
switch (potentialVessel.vesselType) switch (potentialVessel.vesselType)
{ {
case VesselType.Debris: case VesselType.Debris:
case VesselType.Flag: case VesselType.Flag:
case VesselType.EVA: case VesselType.EVA:
case VesselType.SpaceObject: case VesselType.SpaceObject:
case VesselType.Unknown: case VesselType.Unknown:
continue; continue;
default: default:
break; break;
} }
   
// Skip vessels with the wrong ID // Skip vessels with the wrong ID
if (potentialVessel.id == vessel.id) if (potentialVessel.id == vessel.id)
{ {
continue; continue;
} }
   
// Find the distance from here to the vessel... // Find the distance from here to the vessel...
double potentialSqrDistance = this.sqrDistanceTo(potentialVessel); double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
   
CelestialBody fob = null; CelestialBody fob = null;
   
// Skip vessels to which we do not have line of sight. // Skip vessels to which we do not have line of sight.
if ( if (
ARConfiguration.RequireLineOfSight && ARConfiguration.RequireLineOfSight &&
!this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio) !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
) )
{ {
this.firstOccludingBody = fob; this.firstOccludingBody = fob;
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage("{6}: Vessel {0} discarded because we do not have line of sight." + Tools.PostDebugMessage("{6}: Vessel {0} discarded because we do not have line of sight." +
"\npotentialSqrDistance: {1}, bestOccludedSqrDistance: {2}, maxTransmitSqrDistance: {3}" + "\npotentialSqrDistance: {1}, bestOccludedSqrDistance: {2}, maxTransmitSqrDistance: {3}" +
"\npotentialSqrDistance < bestOccludedSqrDistance: {4}" + "\npotentialSqrDistance < bestOccludedSqrDistance: {4}" +
"\npotentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance): {5}", "\npotentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance): {5}",
potentialVessel.vesselName, potentialVessel.vesselName,
potentialSqrDistance, bestOccludedSqrDistance, this.maxTransmitDistance * this.maxTransmitDistance, potentialSqrDistance, bestOccludedSqrDistance, this.maxTransmitDistance * this.maxTransmitDistance,
potentialSqrDistance < bestOccludedSqrDistance, potentialSqrDistance < bestOccludedSqrDistance,
potentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance), potentialSqrDistance < (this.maxTransmitDistance * this.maxTransmitDistance),
this.ToString() this.ToString()
); );
} }
   
if ( if (
(potentialSqrDistance < bestOccludedSqrDistance) && (potentialSqrDistance < bestOccludedSqrDistance) &&
(potentialSqrDistance < maxTransmitSqrDistance) (potentialSqrDistance < maxTransmitSqrDistance)
) )
{ {
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage("{0}: Checking {1} relays on {2}.", Tools.PostDebugMessage("{0}: Checking {1} relays on {2}.",
this.ToString(), this.ToString(),
potentialVessel.GetAntennaRelays().Count(), potentialVessel.GetAntennaRelays().Count(),
potentialVessel potentialVessel
); );
} }
   
foreach (IAntennaRelay occludedRelay in potentialVessel.GetAntennaRelays()) foreach (IAntennaRelay occludedRelay in potentialVessel.GetAntennaRelays())
{ {
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage(this.ToString() + " Checking candidate for bestOccludedRelay: {0}" + Tools.PostDebugMessage(this.ToString() + " Checking candidate for bestOccludedRelay: {0}" +
"\n\tCanTransmit: {1}", occludedRelay, occludedRelay.CanTransmit()); "\n\tCanTransmit: {1}", occludedRelay, occludedRelay.CanTransmit());
} }
   
if (occludedRelay.CanTransmit()) if (occludedRelay.CanTransmit())
{ {
this.bestOccludedRelay = occludedRelay; this.bestOccludedRelay = occludedRelay;
bodyOccludingBestOccludedRelay = fob; bodyOccludingBestOccludedRelay = fob;
bestOccludedSqrDistance = potentialSqrDistance; bestOccludedSqrDistance = potentialSqrDistance;
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage(this.ToString() + " Found new bestOccludedRelay: {0}" + Tools.PostDebugMessage(this.ToString() + " Found new bestOccludedRelay: {0}" +
"\nfirstOccludingBody: {1}" + "\nfirstOccludingBody: {1}" +
"\nbestOccludedSqrDistance: {2}", "\nbestOccludedSqrDistance: {2}",
occludedRelay, occludedRelay,
fob, fob,
potentialSqrDistance potentialSqrDistance
); );
} }
break; break;
} }
} }
} }
   
continue; continue;
} }
   
/* /*
* ...so that we can skip the vessel if it is further away than a vessel we've already checked. * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
* */ * */
if (potentialSqrDistance > nearestRelaySqrDistance) if (potentialSqrDistance > nearestRelaySqrDistance)
{ {
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage("{0}: Vessel {1} discarded because it is out of range, or farther than another relay.", Tools.PostDebugMessage("{0}: Vessel {1} discarded because it is out of range, or farther than another relay.",
this.ToString(), this.ToString(),
potentialVessel.vesselName potentialVessel.vesselName
); );
} }
continue; continue;
} }
   
nearestRelaySqrDistance = potentialSqrDistance; nearestRelaySqrDistance = potentialSqrDistance;
   
foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays()) foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
{ {
if (potentialRelay.CanTransmit() && potentialRelay.targetRelay != this) if (potentialRelay.CanTransmit() && potentialRelay.targetRelay != this)
{ {
this.nearestRelay = potentialRelay; this.nearestRelay = potentialRelay;
   
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id) if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.id == this.vessel.id)
{ {
Tools.PostLogMessage(string.Format("{0}: found new best relay {1} ({2})", Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
this.ToString(), this.ToString(),
this.nearestRelay.ToString(), this.nearestRelay.ToString(),
this.nearestRelay.vessel.id this.nearestRelay.vessel.id
)); ));
} }
break; break;
} }
} }
} }
   
CelestialBody bodyOccludingKerbin = null; CelestialBody bodyOccludingKerbin = null;
   
double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius; double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
kerbinSqrDistance *= kerbinSqrDistance; kerbinSqrDistance *= kerbinSqrDistance;
   
System.Text.StringBuilder log = new System.Text.StringBuilder(); Tools.DebugLogger log = Tools.DebugLogger.New(this);
   
log.AppendFormat("{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name); log.AppendFormat("{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
   
// If we don't have LOS to Kerbin, focus on relays // If we don't have LOS to Kerbin, focus on relays
if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)) if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
{ {
log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName); log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
   
// nearestRelaySqrDistance will be infinity if all relays are occluded or none exist. // nearestRelaySqrDistance will be infinity if all relays are occluded or none exist.
// Therefore, this will only be true if a valid relay is in range. // Therefore, this will only be true if a valid relay is in range.
if (nearestRelaySqrDistance <= maxTransmitSqrDistance) if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).", log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.canTransmit = true; this.canTransmit = true;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
else else
{ {
log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).", log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, target it. // If the best occluded relay is closer than Kerbin, target it.
if (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).", log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).",
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedSqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.bestOccludedRelay; this.targetRelay = this.bestOccludedRelay;
this.firstOccludingBody = bodyOccludingBestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay;
} }
// Otherwise, target Kerbin and report the first body blocking it. // Otherwise, target Kerbin and report the first body blocking it.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).", log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).",
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedSqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
this.firstOccludingBody = bodyOccludingKerbin; this.firstOccludingBody = bodyOccludingKerbin;
} }
} }
} }
// If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
else else
{ {
log.AppendFormat("\n\tKerbin is in LOS."); log.AppendFormat("\n\tKerbin is in LOS.");
   
// If the nearest relay is closer than Kerbin and in range, transmit to it. // If the nearest relay is closer than Kerbin and in range, transmit to it.
if (nearestRelaySqrDistance <= maxTransmitSqrDistance) if (nearestRelaySqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).", log.AppendFormat("\n\tCan transmit to nearby relay {0} ({1} <= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = true; this.canTransmit = true;
   
// If the nearestRelay is closer than Kerbin, use it. // If the nearestRelay is closer than Kerbin, use it.
if (nearestRelaySqrDistance < kerbinSqrDistance) if (nearestRelaySqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\tPicking relay {0} over Kerbin ({1} < {2}).", log.AppendFormat("\n\tPicking relay {0} over Kerbin ({1} < {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.nearestRelay; this.targetRelay = this.nearestRelay;
} }
// Otherwise, Kerbin is closer, so use it. // Otherwise, Kerbin is closer, so use it.
else else
{ {
log.AppendFormat("\n\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).", log.AppendFormat("\n\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, kerbinSqrDistance); nearestRelaySqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
} }
// If the nearest relay is out of range, we still need to check on Kerbin. // If the nearest relay is out of range, we still need to check on Kerbin.
else else
{ {
log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).", log.AppendFormat("\n\tCan't transmit to nearby relay {0} ({1} > {2}).",
this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
nearestRelaySqrDistance, maxTransmitSqrDistance); nearestRelaySqrDistance, maxTransmitSqrDistance);
   
// If Kerbin is in range, use it. // If Kerbin is in range, use it.
if (kerbinSqrDistance <= maxTransmitSqrDistance) if (kerbinSqrDistance <= maxTransmitSqrDistance)
{ {
log.AppendFormat("\n\tCan transmit to Kerbin ({0} <= {1}).", log.AppendFormat("\n\tCan transmit to Kerbin ({0} <= {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = true; this.canTransmit = true;
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
} }
// If Kerbin is out of range and the nearest relay is out of range, pick a second best between // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
// Kerbin and bestOccludedRelay // Kerbin and bestOccludedRelay
else else
{ {
log.AppendFormat("\n\tCan't transmit to Kerbin ({0} > {1}).", log.AppendFormat("\n\tCan't transmit to Kerbin ({0} > {1}).",
kerbinSqrDistance, maxTransmitSqrDistance); kerbinSqrDistance, maxTransmitSqrDistance);
   
this.canTransmit = false; this.canTransmit = false;
   
// If the best occluded relay is closer than Kerbin, use it. // If the best occluded relay is closer than Kerbin, use it.
// Since bestOccludedSqrDistance is infinity if there are no occluded relays, // Since bestOccludedSqrDistance is infinity if there are no occluded relays,
// this is safe // this is safe
if (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).", log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).",
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedSqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = bestOccludedRelay; this.targetRelay = bestOccludedRelay;
this.firstOccludingBody = bodyOccludingBestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay;
} }
// Otherwise, target Kerbin. Since we have LOS, blank the first occluding body. // Otherwise, target Kerbin. Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).", log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).",
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedSqrDistance, kerbinSqrDistance);
   
this.KerbinDirect = true; this.KerbinDirect = true;
this.targetRelay = null; this.targetRelay = null;
this.firstOccludingBody = null; this.firstOccludingBody = null;
} }
} }
} }
} }
   
log.AppendFormat("\n{0}: Status determination complete.", this.ToString()); log.AppendFormat("\n{0}: Status determination complete.", this.ToString());
   
Tools.PostLogMessage(log.ToString()); log.Print();
   
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id); RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
} }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
public override string ToString() public override string ToString()
{ {
if (this is ProtoAntennaRelay) if (this is ProtoAntennaRelay)
{ {
return (this as ProtoAntennaRelay).ToString(); return (this as ProtoAntennaRelay).ToString();
} }
return this.moduleRef.ToString(); return this.moduleRef.ToString();
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="module">The module reference underlying this AntennaRelay,
  /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
public AntennaRelay(IAntennaRelay module) public AntennaRelay(IAntennaRelay module)
{ {
this.moduleRef = module; this.moduleRef = module;
   
this.searchTimer = new System.Diagnostics.Stopwatch();  
this.millisecondsBetweenSearches = 125L;  
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// IAntennaRelay.cs // IAntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* Interface defining the basic functionality of AntennaRelay modules for AntennaRange. /// Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
* */ /// </summary>
public interface IAntennaRelay public interface IAntennaRelay
{ {
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value>  
Vessel vessel { get; } Vessel vessel { get; }
   
IAntennaRelay nearestRelay { get; } /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
IAntennaRelay bestOccludedRelay { get; } /// </summary>
   
IAntennaRelay targetRelay { get; } IAntennaRelay targetRelay { get; }
   
/// <summary> /// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer. /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary> /// </summary>
/// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>  
double transmitDistance { get; } double transmitDistance { get; }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
double nominalTransmitDistance { get; } double nominalTransmitDistance { get; }
   
/// <summary> /// <summary>
/// The maximum distance at which this relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> double maxTransmitDistance { get; }
float maxTransmitDistance { get; }  
   
/// <summary> /// <summary>
/// The first CelestialBody blocking line of sight to a /// The first CelestialBody blocking line of sight to a
/// </summary> /// </summary>
/// <value>The first occluding body.</value>  
CelestialBody firstOccludingBody { get; } CelestialBody firstOccludingBody { 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> /// <summary>
/// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
/// directly with Kerbin. /// directly with Kerbin.
/// </summary> /// </summary>
bool KerbinDirect { get; } bool KerbinDirect { get; }
   
/// <summary> /// <summary>
  /// Gets the Part title.
  /// </summary>
  string Title { get; }
   
  /// <summary>
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>  
bool CanTransmit(); bool CanTransmit();
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
  /// </summary>
string ToString(); string ToString();
   
string Title { get; }  
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// ModuleLimitedDataTransmitter.cs // ModuleLimitedDataTransmitter.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /// <summary>
* ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range- /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
* finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
* three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files. /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
* ///
* In general, the scaling functions assume the following relation: /// <para>In general, the scaling functions assume the following relation:</para>
* ///
* D² α P/R, /// <para> D² α P/R,</para>
* ///
* where D is the total transmission distance, P is the transmission power, and R is the data rate. /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
* /// </summary>
* */  
   
/*  
* Fields  
* */  
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{ {
// Stores the packetResourceCost as defined in the .cfg file. // Stores the packetResourceCost as defined in the .cfg file.
protected float _basepacketResourceCost; private float _basepacketResourceCost;
   
// Stores the packetSize as defined in the .cfg file. // Stores the packetSize as defined in the .cfg file.
protected float _basepacketSize; private float _basepacketSize;
   
// Every antenna is a relay. // Every antenna is a relay.
protected AntennaRelay relay; private 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. // Sometimes we will need to communicate errors; this is how we do it.
protected ScreenMessage ErrorMsg; private ScreenMessage ErrorMsg;
   
// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost /// <summary>
// and packetSize. /// The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
  /// and packetSize.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float nominalRange; public double nominalRange;
   
  /// <summary>
  /// Relay status string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Status")] [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
public string UIrelayStatus; public string UIrelayStatus;
   
  /// <summary>
  /// Relay target string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
public string UIrelayTarget; public string UIrelayTarget;
   
  /// <summary>
  /// Transmit distance string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance; public string UItransmitDistance;
   
  /// <summary>
  /// Maximum distance string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")] [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
public string UImaxTransmitDistance; public string UImaxTransmitDistance;
   
  /// <summary>
  /// Packet size string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize; public string UIpacketSize;
   
  /// <summary>
  /// Packet cost string for use in action menus.
  /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")] [KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost; public string UIpacketCost;
   
// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power /// <summary>
// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail. /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
  /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxPowerFactor; public float maxPowerFactor;
   
// The multipler on packetSize that defines the maximum data bandwidth of the antenna. /// <summary>
  /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
  /// </summary>
[KSPField(isPersistant = false)] [KSPField(isPersistant = false)]
public float maxDataFactor; public float maxDataFactor;
   
  /// <summary>
  /// The packet throttle.
  /// </summary>
[KSPField( [KSPField(
isPersistant = true, isPersistant = true,
guiName = "Packet Throttle", guiName = "Packet Throttle",
guiUnits = "%", guiUnits = "%",
guiActive = true, guiActive = true,
guiActiveEditor = false guiActiveEditor = false
)] )]
[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)] [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
public float packetThrottle; public float packetThrottle;
   
protected bool actionUIUpdate; private bool actionUIUpdate;
   
/* /*
* Properties * Properties
* */ * */
// Returns the parent vessel housing this antenna. /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
public new Vessel vessel public new Vessel vessel
{ {
get get
{ {
if (base.vessel != null) if (base.vessel != null)
{ {
return base.vessel; return base.vessel;
} }
else if (this.part != null) else if (this.part != null)
{ {
return this.part.vessel; return this.part.vessel;
} }
   
else else
{ {
return null; return null;
} }
} }
} }
   
public IAntennaRelay nearestRelay /// <summary>
  /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
  /// </summary>
  public IAntennaRelay targetRelay
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null; return null;
} }
   
return this.relay.nearestRelay; return this.relay.targetRelay;
} }
} }
   
public IAntennaRelay bestOccludedRelay /// <summary>
  /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
  /// </summary>
  public double transmitDistance
{ {
get get
{ {
if (this.relay == null) if (this.relay == null)
{ {
return null;  
}  
   
return this.relay.bestOccludedRelay;  
}  
}  
   
public IAntennaRelay targetRelay  
{  
get  
{  
if (this.relay == null)  
{  
return null;  
}  
   
return this.relay.targetRelay;  
}  
}  
   
// Returns the distance to the nearest relay or Kerbin, whichever is closer.  
public double transmitDistance  
{  
get  
{  
if (this.relay == null)  
{  
return double.PositiveInfinity; return double.PositiveInfinity;
} }
   
return this.relay.transmitDistance; return this.relay.transmitDistance;
} }
} }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
public double nominalTransmitDistance public double nominalTransmitDistance
{ {
get get
{ {
return this.nominalRange; return this.nominalRange;
} }
} }
   
// Returns the maximum distance this module can transmit /// <summary>
public float maxTransmitDistance /// The maximum distance at which this relay can operate.
  /// </summary>
  public double maxTransmitDistance
{ {
get get
{ {
// TODO: Cache this in a way that doesn't break everything. // TODO: Cache this in a way that doesn't break everything.
return Mathf.Sqrt(this.maxPowerFactor) * this.nominalRange; return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
} }
} }
   
  /// <summary>
  /// The first CelestialBody blocking line of sight to a
  /// </summary>
public CelestialBody firstOccludingBody public CelestialBody firstOccludingBody
{ {
get get
{ {
return this.relay.firstOccludingBody; return this.relay.firstOccludingBody;
} }
} }
   
/* /*
* The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except * The next two functions overwrite the behavior of the stock functions and do not perform equivalently, except
* in that they both return floats. Here's some quick justification: * in that they both return floats. Here's some quick justification:
* *
* The stock implementation of GetTransmitterScore (which I cannot override) is: * The stock implementation of GetTransmitterScore (which I cannot override) is:
* Score = (1 + DataResourceCost) / DataRate * Score = (1 + DataResourceCost) / DataRate
* *
* The stock DataRate and DataResourceCost are: * The stock DataRate and DataResourceCost are:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost / packetSize * DataResourceCost = packetResourceCost / packetSize
* *
* So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it * So, the resulting score is essentially in terms of joules per byte per baud. Rearranging that a bit, it
* could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way, * could also look like joule-seconds per byte per byte, or newton-meter-seconds per byte per byte. Either way,
* that metric is not a very reasonable one. * that metric is not a very reasonable one.
* *
* Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case * Two metrics that might make more sense are joules per byte or joules per byte per second. The latter case
* would look like: * would look like:
* DataRate = packetSize / packetInterval * DataRate = packetSize / packetInterval
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* The former case, which I've chosen to implement below, is: * The former case, which I've chosen to implement below, is:
* DataRate = packetSize * DataRate = packetSize
* DataResourceCost = packetResourceCost * DataResourceCost = packetResourceCost
* *
* So... hopefully that doesn't screw with anything else. * So... hopefully that doesn't screw with anything else.
* */ * */
// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in /// <summary>
// terms of joules/byte /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
  /// terms of joules/byte
  /// </summary>
public new float DataRate public new float DataRate
{ {
get get
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetSize; return this.packetSize;
} }
else else
{ {
return float.Epsilon; return float.Epsilon;
} }
} }
} }
   
// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas /// <summary>
// to be scored in terms of joules/byte /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
public new float DataResourceCost /// to be scored in terms of joules/byte
  /// </summary>
  public new double DataResourceCost
{ {
get get
{ {
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
return this.packetResourceCost; return this.packetResourceCost;
} }
else else
{ {
return float.PositiveInfinity; return float.PositiveInfinity;
} }
} }
} }
   
// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay. /// <summary>
public bool relayChecked /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
  /// directly with Kerbin.
  /// </summary>
  public bool KerbinDirect
{ {
get get
{ {
if (this.relay != null) if (this.relay != null)
{ {
return this.relay.relayChecked;  
}  
   
// If our relay is null, always return null so we're never checked.  
return true;  
}  
}  
   
public bool KerbinDirect  
{  
get  
{  
if (this.relay != null)  
{  
return this.relay.KerbinDirect; return this.relay.KerbinDirect;
} }
   
return false; return false;
} }
} }
   
  /// <summary>
  /// Gets the Part title.
  /// </summary>
public string Title public string Title
{ {
get get
{ {
if (this.part != null && this.part.partInfo != null) if (this.part != null && this.part.partInfo != null)
{ {
return this.part.partInfo.title; return this.part.partInfo.title;
} }
   
return string.Empty; return string.Empty;
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Build ALL the objects. // Build ALL the objects.
public ModuleLimitedDataTransmitter () : base() public ModuleLimitedDataTransmitter () : base()
{ {
this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT); this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
this.packetThrottle = 100f; this.packetThrottle = 100f;
} }
   
  /// <summary>
  /// PartModule OnAwake override; runs at Unity Awake.
  /// </summary>
public override void OnAwake() public override void OnAwake()
{ {
base.OnAwake(); base.OnAwake();
   
this._basepacketSize = base.packetSize; this._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost; this._basepacketResourceCost = base.packetResourceCost;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0} loaded:\n" + "{0} loaded:\n" +
"packetSize: {1}\n" + "packetSize: {1}\n" +
"packetResourceCost: {2}\n" + "packetResourceCost: {2}\n" +
"nominalRange: {3}\n" + "nominalRange: {3}\n" +
"maxPowerFactor: {4}\n" + "maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n", "maxDataFactor: {5}\n",
this.name, this.name,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
this.nominalRange, this.nominalRange,
this.maxPowerFactor, this.maxPowerFactor,
this.maxDataFactor this.maxDataFactor
)); ));
} }
   
// At least once, when the module starts with a state on the launch pad or later, go find Kerbin. /// <summary>
  /// PartModule OnStart override; runs at Unity Start.
  /// </summary>
  /// <param name="state">State.</param>
public override void OnStart (StartState state) public override void OnStart (StartState state)
{ {
base.OnStart (state); base.OnStart (state);
   
if (state >= StartState.PreLaunch) if (state >= StartState.PreLaunch)
{ {
this.relay = new AntennaRelay(this); this.relay = new AntennaRelay(this);
this.relay.maxTransmitDistance = this.maxTransmitDistance; this.relay.maxTransmitDistance = this.maxTransmitDistance;
this.relay.nominalTransmitDistance = this.nominalRange; this.relay.nominalTransmitDistance = this.nominalRange;
   
this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m"; this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
   
GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate); GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss); GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
} }
} }
   
// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because /// <summary>
// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
// work. /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
  /// work.
  /// </summary>
  /// <param name="node"><see cref="ConfigNode"/> with data for this module.</param>
public override void OnLoad(ConfigNode node) public override void OnLoad(ConfigNode node)
{ {
this.Fields.Load(node); this.Fields.Load(node);
base.Fields.Load(node); base.Fields.Load(node);
   
base.OnLoad (node); base.OnLoad (node);
} }
   
// Post an error in the communication messages describing the reason transmission has failed. Currently there /// <summary>
// is only one reason for this. /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
protected void PostCannotTransmitError() /// </summary>
{  
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  
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point  
// transmission fails (see CanTransmit).  
protected void PreTransmit_SetPacketResourceCost()  
{  
if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)  
{  
base.packetResourceCost = this._basepacketResourceCost;  
}  
else  
{  
double rangeFactor = (this.transmitDistance / this.nominalRange);  
rangeFactor *= rangeFactor;  
   
base.packetResourceCost = this._basepacketResourceCost  
* (float)rangeFactor;  
   
Tools.PostDebugMessage(  
this,  
"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",  
base.packetResourceCost,  
rangeFactor);  
}  
   
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 (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)  
{  
base.packetSize = this._basepacketSize;  
}  
else  
{  
double rangeFactor = (this.nominalRange / this.transmitDistance);  
rangeFactor *= rangeFactor;  
   
base.packetSize = Math.Min(  
this._basepacketSize * (float)rangeFactor,  
this._basepacketSize * this.maxDataFactor);  
   
Tools.PostDebugMessage(  
this,  
"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",  
base.packetSize,  
rangeFactor);  
}  
   
base.packetSize *= this.packetThrottle / 100f;  
}  
   
// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.  
public override string GetInfo() public override string GetInfo()
{ {
string text = base.GetInfo(); string text = base.GetInfo();
text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n"; text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n"; text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
return text; return text;
} }
   
// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
public new bool CanTransmit() public new bool CanTransmit()
{ {
if (this.part == null || this.relay == null) if (this.part == null || this.relay == null)
{ {
return false; return false;
} }
   
PartStates partState = this.part.State; switch (this.part.State)
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) {
{ case PartStates.DEAD:
Tools.PostDebugMessage(string.Format( case PartStates.DEACTIVATED:
"{0}: {1} on {2} cannot transmit: {3}", Tools.PostDebugMessage(string.Format(
this.GetType().Name, "{0}: {1} on {2} cannot transmit: {3}",
this.part.partInfo.title, this.GetType().Name,
this.vessel.vesselName, this.part.partInfo.title,
Enum.GetName(typeof(PartStates), partState) this.vessel.vesselName,
)); Enum.GetName(typeof(PartStates), this.part.State)
return false; ));
} return false;
  default:
  break;
  }
   
return this.relay.CanTransmit(); return this.relay.CanTransmit();
} }
   
// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit /// <summary>
// returns false. /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
public new void TransmitData(List<ScienceData> dataQueue) /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  /// <param name="callback">Callback function</param>
  public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
{ {
this.PreTransmit_SetPacketSize(); this.PreTransmit_SetPacketSize();
this.PreTransmit_SetPacketResourceCost(); this.PreTransmit_SetPacketResourceCost();
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message.Append("["); base.TransmitData(dataQueue, callback);
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
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 else
{ {
Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
   
var logger = Tools.DebugLogger.New(this); var logger = Tools.DebugLogger.New(this);
   
foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>()) foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())
{ {
logger.AppendFormat("Checking ModuleScienceContainer in {0}\n", logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
scienceContainer.part.partInfo.title); scienceContainer.part.partInfo.title);
   
if ( if (
scienceContainer.capacity != 0 && scienceContainer.capacity != 0 &&
scienceContainer.GetScienceCount() >= scienceContainer.capacity scienceContainer.GetScienceCount() >= scienceContainer.capacity
) )
{ {
logger.Append("\tInsufficient capacity, skipping.\n"); logger.Append("\tInsufficient capacity, skipping.\n");
continue; continue;
} }
   
List<ScienceData> dataStored = new List<ScienceData>(); List<ScienceData> dataStored = new List<ScienceData>();
   
foreach (ScienceData data in dataQueue) foreach (ScienceData data in dataQueue)
{ {
if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data)) if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
{ {
logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n"); logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
continue; continue;
} }
   
logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID); logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
if (scienceContainer.AddData(data)) if (scienceContainer.AddData(data))
{ {
logger.Append("done, removing from queue.\n"); logger.Append("done, removing from queue.\n");
   
dataStored.Add(data); dataStored.Add(data);
} }
#if DEBUG #if DEBUG
else else
{ {
logger.Append("failed.\n"); logger.Append("failed.\n");
} }
#endif #endif
} }
   
dataQueue.RemoveAll(i => dataStored.Contains(i)); dataQueue.RemoveAll(i => dataStored.Contains(i));
   
logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count); logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
} }
   
logger.Print(); logger.Print();
   
if (dataQueue.Count > 0) if (dataQueue.Count > 0)
{ {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
   
msg.Append('['); msg.Append('[');
msg.Append(this.part.partInfo.title); msg.Append(this.part.partInfo.title);
msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
msg.Append("Data to be discarded:\n"); msg.Append("Data to be discarded:\n");
   
foreach (ScienceData data in dataQueue) foreach (ScienceData data in dataQueue)
{ {
msg.AppendFormat("\n{0}\n", data.title); msg.AppendFormat("\t{0}\n", data.title);
} }
   
ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
Tools.PostDebugMessage(msg.ToString()); Tools.PostDebugMessage(msg.ToString());
} }
   
this.PostCannotTransmitError (); this.PostCannotTransmitError();
} }
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
} }
   
// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit /// <summary>
// returns false. /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
  /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
  public new void TransmitData(List<ScienceData> dataQueue)
  {
  this.TransmitData(dataQueue, null);
  }
   
  /// <summary>
  /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
  /// returns false.
  /// </summary>
public new void StartTransmission() public new void StartTransmission()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
Tools.PostDebugMessage ( Tools.PostDebugMessage (
"distance: " + this.transmitDistance "distance: " + this.transmitDistance
+ " packetSize: " + this.packetSize + " packetSize: " + this.packetSize
+ " packetResourceCost: " + this.packetResourceCost + " packetResourceCost: " + this.packetResourceCost
); );
   
if (this.CanTransmit()) if (this.CanTransmit())
{ {
StringBuilder message = new StringBuilder(); ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
   
message.Append("[");  
message.Append(base.part.partInfo.title);  
message.Append("]: ");  
   
message.Append("Beginning transmission ");  
   
// @DONE TODO: Fix this to fall back to Kerbin if nearestRelay cannot be contacted.  
// @DONE TODO: Remove nearestRelay == null  
if (this.KerbinDirect)  
{  
message.Append("directly to Kerbin.");  
}  
else  
{  
message.Append("via ");  
message.Append(this.relay.nearestRelay);  
}  
   
ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);  
   
base.StartTransmission(); base.StartTransmission();
} }
else else
{ {
this.PostCannotTransmitError (); this.PostCannotTransmitError ();
} }
} }
   
  /// <summary>
  /// MonoBehaviour Update
  /// </summary>
public void Update() public void Update()
{ {
if (this.actionUIUpdate) if (this.actionUIUpdate)
{ {
if (this.CanTransmit()) if (this.CanTransmit())
{ {
this.UIrelayStatus = "Connected"; this.UIrelayStatus = "Connected";
this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
} }
else else
{ {
if (this.relay.firstOccludingBody == null) if (this.relay.firstOccludingBody == null)
{ {
this.UIrelayStatus = "Out of range"; this.UIrelayStatus = "Out of range";
} }
else else
{ {
this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
} }
this.UImaxTransmitDistance = "N/A"; this.UImaxTransmitDistance = "N/A";
this.UIpacketSize = "N/A"; this.UIpacketSize = "N/A";
this.UIpacketCost = "N/A"; this.UIpacketCost = "N/A";
} }
   
if (this.KerbinDirect) if (this.KerbinDirect)
{ {
this.UIrelayTarget = AntennaRelay.Kerbin.bodyName; this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
} }
else else
{ {
this.UIrelayTarget = this.targetRelay.ToString(); this.UIrelayTarget = this.targetRelay.ToString();
} }
} }
} }
   
public void onPartActionUICreate(Part eventPart) /// <summary>
{ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
if (eventPart == base.part) /// </summary>
{ /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
this.actionUIUpdate = true;  
}  
}  
   
public void onPartActionUIDismiss(Part eventPart)  
{  
if (eventPart == base.part)  
{  
this.actionUIUpdate = false;  
}  
}  
   
public override string ToString() public override string ToString()
{ {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
   
msg.Append(this.part.partInfo.title); msg.Append(this.part.partInfo.title);
   
if (vessel != null) if (vessel != null)
{ {
msg.Append(" on "); msg.Append(" on ");
msg.Append(vessel.vesselName); msg.Append(vessel.vesselName);
} }
else if ( else if (
this.part != null && this.part != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot != null && this.part.protoPartSnapshot != null &&
this.part.protoPartSnapshot.pVesselRef != null this.part.protoPartSnapshot.pVesselRef != null
) )
{ {
msg.Append(" on "); msg.Append(" on ");
msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName); msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
} }
   
return msg.ToString(); return msg.ToString();
} }
   
  // 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(
  "<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).
  private void PreTransmit_SetPacketResourceCost()
  {
  if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
  {
  base.packetResourceCost = this._basepacketResourceCost;
  }
  else
  {
  float rangeFactor = (float)(this.transmitDistance / this.nominalRange);
  rangeFactor *= rangeFactor;
   
  base.packetResourceCost = this._basepacketResourceCost
  * rangeFactor;
   
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
  base.packetResourceCost,
  rangeFactor);
  }
   
  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.
  private void PreTransmit_SetPacketSize()
  {
  if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
  {
  base.packetSize = this._basepacketSize;
  }
  else
  {
  float rangeFactor = (float)(this.nominalRange / this.transmitDistance);
  rangeFactor *= rangeFactor;
   
  base.packetSize = Mathf.Min(
  this._basepacketSize * rangeFactor,
  this._basepacketSize * this.maxDataFactor);
   
  Tools.PostDebugMessage(
  this,
  "Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
  base.packetSize,
  rangeFactor);
  }
   
  base.packetSize *= this.packetThrottle / 100f;
  }
   
  private string buildTransmitMessage()
  {
  StringBuilder message = new StringBuilder();
   
  message.Append("[");
  message.Append(base.part.partInfo.title);
  message.Append("]: ");
   
  message.Append("Beginning transmission ");
   
  if (this.KerbinDirect)
  {
  message.Append("directly to Kerbin.");
  }
  else
  {
  message.Append("via ");
  message.Append(this.relay.targetRelay);
  }
   
  return message.ToString();
  }
   
  #if DEBUG
// When debugging, it's nice to have a button that just tells you everything. // When debugging, it's nice to have a button that just tells you everything.
   
[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)] [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
public void DebugInfo() public void DebugInfo()
{ {
PreTransmit_SetPacketSize (); PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost (); PreTransmit_SetPacketResourceCost ();
   
string msg = string.Format( string msg = string.Format(
"'{0}'\n" + "'{0}'\n" +
"_basepacketSize: {1}\n" + "_basepacketSize: {1}\n" +
"packetSize: {2}\n" + "packetSize: {2}\n" +
"_basepacketResourceCost: {3}\n" + "_basepacketResourceCost: {3}\n" +
"packetResourceCost: {4}\n" + "packetResourceCost: {4}\n" +
"maxTransmitDistance: {5}\n" + "maxTransmitDistance: {5}\n" +
"transmitDistance: {6}\n" + "transmitDistance: {6}\n" +
"nominalRange: {7}\n" + "nominalRange: {7}\n" +
"CanTransmit: {8}\n" + "CanTransmit: {8}\n" +
"DataRate: {9}\n" + "DataRate: {9}\n" +
"DataResourceCost: {10}\n" + "DataResourceCost: {10}\n" +
"TransmitterScore: {11}\n" + "TransmitterScore: {11}\n" +
"NearestRelay: {12}\n" + "targetRelay: {12}\n" +
"BestOccludedRelay: {13}\n" + "KerbinDirect: {13}\n" +
"KerbinDirect: {14}\n" + "Vessel ID: {14}",
"Vessel ID: {15}",  
this.name, this.name,
this._basepacketSize, this._basepacketSize,
base.packetSize, base.packetSize,
this._basepacketResourceCost, this._basepacketResourceCost,
base.packetResourceCost, base.packetResourceCost,
this.maxTransmitDistance, this.maxTransmitDistance,
this.transmitDistance, this.transmitDistance,
this.nominalRange, this.nominalRange,
this.CanTransmit(), this.CanTransmit(),
this.DataRate, this.DataRate,
this.DataResourceCost, this.DataResourceCost,
ScienceUtil.GetTransmitterScore(this), ScienceUtil.GetTransmitterScore(this),
this.relay.nearestRelay == null ? "null" : this.relay.nearestRelay.ToString(), this.relay.targetRelay == null ? "null" : this.relay.targetRelay.ToString(),
this.relay.bestOccludedRelay == null ? "null" : this.relay.bestOccludedRelay.ToString(),  
this.KerbinDirect, this.KerbinDirect,
this.vessel.id this.vessel.id
); );
   
Tools.PostLogMessage(msg); Tools.PostLogMessage(msg);
} }
   
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)] [KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels() public void PrintAllVessels()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
   
sb.Append("Dumping FlightGlobals.Vessels:"); sb.Append("Dumping FlightGlobals.Vessels:");
   
foreach (Vessel vessel in FlightGlobals.Vessels) foreach (Vessel vessel in FlightGlobals.Vessels)
{ {
sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id); sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
} }
   
/*[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)] [KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
public void DumpRelayDB() public void DumpRelayDB()
{ {
RelayDatabase.Instance.Dump(); RelayDatabase.Instance.Dump();
}*/ }
  #endif
} }
} }
// AntennaRange // AntennaRange
// //
// ProtoAntennaRelay.cs // ProtoAntennaRelay.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using KSP; using KSP;
using System; using System;
using System.Linq; using System.Linq;
using ToadicusTools; using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /*
* Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay. * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
* This is used for finding relays in unloaded Vessels. * This is used for finding relays in unloaded Vessels.
* */ * */
public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
{ {
// Stores the prototype part so we can make sure we haven't exploded or so. // Stores the prototype part so we can make sure we haven't exploded or so.
protected ProtoPartSnapshot protoPart; private ProtoPartSnapshot protoPart;
   
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
public override Vessel vessel public override Vessel vessel
{ {
get get
{ {
return this.protoPart.pVesselRef.vesselRef; if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  return this.protoPart.pVesselRef.vesselRef;
  }
  else
  {
  return null;
  }
} }
} }
   
  /// <summary>
  /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
  /// </summary>
public override double nominalTransmitDistance public override double nominalTransmitDistance
{ {
get get
{ {
return this.moduleRef.nominalTransmitDistance; return this.moduleRef.nominalTransmitDistance;
} }
} }
   
/// <summary> /// <summary>
/// The maximum distance at which this transmitter can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
/// <value>The max transmit distance.</value> public override double maxTransmitDistance
public override float maxTransmitDistance  
{ {
get get
{ {
return moduleRef.maxTransmitDistance; return moduleRef.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> /// <summary>
/// Gets the underlying part's title. /// Gets the underlying part's title.
/// </summary> /// </summary>
/// <value>The title.</value> /// <value>The title.</value>
public string Title public string Title
{ {
get get
{ {
if (this.protoPart != null && this.protoPart.partInfo != null) if (this.protoPart != null && this.protoPart.partInfo != null)
{ {
return this.protoPart.partInfo.title; return this.protoPart.partInfo.title;
} }
   
return string.Empty; return string.Empty;
} }
} }
   
  /// <summary>
  /// Determines whether this instance can transmit.
  /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
  /// </summary>
public override bool CanTransmit() public override bool CanTransmit()
{ {
PartStates partState = (PartStates)this.protoPart.state; PartStates partState = (PartStates)this.protoPart.state;
if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}", "{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name, this.GetType().Name,
this.Title, this.Title,
this.vessel.vesselName, this.vessel.vesselName,
Enum.GetName(typeof(PartStates), partState) Enum.GetName(typeof(PartStates), partState)
)); ));
return false; return false;
} }
return base.CanTransmit(); return base.CanTransmit();
} }
   
  /// <summary>
  /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
  /// </summary>
  /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
public override string ToString() public override string ToString()
{ {
return string.Format( System.Text.StringBuilder sb = new System.Text.StringBuilder();
"{0} on {1}",  
this.Title, sb.Append(this.Title);
this.protoPart.pVesselRef.vesselName  
); if (this.protoPart != null && this.protoPart.pVesselRef != null)
  {
  sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
  }
   
  return sb.ToString();
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param> /// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
/// <param name="vessel">The parent Vessel</param> /// <param name="vessel">The parent Vessel</param>
public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay) public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
{ {
this.protoPart = pps; this.protoPart = pps;
} }
   
~ProtoAntennaRelay() ~ProtoAntennaRelay()
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: destroyed", "{0}: destroyed",
this.ToString() this.ToString()
)); ));
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// Extensions.cs // Extensions.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ToadicusTools; using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
/* /*
* A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin. * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
* */ * */
public static class RelayExtensions public static class RelayExtensions
{ {
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a Vessel /// Returns the distance between this IAntennaRelay and a Vessel
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="Vessel">A <see cref="Vessel"/></param> /// <param name="Vessel">A <see cref="Vessel"/></param>
public static double DistanceTo(this AntennaRelay relay, Vessel Vessel) public static double DistanceTo(this AntennaRelay relay, Vessel Vessel)
{ {
return relay.vessel.DistanceTo(Vessel); return relay.vessel.DistanceTo(Vessel);
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and a CelestialBody /// Returns the distance between this IAntennaRelay and a CelestialBody
/// </summary> /// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param> /// <param name="relay">This <see cref="IAntennaRelay"/></param>
/// <param name="body">A <see cref="CelestialBody"/></param> /// <param name="body">A <see cref="CelestialBody"/></param>
public static double DistanceTo(this AntennaRelay relay, CelestialBody body) public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.DistanceTo(body) - body.Radius; return relay.vessel.DistanceTo(body) - body.Radius;
} }
   
/// <summary> /// <summary>
/// Returns the distance between this IAntennaRelay and another IAntennaRelay /// Returns the distance between this IAntennaRelay and another IAntennaRelay
/// </summary> /// </summary>
/// <param name="relayOne">This <see cref="IAntennaRelay"/></param> /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
/// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param> /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo) public static double DistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.DistanceTo(relayTwo.vessel); return relayOne.DistanceTo(relayTwo.vessel);
} }
   
public static double sqrDistanceTo(this AntennaRelay relay, Vessel vessel) public static double sqrDistanceTo(this AntennaRelay relay, Vessel vessel)
{ {
return relay.vessel.sqrDistanceTo(vessel); return relay.vessel.sqrDistanceTo(vessel);
} }
   
public static double sqrDistanceTo(this AntennaRelay relay, CelestialBody body) public static double sqrDistanceTo(this AntennaRelay relay, CelestialBody body)
{ {
return relay.vessel.sqrDistanceTo(body); return relay.vessel.sqrDistanceTo(body);
} }
   
public static double sqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo) public static double sqrDistanceTo(this AntennaRelay relayOne, IAntennaRelay relayTwo)
{ {
return relayOne.vessel.sqrDistanceTo(relayTwo.vessel); return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
} }
   
/// <summary> /// <summary>
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel. /// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
/// </summary> /// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param> /// <param name="vessel">This <see cref="Vessel"/></param>
public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel) public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
{ {
return RelayDatabase.Instance[vessel].Values.ToList().AsReadOnly(); return RelayDatabase.Instance[vessel].Values.ToList().AsReadOnly();
} }
   
/// <summary> /// <summary>
/// Determines if the specified vessel has a connected relay. /// Determines if the specified vessel has a connected relay.
/// </summary> /// </summary>
/// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified vessel has a connected relay; otherwise, <c>false</c>.</returns>
/// <param name="vessel"></param> /// <param name="vessel"></param>
public static bool HasConnectedRelay(this Vessel vessel) public static bool HasConnectedRelay(this Vessel vessel)
{ {
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)
{ {
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
return true; return true;
} }
} }
   
return false; return false;
} }
   
public static ConnectionStatus GetConnectionStatus(this Vessel vessel) public static ConnectionStatus GetConnectionStatus(this Vessel vessel)
{ {
bool canTransmit = false; bool canTransmit = false;
   
foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)
{ {
if (relay.CanTransmit()) if (relay.CanTransmit())
{ {
canTransmit = true; canTransmit = true;
if (relay.transmitDistance <= relay.nominalTransmitDistance) if (relay.transmitDistance <= relay.nominalTransmitDistance)
{ {
return ConnectionStatus.Optimal; return ConnectionStatus.Optimal;
} }
} }
} }
   
if (canTransmit) if (canTransmit)
{ {
return ConnectionStatus.Suboptimal; return ConnectionStatus.Suboptimal;
} }
else else
{ {
return ConnectionStatus.None; return ConnectionStatus.None;
} }
} }
   
  public static IAntennaRelay GetBestRelay(this Vessel vessel)
  {
  IAntennaRelay bestRelay = null;
  double bestScore = double.PositiveInfinity;
  double relayScore = double.NaN;
   
  foreach (IAntennaRelay relay in vessel.GetAntennaRelays())
  {
  relayScore = relay.transmitDistance / relay.maxTransmitDistance;
   
  if (relayScore < bestScore)
  {
  bestScore = relayScore;
  bestRelay = relay;
  }
  }
   
  return bestRelay;
  }
} }
   
public enum ConnectionStatus public enum ConnectionStatus
{ {
None, None,
Suboptimal, Suboptimal,
Optimal Optimal
} }
} }