A bunch more changes. Relays mostly work now, I think, except for the infinite recursion.
[AntennaRange.git] / AntennaRelay.cs
blob:a/AntennaRelay.cs -> blob:b/AntennaRelay.cs
  // AntennaRange
  //
  // AntennaRelay.cs
  //
  // Copyright © 2014, toadicus
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without modification,
  // are permitted provided that the following conditions are met:
  //
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  //
  // 2. Redistributions in binary form must reproduce the above copyright notice,
  // this list of conditions and the following disclaimer in the documentation and/or other
  // materials provided with the distribution.
  //
  // 3. Neither the name of the copyright holder nor the names of its contributors may be used
  // to endorse or promote products derived from this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
  using ToadicusTools;
   
namespace AntennaRange namespace AntennaRange
{ {
public class AntennaRelay : IAntennaRelay public class AntennaRelay
{ {
  // We don't have a Bard, so we'll hide Kerbin here.
protected CelestialBody Kerbin; protected CelestialBody Kerbin;
   
public Vessel vessel protected IAntennaRelay _nearestRelayCache;
  protected IAntennaRelay moduleRef;
   
  protected System.Diagnostics.Stopwatch searchTimer;
  protected long millisecondsBetweenSearches;
   
  /// <summary>
  /// Gets the parent Vessel.
  /// </summary>
  /// <value>The parent Vessel.</value>
  public virtual Vessel vessel
  {
  get
  {
  return this.moduleRef.vessel;
  }
  }
   
  /// <summary>
  /// Gets or sets the nearest relay.
  /// </summary>
  /// <value>The nearest relay</value>
  public IAntennaRelay nearestRelay
  {
  get
  {
  if (this.searchTimer.IsRunning &&
  this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
  {
  this._nearestRelayCache = this.FindNearestRelay();
  this.searchTimer.Restart();
  }
   
  return this._nearestRelayCache;
  }
  protected set
  {
  this._nearestRelayCache = value;
  }
  }
   
  /// <summary>
  /// Gets the transmit distance.
  /// </summary>
  /// <value>The transmit distance.</value>
  public double transmitDistance
  {
  get
  {
  this.nearestRelay = this.FindNearestRelay();
   
  // If there is no available relay nearby...
  if (this.nearestRelay == null)
  {
  // .. return the distance to Kerbin
  return this.DistanceTo(this.Kerbin);
  }
  else
  {
  /// ...otherwise, return the distance to the nearest available relay.
  return this.DistanceTo(nearestRelay);
  }
  }
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
  public virtual float maxTransmitDistance
  {
  get;
  set;
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
  /// the current relay attempt.
  /// </summary>
  /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
  public virtual bool relayChecked
{ {
get; get;
protected set; protected set;
} }
   
// Returns the current distance to the center of Kerbin, which is totally where the Kerbals keep their radioes. /// <summary>
public double transmitDistance /// Determines whether this instance can transmit.
{ /// </summary>
get /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
{ public virtual bool CanTransmit()
IAntennaRelay nearestRelay = this.FindNearestRelay();  
   
if (nearestRelay == null)  
{  
return this.DistanceTo(this.Kerbin);  
}  
else  
{  
return this.DistanceTo(nearestRelay);  
}  
}  
}  
   
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 bool CanTransmit()  
{ {
if (this.transmitDistance > this.maxTransmitDistance) if (this.transmitDistance > this.maxTransmitDistance)
{ {
return false; return false;
} }
else else
{ {
return true; return true;
} }
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
/// <returns>The nearest relay.</returns> /// <returns>The nearest relay or null, if no relays in range.</returns>
public IAntennaRelay FindNearestRelay() public IAntennaRelay FindNearestRelay()
{ {
this.relayChecked = true; if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
  {
List<Vessel> nearbyVessels = FlightGlobals.Vessels return this.nearestRelay;
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance) }
.ToList();  
  if (this.searchTimer.IsRunning)
  {
  this.searchTimer.Stop();
  this.searchTimer.Reset();
  }
   
  this.searchTimer.Start();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Vessels in range: {1}", "{0}: finding nearest relay for {1} ({2})",
this.GetType().Name, this.GetType().Name,
nearbyVessels.Count this,
)); this.vessel.id
  ));
nearbyVessels.RemoveAll(v => v.id == vessel.id);  
  // Set this vessel as checked, so that we don't check it again.
Tools.PostDebugMessage(string.Format( RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
"{0}: Vessels in range excluding self: {1}",  
this.GetType().Name, double nearestDistance = double.PositiveInfinity;
nearbyVessels.Count IAntennaRelay _nearestRelay = null;
));  
  /*
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); * 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
Tools.PostDebugMessage(string.Format( * and that can transmit. Once we find a suitable candidate, assign it to _nearestRelay for comparison
"{0}: Found {1} nearby relays.", * against future finds.
this.GetType().Name, * */
nearbyRelays.Count foreach (Vessel potentialVessel in FlightGlobals.Vessels)
)); {
  // Skip vessels that have already been checked for a nearest relay this pass.
nearbyRelays.RemoveAll(r => r.relayChecked); try
  {
Tools.PostDebugMessage(string.Format( if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id])
"{0}: Found {1} nearby relays not already checked.", {
this.GetType().Name, continue;
nearbyRelays.Count }
)); }
  catch (KeyNotFoundException) { /* If the key doesn't exist, don't skip it. */}
nearbyRelays.RemoveAll(r => !r.CanTransmit());  
  // Skip vessels of the wrong type.
Tools.PostDebugMessage(string.Format( switch (potentialVessel.vesselType)
"{0}: Found {1} nearby relays not already checked that can transmit.", {
this.GetType().Name, case VesselType.Debris:
nearbyRelays.Count case VesselType.Flag:
)); case VesselType.EVA:
  case VesselType.SpaceObject:
nearbyRelays.Sort(new RelayComparer(this.vessel)); case VesselType.Unknown:
  continue;
IAntennaRelay nearestRelay = nearbyRelays.FirstOrDefault(); default:
  break;
this.relayChecked = false; }
   
return nearestRelay; // Skip vessels with the wrong ID
  if (potentialVessel.id == vessel.id)
  {
  continue;
  }
   
  // Find the distance from here to the vessel...
  double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude;
   
  /*
  * ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a
  * vessel we've already checked.
  * */
  if (potentialDistance > Tools.Min(this.maxTransmitDistance, nearestDistance, vessel.DistanceTo(Kerbin)))
  {
  continue;
  }
   
  nearestDistance = potentialDistance;
   
  foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
  {
  if (potentialRelay.CanTransmit())
  {
  _nearestRelay = potentialRelay;
  Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
  this.GetType().Name,
  _nearestRelay.ToString(),
  _nearestRelay.vessel.id
  ));
  break;
  }
  }
  }
   
  // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
  // used next time.
  RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
   
  // Return the nearest available relay, or null if there are no available relays nearby.
  return _nearestRelay;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
public AntennaRelay(Vessel v) public AntennaRelay(IAntennaRelay module)
{ {
this.vessel = v; this.moduleRef = module;
   
  this.searchTimer = new System.Diagnostics.Stopwatch();
  this.millisecondsBetweenSearches = 5000;
   
  // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
  // we hope it is safe enough.
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
}  
   
internal class RelayComparer : IComparer<IAntennaRelay>  
{  
protected Vessel referenceVessel;  
   
private RelayComparer() {}  
   
public RelayComparer(Vessel reference)  
{  
this.referenceVessel = reference;  
}  
   
public int Compare(IAntennaRelay one, IAntennaRelay two)  
{  
double distanceOne;  
double distanceTwo;  
   
distanceOne = one.vessel.DistanceTo(referenceVessel);  
distanceTwo = two.vessel.DistanceTo(referenceVessel);  
   
return distanceOne.CompareTo(distanceTwo);  
}  
} }
} }
} }