IAntennaRelay: XML comments
--- /dev/null
+++ b/.gitattributes
@@ -1,1 +1,13 @@
+* text=auto
+* eol=lf
+# These files are text and should be normalized (convert crlf => lf)
+*.cs text diff=csharp
+*.cfg text
+*.csproj text
+*.sln text
+
+# Images should be treated as binary
+# (binary is a macro for -text -diff)
+*.png binary
+
--- a/ARConfiguration.cs
+++ b/ARConfiguration.cs
@@ -8,17 +8,123 @@
using ToadicusTools;
using UnityEngine;
-[assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
-
namespace AntennaRange
{
+ /// <summary>
+ /// A <see cref="UnityEngine.MonoBehaviour"/> responsible for managing configuration items for AntennaRange.
+ /// </summary>
[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class ARConfiguration : MonoBehaviour
{
+ private const string WINDOW_POS_KEY = "configWindowPos";
+ private const string REQUIRE_LOS_KEY = "requireLineOfSight";
+ private const string GRACE_RATIO_KEY = "graceRatio";
+ private const string REQUIRE_PROBE_CONNECTION_KEY = "requireConnectionForControl";
+ private const string FIXED_POWER_KEY = "fixedPowerCost";
+ private const string PRETTY_LINES_KEY = "drawPrettyLines";
+ private const string UPDATE_DELAY_KEY = "updateDelay";
+ private const string USE_ADDITIVE_KEY = "useAdditiveRanges";
+
+ private const string TRACKING_STATION_RANGES_KEY = "TRACKING_STATION_RANGES";
+ private const string RANGE_KEY = "range";
+
+ /// <summary>
+ /// Indicates whether connections require line of sight.
+ /// </summary>
+ public static bool RequireLineOfSight
+ {
+ get;
+ 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
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Indicates whether unmanned vessels require a connection for control.
+ /// </summary>
+ public static bool RequireConnectionForControl
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// If true, relays will fix their power cost when above nominal range, decreasing data rate instead.
+ /// </summary>
+ public static bool FixedPowerCost
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Indicates whether this AntennaRange will draw pretty lines in map view.
+ /// </summary>
+ public static bool PrettyLines
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Gets the update delay.
+ /// </summary>
+ public static long UpdateDelay
+ {
+ get;
+ private set;
+ }
+
+ public static bool UseAdditiveRanges
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Gets Kerbin's relay range based on the current tracking station level.
+ /// </summary>
+ public static double KerbinRelayRange
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Gets Kerbin's nominal relay range based on the current tracking station level.
+ /// </summary>
+ public static double KerbinNominalRange
+ {
+ get
+ {
+ return KerbinRelayRange / 2.8284271247461901d;
+ }
+ }
+
+#pragma warning disable 1591
+
private bool showConfigWindow;
private Rect configWindowPos;
+ private string updateDelayStr;
+ private long updateDelay;
+
private IButton toolbarButton;
+ private ApplicationLauncherButton appLauncherButton;
+
+ private double[] trackingStationRanges;
+
+ private System.Version runningVersion;
+
+ private bool runOnce;
private KSP.IO.PluginConfiguration _config;
private KSP.IO.PluginConfiguration config
@@ -38,32 +144,120 @@
{
Tools.PostDebugMessage(this, "Waking up.");
+ this.runningVersion = this.GetType().Assembly.GetName().Version;
+
this.showConfigWindow = false;
this.configWindowPos = new Rect(Screen.width / 4, Screen.height / 2, 180, 15);
+
+ this.configWindowPos = this.LoadConfigValue(WINDOW_POS_KEY, this.configWindowPos);
+
+ ARConfiguration.RequireLineOfSight = this.LoadConfigValue(REQUIRE_LOS_KEY, false);
+
+ ARConfiguration.RadiusRatio = (1 - this.LoadConfigValue(GRACE_RATIO_KEY, .05d));
+ ARConfiguration.RadiusRatio *= ARConfiguration.RadiusRatio;
+
+ ARConfiguration.RequireConnectionForControl =
+ this.LoadConfigValue(REQUIRE_PROBE_CONNECTION_KEY, false);
+
+ ARConfiguration.FixedPowerCost = this.LoadConfigValue(FIXED_POWER_KEY, false);
+
+ ARConfiguration.PrettyLines = this.LoadConfigValue(PRETTY_LINES_KEY, true);
+
+ ARConfiguration.UpdateDelay = this.LoadConfigValue(UPDATE_DELAY_KEY, 16L);
+
+ ARConfiguration.UseAdditiveRanges = this.LoadConfigValue(USE_ADDITIVE_KEY, true);
+
+ this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+
+ GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
+ GameEvents.OnKSCFacilityUpgraded.Add(this.onFacilityUpgraded);
+
+ Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion));
+
+ ConfigNode[] tsRangeNodes = GameDatabase.Instance.GetConfigNodes(TRACKING_STATION_RANGES_KEY);
+
+ if (tsRangeNodes.Length > 0)
+ {
+ string[] rangeValues = tsRangeNodes[0].GetValues(RANGE_KEY);
+
+ this.trackingStationRanges = new double[rangeValues.Length];
+
+ for (int idx = 0; idx < rangeValues.Length; idx++)
+ {
+ if (!double.TryParse(rangeValues[idx], out this.trackingStationRanges[idx]))
+ {
+ this.LogError("Could not parse value '{0}' to double; Tracking Station ranges may not work!");
+ this.trackingStationRanges[idx] = 0d;
+ }
+ }
+
+ this.Log("Loaded Tracking Station ranges from config: [{0}]", this.trackingStationRanges.SPrint());
+ }
+ else
+ {
+ this.trackingStationRanges = new double[]
+ {
+ 51696576d,
+ 37152180000d,
+ 224770770000d
+ };
+
+ this.LogWarning("Failed to load Tracking Station ranges from config, using hard-coded values: [{0}]",
+ this.trackingStationRanges.SPrint());
+ }
+
+ this.runOnce = true;
+
Tools.PostDebugMessage(this, "Awake.");
}
+ public void Update()
+ {
+ if (
+ this.runOnce &&
+ (ScenarioUpgradeableFacilities.Instance != null || HighLogic.CurrentGame.Mode != Game.Modes.CAREER)
+ )
+ {
+ this.runOnce = false;
+
+ this.SetKerbinRelayRange();
+ }
+ }
+
public void OnGUI()
{
- if (this.toolbarButton == null && ToolbarManager.ToolbarAvailable)
- {
- Tools.PostDebugMessage(this, "Toolbar available; initializing button.");
-
- this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
- this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
- this.toolbarButton.Text = "AR";
- this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
- this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
- this.toolbarButton.OnClick += delegate(ClickEvent e)
- {
- this.showConfigWindow = !this.showConfigWindow;
- };
-
- this.configWindowPos = this.LoadConfigValue("configWindowPos", this.configWindowPos);
- AntennaRelay.requireLineOfSight = this.LoadConfigValue("requireLineOfSight", false);
- ARFlightController.requireConnectionForControl =
- this.LoadConfigValue("requireConnectionForControl", false);
+ // Only runs once, if the Toolbar is available.
+ if (ToolbarManager.ToolbarAvailable)
+ {
+ if (this.toolbarButton == null)
+ {
+ Tools.PostDebugMessage(this, "Toolbar available; initializing toolbar button.");
+
+ this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConfiguration");
+ this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.SPACECENTER);
+ this.toolbarButton.Text = "AR";
+ this.toolbarButton.TexturePath = "AntennaRange/Textures/toolbarIcon";
+ this.toolbarButton.TextColor = (Color)XKCDColors.Amethyst;
+ this.toolbarButton.OnClick += delegate(ClickEvent e)
+ {
+ this.toggleConfigWindow();
+ };
+ }
+ }
+ else if (this.appLauncherButton == null && ApplicationLauncher.Ready)
+ {
+ Tools.PostDebugMessage(this, "Toolbar available; initializing AppLauncher button.");
+
+ this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
+ this.toggleConfigWindow,
+ this.toggleConfigWindow,
+ ApplicationLauncher.AppScenes.SPACECENTER,
+ GameDatabase.Instance.GetTexture(
+ "AntennaRange/Textures/appLauncherIcon",
+ false
+ )
+ );
}
if (this.showConfigWindow)
@@ -71,7 +265,7 @@
Rect configPos = GUILayout.Window(354163056,
this.configWindowPos,
this.ConfigWindow,
- "AntennaRange Configuration",
+ string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor),
GUILayout.ExpandHeight(true),
GUILayout.ExpandWidth(true)
);
@@ -81,7 +275,7 @@
if (configPos != this.configWindowPos)
{
this.configWindowPos = configPos;
- this.SaveConfigValue("configWindowPos", this.configWindowPos);
+ this.SaveConfigValue(WINDOW_POS_KEY, this.configWindowPos);
}
}
}
@@ -92,11 +286,11 @@
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
- bool requireLineOfSight = GUILayout.Toggle(AntennaRelay.requireLineOfSight, "Require Line of Sight");
- if (requireLineOfSight != AntennaRelay.requireLineOfSight)
- {
- AntennaRelay.requireLineOfSight = requireLineOfSight;
- this.SaveConfigValue("requireLineOfSight", requireLineOfSight);
+ bool requireLineOfSight = GUITools.Toggle(ARConfiguration.RequireLineOfSight, "Require Line of Sight");
+ if (requireLineOfSight != ARConfiguration.RequireLineOfSight)
+ {
+ ARConfiguration.RequireLineOfSight = requireLineOfSight;
+ this.SaveConfigValue(REQUIRE_LOS_KEY, requireLineOfSight);
}
GUILayout.EndHorizontal();
@@ -104,29 +298,164 @@
GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
bool requireConnectionForControl =
- GUILayout.Toggle(
- ARFlightController.requireConnectionForControl,
+ GUITools.Toggle(
+ ARConfiguration.RequireConnectionForControl,
"Require Connection for Probe Control"
);
- if (requireConnectionForControl != ARFlightController.requireConnectionForControl)
- {
- ARFlightController.requireConnectionForControl = requireConnectionForControl;
- this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl);
- }
-
- GUILayout.EndHorizontal();
+ if (requireConnectionForControl != ARConfiguration.RequireConnectionForControl)
+ {
+ ARConfiguration.RequireConnectionForControl = requireConnectionForControl;
+ this.SaveConfigValue(REQUIRE_PROBE_CONNECTION_KEY, requireConnectionForControl);
+ }
+
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal();
+
+ bool fixedPowerCost = GUITools.Toggle(ARConfiguration.FixedPowerCost, "Use Fixed Power Cost");
+ if (fixedPowerCost != ARConfiguration.FixedPowerCost)
+ {
+ ARConfiguration.FixedPowerCost = fixedPowerCost;
+ this.SaveConfigValue(FIXED_POWER_KEY, fixedPowerCost);
+ }
+
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal();
+
+ bool useAdditive = GUITools.Toggle(ARConfiguration.UseAdditiveRanges, "Use Additive Ranges");
+ if (useAdditive != ARConfiguration.UseAdditiveRanges)
+ {
+ ARConfiguration.UseAdditiveRanges = useAdditive;
+ this.SaveConfigValue(USE_ADDITIVE_KEY, useAdditive);
+ }
+
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal();
+
+ bool prettyLines = GUITools.Toggle(ARConfiguration.PrettyLines, "Draw Pretty Lines");
+ if (prettyLines != ARConfiguration.PrettyLines)
+ {
+ ARConfiguration.PrettyLines = prettyLines;
+ this.SaveConfigValue(PRETTY_LINES_KEY, prettyLines);
+ }
+
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal();
+
+ GUILayout.Label("Update Delay", GUILayout.ExpandWidth(false));
+
+ this.updateDelayStr = GUILayout.TextField(this.updateDelayStr, 4, GUILayout.Width(40f));
+
+ GUILayout.Label("ms", GUILayout.ExpandWidth(false));
+
+ GUILayout.EndHorizontal();
+
+ if (this.updateDelayStr.Length > 1 && long.TryParse(this.updateDelayStr, out this.updateDelay))
+ {
+ ARConfiguration.UpdateDelay = Math.Min(Math.Max(this.updateDelay, 16), 2500);
+ this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
+ }
+
+ if (requireLineOfSight)
+ {
+ GUILayout.BeginHorizontal();
+
+ double graceRatio = 1d - Math.Sqrt(ARConfiguration.RadiusRatio);
+ double newRatio;
+
+ GUILayout.Label(string.Format("Line of Sight 'Fudge Factor': {0:P0}", graceRatio));
+
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal();
+
+ newRatio = GUILayout.HorizontalSlider((float)graceRatio, 0f, 1f, GUILayout.ExpandWidth(true));
+ newRatio = Math.Round(newRatio, 2);
+
+ if (newRatio != graceRatio)
+ {
+ ARConfiguration.RadiusRatio = (1d - newRatio) * (1d - newRatio);
+ this.SaveConfigValue(GRACE_RATIO_KEY, newRatio);
+ }
+
+ GUILayout.EndHorizontal();
+ }
GUILayout.EndVertical();
GUI.DragWindow();
}
- public void Destroy()
- {
+ public void OnDestroy()
+ {
+ GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
+ GameEvents.OnKSCFacilityUpgraded.Remove(this.onFacilityUpgraded);
+
if (this.toolbarButton != null)
{
this.toolbarButton.Destroy();
- }
+ this.toolbarButton = null;
+ }
+
+ if (this.appLauncherButton != null)
+ {
+ ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
+ this.appLauncherButton = null;
+ }
+ }
+
+ private void onSceneChangeRequested(GameScenes scene)
+ {
+ if (scene != GameScenes.SPACECENTER)
+ {
+ print("ARConfiguration: Requesting Destruction.");
+ MonoBehaviour.Destroy(this);
+ }
+ }
+
+ private void onFacilityUpgraded(Upgradeables.UpgradeableFacility fac, int lvl)
+ {
+ if (fac.id == "SpaceCenter/TrackingStation")
+ {
+ this.Log("Caught onFacilityUpgraded for {0} at level {1}", fac.id, lvl);
+ this.SetKerbinRelayRange();
+ }
+ }
+
+ private void SetKerbinRelayRange()
+ {
+ int tsLevel;
+
+ if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
+ {
+ tsLevel = ScenarioUpgradeableFacilities.protoUpgradeables["SpaceCenter/TrackingStation"]
+ .facilityRefs[0].FacilityLevel;
+
+
+ }
+ else
+ {
+ tsLevel = this.trackingStationRanges.Length - 1;
+ }
+
+ if (tsLevel < this.trackingStationRanges.Length && tsLevel >= 0)
+ {
+ KerbinRelayRange = this.trackingStationRanges[tsLevel];
+ this.Log("Setting Kerbin's range to {0}", KerbinRelayRange);
+ }
+ else
+ {
+ this.LogError("Could not set Kerbin's range with invalid Tracking Station level {0}", tsLevel);
+ }
+ }
+
+ private void toggleConfigWindow()
+ {
+ this.showConfigWindow = !this.showConfigWindow;
+ this.updateDelayStr = ARConfiguration.UpdateDelay.ToString();
}
private T LoadConfigValue<T>(string key, T defaultValue)
--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -2,7 +2,7 @@
//
// ARFlightController.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -26,8 +26,11 @@
// 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.
+#pragma warning disable 1591
+
using KSP;
using System;
+using System.Collections.Generic;
using ToadicusTools;
using UnityEngine;
@@ -36,11 +39,43 @@
[KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour
{
- #region Static Members
- public static bool requireConnectionForControl;
+ #region Fields
+ private Dictionary<ConnectionStatus, string> toolbarTextures;
+ private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
+
+ private ARMapRenderer mapRenderer;
+
+ private IButton toolbarButton;
+
+ private ApplicationLauncherButton appLauncherButton;
+ private Tools.DebugLogger log;
+
+ private System.Diagnostics.Stopwatch updateTimer;
#endregion
#region Properties
+ public ConnectionStatus currentConnectionStatus
+ {
+ get;
+ private set;
+ }
+
+ private string currentConnectionTexture
+ {
+ get
+ {
+ return this.toolbarTextures[this.currentConnectionStatus];
+ }
+ }
+
+ private Texture currentAppLauncherTexture
+ {
+ get
+ {
+ return this.appLauncherTextures[this.currentConnectionStatus];
+ }
+ }
+
public ControlTypes currentControlLock
{
get
@@ -57,14 +92,14 @@
public string lockID
{
get;
- protected set;
+ private set;
}
public ControlTypes lockSet
{
get
{
- return ControlTypes.GROUPS_ALL | ControlTypes.STAGING | ControlTypes.SAS | ControlTypes.RCS;
+ return ControlTypes.ALL_SHIP_CONTROLS;
}
}
@@ -83,43 +118,243 @@
#endregion
#region MonoBehaviour LifeCycle
- protected void Awake()
+ private void Awake()
{
this.lockID = "ARConnectionRequired";
- }
-
- protected void FixedUpdate()
- {
+
+ this.log = Tools.DebugLogger.New(this);
+
+ this.updateTimer = new System.Diagnostics.Stopwatch();
+
+ this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
+
+ this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
+ this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
+ this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
+
+ this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
+
+ this.appLauncherTextures[ConnectionStatus.None] =
+ GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
+ this.appLauncherTextures[ConnectionStatus.Suboptimal] =
+ GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
+ this.appLauncherTextures[ConnectionStatus.Optimal] =
+ GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
+
+ if (ToolbarManager.ToolbarAvailable)
+ {
+ this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
+
+ this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
+ this.toolbarButton.Text = "AntennaRange";
+ this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
+ this.toolbarButton.OnClick += (e) => (this.buttonToggle());
+ }
+
+ GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
+ GameEvents.onVesselChange.Add(this.onVesselChange);
+ }
+
+ private void Start()
+ {
+ this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
+ }
+
+ private void FixedUpdate()
+ {
+ this.log.Clear();
+
+ VesselCommand availableCommand;
+
+ if (ARConfiguration.RequireConnectionForControl)
+ {
+ availableCommand = this.vessel.CurrentCommand();
+ }
+ else
+ {
+ availableCommand = VesselCommand.Crew;
+ }
+
+ log.AppendFormat("availableCommand: {0}\n\t" +
+ "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
+ "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
+ "vessel.HasConnectedRelay(): {3}",
+ (int)availableCommand,
+ (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
+ (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
+ vessel.HasConnectedRelay()
+ );
+
+ // 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...
if (
- requireConnectionForControl &&
+ HighLogic.LoadedSceneIsFlight &&
+ ARConfiguration.RequireConnectionForControl &&
this.vessel != null &&
- !this.vessel.hasCrewCommand() &&
- this.vessel.IsControllable
- )
- {
- if (this.vessel.HasConnectedRelay())
- {
- if (currentControlLock != ControlTypes.None)
+ this.vessel.vesselType != VesselType.EVA &&
+ !(
+ (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
+ (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
+ ))
+ {
+ // ...and if the controls are not currently locked...
+ if (currentControlLock == ControlTypes.None)
+ {
+ // ...lock the controls.
+ InputLockManager.SetControlLock(this.lockSet, this.lockID);
+ }
+ }
+ // ...otherwise, if the controls are locked...
+ else if (currentControlLock != ControlTypes.None)
+ {
+ // ...unlock the controls.
+ InputLockManager.RemoveControlLock(this.lockID);
+ }
+
+ log.Print();
+ }
+
+ private void Update()
+ {
+ if (this.toolbarButton != null)
+ {
+ this.toolbarButton.Enabled = MapView.MapIsEnabled;
+ }
+
+ if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
+ {
+ this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
+ this.buttonToggle, this.buttonToggle,
+ ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
+ this.appLauncherTextures[ConnectionStatus.None]
+ );
+ }
+
+ if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
+ {
+ this.updateTimer.Restart();
+ }
+ else
+ {
+ return;
+ }
+
+ this.log.Clear();
+
+ if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ActiveVessel != null)
+ {
+ Vessel vessel;
+ IAntennaRelay relay;
+ IList<IAntennaRelay> activeVesselRelays;
+
+ for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
+ {
+ vessel = FlightGlobals.Vessels[vIdx];
+
+ if (vessel == null || vessel == FlightGlobals.ActiveVessel)
{
- InputLockManager.RemoveControlLock(this.lockID);
+ continue;
}
- }
- else
- {
- if (currentControlLock == ControlTypes.None)
+
+ log.AppendFormat("Fetching best relay for vessel {0}", vessel);
+
+ relay = vessel.GetBestRelay();
+
+ if (relay != null)
{
- InputLockManager.SetControlLock(this.lockSet, this.lockID);
+ log.AppendFormat("Finding nearest relay for best relay {0}", relay);
+
+ relay.FindNearestRelay();
}
}
- }
- }
-
- protected void Destroy()
- {
-
+
+ activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
+ for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
+ {
+ relay = activeVesselRelays[rIdx];
+
+ relay.FindNearestRelay();
+ }
+
+ if (this.toolbarButton != null || this.appLauncherButton != null)
+ {
+ log.Append("Checking vessel relay status.\n");
+
+ this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
+
+ log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}",
+ this.currentConnectionStatus, this.currentConnectionTexture);
+
+ if (this.toolbarButton != null)
+ {
+ this.toolbarButton.TexturePath = this.currentConnectionTexture;
+
+ if (this.currentConnectionStatus == ConnectionStatus.None)
+ {
+ if (!this.toolbarButton.Important) this.toolbarButton.Important = true;
+ }
+ else
+ {
+ if (this.toolbarButton.Important) this.toolbarButton.Important = false;
+ }
+ }
+ if (this.appLauncherButton != null)
+ {
+ this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
+ }
+ }
+ }
+
+ log.Print();
+ }
+
+ private void OnDestroy()
+ {
+ InputLockManager.RemoveControlLock(this.lockID);
+
+ if (this.mapRenderer != null)
+ {
+ GameObject.Destroy(this.mapRenderer);
+ }
+
+ if (this.toolbarButton != null)
+ {
+ this.toolbarButton.Destroy();
+ }
+
+ if (this.appLauncherButton != null)
+ {
+ ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
+ this.appLauncherButton = null;
+ }
+
+ GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
+ GameEvents.onVesselChange.Remove(this.onVesselChange);
+
+ print("ARFlightController: Destroyed.");
+ }
+ #endregion
+
+ private void buttonToggle()
+ {
+ if (MapView.MapIsEnabled)
+ {
+ ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
+ }
+ }
+
+ #region Event Handlers
+ private void onSceneChangeRequested(GameScenes scene)
+ {
+ print("ARFlightController: Requesting Destruction.");
+ MonoBehaviour.Destroy(this);
+ }
+
+ private void onVesselChange(Vessel vessel)
+ {
+ InputLockManager.RemoveControlLock(this.lockID);
}
#endregion
}
}
-
--- /dev/null
+++ b/ARMapRenderer.cs
@@ -1,1 +1,329 @@
-
+// AntennaRange
+//
+// ARMapRenderer.cs
+//
+// Copyright © 2014-2015, toadicus
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma warning disable 1591
+
+using KSP;
+using System;
+using System.Collections.Generic;
+using ToadicusTools;
+using UnityEngine;
+
+namespace AntennaRange
+{
+ public class ARMapRenderer : MonoBehaviour
+ {
+ #region Fields
+ private Dictionary<Guid, LineRenderer> vesselLineRenderers;
+
+ // Debug Stuff
+ #pragma warning disable 649
+ private System.Diagnostics.Stopwatch timer;
+ private Tools.DebugLogger log;
+ private long relayStart;
+ private long start;
+ #pragma warning restore 649
+
+ #pragma warning disable 414
+ private Color thisColor;
+ #pragma warning restore 414
+ #endregion
+
+ #region Properties
+ public LineRenderer this[Guid idx]
+ {
+ get
+ {
+ if (this.vesselLineRenderers == null)
+ {
+ this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
+ }
+
+ LineRenderer lr;
+
+ if (this.vesselLineRenderers.TryGetValue(idx, out lr))
+ {
+ return lr;
+ }
+ else
+ {
+ GameObject obj = new GameObject();
+ obj.layer = 31;
+
+ lr = obj.AddComponent<LineRenderer>();
+
+ // lr.SetColors(Color.green, Color.green);
+ lr.material = MapView.OrbitLinesMaterial;
+ // lr.SetVertexCount(2);
+
+ this.vesselLineRenderers[idx] = lr;
+
+ return lr;
+ }
+ }
+ }
+ #endregion
+
+ #region MonoBehaviour Lifecycle
+ private void Awake()
+ {
+ if (ARConfiguration.PrettyLines)
+ {
+ this.vesselLineRenderers = new Dictionary<Guid, LineRenderer>();
+ }
+
+ #if DEBUG
+ this.timer = new System.Diagnostics.Stopwatch();
+ this.log = Tools.DebugLogger.New(this);
+ #endif
+ }
+
+ private void OnPreCull()
+ {
+ if (!HighLogic.LoadedSceneIsFlight || !MapView.MapIsEnabled || !ARConfiguration.PrettyLines)
+ {
+ this.Cleanup();
+
+ return;
+ }
+
+ #if DEBUG
+ timer.Restart();
+ #endif
+
+ try
+ {
+ log.Clear();
+
+ log.AppendFormat("OnPreCull.\n");
+
+ log.AppendFormat("\tMapView: Draw3DLines: {0}\n" +
+ "\tMapView.MapCamera.camera.fieldOfView: {1}\n" +
+ "\tMapView.MapCamera.Distance: {2}\n",
+ MapView.Draw3DLines,
+ MapView.MapCamera.camera.fieldOfView,
+ MapView.MapCamera.Distance
+ );
+
+ if (FlightGlobals.ready && FlightGlobals.Vessels != null)
+ {
+ log.AppendLine("FlightGlobals ready and Vessels list not null.");
+
+ for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+ {
+ Vessel vessel = FlightGlobals.Vessels[i];
+
+ log.AppendFormat("\nStarting check for vessel {0} at {1}ms", vessel, timer.ElapsedMilliseconds);
+
+ if (vessel == null)
+ {
+ log.AppendFormat("\n\tSkipping vessel {0} altogether because it is null.", vessel);
+ continue;
+ }
+
+ switch (vessel.vesselType)
+ {
+ case VesselType.Debris:
+ case VesselType.EVA:
+ case VesselType.Unknown:
+ case VesselType.SpaceObject:
+ log.AppendFormat("\n\tDiscarded because vessel is of invalid type {0}",
+ vessel.vesselType);
+ continue;
+ }
+
+ log.AppendFormat("\n\tChecking vessel {0}.", vessel.vesselName);
+
+ #if DEBUG
+ start = timer.ElapsedMilliseconds;
+ #endif
+
+ IAntennaRelay vesselRelay = vessel.GetBestRelay();
+
+ if (vesselRelay == null)
+ {
+ log.AppendFormat("\n\tGot null relay for vessel {0}", vessel.vesselName);
+ continue;
+ }
+
+ log.AppendFormat("\n\tGot best relay {0} ({3}) for vessel {1} in {2} ms",
+ vesselRelay, vessel, timer.ElapsedMilliseconds - start, vesselRelay.GetType().Name);
+
+ if (vesselRelay != null)
+ {
+ #if DEBUG
+ start = timer.ElapsedMilliseconds;
+ #endif
+
+ this.SetRelayVertices(vesselRelay);
+
+ log.AppendFormat("\n\tSet relay vertices for {0} in {1}ms",
+ vessel, timer.ElapsedMilliseconds - start);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ this.LogError("Caught {0}: {1}\n{2}\n", ex.GetType().Name, ex.ToString(), ex.StackTrace.ToString());
+ this.Cleanup();
+ }
+ #if DEBUG
+ finally
+ {
+ log.AppendFormat("\n\tOnPreCull finished in {0}ms\n", timer.ElapsedMilliseconds);
+
+ log.Print();
+ }
+ #endif
+ }
+
+ private void OnDestroy()
+ {
+ this.Cleanup();
+
+ this.Log("Destroyed");
+ }
+ #endregion
+
+ #region Utility
+ private void SetRelayVertices(IAntennaRelay relay)
+ {
+ log.AppendFormat("\n\t\tDrawing line for relay chain starting at {0}.", relay);
+
+ if (relay.vessel == null)
+ {
+ log.Append("\n\t\tvessel is null, bailing out");
+ return;
+ }
+
+ LineRenderer renderer = this[relay.vessel.id];
+ Vector3d start = ScaledSpace.LocalToScaledSpace(relay.vessel.GetWorldPos3D());
+
+ float lineWidth;
+ float d = Screen.height / 2f + 0.01f;
+
+ if (MapView.Draw3DLines)
+ {
+ lineWidth = 0.005859375f * MapView.MapCamera.Distance;
+ }
+ else
+ {
+ lineWidth = 2f;
+
+ start = MapView.MapCamera.camera.WorldToScreenPoint(start);
+
+ start.z = start.z >= 0f ? d : -d;
+ }
+
+ renderer.SetWidth(lineWidth, lineWidth);
+
+ renderer.SetPosition(0, start);
+
+ int idx = 0;
+
+ #if DEBUG
+ relayStart = timer.ElapsedMilliseconds;
+ #endif
+
+ Vector3d nextPoint;
+
+ renderer.enabled = true;
+
+ if (!relay.CanTransmit())
+ {
+ thisColor = Color.red;
+ }
+ else
+ {
+ if (relay.LinkStatus == ConnectionStatus.Optimal)
+ {
+ thisColor = Color.green;
+ }
+ else
+ {
+ thisColor = Color.yellow;
+ }
+ }
+
+ if (relay.KerbinDirect)
+ {
+ nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
+ }
+ else
+ {
+ if (relay.targetRelay == null || relay.targetRelay.vessel == null)
+ {
+ this.LogError(
+ "SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
+ relay
+ );
+
+ renderer.enabled = false;
+ return;
+ }
+
+ nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
+ }
+
+ renderer.SetColors(thisColor, thisColor);
+
+ if (!MapView.Draw3DLines)
+ {
+ nextPoint = MapView.MapCamera.camera.WorldToScreenPoint(nextPoint);
+ nextPoint.z = nextPoint.z >= 0f ? d : -d;
+ }
+
+ idx++;
+
+ renderer.SetVertexCount(idx + 1);
+ renderer.SetPosition(idx, nextPoint);
+
+ log.AppendFormat("\n\t\t\t...finished segment in {0} ms", timer.ElapsedMilliseconds - relayStart);
+ }
+
+ private void Cleanup()
+ {
+ if (this.vesselLineRenderers != null && this.vesselLineRenderers.Count > 0)
+ {
+ IEnumerator<LineRenderer> enumerator = this.vesselLineRenderers.Values.GetEnumerator();
+ LineRenderer lineRenderer;
+
+ while (enumerator.MoveNext())
+ {
+ lineRenderer = enumerator.Current;
+ lineRenderer.enabled = false;
+ GameObject.Destroy(lineRenderer.gameObject);
+ }
+ this.vesselLineRenderers.Clear();
+ }
+ }
+ #endregion
+ }
+}
+
--- a/AntennaRange.cfg
+++ /dev/null
@@ -1,72 +1,1 @@
-// AntennaRange
-//
-// AntennaRange.cfg
-//
-// Copyright © 2014, toadicus
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without modification,
-// are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation and/or other
-// materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its contributors may be used
-// to endorse or promote products derived from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
-// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-//
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
-//
-// Specifications:
-// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by
-// packetResourceCost and packetSize.
-// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
-// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
-// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
-//
-@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 1500000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
-
-@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 30000000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
-
-@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
-{
- @MODULE[ModuleDataTransmitter]
- {
- @name = ModuleLimitedDataTransmitter
- nominalRange = 80000000000
- maxPowerFactor = 8
- maxDataFactor = 4
- }
-}
-
--- a/AntennaRange.csproj
+++ b/AntennaRange.csproj
@@ -9,9 +9,10 @@
<OutputType>Library</OutputType>
<RootNamespace>AntennaRange</RootNamespace>
<AssemblyName>AntennaRange</AssemblyName>
- <ReleaseVersion>1.1</ReleaseVersion>
+ <ReleaseVersion>1.3</ReleaseVersion>
<SynchReleaseVersion>false</SynchReleaseVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <UseMSBuildEngine>False</UseMSBuildEngine>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_win|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,8 +25,7 @@
<ConsolePause>false</ConsolePause>
<CustomCommands>
<CustomCommands>
- <Command type="AfterBuild" command="xcopy /y ${ProjectDir}\AntennaRange.cfg C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
- <Command type="AfterBuild" command="xcopy /y ${TargetFile} C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
+ <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands>
</CustomCommands>
</PropertyGroup>
@@ -37,10 +37,10 @@
<ConsolePause>false</ConsolePause>
<CustomCommands>
<CustomCommands>
- <Command type="AfterBuild" command="xcopy /y ${ProjectDir}\AntennaRange.cfg C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
- <Command type="AfterBuild" command="xcopy /y ${TargetFile} C:\Users\andy\Games\KSP_win\GameData\AntennaRange\" />
+ <Command type="AfterBuild" command="xcopy /y ${TargetFile} ${ProjectDir}\GameData\AntennaRange\" />
</CustomCommands>
</CustomCommands>
+ <DocumentationFile>bin\Release\AntennaRange.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_linux|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -53,7 +53,7 @@
<ConsolePause>false</ConsolePause>
<CustomCommands>
<CustomCommands>
- <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/${ProjectName}.cfg /opt/games/KSP_linux/GameData/${ProjectName}/" />
+ <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands>
</CustomCommands>
</PropertyGroup>
@@ -64,7 +64,7 @@
<WarningLevel>4</WarningLevel>
<CustomCommands>
<CustomCommands>
- <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/${ProjectName}.cfg /opt/games/KSP_linux/GameData/${ProjectName}/" />
+ <Command type="AfterBuild" command="cp -afv ${TargetFile} ${ProjectDir}/GameData/${ProjectName}/" />
</CustomCommands>
</CustomCommands>
<ConsolePause>false</ConsolePause>
@@ -79,12 +79,22 @@
<Compile Include="RelayExtensions.cs" />
<Compile Include="ARConfiguration.cs" />
<Compile Include="ARFlightController.cs" />
+ <Compile Include="ARMapRenderer.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
- <None Include="AntennaRange.cfg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
+ <Reference Include="Assembly-CSharp">
+ <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
+ <Reference Include="System">
+ <HintPath>..\_KSPAssemblies\System.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
+ <Reference Include="UnityEngine">
+ <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ToadicusTools\ToadicusTools.csproj">
@@ -93,14 +103,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
- <Reference Include="Assembly-CSharp">
- <HintPath>..\_KSPAssemblies\Assembly-CSharp.dll</HintPath>
- </Reference>
- <Reference Include="System">
- <HintPath>..\_KSPAssemblies\System.dll</HintPath>
- </Reference>
- <Reference Include="UnityEngine">
- <HintPath>..\_KSPAssemblies\UnityEngine.dll</HintPath>
- </Reference>
+ <None Include="GameData\AntennaRange\AntennaRange.cfg" />
+ <None Include="GameData\AntennaRange\ATM_AntennaRange.cfg" />
</ItemGroup>
</Project>
--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -2,7 +2,7 @@
//
// AntennaRelay.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -28,23 +28,45 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using ToadicusTools;
namespace AntennaRange
{
+ /// <summary>
+ /// Relay code at the heart of AntennaRange
+ /// </summary>
public class AntennaRelay
{
- public static bool requireLineOfSight;
-
// We don't have a Bard, so we'll hide Kerbin here.
- protected CelestialBody Kerbin;
-
- protected IAntennaRelay _nearestRelayCache;
+ private static CelestialBody _Kerbin;
+
+ /// <summary>
+ /// Fetches, caches, and returns a <see cref="CelestialBody"/> reference to Kerbin
+ /// </summary>
+ public static CelestialBody Kerbin
+ {
+ get
+ {
+ if (_Kerbin == null && FlightGlobals.ready)
+ {
+ _Kerbin = FlightGlobals.GetHomeBody();
+ }
+
+ return _Kerbin;
+ }
+ }
+
+ private bool canTransmit;
+ private bool isChecked;
+
+ private IAntennaRelay nearestRelay;
+ private IAntennaRelay bestOccludedRelay;
+
+ /// <summary>
+ /// The <see cref="AntennaRange.ModuleLimitedDataTransmitter"/> reference underlying this AntennaRelay, as an
+ /// <see cref="AntennaRange.IAntennaRelay"/>
+ /// </summary>
protected IAntennaRelay moduleRef;
-
- protected System.Diagnostics.Stopwatch searchTimer;
- protected long millisecondsBetweenSearches;
/// <summary>
/// Gets the parent Vessel.
@@ -59,26 +81,49 @@
}
/// <summary>
- /// Gets or sets the nearest relay.
- /// </summary>
- /// <value>The nearest relay</value>
- public IAntennaRelay nearestRelay
- {
- get
- {
- if (this.searchTimer.IsRunning &&
- this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
- {
- this._nearestRelayCache = this.FindNearestRelay();
- this.searchTimer.Restart();
- }
-
- return this._nearestRelayCache;
- }
- protected set
- {
- this._nearestRelayCache = value;
- }
+ /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
+ /// </summary>
+ public IAntennaRelay targetRelay
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+ /// directly with Kerbin.
+ /// </summary>
+ public virtual bool KerbinDirect
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets or sets the nominal link distance, in meters.
+ /// </summary>
+ public virtual double NominalLinkDistance
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum link distance, in meters.
+ /// </summary>
+ public virtual double MaximumLinkDistance
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets the first <see cref="CelestialBody"/> found to be blocking line of sight.
+ /// </summary>
+ public virtual CelestialBody firstOccludingBody
+ {
+ get;
+ protected set;
}
/// <summary>
@@ -89,41 +134,40 @@
{
get
{
- this.nearestRelay = this.FindNearestRelay();
-
- // If there is no available relay nearby...
- if (this.nearestRelay == null)
- {
- // .. return the distance to Kerbin
- return this.DistanceTo(this.Kerbin);
+ if (this.KerbinDirect || this.targetRelay == null)
+ {
+ return this.DistanceTo(Kerbin);
}
else
{
- /// ...otherwise, return the distance to the nearest available relay.
- return this.DistanceTo(nearestRelay);
- }
- }
- }
-
- /// <summary>
- /// The maximum distance at which this relay can operate.
- /// </summary>
- /// <value>The max transmit distance.</value>
- public virtual float maxTransmitDistance
+ return this.DistanceTo(this.targetRelay);
+ }
+ }
+ }
+
+ public virtual ConnectionStatus LinkStatus
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
+ /// </summary>
+ public virtual double nominalTransmitDistance
{
get;
set;
}
/// <summary>
- /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
- /// the current relay attempt.
- /// </summary>
- /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
- public virtual bool relayChecked
+ /// The maximum distance at which this relay can operate.
+ /// </summary>
+ /// <value>The max transmit distance.</value>
+ public virtual double maxTransmitDistance
{
get;
- protected set;
+ set;
}
/// <summary>
@@ -132,66 +176,94 @@
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit()
{
- if (
- this.transmitDistance > this.maxTransmitDistance ||
- (requireLineOfSight && this.nearestRelay == null && !this.vessel.hasLineOfSightTo(this.Kerbin))
- )
- {
- return false;
- }
- else
- {
- return true;
- }
+ return this.canTransmit;
}
/// <summary>
/// Finds the nearest relay.
/// </summary>
/// <returns>The nearest relay or null, if no relays in range.</returns>
- public IAntennaRelay FindNearestRelay()
- {
- if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
- {
- return this.nearestRelay;
- }
-
- if (this.searchTimer.IsRunning)
- {
- this.searchTimer.Stop();
- this.searchTimer.Reset();
- }
-
- this.searchTimer.Start();
-
- Tools.PostDebugMessage(string.Format(
- "{0}: finding nearest relay for {1} ({2})",
- this.GetType().Name,
- this,
- this.vessel.id
- ));
-
+ public void FindNearestRelay()
+ {
+ if (!FlightGlobals.ready)
+ {
+ return;
+ }
+
+ Tools.DebugLogger log;
+ #if DEBUG
+ log = Tools.DebugLogger.New(this);
+ #endif
+
+ // Skip vessels that have already been checked for a nearest relay this pass.
+ if (this.isChecked)
+ {
+ log.AppendFormat("{0}: Target search skipped because our vessel has been checked already this search.",
+ this);
+ log.Print();
+ return;
+ }
+
+ log.AppendFormat("{0}: Target search started).", this.ToString());
+
+ #if DEBUG
+ try {
+ #endif
// Set this vessel as checked, so that we don't check it again.
- RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
-
- double nearestSqrDistance = double.PositiveInfinity;
- IAntennaRelay _nearestRelay = null;
+ this.isChecked = true;
+
+ // Blank everything we're trying to find before the search.
+ this.firstOccludingBody = null;
+ this.bestOccludedRelay = null;
+ this.targetRelay = null;
+ this.nearestRelay = null;
+
+ // Default to KerbinDirect = true in case something in here doesn't work right.
+ this.KerbinDirect = true;
+
+ CelestialBody bodyOccludingBestOccludedRelay = null;
+ IAntennaRelay needle;
+
+ // double nearestRelaySqrDistance = double.PositiveInfinity;
+ // double bestOccludedSqrDistance = double.PositiveInfinity;
+
+ // double maxTransmitSqrDistance = double.NegativeInfinity;
+
+ double nearestRelaySqrQuotient = double.PositiveInfinity;
+ double bestOccludedSqrQuotient = double.PositiveInfinity;
/*
* 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
- * 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.
* */
- foreach (Vessel potentialVessel in FlightGlobals.Vessels)
- {
- // Skip vessels that have already been checked for a nearest relay this pass.
- if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(potentialVessel.id))
- {
- continue;
- }
+ Vessel potentialVessel;
+ IAntennaRelay potentialBestRelay;
+ CelestialBody fob;
+
+ // IList<IAntennaRelay> vesselRelays;
+ for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
+ {
+ log.AppendFormat("\nFetching vessel at index {0}", vIdx);
+ potentialVessel = FlightGlobals.Vessels[vIdx];
+
+ if (potentialVessel == null)
+ {
+ Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
+ log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
+ log.Print();
+ return;
+ }
+ #if DEBUG
+ else
+ {
+ log.AppendFormat("\n\tGot vessel {0}", potentialVessel);
+ }
+ #endif
// Skip vessels of the wrong type.
+ log.Append("\n\tchecking vessel type");
switch (potentialVessel.vesselType)
{
case VesselType.Debris:
@@ -199,101 +271,496 @@
case VesselType.EVA:
case VesselType.SpaceObject:
case VesselType.Unknown:
+ log.Append("\n\tSkipping because vessel is the wrong type.");
continue;
default:
break;
}
-
+
+ log.Append("\n\tchecking if vessel is this vessel");
// Skip vessels with the wrong ID
if (potentialVessel.id == vessel.id)
{
+ log.Append("\n\tSkipping because vessel is this vessel.");
continue;
}
+ potentialBestRelay = potentialVessel.GetBestRelay();
+ log.AppendFormat("\n\t\tgot best vessel relay {0}",
+ potentialBestRelay == null ? "null" : potentialBestRelay.ToString());
+
+ if (potentialBestRelay == null)
+ {
+ log.Append("\n\t\t...skipping null relay");
+ continue;
+ }
+
+ // Find the distance from here to the vessel...
+ log.Append("\n\tgetting distance to potential vessel");
+ double potentialSqrDistance = this.sqrDistanceTo(potentialVessel);
+ log.Append("\n\tgetting best vessel relay");
+
+ log.Append("\n\tgetting max link distance to potential relay");
+ double maxLinkSqrDistance;
+
+ if (ARConfiguration.UseAdditiveRanges)
+ {
+ maxLinkSqrDistance = this.maxTransmitDistance * potentialBestRelay.maxTransmitDistance;
+ }
+ else
+ {
+ maxLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance;
+ }
+
+ log.AppendFormat("\n\tmax link distance: {0}", maxLinkSqrDistance);
+
+ double potentialSqrQuotient = potentialSqrDistance / maxLinkSqrDistance;
+
+ log.Append("\n\t\tdoing LOS check");
// Skip vessels to which we do not have line of sight.
- if (requireLineOfSight && !this.vessel.hasLineOfSightTo(potentialVessel))
- {
- Tools.PostDebugMessage(
- this,
- "Vessel {0} discarded because we do not have line of sight.",
+ if (
+ ARConfiguration.RequireLineOfSight &&
+ !this.vessel.hasLineOfSightTo(potentialVessel, out fob, ARConfiguration.RadiusRatio)
+ )
+ {
+ log.Append("\n\t\t...failed LOS check");
+
+ log.AppendFormat("\n\t\t\t{0}: Vessel {1} not in line of sight.",
+ this.ToString(), potentialVessel.vesselName);
+
+ log.AppendFormat("\n\t\t\tpotentialSqrDistance: {0}", potentialSqrDistance);
+ log.AppendFormat("\n\t\t\tbestOccludedSqrQuotient: {0}", bestOccludedSqrQuotient);
+ log.AppendFormat("\n\t\t\tmaxTransmitSqrDistance: {0}", maxLinkSqrDistance);
+
+ if (
+ (potentialSqrQuotient < bestOccludedSqrQuotient) &&
+ (potentialSqrQuotient <= 1d) &&
+ potentialBestRelay.CanTransmit()
+ )
+ {
+ log.Append("\n\t\t...vessel is close enough to and potentialBestRelay can transmit");
+ log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay);
+
+ this.bestOccludedRelay = potentialBestRelay;
+ bodyOccludingBestOccludedRelay = fob;
+ bestOccludedSqrQuotient = potentialSqrQuotient;
+ }
+ else
+ {
+ log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on");
+ }
+
+ continue;
+ }
+
+ log.Append("\n\t\t...passed LOS check");
+
+ /*
+ * ...so that we can skip the vessel if it is further away than a vessel we've already checked.
+ * */
+ if (potentialSqrQuotient > nearestRelaySqrQuotient)
+ {
+
+ log.AppendFormat("\n\t{0}: Vessel {1} discarded because it is farther than another the nearest relay.",
+ this.ToString(),
potentialVessel.vesselName
);
continue;
}
- // Find the distance from here to the vessel...
- double potentialSqrDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).sqrMagnitude;
-
- /*
- * ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a
- * vessel we've already checked.
- * */
- if (
- potentialSqrDistance > Tools.Min(
- this.maxTransmitDistance * this.maxTransmitDistance,
- nearestSqrDistance,
- this.vessel.sqrDistanceTo(Kerbin)
- )
- )
- {
- Tools.PostDebugMessage(
- this,
- "Vessel {0} discarded because it is out of range, or farther than another relay.",
- potentialVessel.vesselName
- );
- continue;
- }
-
- nearestSqrDistance = potentialSqrDistance;
-
- foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
- {
- if (potentialRelay.CanTransmit())
- {
- _nearestRelay = potentialRelay;
- Tools.PostDebugMessage(string.Format("{0}: found new best relay {1} ({2})",
- this.GetType().Name,
- _nearestRelay.ToString(),
- _nearestRelay.vessel.id
- ));
- break;
- }
- }
- }
-
+ log.Append("\n\t\t...passed distance check");
+
+ if (potentialBestRelay.CanTransmit())
+ {
+ needle = potentialBestRelay;
+ bool isCircular = false;
+
+ int iterCount = 0;
+ while (needle != null)
+ {
+ iterCount++;
+
+ if (needle.KerbinDirect)
+ {
+ break;
+ }
+
+ if (needle.targetRelay == null)
+ {
+ break;
+ }
+
+ if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
+ {
+ isCircular = true;
+ break;
+ }
+
+ // Avoid infinite loops when we're not catching things right.
+ if (iterCount > FlightGlobals.Vessels.Count)
+ {
+ Tools.PostErrorMessage(
+ "[{0}] iterCount exceeded while checking for circular network; assuming it is circular" +
+ "\n\tneedle={1}" +
+ "\n\tthis.moduleRef={2}",
+ this,
+ needle == null ? "null" : string.Format(
+ "{0}, needle.KerbinDirect={1}, needle.targetRelay={2}",
+ needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format(
+ "{0}\n\tneedle.targetRelay.vessel={1}",
+ needle.targetRelay,
+ needle.targetRelay.vessel == null ?
+ "null" : needle.targetRelay.vessel.vesselName
+ )
+ ),
+ this.moduleRef == null ? "null" : this.moduleRef.ToString()
+ );
+ isCircular = true;
+ break;
+ }
+
+ needle = needle.targetRelay;
+ }
+
+ if (!isCircular)
+ {
+ nearestRelaySqrQuotient = potentialSqrQuotient;
+ this.nearestRelay = potentialBestRelay;
+
+ log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m²)",
+ this.ToString(),
+ this.nearestRelay.ToString(),
+ Math.Sqrt(nearestRelaySqrQuotient)
+ );
+ }
+ else
+ {
+ log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
+ potentialBestRelay
+ );
+ }
+ }
+ }
+
+ CelestialBody bodyOccludingKerbin = null;
+
+ double kerbinSqrDistance = this.vessel.DistanceTo(Kerbin) - Kerbin.Radius;
+ kerbinSqrDistance *= kerbinSqrDistance;
+
+ double kerbinSqrQuotient;
+
+ if (ARConfiguration.UseAdditiveRanges)
+ {
+ kerbinSqrQuotient = kerbinSqrDistance /
+ (this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
+ }
+ else
+ {
+ kerbinSqrQuotient = kerbinSqrDistance /
+ (this.maxTransmitDistance * this.maxTransmitDistance);
+ }
+
+ log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
+ log.AppendFormat(
+ "\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
+ this,
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient,
+ this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
+ bestOccludedSqrQuotient,
+ kerbinSqrDistance
+ );
+
+ // If we don't have LOS to Kerbin, focus on relays
+ if (
+ ARConfiguration.RequireLineOfSight &&
+ !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio)
+ )
+ {
+ log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName);
+
+ // 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.
+ if (nearestRelaySqrQuotient <= 1d)
+ {
+ log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, 1d);
+
+ this.KerbinDirect = false;
+ this.canTransmit = true;
+ this.targetRelay = this.nearestRelay;
+ }
+ // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway
+ else
+ {
+ log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, 1d);
+
+ this.canTransmit = false;
+
+ // 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 (bestOccludedSqrQuotient < kerbinSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
+ bestOccludedRelay, kerbinSqrDistance);
+
+ this.KerbinDirect = false;
+
+ // 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.
+ if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
+ nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+
+ this.targetRelay = this.nearestRelay;
+ this.firstOccludingBody = null;
+ }
+ // Otherwise, target the best occluded relay.
+ else
+ {
+ log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
+ nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+
+ this.targetRelay = bestOccludedRelay;
+ this.firstOccludingBody = bodyOccludingBestOccludedRelay;
+ }
+ }
+ // Otherwise, check Kerbin against the nearest relay.
+ // Since we have LOS, blank the first occluding body.
+ else
+ {
+ log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
+ bestOccludedRelay, kerbinSqrDistance);
+
+ // If the nearest relay is closer than Kerbin, pick it.
+ // Since nearestRelaySqrDistane is infinity if there are no nearby relays, this is safe.
+ if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = false;
+ this.firstOccludingBody = null;
+ this.targetRelay = this.nearestRelay;
+ }
+ // Otherwise, pick Kerbin.
+ else
+ {
+ log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = true;
+ this.firstOccludingBody = bodyOccludingKerbin;
+ this.targetRelay = null;
+ }
+ }
+ }
+ }
+ // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin
+ else
+ {
+ log.AppendFormat("\n\tKerbin is in LOS.");
+
+ // If the nearest relay is closer than Kerbin and in range, transmit to it.
+ if (nearestRelaySqrQuotient <= 1d)
+ {
+ log.AppendFormat("\n\t\tCan transmit to nearby relay {0} ({1} <= {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, 1d);
+
+ this.canTransmit = true;
+
+ // If the nearestRelay is closer than Kerbin, use it.
+ if (nearestRelaySqrQuotient < kerbinSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = false;
+ this.targetRelay = this.nearestRelay;
+ }
+ // Otherwise, Kerbin is closer, so use it.
+ else
+ {
+ log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = true;
+ this.targetRelay = null;
+ }
+ }
+ // If the nearest relay is out of range, we still need to check on Kerbin.
+ else
+ {
+ log.AppendFormat("\n\t\tCan't transmit to nearby relay {0} ({1} > {2}).",
+ this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+ nearestRelaySqrQuotient, 1d);
+
+ // If Kerbin is in range, use it.
+ if (kerbinSqrQuotient <= 1d)
+ {
+ log.AppendFormat("\n\t\t\tCan transmit to Kerbin ({0} <= {1}).",
+ kerbinSqrQuotient, 1d);
+
+ this.canTransmit = true;
+ this.KerbinDirect = true;
+ this.targetRelay = null;
+ }
+ // If Kerbin is out of range and the nearest relay is out of range, pick a second best between
+ // Kerbin and bestOccludedRelay
+ else
+ {
+ log.AppendFormat("\n\t\t\tCan't transmit to Kerbin ({0} > {1}).",
+ kerbinSqrQuotient, 1d);
+
+ this.canTransmit = false;
+
+ // 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 (bestOccludedSqrQuotient < kerbinSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})",
+ bestOccludedRelay, kerbinSqrDistance);
+
+ this.KerbinDirect = false;
+
+ // 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.
+ if (nearestRelaySqrQuotient < bestOccludedSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
+ nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+
+ this.targetRelay = this.nearestRelay;
+ this.firstOccludingBody = null;
+ }
+ // Otherwise, target the best occluded relay.
+ else
+ {
+ log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
+ nearestRelaySqrQuotient, bestOccludedSqrQuotient);
+
+ this.targetRelay = bestOccludedRelay;
+ this.firstOccludingBody = bodyOccludingBestOccludedRelay;
+ }
+ }
+ // Otherwise, check Kerbin against the nearest relay.
+ // Since we have LOS, blank the first occluding body.
+ else
+ {
+ log.AppendFormat("\n\t\t\tKerbin is closer than the best occluded relay ({0} >= {1})",
+ bestOccludedRelay, kerbinSqrDistance);
+
+ 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 (nearestRelaySqrQuotient < kerbinSqrQuotient)
+ {
+ log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = false;
+ this.targetRelay = this.nearestRelay;
+ }
+ // Otherwise, pick Kerbin.
+ else
+ {
+ log.AppendFormat("\n\t\t\t\t...and closer than the nearest relay ({0} >= {1}), so picking it.",
+ nearestRelaySqrQuotient, kerbinSqrQuotient);
+
+ this.KerbinDirect = true;
+ this.targetRelay = null;
+ }
+ }
+ }
+ }
+ }
+
+ if (ARConfiguration.UseAdditiveRanges)
+ {
+ if (this.KerbinDirect)
+ {
+ this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange);
+ this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * ARConfiguration.KerbinRelayRange);
+ }
+ else
+ {
+ this.NominalLinkDistance = Math.Sqrt(this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance);
+ this.MaximumLinkDistance = Math.Sqrt(this.maxTransmitDistance * this.targetRelay.maxTransmitDistance);
+ }
+ }
+ else
+ {
+ this.NominalLinkDistance = this.nominalTransmitDistance;
+ this.MaximumLinkDistance = this.maxTransmitDistance;
+ }
+
+ if (this.canTransmit)
+ {
+ if (this.transmitDistance < this.NominalLinkDistance)
+ {
+ this.LinkStatus = ConnectionStatus.Optimal;
+ }
+ else
+ {
+ this.LinkStatus = ConnectionStatus.Suboptimal;
+ }
+ }
+ else
+ {
+ this.LinkStatus = ConnectionStatus.None;
+ }
+
+ log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString());
+
+ #if DEBUG
+ } catch (Exception ex) {
+ log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace);
+ #if QUIT_ON_EXCEPTION
+ UnityEngine.Application.Quit();
+ #endif
+ } finally {
+ #endif
+ log.Print(false);
+ #if DEBUG
+ }
+ #endif
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time.
- RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
-
- // Return the nearest available relay, or null if there are no available relays nearby.
- return _nearestRelay;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
- /// </summary>
- /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
+ this.isChecked = false;
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.
+ /// </summary>
+ /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.AntennaRelay"/>.</returns>
+ public override string ToString()
+ {
+ if (this is ProtoAntennaRelay)
+ {
+ return (this as ProtoAntennaRelay).ToString();
+ }
+ return this.moduleRef.ToString();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
+ /// </summary>
+ /// <param name="module">The module reference underlying this AntennaRelay,
+ /// as an <see cref="AntennaRange.IAntennaRelay"/></param>
public AntennaRelay(IAntennaRelay module)
{
this.moduleRef = module;
-
- this.searchTimer = new System.Diagnostics.Stopwatch();
- this.millisecondsBetweenSearches = 5000;
-
- // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
- // we hope it is safe enough.
- this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
- }
-
- static AntennaRelay()
- {
- var config = KSP.IO.PluginConfiguration.CreateForType<AntennaRelay>();
-
- config.load();
-
- AntennaRelay.requireLineOfSight = config.GetValue<bool>("requireLineOfSight", false);
-
- config.save();
+ this.isChecked = false;
+
+ Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
}
}
}
--- /dev/null
+++ b/GameData/AntennaRange/ATM_AntennaRange.cfg
@@ -1,1 +1,15 @@
-
+ACTIVE_TEXTURE_MANAGER_CONFIG
+{
+ folder = AntennaRange
+ enabled = true
+ OVERRIDES
+ {
+ AntennaRange/.*
+ {
+ compress = true
+ mipmaps = false
+ scale = 1
+ max_size = 0
+ }
+ }
+}
--- /dev/null
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -1,1 +1,140 @@
+// AntennaRange
+//
+// AntennaRange.cfg
+//
+// Copyright © 2014-2015, toadicus
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+// to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
+// 3.0 Uported License.
+//
+// Specifications:
+// nominalRange: The distance from Kerbin at which the antenna will perform exactly as prescribed by
+// packetResourceCost and packetSize.
+// maxPowerFactor: The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the
+// power cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+// maxDataFactor: The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+//
+@PART[longAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ nominalRange = 6364
+ simpleRange = 20500000
+ maxPowerFactor = 8
+ maxDataFactor = 4
+ }
+
+ MODULE
+ {
+ name = ModuleScienceContainer
+
+ dataIsCollectable = true
+ dataIsStorable = false
+
+ storageRange = 2
+ }
+}
+
+@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ nominalRange = 3500000000
+ simpleRange = 18000000000
+ maxPowerFactor = 4
+ maxDataFactor = 8
+ }
+
+ MODULE
+ {
+ name = ModuleScienceContainer
+
+ dataIsCollectable = true
+ dataIsStorable = false
+
+ storageRange = 2
+ }
+}
+
+@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2]
+{
+ @MODULE[ModuleDataTransmitter]
+ {
+ @name = ModuleLimitedDataTransmitter
+ @packetResourceCost /= 1.414213
+ nominalRange = 10000000000
+ simpleRange = 56250000000
+ maxPowerFactor = 16
+ maxDataFactor = 2
+ }
+
+ MODULE
+ {
+ name = ModuleScienceContainer
+
+ dataIsCollectable = true
+ dataIsStorable = false
+
+ storageRange = 2
+ }
+}
+
+TRACKING_STATION_RANGES
+{
+ range = 800000
+ range = 200000000000
+ range = 2250000000000
+}
+
+EVA_MODULE
+{
+ name = ModuleLimitedDataTransmitter
+
+ nominalRange = 1389
+ maxPowerFactor = 1
+ maxDataFactor = 1
+
+ packetInterval = 0.2
+ packetSize = 1
+ packetResourceCost = 6.25
+
+ requiredResource = ElectricCharge
+}
+
+EVA_RESOURCE
+{
+ name = ElectricCharge
+ amount = 100
+ maxAmount = 100
+}
+
+@EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport]
+{
+ !name = DELETE
+}
+
Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
Binary files /dev/null and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
Binary files /dev/null and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -2,7 +2,7 @@
//
// IAntennaRelay.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -31,41 +31,83 @@
namespace AntennaRange
{
- /*
- * Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
- * */
+ /// <summary>
+ /// Interface defining the basic functionality of AntennaRelay modules for AntennaRange.
+ /// </summary>
public interface IAntennaRelay
{
/// <summary>
/// Gets the parent Vessel.
/// </summary>
- /// <value>The parent Vessel.</value>
Vessel vessel { get; }
+
+ /// <summary>
+ /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
+ /// </summary>
+ IAntennaRelay targetRelay { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+ /// directly with Kerbin.
+ /// </summary>
+ bool KerbinDirect { get; }
+
+ /// <summary>
+ /// The link distance, in meters, at which this relay behaves nominally.
+ /// </summary>
+ double NominalLinkDistance { get; }
+
+
+ /// <summary>
+ /// The link distance, in meters, beyond which this relay cannot operate.
+ /// </summary>
+ double MaximumLinkDistance { get; }
/// <summary>
/// Gets the distance to the nearest relay or Kerbin, whichever is closer.
/// </summary>
- /// <value>The distance to the nearest relay or Kerbin, whichever is closer.</value>
double transmitDistance { get; }
+
+ /// <summary>
+ /// Gets the link status.
+ /// </summary>
+ ConnectionStatus LinkStatus { get; }
+
+ /// <summary>
+ /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
+ /// </summary>
+ double nominalTransmitDistance { get; }
/// <summary>
/// The maximum distance at which this relay can operate.
/// </summary>
- /// <value>The max transmit distance.</value>
- float maxTransmitDistance { get; }
+ double maxTransmitDistance { get; }
/// <summary>
- /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
- /// the current relay attempt.
+ /// The first CelestialBody blocking line of sight to a
/// </summary>
- /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
- bool relayChecked { get; }
+ CelestialBody firstOccludingBody { get; }
+
+ /// <summary>
+ /// Gets the Part title.
+ /// </summary>
+ string Title { get; }
/// <summary>
/// Determines whether this instance can transmit.
+ /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary>
- /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
bool CanTransmit();
+
+ /// <summary>
+ /// Finds the nearest relay.
+ /// </summary>
+ void FindNearestRelay();
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.IAntennaRelay"/>.
+ /// </summary>
+ string ToString();
}
}
--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -2,7 +2,7 @@
//
// ModuleLimitedDataTransmitter.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -29,72 +29,109 @@
using KSP;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Text;
using ToadicusTools;
using UnityEngine;
namespace AntennaRange
{
- /*
- * ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles range-
- * finding, power scaling, and data scaling for antennas during science transmission. Its functionality varies with
- * three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.
- *
- * In general, the scaling functions assume the following relation:
- *
- * D² α P/R,
- *
- * where D is the total transmission distance, P is the transmission power, and R is the data rate.
- *
- * */
-
- /*
- * Fields
- * */
+ /// <summary>
+ /// <para>ModuleLimitedDataTransmitter is designed as a drop-in replacement for ModuleDataTransmitter, and handles
+ /// rangefinding, power scaling, and data scaling for antennas during science transmission. Its functionality
+ /// varies with three tunables: nominalRange, maxPowerFactor, and maxDataFactor, set in .cfg files.</para>
+ ///
+ /// <para>In general, the scaling functions assume the following relation:</para>
+ ///
+ /// <para> D² α P/R,</para>
+ ///
+ /// <para>where D is the total transmission distance, P is the transmission power, and R is the data rate.</para>
+ /// </summary>
public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay
{
// Stores the packetResourceCost as defined in the .cfg file.
- protected float _basepacketResourceCost;
+ private float _basepacketResourceCost;
// Stores the packetSize as defined in the .cfg file.
- protected float _basepacketSize;
+ private float _basepacketSize;
// Every antenna is a relay.
- protected AntennaRelay relay;
-
- // Keep track of vessels with transmitters for relay purposes.
- protected List<Vessel> _relayVessels;
+ private AntennaRelay relay;
// Sometimes we will need to communicate errors; this is how we do it.
- protected ScreenMessage ErrorMsg;
-
- // The distance from Kerbin at which the antenna will perform exactly as prescribed by packetResourceCost
- // and packetSize.
+ private ScreenMessage ErrorMsg;
+
+ /// <summary>
+ /// When additive ranges are enabled, the distance from Kerbin at which the antenna will perform exactly as
+ /// prescribed by packetResourceCost and packetSize.
+ /// </summary>
[KSPField(isPersistant = false)]
- public float nominalRange;
-
+ public double nominalRange;
+
+ /// <summary>
+ /// When additive ranges are disabled, the distance from Kerbin at which the antenna will perform exactly as
+ /// prescribed by packetResourceCost and packetSize.
+ /// </summary>
+ [KSPField(isPersistant = false)]
+ public double simpleRange;
+
+ /// <summary>
+ /// Relay status string for use in action menus.
+ /// </summary>
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
+ public string UIrelayStatus;
+
+ /// <summary>
+ /// Relay target string for use in action menus.
+ /// </summary>
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
+ public string UIrelayTarget;
+
+ /// <summary>
+ /// Transmit distance string for use in action menus.
+ /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
public string UItransmitDistance;
- [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Distance")]
+ /// <summary>
+ /// The nominal range string for use in action menus.
+ /// </summary>
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Nominal Range")]
+ public string UInominalLinkDistance;
+
+ /// <summary>
+ /// Maximum distance string for use in action menus.
+ /// </summary>
+ [KSPField(isPersistant = false, guiActive = true, guiName = "Maximum Range")]
public string UImaxTransmitDistance;
+ /// <summary>
+ /// Packet size string for use in action menus.
+ /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Size")]
public string UIpacketSize;
+ /// <summary>
+ /// Packet cost string for use in action menus.
+ /// </summary>
[KSPField(isPersistant = false, guiActive = true, guiName = "Packet Cost")]
public string UIpacketCost;
- // The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
- // cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+ /// <summary>
+ /// The multiplier on packetResourceCost that defines the maximum power output of the antenna. When the power
+ /// cost exceeds packetResourceCost * maxPowerFactor, transmission will fail.
+ /// </summary>
[KSPField(isPersistant = false)]
public float maxPowerFactor;
- // The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+ /// <summary>
+ /// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
+ /// </summary>
[KSPField(isPersistant = false)]
public float maxDataFactor;
+ /// <summary>
+ /// The packet throttle.
+ /// </summary>
[KSPField(
isPersistant = true,
guiName = "Packet Throttle",
@@ -105,35 +142,174 @@
[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
public float packetThrottle;
- protected bool actionUIUpdate;
+ private bool actionUIUpdate;
/*
* Properties
* */
- // Returns the parent vessel housing this antenna.
+ /// <summary>
+ /// Gets the parent Vessel.
+ /// </summary>
public new Vessel vessel
{
get
{
- return base.vessel;
- }
- }
-
- // Returns the distance to the nearest relay or Kerbin, whichever is closer.
+ if (base.vessel != null)
+ {
+ return base.vessel;
+ }
+ else if (this.part != null && this.part.vessel != null)
+ {
+ return this.part.vessel;
+ }
+ else if (
+ this.part.protoPartSnapshot != null &&
+ this.part.protoPartSnapshot.pVesselRef != null &&
+ this.part.protoPartSnapshot.pVesselRef.vesselRef != null
+ )
+ {
+ return this.part.protoPartSnapshot.pVesselRef.vesselRef;
+ }
+ else
+ {
+ this.LogError("Vessel and/or part reference are null, returning null vessel.");
+ return null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the target <see cref="AntennaRange.IAntennaRelay"/>relay.
+ /// </summary>
+ public IAntennaRelay targetRelay
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return null;
+ }
+
+ return this.relay.targetRelay;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="AntennaRange.IAntennaRelay"/> Relay is communicating
+ /// directly with Kerbin.
+ /// </summary>
+ public bool KerbinDirect
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.KerbinDirect;
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the nominal link distance, in meters.
+ /// </summary>
+ public double NominalLinkDistance
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.NominalLinkDistance;
+ }
+
+ return 0d;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum link distance, in meters.
+ /// </summary>
+ public double MaximumLinkDistance
+ {
+ get
+ {
+ if (this.relay != null)
+ {
+ return this.relay.MaximumLinkDistance;
+ }
+
+ return 0d;
+ }
+ }
+
+ /// <summary>
+ /// Gets the distance to the nearest relay or Kerbin, whichever is closer.
+ /// </summary>
public double transmitDistance
{
get
{
+ if (this.relay == null)
+ {
+ return double.PositiveInfinity;
+ }
+
return this.relay.transmitDistance;
}
}
- // Returns the maximum distance this module can transmit
- public float maxTransmitDistance
- {
- get
- {
- return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
+ /// <summary>
+ /// Gets the link status.
+ /// </summary>
+ public ConnectionStatus LinkStatus
+ {
+ get
+ {
+ if (this.relay == null)
+ {
+ return ConnectionStatus.None;
+ }
+
+ return this.relay.LinkStatus;
+ }
+ }
+
+ /// <summary>
+ /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
+ /// </summary>
+ public double nominalTransmitDistance
+ {
+ get
+ {
+ if (ARConfiguration.UseAdditiveRanges)
+ {
+ return this.nominalRange;
+ }
+ else
+ {
+ return this.simpleRange;
+ }
+ }
+ }
+
+ /// <summary>
+ /// The maximum distance at which this relay can operate.
+ /// </summary>
+ public double maxTransmitDistance
+ {
+ get;
+ protected set;
+ }
+
+ /// <summary>
+ /// The first CelestialBody blocking line of sight to a
+ /// </summary>
+ public CelestialBody firstOccludingBody
+ {
+ get
+ {
+ return this.relay.firstOccludingBody;
}
}
@@ -163,8 +339,10 @@
*
* So... hopefully that doesn't screw with anything else.
* */
- // Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
- // terms of joules/byte
+ /// <summary>
+ /// Override ModuleDataTransmitter.DataRate to just return packetSize, because we want antennas to be scored in
+ /// terms of joules/byte
+ /// </summary>
public new float DataRate
{
get
@@ -182,9 +360,11 @@
}
}
- // Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
- // to be scored in terms of joules/byte
- public new float DataResourceCost
+ /// <summary>
+ /// Override ModuleDataTransmitter.DataResourceCost to just return packetResourceCost, because we want antennas
+ /// to be scored in terms of joules/byte
+ /// </summary>
+ public new double DataResourceCost
{
get
{
@@ -201,12 +381,19 @@
}
}
- // Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
- public bool relayChecked
- {
- get
- {
- return this.relay.relayChecked;
+ /// <summary>
+ /// Gets the Part title.
+ /// </summary>
+ public string Title
+ {
+ get
+ {
+ if (this.part != null && this.part.partInfo != null)
+ {
+ return this.part.partInfo.title;
+ }
+
+ return string.Empty;
}
}
@@ -220,32 +407,12 @@
this.packetThrottle = 100f;
}
- // At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
- public override void OnStart (StartState state)
- {
- base.OnStart (state);
-
- if (state >= StartState.PreLaunch)
- {
- this.relay = new AntennaRelay(this);
- this.relay.maxTransmitDistance = this.maxTransmitDistance;
-
- this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
-
- GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
- GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
- }
- }
-
- // When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
- // overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
- // work.
- public override void OnLoad(ConfigNode node)
- {
- this.Fields.Load(node);
- base.Fields.Load(node);
-
- base.OnLoad (node);
+ /// <summary>
+ /// PartModule OnAwake override; runs at Unity Awake.
+ /// </summary>
+ public override void OnAwake()
+ {
+ base.OnAwake();
this._basepacketSize = base.packetSize;
this._basepacketResourceCost = base.packetResourceCost;
@@ -254,21 +421,388 @@
"{0} loaded:\n" +
"packetSize: {1}\n" +
"packetResourceCost: {2}\n" +
- "nominalRange: {3}\n" +
+ "nominalTransmitDistance: {3}\n" +
"maxPowerFactor: {4}\n" +
"maxDataFactor: {5}\n",
this.name,
base.packetSize,
this._basepacketResourceCost,
- this.nominalRange,
+ this.nominalTransmitDistance,
this.maxPowerFactor,
this.maxDataFactor
));
}
+ /// <summary>
+ /// PartModule OnStart override; runs at Unity Start.
+ /// </summary>
+ /// <param name="state">State.</param>
+ public override void OnStart (StartState state)
+ {
+ base.OnStart (state);
+
+ if (state >= StartState.PreLaunch)
+ {
+ this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+
+ this.relay = new AntennaRelay(this);
+ this.relay.nominalTransmitDistance = this.nominalTransmitDistance;
+ this.relay.maxTransmitDistance = this.maxTransmitDistance;
+
+ this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.maxTransmitDistance);
+
+ GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
+ GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
+ }
+ }
+
+ /// <summary>
+ /// When the module loads, fetch the Squad KSPFields from the base. This is necessary in part because
+ /// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
+ /// work.
+ /// </summary>
+ /// <param name="node"><see cref="ConfigNode"/> with data for this module.</param>
+ public override void OnLoad(ConfigNode node)
+ {
+ this.Fields.Load(node);
+ base.Fields.Load(node);
+
+ base.OnLoad (node);
+
+ this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalTransmitDistance;
+ }
+
+ /// <summary>
+ /// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
+ /// </summary>
+ public override string GetInfo()
+ {
+ StringBuilder sb = Tools.GetStringBuilder();
+ string text;
+
+ sb.Append(base.GetInfo());
+ sb.AppendFormat(Tools.SIFormatter, "Nominal Range: {0:S3}m\n", this.nominalTransmitDistance);
+ sb.AppendFormat(Tools.SIFormatter, "Maximum Range: {0:S3}m\n", this.maxTransmitDistance);
+
+ text = sb.ToString();
+
+ Tools.PutStringBuilder(sb);
+
+ return text;
+ }
+
+ /// <summary>
+ /// Determines whether this instance can transmit.
+ /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
+ /// </summary>
+ public new bool CanTransmit()
+ {
+ if (this.part == null || this.relay == null)
+ {
+ return false;
+ }
+
+ switch (this.part.State)
+ {
+ case PartStates.DEAD:
+ case PartStates.DEACTIVATED:
+ Tools.PostDebugMessage(string.Format(
+ "{0}: {1} on {2} cannot transmit: {3}",
+ this.GetType().Name,
+ this.part.partInfo.title,
+ this.vessel.vesselName,
+ Enum.GetName(typeof(PartStates), this.part.State)
+ ));
+ return false;
+ default:
+ break;
+ }
+
+ return this.relay.CanTransmit();
+ }
+
+ /// <summary>
+ /// Finds the nearest relay.
+ /// </summary>
+ public void FindNearestRelay()
+ {
+ if (this.relay != null)
+ {
+ this.relay.FindNearestRelay();
+ }
+ }
+
+ /// <summary>
+ /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
+ /// returns false.
+ /// </summary>
+ /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
+ /// <param name="callback">Callback function</param>
+ public new void TransmitData(List<ScienceData> dataQueue, Callback callback)
+ {
+ this.LogDebug(
+ "TransmitData(List<ScienceData> dataQueue, Callback callback) called. dataQueue.Count={0}",
+ dataQueue.Count
+ );
+
+ this.FindNearestRelay();
+
+ this.PreTransmit_SetPacketSize();
+ this.PreTransmit_SetPacketResourceCost();
+
+ if (this.CanTransmit())
+ {
+ ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+ this.LogDebug(
+ "CanTransmit in TransmitData, calling base.TransmitData with dataQueue=[{0}] and callback={1}",
+ dataQueue.SPrint(),
+ callback == null ? "null" : callback.ToString()
+ );
+
+ if (callback == null)
+ {
+ base.TransmitData(dataQueue);
+ }
+ else
+ {
+ base.TransmitData(dataQueue, callback);
+ }
+ }
+ else
+ {
+ Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+ var logger = Tools.DebugLogger.New(this);
+
+ IList<ModuleScienceContainer> vesselContainers = this.vessel.getModulesOfType<ModuleScienceContainer>();
+ ModuleScienceContainer scienceContainer;
+ for (int cIdx = 0; cIdx < vesselContainers.Count; cIdx++)
+ {
+ scienceContainer = vesselContainers[cIdx];
+
+ logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
+ scienceContainer.part.partInfo.title);
+
+ if (
+ scienceContainer.capacity != 0 &&
+ scienceContainer.GetScienceCount() >= scienceContainer.capacity
+ )
+ {
+ logger.Append("\tInsufficient capacity, skipping.\n");
+ continue;
+ }
+
+ List<ScienceData> dataStored = new List<ScienceData>();
+
+ ScienceData data;
+ for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
+ {
+ data = dataQueue[dIdx];
+ if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
+ {
+ logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
+ continue;
+ }
+
+ logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
+ if (scienceContainer.AddData(data))
+ {
+ logger.Append("done, removing from queue.\n");
+
+ dataStored.Add(data);
+ }
+ #if DEBUG
+ else
+ {
+ logger.Append("failed.\n");
+ }
+ #endif
+ }
+
+ dataQueue.RemoveAll(i => dataStored.Contains(i));
+
+ logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
+ }
+
+ logger.Print();
+
+ if (dataQueue.Count > 0)
+ {
+ StringBuilder sb = Tools.GetStringBuilder();
+
+ sb.Append('[');
+ sb.Append(this.part.partInfo.title);
+ sb.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
+ sb.Append("Data to be discarded:\n");
+
+ ScienceData data;
+ for (int dIdx = 0; dIdx < dataQueue.Count; dIdx++)
+ {
+ data = dataQueue[dIdx];
+ sb.AppendFormat("\t{0}\n", data.title);
+ }
+
+ ScreenMessages.PostScreenMessage(sb.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+ Tools.PostDebugMessage(sb.ToString());
+
+ Tools.PutStringBuilder(sb);
+ }
+
+ this.PostCannotTransmitError();
+ }
+
+ Tools.PostDebugMessage (
+ "distance: " + this.transmitDistance
+ + " packetSize: " + this.packetSize
+ + " packetResourceCost: " + this.packetResourceCost
+ );
+ }
+
+ /// <summary>
+ /// Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
+ /// returns false.
+ /// </summary>
+ /// <param name="dataQueue">List of <see cref="ScienceData"/> to transmit.</param>
+ public new void TransmitData(List<ScienceData> dataQueue)
+ {
+ this.LogDebug(
+ "TransmitData(List<ScienceData> dataQueue) called, dataQueue.Count={0}",
+ dataQueue.Count
+ );
+
+ this.TransmitData(dataQueue, null);
+ }
+
+ /// <summary>
+ /// Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
+ /// returns false.
+ /// </summary>
+ public new void StartTransmission()
+ {
+ this.FindNearestRelay();
+
+ PreTransmit_SetPacketSize ();
+ PreTransmit_SetPacketResourceCost ();
+
+ Tools.PostDebugMessage (
+ "distance: " + this.transmitDistance
+ + " packetSize: " + this.packetSize
+ + " packetResourceCost: " + this.packetResourceCost
+ );
+
+ if (this.CanTransmit())
+ {
+ ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+ base.StartTransmission();
+ }
+ else
+ {
+ this.PostCannotTransmitError ();
+ }
+ }
+
+ /// <summary>
+ /// MonoBehaviour Update
+ /// </summary>
+ public void Update()
+ {
+ if (this.actionUIUpdate)
+ {
+ this.UImaxTransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.MaximumLinkDistance);
+ this.UInominalLinkDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.NominalLinkDistance);
+
+ if (this.CanTransmit())
+ {
+ this.UIrelayStatus = this.LinkStatus.ToString();
+ this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.transmitDistance);
+ this.UIpacketSize = string.Format(Tools.SIFormatter, "{0:S3}MiT", this.DataRate);
+ this.UIpacketCost = string.Format(Tools.SIFormatter, "{0:S3}EC", this.DataResourceCost);
+ }
+ else
+ {
+ if (this.relay.firstOccludingBody == null)
+ {
+ this.UItransmitDistance = string.Format(Tools.SIFormatter, "{0:S3}m", this.transmitDistance);
+ this.UIrelayStatus = "Out of range";
+ }
+ else
+ {
+ this.UItransmitDistance = "N/A";
+ this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+ }
+ this.UIpacketSize = "N/A";
+ this.UIpacketCost = "N/A";
+ }
+
+ if (this.KerbinDirect)
+ {
+ this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
+ }
+ else
+ {
+ this.UIrelayTarget = this.targetRelay.ToString();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.
+ /// </summary>
+ /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ModuleLimitedDataTransmitter"/>.</returns>
+ public override string ToString()
+ {
+ StringBuilder sb = Tools.GetStringBuilder();
+ string msg;
+
+ sb.Append(this.part.partInfo.title);
+
+ if (vessel != null)
+ {
+ sb.Append(" on ");
+ sb.Append(vessel.vesselName);
+ }
+ else if (
+ this.part != null &&
+ this.part.protoPartSnapshot != null &&
+ this.part.protoPartSnapshot != null &&
+ this.part.protoPartSnapshot.pVesselRef != null
+ )
+ {
+ sb.Append(" on ");
+ sb.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+ }
+
+ msg = sb.ToString();
+
+ Tools.PutStringBuilder(sb);
+
+ return msg;
+ }
+
+ // When we catch an onPartActionUICreate event for our part, go ahead and update every frame to look pretty.
+ private void onPartActionUICreate(Part eventPart)
+ {
+ if (eventPart == base.part)
+ {
+ this.actionUIUpdate = true;
+ }
+ }
+
+ // When we catch an onPartActionUIDismiss event for our part, stop updating every frame to look pretty.
+ private void onPartActionUIDismiss(Part eventPart)
+ {
+ if (eventPart == base.part)
+ {
+ this.actionUIUpdate = false;
+ }
+ }
+
// Post an error in the communication messages describing the reason transmission has failed. Currently there
// is only one reason for this.
- protected void PostCannotTransmitError()
+ private void PostCannotTransmitError()
{
string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
@@ -289,247 +823,103 @@
// Before transmission, set packetResourceCost. Per above, packet cost increases with the square of
// distance. packetResourceCost maxes out at _basepacketResourceCost * maxPowerFactor, at which point
// transmission fails (see CanTransmit).
- protected void PreTransmit_SetPacketResourceCost()
- {
- if (this.transmitDistance <= this.nominalRange)
+ private void PreTransmit_SetPacketResourceCost()
+ {
+ if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.NominalLinkDistance)
{
base.packetResourceCost = this._basepacketResourceCost;
}
else
{
+ float rangeFactor = (float)(this.transmitDistance / this.NominalLinkDistance);
+ rangeFactor *= rangeFactor;
+
base.packetResourceCost = this._basepacketResourceCost
- * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
- }
+ * rangeFactor;
+ }
+
+ base.packetResourceCost *= this.packetThrottle / 100f;
}
// Before transmission, set packetSize. Per above, packet size increases with the inverse square of
// distance. packetSize maxes out at _basepacketSize * maxDataFactor.
- protected void PreTransmit_SetPacketSize()
- {
- if (this.transmitDistance >= this.nominalRange)
+ private void PreTransmit_SetPacketSize()
+ {
+ if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.NominalLinkDistance)
{
base.packetSize = this._basepacketSize;
}
else
{
- base.packetSize = Math.Min(
- this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
+ float rangeFactor = (float)(this.NominalLinkDistance / this.transmitDistance);
+ rangeFactor *= rangeFactor;
+
+ base.packetSize = Mathf.Min(
+ this._basepacketSize * rangeFactor,
this._basepacketSize * this.maxDataFactor);
}
base.packetSize *= this.packetThrottle / 100f;
}
- // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
- public override string GetInfo()
- {
- string text = base.GetInfo();
- text += "Nominal Range: " + Tools.MuMech_ToSI((double)this.nominalRange, 2) + "m\n";
- text += "Maximum Range: " + Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2) + "m\n";
- return text;
- }
-
- // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
- public new bool CanTransmit()
- {
- PartStates partState = this.part.State;
- if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
- {
- Tools.PostDebugMessage(string.Format(
- "{0}: {1} on {2} cannot transmit: {3}",
- this.GetType().Name,
- this.part.partInfo.title,
- this.vessel.vesselName,
- Enum.GetName(typeof(PartStates), partState)
- ));
- return false;
- }
- return this.relay.CanTransmit();
- }
-
- // Override ModuleDataTransmitter.TransmitData to check against CanTransmit and fail out when CanTransmit
- // returns false.
- public new void TransmitData(List<ScienceData> dataQueue)
- {
- this.PreTransmit_SetPacketSize();
- this.PreTransmit_SetPacketResourceCost();
-
- if (this.CanTransmit())
- {
- StringBuilder message = new StringBuilder();
-
- message.Append("[");
- message.Append(base.part.partInfo.title);
- message.Append("]: ");
-
- message.Append("Beginning transmission ");
-
- if (this.relay.nearestRelay == null)
- {
- message.Append("directly to Kerbin.");
- }
- else
- {
- message.Append("via ");
- message.Append(this.relay.nearestRelay);
- }
-
- ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
- base.TransmitData(dataQueue);
+ private string buildTransmitMessage()
+ {
+ StringBuilder sb = Tools.GetStringBuilder();
+ string msg;
+
+ sb.Append("[");
+ sb.Append(base.part.partInfo.title);
+ sb.Append("]: ");
+
+ sb.Append("Beginning transmission ");
+
+ if (this.KerbinDirect)
+ {
+ sb.Append("directly to Kerbin.");
}
else
{
- this.PostCannotTransmitError ();
- }
-
- Tools.PostDebugMessage (
- "distance: " + this.transmitDistance
- + " packetSize: " + this.packetSize
- + " packetResourceCost: " + this.packetResourceCost
- );
- }
-
- // Override ModuleDataTransmitter.StartTransmission to check against CanTransmit and fail out when CanTransmit
- // returns false.
- public new void StartTransmission()
+ sb.Append("via ");
+ sb.Append(this.relay.targetRelay);
+ }
+
+ msg = sb.ToString();
+
+ Tools.PutStringBuilder(sb);
+
+ return msg;
+ }
+
+ #if DEBUG
+ // When debugging, it's nice to have a button that just tells you everything.
+ [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
+ public void DebugInfo()
{
PreTransmit_SetPacketSize ();
PreTransmit_SetPacketResourceCost ();
- Tools.PostDebugMessage (
- "distance: " + this.transmitDistance
- + " packetSize: " + this.packetSize
- + " packetResourceCost: " + this.packetResourceCost
- );
-
- if (this.CanTransmit())
- {
- StringBuilder message = new StringBuilder();
-
- message.Append("[");
- message.Append(base.part.partInfo.title);
- message.Append("]: ");
-
- message.Append("Beginning transmission ");
-
- if (this.relay.nearestRelay == null)
- {
- message.Append("directly to Kerbin.");
- }
- else
- {
- message.Append("via ");
- message.Append(this.relay.nearestRelay);
- }
-
- ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
- base.StartTransmission();
- }
- else
- {
- this.PostCannotTransmitError ();
- }
- }
-
- public void Update()
- {
- if (this.actionUIUpdate)
- {
- this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
- this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A";
- this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "N/A";
- }
- }
-
- public void onPartActionUICreate(Part eventPart)
- {
- if (eventPart == base.part)
- {
- this.actionUIUpdate = true;
- }
- }
-
- public void onPartActionUIDismiss(Part eventPart)
- {
- if (eventPart == base.part)
- {
- this.actionUIUpdate = false;
- }
- }
-
- public override string ToString()
- {
- StringBuilder msg = new StringBuilder();
-
- msg.Append(this.part.partInfo.title);
-
- if (vessel != null)
- {
- msg.Append(" on ");
- msg.Append(vessel.vesselName);
- }
-
- return msg.ToString();
- }
-
- // When debugging, it's nice to have a button that just tells you everything.
- #if DEBUG
- [KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
- public void DebugInfo()
- {
- PreTransmit_SetPacketSize ();
- PreTransmit_SetPacketResourceCost ();
-
- string msg = string.Format(
- "'{0}'\n" +
- "_basepacketSize: {1}\n" +
- "packetSize: {2}\n" +
- "_basepacketResourceCost: {3}\n" +
- "packetResourceCost: {4}\n" +
- "maxTransmitDistance: {5}\n" +
- "transmitDistance: {6}\n" +
- "nominalRange: {7}\n" +
- "CanTransmit: {8}\n" +
- "DataRate: {9}\n" +
- "DataResourceCost: {10}\n" +
- "TransmitterScore: {11}\n" +
- "NearestRelay: {12}\n" +
- "Vessel ID: {13}",
- this.name,
- this._basepacketSize,
- base.packetSize,
- this._basepacketResourceCost,
- base.packetResourceCost,
- this.maxTransmitDistance,
- this.transmitDistance,
- this.nominalRange,
- this.CanTransmit(),
- this.DataRate,
- this.DataResourceCost,
- ScienceUtil.GetTransmitterScore(this),
- this.relay.FindNearestRelay(),
- this.vessel.id
- );
- Tools.PostDebugMessage(msg);
+ DebugPartModule.DumpClassObject(this);
}
[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
public void PrintAllVessels()
{
- StringBuilder sb = new StringBuilder();
-
+ StringBuilder sb = Tools.GetStringBuilder();
+
sb.Append("Dumping FlightGlobals.Vessels:");
- foreach (Vessel vessel in FlightGlobals.Vessels)
- {
+ Vessel vessel;
+ for (int i = 0; i < FlightGlobals.Vessels.Count; i++)
+ {
+ vessel = FlightGlobals.Vessels[i];
sb.AppendFormat("\n'{0} ({1})'", vessel.vesselName, vessel.id);
}
-
+
Tools.PostDebugMessage(sb.ToString());
- }
-
+
+ Tools.PutStringBuilder(sb);
+ }
+
[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
public void DumpRelayDB()
{
--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -2,7 +2,7 @@
//
// AssemblyInfo.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -29,6 +29,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;
+[assembly: KSPAssemblyDependency("ToadicusTools", 0, 0)]
+
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("AntennaRange")]
@@ -37,10 +39,9 @@
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("1.1.*")]
+[assembly: AssemblyVersion("1.9.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
-
--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -2,7 +2,7 @@
//
// ProtoAntennaRelay.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -28,33 +28,65 @@
using KSP;
using System;
-using System.Linq;
using ToadicusTools;
namespace AntennaRange
{
- /*
- * Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
- * This is used for finding relays in unloaded Vessels.
- * */
+ /// <summary>
+ /// Wrapper class for ProtoPartModuleSnapshot extending AntennaRelay and implementing IAntennaRelay.
+ /// This is used for finding relays in unloaded Vessels.
+ /// </summary>
public class ProtoAntennaRelay : AntennaRelay, IAntennaRelay
{
// Stores the prototype part so we can make sure we haven't exploded or so.
- protected ProtoPartSnapshot protoPart;
+ private ProtoPartSnapshot protoPart;
+ /// <summary>
+ /// Gets the parent Vessel.
+ /// </summary>
public override Vessel vessel
{
get
{
- return this.protoPart.pVesselRef.vesselRef;
+ if (
+ this.protoPart != null &&
+ this.protoPart.pVesselRef != null &&
+ this.protoPart.pVesselRef.vesselRef != null
+ )
+ {
+ return this.protoPart.pVesselRef.vesselRef;
+ }
+ else
+ {
+ Tools.PostErrorMessage("{0}: Could not fetch vessel! {1}{2}{3}",
+ this.ToString(),
+ this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
+ this.protoPart != null && this.protoPart.pVesselRef == null ?
+ "\n\tthis.protoPart.pVesselRef=null" : string.Empty,
+ this.protoPart != null && this.protoPart.pVesselRef != null &&
+ this.protoPart.pVesselRef.vesselRef == null ?
+ "\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty
+ );
+ return null;
+ }
}
}
/// <summary>
- /// The maximum distance at which this transmitter can operate.
+ /// Gets the nominal transmit distance at which the Antenna behaves just as prescribed by Squad's config.
/// </summary>
- /// <value>The max transmit distance.</value>
- public override float maxTransmitDistance
+ public override double nominalTransmitDistance
+ {
+ get
+ {
+ return this.moduleRef.nominalTransmitDistance;
+ }
+ }
+
+ /// <summary>
+ /// The maximum distance at which this relay can operate.
+ /// </summary>
+ public override double maxTransmitDistance
{
get
{
@@ -63,28 +95,26 @@
}
/// <summary>
- /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
- /// the current relay attempt.
+ /// Gets the underlying part's title.
/// </summary>
- /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
- public override bool relayChecked
+ /// <value>The title.</value>
+ public string Title
{
- get;
- protected set;
+ get
+ {
+ if (this.protoPart != null && this.protoPart.partInfo != null)
+ {
+ return this.protoPart.partInfo.title;
+ }
+
+ return string.Empty;
+ }
}
/// <summary>
- /// Gets the underlying part's title.
+ /// Determines whether this instance can transmit.
+ /// <c>true</c> if this instance can transmit; otherwise, <c>false</c>.
/// </summary>
- /// <value>The title.</value>
- public string title
- {
- get
- {
- return this.protoPart.partInfo.title;
- }
- }
-
public override bool CanTransmit()
{
PartStates partState = (PartStates)this.protoPart.state;
@@ -93,7 +123,7 @@
Tools.PostDebugMessage(string.Format(
"{0}: {1} on {2} cannot transmit: {3}",
this.GetType().Name,
- this.title,
+ this.Title,
this.vessel.vesselName,
Enum.GetName(typeof(PartStates), partState)
));
@@ -102,31 +132,37 @@
return base.CanTransmit();
}
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.
+ /// </summary>
+ /// <returns>A <see cref="System.String"/> that represents the current <see cref="AntennaRange.ProtoAntennaRelay"/>.</returns>
public override string ToString()
{
- return string.Format(
- "{0} on {1} (proto)",
- this.title,
- this.protoPart.pVesselRef.vesselName
- );
+ System.Text.StringBuilder sb = Tools.GetStringBuilder();
+
+ sb.Append(this.Title);
+
+ if (this.protoPart != null && this.protoPart.pVesselRef != null)
+ {
+ sb.AppendFormat(" on {0}", this.protoPart.pVesselRef.vesselName);
+ }
+
+ Tools.PutStringBuilder(sb);
+
+ return sb.ToString();
}
/// <summary>
- /// Initializes a new instance of the <see cref="AntennaRange.ProtoAntennaRelay"/> class.
+ /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay"/> class.
/// </summary>
- /// <param name="ms">The ProtoPartModuleSnapshot to wrap</param>
- /// <param name="vessel">The parent Vessel</param>
+ /// <param name="prefabRelay">The module reference underlying this AntennaRelay,
+ /// 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)
{
this.protoPart = pps;
- }
- ~ProtoAntennaRelay()
- {
- Tools.PostDebugMessage(string.Format(
- "{0}: destroyed",
- this.ToString()
- ));
+ Tools.PostLogMessage("{0}: constructed {1}", this.GetType().Name, this.ToString());
}
}
}
--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -2,7 +2,7 @@
//
// RelayDatabase.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -26,6 +26,8 @@
// 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.
+#pragma warning disable 1591
+
using KSP;
using System;
using System.Collections.Generic;
@@ -35,27 +37,8 @@
namespace AntennaRange
{
- public class RelayDatabase
+ public class RelayDatabase : Singleton<RelayDatabase>
{
- /*
- * Static members
- * */
- // Singleton storage
- protected static RelayDatabase _instance;
- // Gets the singleton
- public static RelayDatabase Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new RelayDatabase();
- }
-
- return _instance;
- }
- }
-
/*
* Instance members
* */
@@ -64,22 +47,20 @@
* Fields
* */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
- protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
+ private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
+ private Dictionary<Guid, IAntennaRelay> bestRelayTable;
// Vessel.id-keyed hash table of part counts, used for caching
- protected Dictionary<Guid, int> vesselPartCountTable;
-
- // Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
- public Dictionary<Guid, bool> CheckedVesselsTable;
-
- protected int cacheHits;
- protected int cacheMisses;
+ private Dictionary<Guid, int> vesselPartCountTable;
+
+ private int cacheHits;
+ private int cacheMisses;
/*
* Properties
* */
// Gets the Part-hashed table of relays in a given vessel
- public Dictionary<int, IAntennaRelay> this [Vessel vessel]
+ public IList<IAntennaRelay> this [Vessel vessel]
{
get
{
@@ -104,16 +85,66 @@
}
// Return the Part-hashed table of relays for this vessel
- return relayDatabase[vessel.id];
+ return relayDatabase[vessel.id].AsReadOnly();
}
}
/*
* Methods
* */
+ // Remove a vessel from the cache, if it exists.
+ public void DirtyVessel(Vessel vessel)
+ {
+ #if DEBUG
+ Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
+ vessel, new System.Diagnostics.StackTrace().ToString());
+ #else
+ Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
+ #endif
+
+ this.relayDatabase.Remove(vessel.id);
+ this.vesselPartCountTable.Remove(vessel.id);
+ this.bestRelayTable.Remove(vessel.id);
+ }
+
+ public void ClearCache()
+ {
+ Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
+
+ this.relayDatabase.Clear();
+ this.bestRelayTable.Clear();
+ this.vesselPartCountTable.Clear();
+ }
+
+ // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
+ public bool ContainsKey(Guid key)
+ {
+ return this.relayDatabase.ContainsKey(key);
+ }
+
+ // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
+ public bool ContainsKey(Vessel vessel)
+ {
+ return this.ContainsKey(vessel.id);
+ }
+
+ public IAntennaRelay GetBestVesselRelay(Vessel vessel)
+ {
+ IAntennaRelay relay;
+ if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
+ {
+ return relay;
+ }
+ else
+ {
+ var dump = this[vessel];
+ return null;
+ }
+ }
+
// Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses
- public bool AddVessel(Vessel vessel)
+ private bool AddVessel(Vessel vessel)
{
// If this vessel is already here...
if (this.ContainsKey(vessel))
@@ -133,7 +164,7 @@
else
{
// Build an empty table...
- this.relayDatabase[vessel.id] = new Dictionary<int, IAntennaRelay>();
+ this.relayDatabase[vessel.id] = new List<IAntennaRelay>();
// Update the empty index
this.UpdateVessel(vessel);
@@ -144,7 +175,7 @@
}
// Update the vessel's entry in the table
- public void UpdateVessel(Vessel vessel)
+ private void UpdateVessel(Vessel vessel)
{
// Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel))
@@ -157,37 +188,12 @@
));
}
- Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
+ List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
// Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable);
// Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
- }
-
- // Remove a vessel from the cache, if it exists.
- public void DirtyVessel(Vessel vessel)
- {
- if (this.relayDatabase.ContainsKey(vessel.id))
- {
- this.relayDatabase.Remove(vessel.id);
- }
- if (this.vesselPartCountTable.ContainsKey(vessel.id))
- {
- this.vesselPartCountTable.Remove(vessel.id);
- }
- }
-
- // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
- public bool ContainsKey(Guid key)
- {
- return this.relayDatabase.ContainsKey(key);
- }
-
- // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
- public bool ContainsKey(Vessel vessel)
- {
- return this.ContainsKey(vessel.id);
}
// Runs when a vessel is modified (or when we switch to one, to catch docking events)
@@ -214,18 +220,41 @@
}
// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
- public void onSceneChange(GameScenes scene)
- {
- // If the active vessel is a real thing...
- if (FlightGlobals.ActiveVessel != null)
- {
- // ... dirty its cache
- this.onVesselEvent(FlightGlobals.ActiveVessel);
- }
+ private void onSceneChange(GameScenes scene)
+ {
+ Tools.PostDebugMessage(
+ "RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}",
+ HighLogic.LoadedScene,
+ scene,
+ FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
+ );
+
+ if (scene == GameScenes.FLIGHT)
+ {
+ if (scene == HighLogic.LoadedScene)
+ {
+ if (FlightGlobals.ActiveVessel != null)
+ {
+ Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
+ FlightGlobals.ActiveVessel.vesselName);
+
+ this.onVesselEvent(FlightGlobals.ActiveVessel);
+ }
+ }
+ else
+ {
+ this.ClearCache();
+ }
+ }
+ }
+
+ private void onGameLoaded(object data)
+ {
+ this.ClearCache();
}
// Runs when parts are undocked
- public void onPartEvent(Part part)
+ private void onPartEvent(Part part)
{
if (part != null && part.vessel != null)
{
@@ -234,14 +263,14 @@
}
// Runs when parts are coupled, as in docking
- public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
+ private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{
this.onPartEvent(data.from);
this.onPartEvent(data.to);
}
// Produce a Part-hashed table of relays for the given vessel
- protected void getVesselRelays(Vessel vessel, ref Dictionary<int, IAntennaRelay> relays)
+ private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
{
// We're going to completely regen this table, so dump the current contents.
relays.Clear();
@@ -251,6 +280,10 @@
"IAntennaRelay",
vessel.vesselName
));
+
+ double bestRelayRange = double.NegativeInfinity;
+ IAntennaRelay bestRelay = null;
+ IAntennaRelay relay;
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) {
@@ -261,16 +294,30 @@
));
// Loop through the Parts in the Vessel...
- foreach (Part part in vessel.Parts)
- {
+ Part part;
+ for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
+ {
+ part = vessel.Parts[partIdx];
+
// ...loop through the PartModules in the Part...
- foreach (PartModule module in part.Modules)
+ PartModule module;
+ for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++)
{
+ module = part.Modules[modIdx];
+
// ...if the module is a relay...
if (module is IAntennaRelay)
{
+ relay = (module as IAntennaRelay);
+
+ if (relay.maxTransmitDistance > bestRelayRange)
+ {
+ bestRelayRange = relay.maxTransmitDistance;
+ bestRelay = relay;
+ }
+
// ...add the module to the table
- relays.Add(part.GetHashCode(), module as IAntennaRelay);
+ relays.Add(relay);
// ...neglect relay objects after the first in each part.
break;
}
@@ -287,8 +334,11 @@
));
// Loop through the ProtoPartModuleSnapshots in the Vessel...
- foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
- {
+ ProtoPartSnapshot pps;
+ for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
+ {
+ pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
+
Tools.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}",
this.GetType().Name,
@@ -306,8 +356,11 @@
));
// ...loop through the PartModules in the prefab...
- foreach (PartModule module in partPrefab.Modules)
+ PartModule module;
+ for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
{
+ module = partPrefab.Modules[modIdx];
+
Tools.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}",
this.GetType().Name,
@@ -323,14 +376,24 @@
module
));
+ relay = new ProtoAntennaRelay(module as IAntennaRelay, pps);
+
+ if (relay.maxTransmitDistance > bestRelayRange)
+ {
+ bestRelayRange = relay.maxTransmitDistance;
+ bestRelay = relay;
+ }
+
// ...build a new ProtoAntennaRelay and add it to the table
- relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
+ relays.Add(relay);
// ...neglect relay objects after the first in each part.
break;
}
}
}
}
+
+ this.bestRelayTable[vessel.id] = bestRelay;
Tools.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.",
@@ -342,12 +405,12 @@
}
// Construct the singleton
- protected RelayDatabase()
+ private RelayDatabase()
{
// Initialize the databases
- this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
+ this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
+ this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
this.vesselPartCountTable = new Dictionary<Guid, int>();
- this.CheckedVesselsTable = new Dictionary<Guid, bool>();
this.cacheHits = 0;
this.cacheMisses = 0;
@@ -359,6 +422,7 @@
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent);
+ GameEvents.onGameStateLoad.Add(this.onGameLoaded);
}
~RelayDatabase()
@@ -370,6 +434,7 @@
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent);
+ GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
@@ -385,21 +450,28 @@
#if DEBUG
public void Dump()
{
- StringBuilder sb = new StringBuilder();
+ StringBuilder sb = Tools.GetStringBuilder();
sb.Append("Dumping RelayDatabase:");
- foreach (Guid id in this.relayDatabase.Keys)
- {
- sb.AppendFormat("\nVessel {0}:", id);
-
- foreach (IAntennaRelay relay in this.relayDatabase[id].Values)
- {
+ var dbEnum = this.relayDatabase.GetEnumerator();
+ IList<IAntennaRelay> vesselRelays;
+ while (dbEnum.MoveNext())
+ {
+ sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
+
+ vesselRelays = dbEnum.Current.Value;
+ IAntennaRelay relay;
+ for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
+ {
+ relay = vesselRelays[rIdx];
sb.AppendFormat("\n\t{0}", relay.ToString());
}
}
Tools.PostDebugMessage(sb.ToString());
+
+ Tools.PutStringBuilder(sb);
}
#endif
}
--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -2,7 +2,7 @@
//
// Extensions.cs
//
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
@@ -28,17 +28,60 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using ToadicusTools;
namespace AntennaRange
{
- /*
- * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
- * */
+ /// <summary>
+ /// A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin.
+ /// </summary>
public static class RelayExtensions
{
/// <summary>
+ /// Returns the distance between two IAntennaRelays.
+ /// </summary>
+ /// <param name="relayOne">Relay one.</param>
+ /// <param name="relayTwo">Relay two.</param>
+ public static double DistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
+ {
+ return relayOne.vessel.DistanceTo(relayTwo.vessel);
+ }
+
+ /// <summary>
+ /// Returns the distance from this IAntennaRelay to the given CelestialBody
+ /// </summary>
+ /// <param name="relay">Relay.</param>
+ /// <param name="body">Body.</param>
+ public static double SqrDistanceTo(this IAntennaRelay relay, CelestialBody body)
+ {
+ double range = relay.vessel.DistanceTo(body) - body.Radius;
+
+ return range * range;
+ }
+
+ /// <summary>
+ /// Returns the distance between two IAntennaRelays.
+ /// </summary>
+ /// <param name="relayOne">Relay one.</param>
+ /// <param name="relayTwo">Relay two.</param>
+ public static double SqrDistanceTo(this IAntennaRelay relayOne, IAntennaRelay relayTwo)
+ {
+ return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
+ }
+
+ /// <summary>
+ /// Returns the distance from this IAntennaRelay to the given CelestialBody
+ /// </summary>
+ /// <param name="relay">Relay.</param>
+ /// <param name="body">Body.</param>
+ public static double DistanceTo(this IAntennaRelay relay, CelestialBody body)
+ {
+ double range = relay.vessel.DistanceTo(body) - body.Radius;
+
+ return range;
+ }
+
+ /// <summary>
/// Returns the distance between this IAntennaRelay and a Vessel
/// </summary>
/// <param name="relay">This <see cref="IAntennaRelay"/></param>
@@ -55,7 +98,7 @@
/// <param name="body">A <see cref="CelestialBody"/></param>
public static double DistanceTo(this AntennaRelay relay, CelestialBody body)
{
- return relay.vessel.DistanceTo(body);
+ return relay.vessel.DistanceTo(body) - body.Radius;
}
/// <summary>
@@ -69,12 +112,42 @@
}
/// <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)
+ {
+ 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)
+ {
+ 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)
+ {
+ return relayOne.vessel.sqrDistanceTo(relayTwo.vessel);
+ }
+
+ /// <summary>
/// Returns all of the PartModules or ProtoPartModuleSnapshots implementing IAntennaRelay in this Vessel.
/// </summary>
/// <param name="vessel">This <see cref="Vessel"/></param>
- public static IEnumerable<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
- {
- return RelayDatabase.Instance[vessel].Values.ToList();
+ public static IList<IAntennaRelay> GetAntennaRelays (this Vessel vessel)
+ {
+ return RelayDatabase.Instance[vessel];
}
/// <summary>
@@ -84,8 +157,11 @@
/// <param name="vessel"></param>
public static bool HasConnectedRelay(this Vessel vessel)
{
- foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values)
- {
+ IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
+ IAntennaRelay relay;
+ for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
+ {
+ relay = vesselRelays[rIdx];
if (relay.CanTransmit())
{
return true;
@@ -94,6 +170,60 @@
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)
+ {
+ bool canTransmit = false;
+
+ IList<IAntennaRelay> vesselRelays = RelayDatabase.Instance[vessel];
+ IAntennaRelay relay;
+ for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
+ {
+ relay = vesselRelays[rIdx];
+ if (relay.LinkStatus > ConnectionStatus.None)
+ {
+ canTransmit = true;
+
+ if (relay.LinkStatus == ConnectionStatus.Optimal)
+ {
+ return ConnectionStatus.Optimal;
+ }
+ }
+ }
+
+ if (canTransmit)
+ {
+ return ConnectionStatus.Suboptimal;
+ }
+ else
+ {
+ 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)
+ {
+ return RelayDatabase.Instance.GetBestVesselRelay(vessel);
+ }
+ }
+
+ #pragma warning disable 1591
+ /// <summary>
+ /// An Enum describing the connection status of a vessel or relay.
+ /// </summary>
+ public enum ConnectionStatus
+ {
+ None,
+ Suboptimal,
+ Optimal
}
}
Binary files /dev/null and b/toolbarIcon.xcf differ
Binary files /dev/null and b/toolbarIcon_24x24.xcf differ
Binary files /dev/null and b/toolbarIcon_38x38.xcf differ