Add XML comment docstrings or disable warnings for all files.
Add XML comment docstrings or disable warnings for all files.

// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
   
using KSP; using KSP;
using System; using System;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
  /// <summary>
  /// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange.
  /// </summary>
[KSPAddon(KSPAddon.Startup.SpaceCentre, false)] [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class ARConfiguration : MonoBehaviour public class ARConfiguration : MonoBehaviour
{ {
  /// <summary>
  /// Indicates whether connections require line of sight.
  /// </summary>
public static bool RequireLineOfSight public static bool RequireLineOfSight
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// A "fudge factor" ratio that pretends planets and moons are slightly smaller than reality to make
  /// building communication constellations easier.
  /// </summary>
public static double RadiusRatio public static double RadiusRatio
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// Indicates whether unmanned vessels require a connection for control.
  /// </summary>
public static bool RequireConnectionForControl public static bool RequireConnectionForControl
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// If true, relays will fix their power cost when above nominal range, decreasing data rate instead.
  /// </summary>
public static bool FixedPowerCost public static bool FixedPowerCost
{ {
get; get;
private set; private set;
} }
   
  /// <summary>
  /// Indicates whether this AntennaRange will draw pretty lines in map view.
  /// </summary>
public static bool PrettyLines public static bool PrettyLines
{ {
get; get;
private set; private set;
} }
   
  #pragma warning disable 1591
   
private bool showConfigWindow; private bool showConfigWindow;
private Rect configWindowPos; private Rect configWindowPos;
   
private IButton toolbarButton; private IButton toolbarButton;
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
   
private System.Version runningVersion; private System.Version runningVersion;
   
private KSP.IO.PluginConfiguration _config; private KSP.IO.PluginConfiguration _config;
private KSP.IO.PluginConfiguration config private KSP.IO.PluginConfiguration config
{ {
get get
{ {
if (this._config == null) if (this._config == null)
{ {
this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>(); this._config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
} }
   
return this._config; return this._config;
} }
} }
   
public void Awake() public void Awake()
{ {
Tools.PostDebugMessage(this, "Waking up."); Tools.PostDebugMessage(this, "Waking up.");
   
this.runningVersion = this.GetType().Assembly.GetName().Version; this.runningVersion = this.GetType().Assembly.GetName().Version;
   
this.showConfigWindow = false; this.showConfigWindow = false;
this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15); this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
   
   
this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos); this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
   
ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false); ARConfiguration.RequireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
   
ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d)); ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d));
ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio; ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
   
ARConfiguration.RequireConnectionForControl = ARConfiguration.RequireConnectionForControl =
this.LoadConfigValue("requireConnectionForControl", false); this.LoadConfigValue("requireConnectionForControl", false);
   
ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false); ARConfiguration.FixedPowerCost = this.LoadConfigValue("fixedPowerCost", false);
   
ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true); ARConfiguration.PrettyLines = this.LoadConfigValue("drawPrettyLines", true);
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
   
Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion)); Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
   
Tools.PostDebugMessage(this, "Awake."); Tools.PostDebugMessage(this, "Awake.");
} }
   
public void OnGUI() public void OnGUI()
{ {
// Only runs once, if the Toolbar is available. // Only runs once, if the Toolbar is available.
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
if (this.toolbarButton == null) if (this.toolbarButton == null)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button."); Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
   
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
this.toolbarButton.Text = "AR"; this.toolbarButton.Text = "AR";
this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon"; this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst; this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
this.toolbarButton.OnClick += delegate(ClickEvent e) this.toolbarButton.OnClick += delegate(ClickEvent e)
{ {
this.toggleConfigWindow(); this.toggleConfigWindow();
}; };
} }
} }
else if (this.appLauncherButton == null && ApplicationLauncher.Ready) else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
{ {
Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button."); Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
   
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
this.toggleConfigWindow, this.toggleConfigWindow,
this.toggleConfigWindow, this.toggleConfigWindow,
ApplicationLauncher.AppScenes.SPACECENTER, ApplicationLauncher.AppScenes.SPACECENTER,
GameDatabase.Instance.GetTexture( GameDatabase.Instance.GetTexture(
"AntennaRange/Textures/appLauncherIcon", "AntennaRange/Textures/appLauncherIcon",
false false
) )
); );
} }
   
if (this.showConfigWindow) if (this.showConfigWindow)
{ {
Rect configPos = GUILayout.Window(354163056, Rect configPos = GUILayout.Window(354163056,
this.configWindowPos, this.configWindowPos,
this.ConfigWindow, this.ConfigWindow,
string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor), string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
GUILayout.ExpandHeight(true), GUILayout.ExpandHeight(true),
GUILayout.ExpandWidth(true) GUILayout.ExpandWidth(true)
); );
   
configPos = Tools.ClampRectToScreen(configPos, 20); configPos = Tools.ClampRectToScreen(configPos, 20);
   
if (configPos != this.configWindowPos) if (configPos != this.configWindowPos)
{ {
this.configWindowPos = configPos; this.configWindowPos = configPos;
this.SaveConfigValue("configWindowPos", this.configWindowPos); this.SaveConfigValue("configWindowPos", this.configWindowPos);
} }
} }
} }
   
public void ConfigWindow(int _) public void ConfigWindow(int _)
{ {
GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight"); bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
if (requireLineOfSight != ARConfiguration.RequireLineOfSight) if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
{ {
ARConfiguration.RequireLineOfSight = requireLineOfSight; ARConfiguration.RequireLineOfSight = requireLineOfSight;
this.SaveConfigValue("requireLineOfSight", requireLineOfSight); this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
   
bool requireConnectionForControl = bool requireConnectionForControl =
GUITools.Toggle( GUITools.Toggle(
ARConfiguration.RequireConnectionForControl, ARConfiguration.RequireConnectionForControl,
"Require Connection for Probe Control" "Require Connection for Probe Control"
); );
if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl) if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
{ {
ARConfiguration.RequireConnectionForControl = requireConnectionForControl; ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl); this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost"); bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
if (fixedPowerCost != ARConfiguration.FixedPowerCost) if (fixedPowerCost != ARConfiguration.FixedPowerCost)
{ {
ARConfiguration.FixedPowerCost = fixedPowerCost; ARConfiguration.FixedPowerCost = fixedPowerCost;
this.SaveConfigValue("fixedPowerCost", fixedPowerCost); this.SaveConfigValue("fixedPowerCost", fixedPowerCost);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines"); bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
if (prettyLines != ARConfiguration.PrettyLines) if (prettyLines != ARConfiguration.PrettyLines)
{ {
ARConfiguration.PrettyLines = prettyLines; ARConfiguration.PrettyLines = prettyLines;
this.SaveConfigValue("drawPrettyLines", prettyLines); this.SaveConfigValue("drawPrettyLines", prettyLines);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
if (requireLineOfSight) if (requireLineOfSight)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio); double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio);
double newRatio; double newRatio;
   
GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio)); GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
   
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
   
newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true)); newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
newRatio = Math.Round(newRatio, 2); newRatio = Math.Round(newRatio, 2);
   
if (newRatio != graceRatio) if (newRatio != graceRatio)
{ {
ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio); ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
this.SaveConfigValue("graceRatio", newRatio); this.SaveConfigValue("graceRatio", newRatio);
} }
   
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
   
GUILayout.EndVertical(); GUILayout.EndVertical();
   
GUI.DragWindow(); GUI.DragWindow();
} }
   
public void OnDestroy() public void OnDestroy()
{ {
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
} }
} }
   
protected void onSceneChangeRequested(GameScenes scene) protected void onSceneChangeRequested(GameScenes scene)
{ {
if (scene != GameScenes.SPACECENTER) if (scene != GameScenes.SPACECENTER)
{ {
print("ARConfiguration: Requesting Destruction."); print("ARConfiguration: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
} }
   
private void toggleConfigWindow() private void toggleConfigWindow()
{ {
this.showConfigWindow = !this.showConfigWindow; this.showConfigWindow = !this.showConfigWindow;
} }
   
private T LoadConfigValue<T>(string key, T defaultValue) private T LoadConfigValue<T>(string key, T defaultValue)
{ {
this.config.load(); this.config.load();
   
return config.GetValue(key, defaultValue); return config.GetValue(key, defaultValue);
} }
   
private void SaveConfigValue<T>(string key, T value) private void SaveConfigValue<T>(string key, T value)
{ {
this.config.load(); this.config.load();
   
this.config.SetValue(key, value); this.config.SetValue(key, value);
   
this.config.save(); this.config.save();
} }
} }
} }
   
// AntennaRange // AntennaRange
// //
// ARFlightController.cs // ARFlightController.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.
   
  #pragma warning disable 1591
   
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
{ {
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour public class ARFlightController : MonoBehaviour
{ {
#region Fields #region Fields
protected Dictionary<ConnectionStatus, string> connectionTextures; protected Dictionary<ConnectionStatus, string> connectionTextures;
protected Dictionary<ConnectionStatus, Texture> appLauncherTextures; protected Dictionary<ConnectionStatus, Texture> appLauncherTextures;
   
protected ARMapRenderer mapRenderer; protected ARMapRenderer mapRenderer;
   
protected IButton toolbarButton; protected IButton toolbarButton;
   
protected ApplicationLauncherButton appLauncherButton; protected ApplicationLauncherButton appLauncherButton;
protected Tools.DebugLogger log; protected Tools.DebugLogger log;
   
protected System.Diagnostics.Stopwatch updateTimer; protected System.Diagnostics.Stopwatch updateTimer;
#endregion #endregion
   
#region Properties #region Properties
public ConnectionStatus currentConnectionStatus public ConnectionStatus currentConnectionStatus
{ {
get; get;
protected set; protected set;
} }
   
protected string currentConnectionTexture protected string currentConnectionTexture
{ {
get get
{ {
return this.connectionTextures[this.currentConnectionStatus]; return this.connectionTextures[this.currentConnectionStatus];
} }
} }
   
protected Texture currentAppLauncherTexture protected Texture currentAppLauncherTexture
{ {
get get
{ {
return this.appLauncherTextures[this.currentConnectionStatus]; return this.appLauncherTextures[this.currentConnectionStatus];
} }
} }
   
public ControlTypes currentControlLock public ControlTypes currentControlLock
{ {
get get
{ {
if (this.lockID == string.Empty) if (this.lockID == string.Empty)
{ {
return ControlTypes.None; return ControlTypes.None;
} }
   
return InputLockManager.GetControlLock(this.lockID); return InputLockManager.GetControlLock(this.lockID);
} }
} }
   
public string lockID public string lockID
{ {
get; get;
protected set; protected set;
} }
   
public ControlTypes lockSet public ControlTypes lockSet
{ {
get get
{ {
return ControlTypes.ALL_SHIP_CONTROLS; return ControlTypes.ALL_SHIP_CONTROLS;
} }
} }
   
public Vessel vessel public Vessel vessel
{ {
get get
{ {
if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
return FlightGlobals.ActiveVessel; return FlightGlobals.ActiveVessel;
} }
   
return null; return null;
} }
} }
#endregion #endregion
   
#region MonoBehaviour LifeCycle #region MonoBehaviour LifeCycle
protected void Awake() protected void Awake()
{ {
this.lockID = "ARConnectionRequired"; this.lockID = "ARConnectionRequired";
   
this.log = Tools.DebugLogger.New(this); this.log = Tools.DebugLogger.New(this);
   
this.updateTimer = new System.Diagnostics.Stopwatch(); this.updateTimer = new System.Diagnostics.Stopwatch();
   
this.connectionTextures = new Dictionary<ConnectionStatus, string>(); this.connectionTextures = new Dictionary<ConnectionStatus, string>();
   
this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection"; this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal"; this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon"; this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
   
this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>(); this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
   
this.appLauncherTextures[ConnectionStatus.None] = this.appLauncherTextures[ConnectionStatus.None] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
this.appLauncherTextures[ConnectionStatus.Suboptimal] = this.appLauncherTextures[ConnectionStatus.Suboptimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
this.appLauncherTextures[ConnectionStatus.Optimal] = this.appLauncherTextures[ConnectionStatus.Optimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
   
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
   
this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None]; this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None];
this.toolbarButton.Text = "AntennaRange"; this.toolbarButton.Text = "AntennaRange";
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
this.toolbarButton.Enabled = false; this.toolbarButton.Enabled = false;
} }
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
GameEvents.onVesselChange.Add(this.onVesselChange); GameEvents.onVesselChange.Add(this.onVesselChange);
} }
   
protected void Start() protected void Start()
{ {
this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>(); this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
} }
   
protected void FixedUpdate() protected void FixedUpdate()
{ {
if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready) if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
{ {
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
ApplicationLauncher.AppScenes.FLIGHT, ApplicationLauncher.AppScenes.FLIGHT,
this.appLauncherTextures[ConnectionStatus.None] this.appLauncherTextures[ConnectionStatus.None]
); );
} }
   
this.log.Clear(); this.log.Clear();
   
VesselCommand availableCommand; VesselCommand availableCommand;
   
if (ARConfiguration.RequireConnectionForControl) if (ARConfiguration.RequireConnectionForControl)
{ {
availableCommand = this.vessel.CurrentCommand(); availableCommand = this.vessel.CurrentCommand();
} }
else else
{ {
availableCommand = VesselCommand.Crew; availableCommand = VesselCommand.Crew;
} }
   
log.AppendFormat("availableCommand: {0}\n\t" + log.AppendFormat("availableCommand: {0}\n\t" +
"(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" + "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
"(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" + "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
"vessel.HasConnectedRelay(): {3}", "vessel.HasConnectedRelay(): {3}",
(int)availableCommand, (int)availableCommand,
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew, (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe, (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
vessel.HasConnectedRelay() vessel.HasConnectedRelay()
); );
   
// If we are requiring a connection for control, the vessel does not have any adequately staffed pods, // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
// and the vessel does not have any connected relays... // and the vessel does not have any connected relays...
if ( if (
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
ARConfiguration.RequireConnectionForControl && ARConfiguration.RequireConnectionForControl &&
this.vessel != null && this.vessel != null &&
this.vessel.vesselType != VesselType.EVA && this.vessel.vesselType != VesselType.EVA &&
!( !(
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew || (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay() (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
)) ))
{ {
// ...and if the controls are not currently locked... // ...and if the controls are not currently locked...
if (currentControlLock == ControlTypes.None) if (currentControlLock == ControlTypes.None)
{ {
// ...lock the controls. // ...lock the controls.
InputLockManager.SetControlLock(this.lockSet, this.lockID); InputLockManager.SetControlLock(this.lockSet, this.lockID);
} }
} }
// ...otherwise, if the controls are locked... // ...otherwise, if the controls are locked...
else if (currentControlLock != ControlTypes.None) else if (currentControlLock != ControlTypes.None)
{ {
// ...unlock the controls. // ...unlock the controls.
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
   
log.Print(); log.Print();
} }
   
protected void Update() protected void Update()
{ {
if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 125L) if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 125L)
{ {
this.updateTimer.Reset(); this.updateTimer.Reset();
} }
else else
{ {
return; return;
} }
   
this.log.Clear(); this.log.Clear();
   
if ( if (
(this.toolbarButton != null || this.appLauncherButton != null) && (this.toolbarButton != null || this.appLauncherButton != null) &&
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
FlightGlobals.ActiveVessel != null FlightGlobals.ActiveVessel != null
) )
{ {
log.Append("Checking vessel relay status.\n"); log.Append("Checking vessel relay status.\n");
   
this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus(); this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
   
log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}", log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
this.currentConnectionStatus, this.currentConnectionTexture); this.currentConnectionStatus, this.currentConnectionTexture);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.TexturePath = this.currentConnectionTexture; this.toolbarButton.TexturePath = this.currentConnectionTexture;
   
if (this.currentConnectionStatus == ConnectionStatus.None) if (this.currentConnectionStatus == ConnectionStatus.None)
{ {
this.toolbarButton.Important = true; this.toolbarButton.Important = true;
} }
else else
{ {
this.toolbarButton.Important = false; this.toolbarButton.Important = false;
} }
} }
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
this.appLauncherButton.SetTexture(this.currentAppLauncherTexture); this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
} }
} }
   
log.Print(); log.Print();
} }
   
protected void OnDestroy() protected void OnDestroy()
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
   
if (this.mapRenderer != null) if (this.mapRenderer != null)
{ {
GameObject.Destroy(this.mapRenderer); GameObject.Destroy(this.mapRenderer);
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
   
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
GameEvents.onVesselChange.Remove(this.onVesselChange); GameEvents.onVesselChange.Remove(this.onVesselChange);
   
print("ARFlightController: Destroyed."); print("ARFlightController: Destroyed.");
} }
#endregion #endregion
   
#region Event Handlers #region Event Handlers
protected void onSceneChangeRequested(GameScenes scene) protected void onSceneChangeRequested(GameScenes scene)
{ {
print("ARFlightController: Requesting Destruction."); print("ARFlightController: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
   
protected void onVesselChange(Vessel vessel) protected void onVesselChange(Vessel vessel)
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
#endregion #endregion
} }
} }
   
// 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.
   
  #pragma warning disable 1591
   
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 Dictionary<Guid, bool> vesselFrameCache;
   
  #pragma warning disable 414
private bool dumpBool; private bool dumpBool;
  private Color lastColor;
  private Color thisColor;
  #pragma warning restore 414
#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>();
} }
   
LineRenderer lr; LineRenderer lr;
   
if (this.vesselLineRenderers.TryGetValue(idx, out lr)) if (this.vesselLineRenderers.TryGetValue(idx, out lr))
{ {
return lr; return lr;
} }
else else
{ {
GameObject obj = new GameObject(); GameObject obj = new GameObject();
obj.layer = 31; obj.layer = 31;
   
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 lr;
} }
} }
} }
#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>(); 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(); 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; continue;
} }
   
if (this.vesselFrameCache.TryGetValue(vessel.id, out dumpBool)) if (this.vesselFrameCache.TryGetValue(vessel.id, out dumpBool))
{ {
log.AppendFormat("Skipping vessel {0} because it's already been processed this frame."); 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;
} }
   
IAntennaRelay vesselRelay = vessel.GetBestRelay(); IAntennaRelay vesselRelay = vessel.GetBestRelay();
   
if (vesselRelay != null) if (vesselRelay != null)
{ {
this.SetRelayVertices(vesselRelay); this.SetRelayVertices(vesselRelay);
} }
} }
} }
} }
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
   
  #region Utility
private void SetRelayVertices(IAntennaRelay relay) private void SetRelayVertices(IAntennaRelay relay)
{ {
Color lastColor = default(Color); lastColor = default(Color);
Color thisColor;  
   
LineRenderer renderer = this[relay.vessel.id]; LineRenderer renderer = this[relay.vessel.id];
Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D()); Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
   
float lineWidth; float lineWidth;
float d = Screen.height / 2f + 0.01f; float d = Screen.height / 2f + 0.01f;
   
if (MapView.Draw3DLines) if (MapView.Draw3DLines)
{ {
lineWidth = 0.005859375f * MapView.MapCamera.Distance; lineWidth = 0.005859375f * MapView.MapCamera.Distance;
} }
else else
{ {
lineWidth = 2f; lineWidth = 2f;
   
start = MapView.MapCamera.camera.WorldToScreenPoint(start); start = MapView.MapCamera.camera.WorldToScreenPoint(start);
   
start.z = start.z >= 0f ? d : -d; start.z = start.z >= 0f ? d : -d;
} }
   
renderer.SetWidth(lineWidth, lineWidth); renderer.SetWidth(lineWidth, lineWidth);
   
renderer.SetPosition(0, start); renderer.SetPosition(0, start);
   
int idx = 0; int idx = 0;
   
while (relay != null) while (relay != null)
{ {
Vector3d nextPoint; Vector3d nextPoint;
   
renderer.enabled = true; renderer.enabled = true;
   
if (!relay.CanTransmit()) if (!relay.CanTransmit())
{ {
thisColor = Color.red; thisColor = Color.red;
} }
else else
{ {
if (relay.transmitDistance < relay.nominalTransmitDistance) if (relay.transmitDistance < relay.nominalTransmitDistance)
{ {
thisColor = Color.green; thisColor = Color.green;
} }
else else
{ {
thisColor = Color.yellow; thisColor = Color.yellow;
} }
} }
   
if (lastColor != default(Color) && thisColor != lastColor) if (lastColor != default(Color) && thisColor != lastColor)
{ {
break; break;
} }
   
lastColor = thisColor; lastColor = thisColor;
renderer.SetColors(thisColor, thisColor); renderer.SetColors(thisColor, thisColor);
   
this.vesselFrameCache[relay.vessel.id] = true; this.vesselFrameCache[relay.vessel.id] = true;
   
if (relay.KerbinDirect) if (relay.KerbinDirect)
{ {
nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position); nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
relay = null; relay = null;
} }
else else
{ {
if (relay.targetRelay == null) if (relay.targetRelay == null)
{ {
return; return;
} }
   
nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D()); nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
relay = relay.targetRelay; relay = relay.targetRelay;
} }
   
if (!MapView.Draw3DLines) if (!MapView.Draw3DLines)
{ {
nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint); nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
nextPoint.z = nextPoint.z >= 0f ? d : -d; nextPoint.z = nextPoint.z >= 0f ? d : -d;
} }
   
renderer.SetPosition(++idx, nextPoint); renderer.SetPosition(++idx, nextPoint);
} }
} }
   
public void Cleanup() private 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) if (this.vesselFrameCache != null && this.vesselFrameCache.Count > 0)
{ {
this.vesselFrameCache.Clear(); this.vesselFrameCache.Clear();
} }
} }
  #endregion
} }
} }
   
   
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug_win</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion> <ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid> <ProjectGuid>{B36F2C11-962E-4A75-9F41-61AD56D11493}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>AntennaRange</RootNamespace> <RootNamespace>AntennaRange</RootNamespace>
<AssemblyName>AntennaRange</AssemblyName> <AssemblyName>AntennaRange</AssemblyName>
<ReleaseVersion>1.3</ReleaseVersion> <ReleaseVersion>1.3</ReleaseVersion>
<SynchReleaseVersion>false</SynchReleaseVersion> <SynchReleaseVersion>false</SynchReleaseVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<UseMSBuildEngine>False</UseMSBuildEngine> <UseMSBuildEngine>False</UseMSBuildEngine>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE;</DefineConstants> <DefineConstants>DEBUG;TRACE;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" /> <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_win|AnyCPU' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" /> <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
  <DocumentationFile>bin\Release\AntennaRange.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE;</DefineConstants> <DefineConstants>DEBUG;TRACE;</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" /> <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_linux|AnyCPU' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath> <OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
<Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" /> <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
<ConsolePause>false</ConsolePause> <ConsolePause>false</ConsolePause>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="IAntennaRelay.cs" /> <Compile Include="IAntennaRelay.cs" />
<Compile Include="ModuleLimitedDataTransmitter.cs" /> <Compile Include="ModuleLimitedDataTransmitter.cs" />
<Compile Include="AntennaRelay.cs" /> <Compile Include="AntennaRelay.cs" />
<Compile Include="ProtoAntennaRelay.cs" /> <Compile Include="ProtoAntennaRelay.cs" />
<Compile Include="RelayDatabase.cs" /> <Compile Include="RelayDatabase.cs" />
<Compile Include="RelayExtensions.cs" /> <Compile Include="RelayExtensions.cs" />
<Compile Include="ARConfiguration.cs" /> <Compile Include="ARConfiguration.cs" />
<Compile Include="ARFlightController.cs" /> <Compile Include="ARFlightController.cs" />
<Compile Include="ARMapRenderer.cs" /> <Compile Include="ARMapRenderer.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath> <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="System"> <Reference Include="System">
<HintPath>..\_KSPAssemblies\System.dll</HintPath> <HintPath>..\_KSPAssemblies\System.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath> <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj"> <ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
<Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project> <Project>{D48A5542-6655-4149-BC27-B27DF0466F1C}</Project>
<Name>ToadicusTools</Name> <Name>ToadicusTools</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="GameData\AntennaRange\AntennaRange.cfg" /> <None Include="GameData\AntennaRange\AntennaRange.cfg" />
<None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" /> <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
</ItemGroup> </ItemGroup>
</Project> </Project>
// 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;
   
namespace AntennaRange namespace AntennaRange
{ {
/// <summary> /// <summary>
/// Relay code at the heart of AntennaRange /// Relay code at the heart of AntennaRange
/// </summary> /// </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> /// <summary>
/// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
/// </summary> /// </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;
} }
} }
   
  private long lastSearch;
   
private bool canTransmit; private bool canTransmit;
   
private IAntennaRelay nearestRelay; private IAntennaRelay nearestRelay;
private IAntennaRelay bestOccludedRelay; private IAntennaRelay bestOccludedRelay;
   
private System.Diagnostics.Stopwatch searchTimer;  
private long millisecondsBetweenSearches;  
   
/// <summary> /// <summary>
/// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
/// <see cref="AntennaRange.IAntennaRelay"/> /// <see cref="AntennaRange.IAntennaRelay"/>
/// </summary> /// </summary>
protected IAntennaRelay moduleRef; protected IAntennaRelay moduleRef;
   
/// <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 the target <see cref="AntennaRange.IAntennaRelay"/>relay. /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
/// </summary> /// </summary>
public IAntennaRelay targetRelay public IAntennaRelay targetRelay
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight. /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
/// </summary> /// </summary>
public virtual CelestialBody firstOccludingBody public virtual CelestialBody firstOccludingBody
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the transmit distance. /// Gets the transmit distance.
/// </summary> /// </summary>
/// <value>The transmit distance.</value> /// <value>The transmit distance.</value>
public double transmitDistance public double transmitDistance
{ {
get get
{ {
this.FindNearestRelay(); this.FindNearestRelay();
   
if (this.KerbinDirect || this.targetRelay == null) if (this.KerbinDirect || this.targetRelay == null)
{ {
return this.DistanceTo(Kerbin); return this.DistanceTo(Kerbin);
} }
else else
{ {
return this.DistanceTo(this.targetRelay); return this.DistanceTo(this.targetRelay);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </summary>
public virtual double nominalTransmitDistance public virtual double nominalTransmitDistance
{ {
get; get;
set; set;
} }
   
/// <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> /// <value>The max transmit distance.</value>
public virtual double maxTransmitDistance public virtual double maxTransmitDistance
{ {
get; get;
set; set;
} }
   
/// <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>
public virtual bool KerbinDirect public virtual bool KerbinDirect
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <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.PostDebugMessage(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.PostDebugMessage("{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.PostDebugMessage("{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.PostDebugMessage(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.PostDebugMessage(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.PostDebugMessage("{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.PostDebugMessage(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;
   
Tools.DebugLogger log = Tools.DebugLogger.New(this); 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, check it against the nearest relay.
  // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe
if (bestOccludedSqrDistance < kerbinSqrDistance) if (bestOccludedSqrDistance < kerbinSqrDistance)
{ {
log.AppendFormat("\n\t\tPicking occluded relay {0} as target ({1} < {2}).", log.AppendFormat("\n\tBest occluded relay is closer than Kerbin ({0} < {1})",
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), bestOccludedRelay, kerbinSqrDistance);
bestOccludedSqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = this.bestOccludedRelay;  
this.firstOccludingBody = bodyOccludingBestOccludedRelay; // If the nearest relay is closer than the best occluded relay, pick it.
} // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
// Otherwise, target Kerbin and report the first body blocking it. if (nearestRelaySqrDistance < bestOccludedSqrDistance)
  {
  log.AppendFormat("\n\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
  }
  // Otherwise, target the best occluded relay.
  else
  {
  log.AppendFormat("\n\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, bestOccludedSqrDistance);
   
  this.targetRelay = bestOccludedRelay;
  this.firstOccludingBody = bodyOccludingBestOccludedRelay;
  }
  }
  // Otherwise, check Kerbin against the nearest relay.
  // Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).", log.AppendFormat("\n\tKerbin is closer than the best occluded relay ({0} >= {1})",
bestOccludedSqrDistance, kerbinSqrDistance); bestOccludedRelay, kerbinSqrDistance);
   
this.KerbinDirect = true; this.firstOccludingBody = null;
this.targetRelay = null;  
this.firstOccludingBody = bodyOccludingKerbin; // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  log.AppendFormat("\n\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  log.AppendFormat("\n\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
  nearestRelaySqrDistance, kerbinSqrDistance);
   
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
} }
} }
} }
// 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, check it against the nearest relay.
// 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}).",  
this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),  
bestOccludedSqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = false; this.KerbinDirect = false;
this.targetRelay = bestOccludedRelay;  
this.firstOccludingBody = bodyOccludingBestOccludedRelay; // If the nearest relay is closer than the best occluded relay, pick it.
} // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
// Otherwise, target Kerbin. Since we have LOS, blank the first occluding body. if (nearestRelaySqrDistance < bestOccludedSqrDistance)
  {
  this.targetRelay = nearestRelay;
  this.firstOccludingBody = null;
  }
  // Otherwise, target the best occluded relay.
  else
  {
  this.targetRelay = bestOccludedRelay;
  this.firstOccludingBody = bodyOccludingBestOccludedRelay;
  }
  }
  // Otherwise, check Kerbin against the nearest relay.
  // Since we have LOS, blank the first occluding body.
else else
{ {
log.AppendFormat("\n\t\tPicking Kerbin as target ({0} >= {1}).",  
bestOccludedSqrDistance, kerbinSqrDistance);  
   
this.KerbinDirect = true;  
this.targetRelay = null;  
this.firstOccludingBody = null; this.firstOccludingBody = null;
   
  // If the nearest relay is closer than Kerbin, pick it.
  // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
  if (nearestRelaySqrDistance < kerbinSqrDistance)
  {
  this.KerbinDirect = false;
  this.targetRelay = nearestRelay;
  }
  // Otherwise, pick Kerbin.
  else
  {
  this.KerbinDirect = true;
  this.targetRelay = null;
  }
} }
} }
} }
} }
   
log.AppendFormat("\n{0}: Status determination complete.", this.ToString()); log.AppendFormat("\n{0}: Status determination complete.", this.ToString());
   
log.Print(); 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> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns> /// <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.AntennaRelay"/> class. /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="module">The module reference underlying this AntennaRelay, /// <param name="module">The module reference underlying this AntennaRelay,
/// as an <see cref="AntennaRange.IAntennaRelay"/></param> /// 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
// //
// 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
{ {
/* /// <summary>
* 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.
* */ /// </summary>
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.
private ProtoPartSnapshot protoPart; private ProtoPartSnapshot protoPart;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
public override Vessel vessel public override Vessel vessel
{ {
get get
{ {
if (this.protoPart != null && this.protoPart.pVesselRef != null) if (this.protoPart != null && this.protoPart.pVesselRef != null)
{ {
return this.protoPart.pVesselRef.vesselRef; return this.protoPart.pVesselRef.vesselRef;
} }
else else
{ {
return null; return null;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config. /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary> /// </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 relay can operate. /// The maximum distance at which this relay can operate.
/// </summary> /// </summary>
public override double maxTransmitDistance public override double maxTransmitDistance
{ {
get get
{ {
return moduleRef.maxTransmitDistance; return moduleRef.maxTransmitDistance;
} }
} }
   
/// <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> /// <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>. /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary> /// </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> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns> /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
public override string ToString() public override string ToString()
{ {
System.Text.StringBuilder sb = new System.Text.StringBuilder(); System.Text.StringBuilder sb = new System.Text.StringBuilder();
   
sb.Append(this.Title); sb.Append(this.Title);
   
if (this.protoPart != null && this.protoPart.pVesselRef != null) if (this.protoPart != null && this.protoPart.pVesselRef != null)
{ {
sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName); sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
} }
   
return sb.ToString(); 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.AntennaRelay"/> class.
/// </summary> /// </summary>
/// <param name="ms">The ProtoPartModuleSnapshot to wrap</param> /// <param name="prefabRelay">The module reference underlying this AntennaRelay,
/// <param name="vessel">The parent Vessel</param> /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
  /// <param name="pps">The prototype partreference on which the module resides.</param>
public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay) public ProtoAntennaRelay(IAntennaRelay prefabRelay, ProtoPartSnapshot pps) : base(prefabRelay)
{ {
this.protoPart = pps; this.protoPart = pps;
}  
   
~ProtoAntennaRelay()  
{  
Tools.PostDebugMessage(string.Format(  
"{0}: destroyed",  
this.ToString()  
));  
} }
} }
} }
   
   
// AntennaRange // AntennaRange
// //
// RelayDatabase.cs // RelayDatabase.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.
   
  #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase public class RelayDatabase
{ {
/* /*
* Static members * Static members
* */ * */
// Singleton storage // Singleton storage
protected static RelayDatabase _instance; protected static RelayDatabase _instance;
// Gets the singleton // Gets the singleton
public static RelayDatabase Instance public static RelayDatabase Instance
{ {
get get
{ {
if (_instance == null) if (_instance == null)
{ {
_instance = new RelayDatabase(); _instance = new RelayDatabase();
} }
   
return _instance; return _instance;
} }
} }
   
/* /*
* Instance members * Instance members
* */ * */
   
/* /*
* Fields * Fields
* */ * */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects. // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase; protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
   
// Vessel.id-keyed hash table of part counts, used for caching // Vessel.id-keyed hash table of part counts, used for caching
protected Dictionary<Guid, int> vesselPartCountTable; protected Dictionary<Guid, int> vesselPartCountTable;
   
// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time. // Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
public Dictionary<Guid, bool> CheckedVesselsTable; public Dictionary<Guid, bool> CheckedVesselsTable;
   
protected int cacheHits; protected int cacheHits;
protected int cacheMisses; protected int cacheMisses;
   
/* /*
* Properties * Properties
* */ * */
// Gets the Part-hashed table of relays in a given vessel // Gets the Part-hashed table of relays in a given vessel
public Dictionary<int, IAntennaRelay> this [Vessel vessel] public Dictionary<int, IAntennaRelay> this [Vessel vessel]
{ {
get get
{ {
// If we don't have an entry for this vessel... // If we don't have an entry for this vessel...
if (!this.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel.id))
{ {
// ...Generate an entry for this vessel. // ...Generate an entry for this vessel.
this.AddVessel(vessel); this.AddVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
{ {
// ...Update the our vessel in the cache // ...Update the our vessel in the cache
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// Otherwise, it's a hit // Otherwise, it's a hit
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
   
// Return the Part-hashed table of relays for this vessel // Return the Part-hashed table of relays for this vessel
return relayDatabase[vessel.id]; return relayDatabase[vessel.id];
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Adds a vessel to the database // Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses // The return for this function isn't used yet, but seems useful for potential future API-uses
public bool AddVessel(Vessel vessel) public bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// ...post an error // ...post an error
Debug.LogWarning(string.Format( Debug.LogWarning(string.Format(
"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.", "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// ...and refuse to add // ...and refuse to add
return false; return false;
} }
// otherwise, add the vessel to our tables... // otherwise, add the vessel to our tables...
else else
{ {
// Build an empty table... // Build an empty table...
this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>(); this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
   
// Update the empty index // Update the empty index
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
   
// Return success // Return success
return true; return true;
} }
} }
   
// Update the vessel's entry in the table // Update the vessel's entry in the table
public void UpdateVessel(Vessel vessel) public void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.", "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
   
// Actually build and assign the table // Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable); this.getVesselRelays(vessel, ref vesselTable);
// Set the part count // Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count; this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
} }
   
// Remove a vessel from the cache, if it exists. // Remove a vessel from the cache, if it exists.
public void DirtyVessel(Vessel vessel) public void DirtyVessel(Vessel vessel)
{ {
if (this.relayDatabase.ContainsKey(vessel.id)) if (this.relayDatabase.ContainsKey(vessel.id))
{ {
this.relayDatabase.Remove(vessel.id); this.relayDatabase.Remove(vessel.id);
} }
if (this.vesselPartCountTable.ContainsKey(vessel.id)) if (this.vesselPartCountTable.ContainsKey(vessel.id))
{ {
this.vesselPartCountTable.Remove(vessel.id); this.vesselPartCountTable.Remove(vessel.id);
} }
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
public bool ContainsKey(Guid key) public bool ContainsKey(Guid key)
{ {
return this.relayDatabase.ContainsKey(key); return this.relayDatabase.ContainsKey(key);
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
public bool ContainsKey(Vessel vessel) public bool ContainsKey(Vessel vessel)
{ {
return this.ContainsKey(vessel.id); return this.ContainsKey(vessel.id);
} }
   
// Runs when a vessel is modified (or when we switch to one, to catch docking events) // Runs when a vessel is modified (or when we switch to one, to catch docking events)
public void onVesselEvent(Vessel vessel) public void onVesselEvent(Vessel vessel)
{ {
// If we have this vessel in our cache... // If we have this vessel in our cache...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// If our part counts disagree (such as if a part has been added or broken off, // If our part counts disagree (such as if a part has been added or broken off,
// or if we've just docked or undocked)... // or if we've just docked or undocked)...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' ({2}).", "{0}: dirtying cache for vessel '{1}' ({2}).",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// Dirty the cache (real vessels will never have negative part counts) // Dirty the cache (real vessels will never have negative part counts)
this.DirtyVessel(vessel); this.DirtyVessel(vessel);
} }
} }
} }
   
// Runs when the player requests a scene change, such as when changing vessels or leaving flight. // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
public void onSceneChange(GameScenes scene) public void onSceneChange(GameScenes scene)
{ {
// If the active vessel is a real thing... // If the active vessel is a real thing...
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
// ... dirty its cache // ... dirty its cache
this.onVesselEvent(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
} }
   
// Runs when parts are undocked // Runs when parts are undocked
public void onPartEvent(Part part) public void onPartEvent(Part part)
{ {
if (part != null && part.vessel != null) if (part != null && part.vessel != null)
{ {
this.onVesselEvent(part.vessel); this.onVesselEvent(part.vessel);
} }
} }
   
// Runs when parts are coupled, as in docking // Runs when parts are coupled, as in docking
public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
this.onPartEvent(data.from); this.onPartEvent(data.from);
this.onPartEvent(data.to); this.onPartEvent(data.to);
} }
   
// Produce a Part-hashed table of relays for the given vessel // Produce a Part-hashed table of relays for the given vessel
protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays) protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
{ {
// We're going to completely regen this table, so dump the current contents. // We're going to completely regen this table, so dump the current contents.
relays.Clear(); relays.Clear();
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Getting antenna relays from vessel {1}.", "{0}: Getting antenna relays from vessel {1}.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly. // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) { if (vessel.loaded) {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is loaded, searching for modules in loaded parts.", "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
foreach (Part part in vessel.Parts) foreach (Part part in vessel.Parts)
{ {
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
foreach (PartModule module in part.Modules) foreach (PartModule module in part.Modules)
{ {
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
// ...add the module to the table // ...add the module to the table
relays.Add(part.GetHashCode(), module as IAntennaRelay); relays.Add(part.GetHashCode(), module as IAntennaRelay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots. // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
else else
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel {1} is not loaded, searching for modules in prototype parts.", "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots) foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}", "{0}: Searching in protopartsnapshot {1}",
this.GetType().Name, this.GetType().Name,
pps pps
)); ));
   
// ...Fetch the prefab, because it's more useful for what we're doing. // ...Fetch the prefab, because it's more useful for what we're doing.
Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab; Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Got partPrefab {1} in protopartsnapshot {2}", "{0}: Got partPrefab {1} in protopartsnapshot {2}",
this.GetType().Name, this.GetType().Name,
partPrefab, partPrefab,
pps pps
)); ));
   
// ...loop through the PartModules in the prefab... // ...loop through the PartModules in the prefab...
foreach (PartModule module in partPrefab.Modules) foreach (PartModule module in partPrefab.Modules)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}", "{0}: Searching in partmodule {1}",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: partmodule {1} is antennarelay", "{0}: partmodule {1} is antennarelay",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...build a new ProtoAntennaRelay and add it to the table // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps)); relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.", "{0}: vessel '{1}' ({2}) has {3} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName, vessel.vesselName,
vessel.id, vessel.id,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
protected RelayDatabase() protected RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
this.CheckedVesselsTable = new Dictionary<Guid, bool>(); this.CheckedVesselsTable = new Dictionary<Guid, bool>();
   
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselEvent); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselEvent); GameEvents.onVesselChange.Add(this.onVesselEvent);
GameEvents.onVesselDestroy.Add(this.onVesselEvent); GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent); GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent); GameEvents.onPartUndock.Add(this.onPartEvent);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselEvent); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselEvent); GameEvents.onVesselChange.Remove(this.onVesselEvent);
GameEvents.onVesselDestroy.Remove(this.onVesselEvent); GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent); GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent); GameEvents.onPartUndock.Remove(this.onPartEvent);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
   
KSPLog.print(string.Format( KSPLog.print(string.Format(
"{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}", "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
this.GetType().Name, this.GetType().Name,
this.cacheHits, this.cacheHits,
this.cacheMisses, this.cacheMisses,
(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits) (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
)); ));
} }
   
#if DEBUG #if DEBUG
public void Dump() public void Dump()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
   
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
foreach (Guid id in this.relayDatabase.Keys) foreach (Guid id in this.relayDatabase.Keys)
{ {
sb.AppendFormat("\nVessel {0}:", id); sb.AppendFormat("\nVessel {0}:", id);
   
foreach (IAntennaRelay relay in this.relayDatabase[id].Values) foreach (IAntennaRelay relay in this.relayDatabase[id].Values)
{ {
sb.AppendFormat("\n\t{0}", relay.ToString()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
} }
#endif #endif
} }
} }
   
   
// 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
{ {
/* /// <summary>
* 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.
* */ /// </summary>
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);
} }
   
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and a Vessel
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="vessel">A <see cref="Vessel"/></param>
public static double 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);
} }
   
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and a CelestialBody
  /// </summary>
  /// <param name="relay">This <see cref="IAntennaRelay"/></param>
  /// <param name="body">A <see cref="CelestialBody"/></param>
public static double 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);
} }
   
  /// <summary>
  /// Returns the square of the distance between this IAntennaRelay and another IAntennaRelay
  /// </summary>
  /// <param name="relayOne">This <see cref="IAntennaRelay"/></param>
  /// <param name="relayTwo">Another <see cref="IAntennaRelay"/></param>
public static double 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;
} }
   
  /// <summary>
  /// Gets the <see cref="AntennaRange.ConnectionStatus"/> for this <see cref="Vessel"/>
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
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;
} }
} }
   
  /// <summary>
  /// Gets the best relay on this Vessel. The best relay may not be able to transmit.
  /// </summary>
  /// <param name="vessel">This <see cref="Vessel"/></param>
public static IAntennaRelay GetBestRelay(this Vessel vessel) public static IAntennaRelay GetBestRelay(this Vessel vessel)
{ {
IAntennaRelay bestRelay = null; IAntennaRelay bestRelay = null;
double bestScore = double.PositiveInfinity; double bestScore = double.PositiveInfinity;
double relayScore = double.NaN; double relayScore = double.NaN;
   
foreach (IAntennaRelay relay in vessel.GetAntennaRelays()) foreach (IAntennaRelay relay in vessel.GetAntennaRelays())
{ {
relayScore = relay.transmitDistance / relay.maxTransmitDistance; relayScore = relay.transmitDistance / relay.maxTransmitDistance;
   
if (relayScore < bestScore) if (relayScore < bestScore)
{ {
bestScore = relayScore; bestScore = relayScore;
bestRelay = relay; bestRelay = relay;
} }
} }
   
return bestRelay; return bestRelay;
} }
} }
   
  #pragma warning disable 1591
  /// <summary>
  /// An Enum describing the connection status of a vessel or relay.
  /// </summary>
public enum ConnectionStatus public enum ConnectionStatus
{ {
None, None,
Suboptimal, Suboptimal,
Optimal Optimal
} }
} }