From: Andy Wilkinson Date: Sat, 03 Jan 2015 05:35:42 +0000 Subject: Reworking the way deployments are handled. X-Git-Tag: 1.7 X-Git-Url: http://git.toad.homelinux.net/projects/AntennaRange.git/commitdiff/6ec3c46 --- Reworking the way deployments are handled. --- --- /dev/null +++ b/ARConfiguration.cs @@ -1,1 +1,239 @@ - +// AntennaRange © 2014 toadicus +// +// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a +// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ + +using KSP; +using System; +using ToadicusTools; +using UnityEngine; + +namespace AntennaRange +{ + [KSPAddon(KSPAddon.Startup.SpaceCentre, false)] + public class ARConfiguration : MonoBehaviour + { + private bool showConfigWindow; + private Rect configWindowPos; + + private IButton toolbarButton; + private ApplicationLauncherButton appLauncherButton; + + private System.Version runningVersion; + + private KSP.IO.PluginConfiguration _config; + private KSP.IO.PluginConfiguration config + { + get + { + if (this._config == null) + { + this._config = KSP.IO.PluginConfiguration.CreateForType(); + } + + return this._config; + } + } + + public void Awake() + { + 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("configWindowPos", this.configWindowPos); + + AntennaRelay.requireLineOfSight = this.LoadConfigValue("requireLineOfSight", false); + + AntennaRelay.radiusRatio = (1 - this.LoadConfigValue("graceRatio", .05d)); + AntennaRelay.radiusRatio *= AntennaRelay.radiusRatio; + + ARFlightController.requireConnectionForControl = + this.LoadConfigValue("requireConnectionForControl", false); + + ModuleLimitedDataTransmitter.fixedPowerCost = this.LoadConfigValue("fixedPowerCost", false); + + GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); + + Debug.Log(string.Format("{0} v{1} - ARConfiguration loaded!", this.GetType().Name, this.runningVersion)); + + Tools.PostDebugMessage(this, "Awake."); + } + + public void OnGUI() + { + // 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) + { + Rect configPos = GUILayout.Window(354163056, + this.configWindowPos, + this.ConfigWindow, + string.Format("AntennaRange {0}.{1}", this.runningVersion.Major, this.runningVersion.Minor), + GUILayout.ExpandHeight(true), + GUILayout.ExpandWidth(true) + ); + + configPos = Tools.ClampRectToScreen(configPos, 20); + + if (configPos != this.configWindowPos) + { + this.configWindowPos = configPos; + this.SaveConfigValue("configWindowPos", this.configWindowPos); + } + } + } + + public void ConfigWindow(int _) + { + GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); + + 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); + } + + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + + bool requireConnectionForControl = + GUILayout.Toggle( + ARFlightController.requireConnectionForControl, + "Require Connection for Probe Control" + ); + if (requireConnectionForControl != ARFlightController.requireConnectionForControl) + { + ARFlightController.requireConnectionForControl = requireConnectionForControl; + this.SaveConfigValue("requireConnectionForControl", requireConnectionForControl); + } + + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + + bool fixedPowerCost = GUILayout.Toggle(ModuleLimitedDataTransmitter.fixedPowerCost, "Use Fixed Power Cost"); + if (fixedPowerCost != ModuleLimitedDataTransmitter.fixedPowerCost) + { + ModuleLimitedDataTransmitter.fixedPowerCost = fixedPowerCost; + this.SaveConfigValue("fixedPowerCost", fixedPowerCost); + } + + GUILayout.EndHorizontal(); + + if (requireLineOfSight) + { + GUILayout.BeginHorizontal(); + + double graceRatio = 1d - Math.Sqrt(AntennaRelay.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) + { + AntennaRelay.radiusRatio = (1d - newRatio) * (1d - newRatio); + this.SaveConfigValue("graceRatio", newRatio); + } + + GUILayout.EndHorizontal(); + } + + GUILayout.EndVertical(); + + GUI.DragWindow(); + } + + public void OnDestroy() + { + GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); + + if (this.toolbarButton != null) + { + this.toolbarButton.Destroy(); + } + + if (this.appLauncherButton != null) + { + ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); + } + } + + protected void onSceneChangeRequested(GameScenes scene) + { + if (scene != GameScenes.SPACECENTER) + { + print("ARConfiguration: Requesting Destruction."); + MonoBehaviour.Destroy(this); + } + } + + private void toggleConfigWindow() + { + this.showConfigWindow = !this.showConfigWindow; + } + + private T LoadConfigValue(string key, T defaultValue) + { + this.config.load(); + + return config.GetValue(key, defaultValue); + } + + private void SaveConfigValue(string key, T value) + { + this.config.load(); + + this.config.SetValue(key, value); + + this.config.save(); + } + } +} + --- /dev/null +++ b/ARFlightController.cs @@ -1,1 +1,334 @@ - +// AntennaRange +// +// ARFlightController.cs +// +// Copyright © 2014, toadicus +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using KSP; +using System; +using System.Collections.Generic; +using ToadicusTools; +using UnityEngine; + +namespace AntennaRange +{ + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class ARFlightController : MonoBehaviour + { + #region Static Members + public static bool requireConnectionForControl; + #endregion + + #region Fields + protected Dictionary connectionTextures; + protected Dictionary appLauncherTextures; + + protected IButton toolbarButton; + + protected ApplicationLauncherButton appLauncherButton; + #endregion + + #region Properties + public ConnectionStatus currentConnectionStatus + { + get; + protected set; + } + + protected string currentConnectionTexture + { + get + { + return this.connectionTextures[this.currentConnectionStatus]; + } + } + + protected Texture currentAppLauncherTexture + { + get + { + return this.appLauncherTextures[this.currentConnectionStatus]; + } + } + + public ControlTypes currentControlLock + { + get + { + if (this.lockID == string.Empty) + { + return ControlTypes.None; + } + + return InputLockManager.GetControlLock(this.lockID); + } + } + + public string lockID + { + get; + protected set; + } + + public ControlTypes lockSet + { + get + { + return ControlTypes.ALL_SHIP_CONTROLS; + } + } + + public Vessel vessel + { + get + { + if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null) + { + return FlightGlobals.ActiveVessel; + } + + return null; + } + } + #endregion + + #region MonoBehaviour LifeCycle + protected void Awake() + { + this.lockID = "ARConnectionRequired"; + + this.connectionTextures = new Dictionary(); + + this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection"; + this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal"; + this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon"; + + this.appLauncherTextures = new Dictionary(); + + 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.connectionTextures[ConnectionStatus.None]; + this.toolbarButton.Text = "AntennaRange"; + this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT); + this.toolbarButton.Enabled = false; + } + + GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); + GameEvents.onVesselChange.Add(this.onVesselChange); + } + + protected void FixedUpdate() + { + if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready) + { + this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( + ApplicationLauncher.AppScenes.FLIGHT, + this.appLauncherTextures[ConnectionStatus.None] + ); + } + + Tools.DebugLogger log = Tools.DebugLogger.New(this); + + VesselCommand availableCommand; + + if (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 ( + HighLogic.LoadedSceneIsFlight && + requireConnectionForControl && + this.vessel != null && + 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); + } + + if ( + (this.toolbarButton != null || this.appLauncherButton != null) && + HighLogic.LoadedSceneIsFlight && + FlightGlobals.ActiveVessel != null + ) + { + log.Append("Checking vessel relay status.\n"); + + List relays = + FlightGlobals.ActiveVessel.getModulesOfType(); + + log.AppendFormat("\t...found {0} relays\n", relays.Count); + + bool vesselCanTransmit = false; + bool vesselHasOptimalRelay = false; + + foreach (ModuleLimitedDataTransmitter relay in relays) + { + log.AppendFormat("\tvesselCanTransmit: {0}, vesselHasOptimalRelay: {1}\n", + vesselCanTransmit, vesselHasOptimalRelay); + + log.AppendFormat("\tChecking relay {0}\n" + + "\t\tCanTransmit: {1}, transmitDistance: {2}, nominalRange: {3}\n", + relay, + relay.CanTransmit(), + relay.transmitDistance, + relay.nominalRange + ); + + bool relayCanTransmit = relay.CanTransmit(); + + if (!vesselCanTransmit && relayCanTransmit) + { + vesselCanTransmit = true; + } + + if (!vesselHasOptimalRelay && + relayCanTransmit && + relay.transmitDistance <= (double)relay.nominalRange) + { + vesselHasOptimalRelay = true; + } + + if (vesselCanTransmit && vesselHasOptimalRelay) + { + break; + } + } + + log.AppendFormat("Done checking. vesselCanTransmit: {0}, vesselHasOptimalRelay: {1}\n", + vesselCanTransmit, vesselHasOptimalRelay); + + if (vesselHasOptimalRelay) + { + this.currentConnectionStatus = ConnectionStatus.Optimal; + } + else if (vesselCanTransmit) + { + this.currentConnectionStatus = ConnectionStatus.Suboptimal; + } + else + { + this.currentConnectionStatus = ConnectionStatus.None; + } + + log.AppendFormat("currentConnectionStatus: {0}, setting texture to {1}", + this.currentConnectionStatus, this.currentConnectionTexture); + + if (this.toolbarButton != null) + { + this.toolbarButton.TexturePath = this.currentConnectionTexture; + } + if (this.appLauncherButton != null) + { + this.appLauncherButton.SetTexture(this.currentAppLauncherTexture); + } + } + + log.Print(); + } + + protected void OnDestroy() + { + InputLockManager.RemoveControlLock(this.lockID); + + 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 + + #region Event Handlers + protected void onSceneChangeRequested(GameScenes scene) + { + print("ARFlightController: Requesting Destruction."); + MonoBehaviour.Destroy(this); + } + + protected void onVesselChange(Vessel vessel) + { + InputLockManager.RemoveControlLock(this.lockID); + } + #endregion + + public enum ConnectionStatus + { + None, + Suboptimal, + Optimal + } + } +} + --- 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] -{ - @MODULE[ModuleDataTransmitter] - { - @name = ModuleLimitedDataTransmitter - nominalRange = 1500000 - maxPowerFactor = 8 - maxDataFactor = 4 - } -} - -@PART[mediumDishAntenna] -{ - @MODULE[ModuleDataTransmitter] - { - @name = ModuleLimitedDataTransmitter - nominalRange = 30000000 - maxPowerFactor = 8 - maxDataFactor = 4 - } -} - -@PART[commDish] -{ - @MODULE[ModuleDataTransmitter] - { - @name = ModuleLimitedDataTransmitter - nominalRange = 80000000000 - maxPowerFactor = 8 - maxDataFactor = 4 - } -} - --- /dev/null +++ b/AntennaRange.csproj @@ -1,1 +1,110 @@ - + + + + Debug_win + AnyCPU + 8.0.30703 + 2.0 + {B36F2C11-962E-4A75-9F41-61AD56D11493} + Library + AntennaRange + AntennaRange + 1.3 + false + v3.5 + False + + + true + full + false + bin\Debug + DEBUG;TRACE; + prompt + 4 + false + + + + + + + + + true + bin\Release + prompt + 4 + false + + + + + + + + + true + full + false + bin\Debug + DEBUG;TRACE; + prompt + 4 + false + + + + + + + + true + bin\Release + prompt + 4 + + + + + + false + + + + + + + + + + + + + + + + PreserveNewest + + + + + ..\_KSPAssemblies\Assembly-CSharp.dll + False + + + ..\_KSPAssemblies\System.dll + False + + + ..\_KSPAssemblies\UnityEngine.dll + False + + + + + {D48A5542-6655-4149-BC27-B27DF0466F1C} + ToadicusTools + + + --- a/AntennaRelay.cs +++ b/AntennaRelay.cs @@ -29,14 +29,20 @@ using System; using System.Collections.Generic; using System.Linq; +using ToadicusTools; namespace AntennaRange { public class AntennaRelay { + public static bool requireLineOfSight; + public static double radiusRatio; + // We don't have a Bard, so we'll hide Kerbin here. protected CelestialBody Kerbin; + protected CelestialBody _firstOccludingBody; + protected IAntennaRelay _nearestRelayCache; protected IAntennaRelay moduleRef; @@ -79,6 +85,18 @@ } /// + /// Gets the first occluding body. + /// + /// The first occluding body. + public CelestialBody firstOccludingBody + { + get + { + return this._firstOccludingBody; + } + } + + /// /// Gets the transmit distance. /// /// The transmit distance. @@ -129,7 +147,14 @@ /// true if this instance can transmit; otherwise, false. public virtual bool CanTransmit() { - if (this.transmitDistance > this.maxTransmitDistance) + if ( + this.transmitDistance > this.maxTransmitDistance || + ( + requireLineOfSight && + this.nearestRelay == null && + !this.vessel.hasLineOfSightTo(this.Kerbin, out this._firstOccludingBody, radiusRatio) + ) + ) { return false; } @@ -165,10 +190,12 @@ this.vessel.id )); + this._firstOccludingBody = null; + // Set this vessel as checked, so that we don't check it again. RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true; - double nearestDistance = double.PositiveInfinity; + double nearestSqrDistance = double.PositiveInfinity; IAntennaRelay _nearestRelay = null; /* @@ -180,14 +207,10 @@ foreach (Vessel potentialVessel in FlightGlobals.Vessels) { // Skip vessels that have already been checked for a nearest relay this pass. - try - { - if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id]) - { - continue; - } - } - catch (KeyNotFoundException) { /* If the key doesn't exist, don't skip it. */} + if (RelayDatabase.Instance.CheckedVesselsTable.ContainsKey(potentialVessel.id)) + { + continue; + } // Skip vessels of the wrong type. switch (potentialVessel.vesselType) @@ -208,19 +231,42 @@ continue; } + // Skip vessels to which we do not have line of sight. + if (requireLineOfSight && + !this.vessel.hasLineOfSightTo(potentialVessel, out this._firstOccludingBody, radiusRatio)) + { + Tools.PostDebugMessage( + this, + "Vessel {0} discarded because we do not have line of sight.", + potentialVessel.vesselName + ); + continue; + } + // Find the distance from here to the vessel... - double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude; + 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 (potentialDistance > Tools.Min(this.maxTransmitDistance, nearestDistance, vessel.DistanceTo(Kerbin))) - { + 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; } - nearestDistance = potentialDistance; + nearestSqrDistance = potentialSqrDistance; foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays()) { @@ -260,6 +306,17 @@ // we hope it is safe enough. this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); } + + static AntennaRelay() + { + var config = KSP.IO.PluginConfiguration.CreateForType(); + + config.load(); + + AntennaRelay.requireLineOfSight = config.GetValue("requireLineOfSight", false); + + config.save(); + } } } --- /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,141 @@ +// 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 + } + + MODULE + { + name = ModuleScienceContainer + + dataIsCollectable = true + dataIsStorable = false + + storageRange = 2 + } +} + +@PART[mediumDishAntenna]:FOR[AntennaRange]:NEEDS[!RemoteTech2] +{ + @MODULE[ModuleDataTransmitter] + { + @name = ModuleLimitedDataTransmitter + nominalRange = 30000000 + maxPowerFactor = 8 + maxDataFactor = 4 + } + + MODULE + { + name = ModuleScienceContainer + + dataIsCollectable = true + dataIsStorable = false + + storageRange = 2 + } +} + +@PART[commDish]:FOR[AntennaRange]:NEEDS[!RemoteTech2] +{ + @MODULE[ModuleDataTransmitter] + { + @name = ModuleLimitedDataTransmitter + nominalRange = 80000000000 + maxPowerFactor = 8 + maxDataFactor = 4 + } + + MODULE + { + name = ModuleScienceContainer + + dataIsCollectable = true + dataIsStorable = false + + storageRange = 2 + } +} + +EVA_MODULE +{ + name = ModuleLimitedDataTransmitter + + nominalRange = 5000 + maxPowerFactor = 8 + maxDataFactor = 4 + + packetInterval = 0.5 + packetSize = 2 + packetResourceCost = .1 + + requiredResource = ElectricCharge +} + +EVA_RESOURCE +{ + name = ElectricCharge + amount = 100 + maxAmount = 100 +} + +@EVA_RESOURCE[ElectricCharge]:AFTER[AntennaRange]:NEEDS[TacLifeSupport] +{ + !name = DELETE +} + +@SUBCATEGORY[*]:HAS[#title[Data?Transmitter]]:FOR[AntennaRange:]NEEDS[FilterExtensions] +{ + FILTER + { + CHECK + { + type = moduleName + value = ModuleLimitedDataTransmitter + } + } +} + --- a/ModuleLimitedDataTransmitter.cs +++ b/ModuleLimitedDataTransmitter.cs @@ -26,11 +26,12 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using KSP; using System; using System.Collections.Generic; using System.Linq; using System.Text; -using KSP; +using ToadicusTools; using UnityEngine; namespace AntennaRange @@ -53,6 +54,10 @@ * */ public class ModuleLimitedDataTransmitter : ModuleDataTransmitter, IScienceDataTransmitter, IAntennaRelay { + // If true, use a fixed power cost at the configured value and degrade data rates instead of increasing power + // requirements. + public static bool fixedPowerCost; + // Stores the packetResourceCost as defined in the .cfg file. protected float _basepacketResourceCost; @@ -73,6 +78,9 @@ [KSPField(isPersistant = false)] public float nominalRange; + [KSPField(isPersistant = false, guiActive = true, guiName = "Relay")] + public string UIrelayStatus; + [KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")] public string UItransmitDistance; @@ -93,6 +101,16 @@ // The multipler on packetSize that defines the maximum data bandwidth of the antenna. [KSPField(isPersistant = false)] public float maxDataFactor; + + [KSPField( + isPersistant = true, + guiName = "Packet Throttle", + guiUnits = "%", + guiActive = true, + guiActiveEditor = false + )] + [UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)] + public float packetThrottle; protected bool actionUIUpdate; @@ -206,34 +224,12 @@ public ModuleLimitedDataTransmitter () : base() { this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT); - } - - // At least once, when the module starts with a state on the launch pad or later, go find Kerbin. - public override void OnStart (StartState state) - { - base.OnStart (state); - - if (state >= StartState.PreLaunch) - { - this.relay = new AntennaRelay(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); + this.packetThrottle = 100f; + } + + public override void OnAwake() + { + base.OnAwake(); this._basepacketSize = base.packetSize; this._basepacketResourceCost = base.packetResourceCost; @@ -254,15 +250,39 @@ )); } + // 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); + } + // Post an error in the communication messages describing the reason transmission has failed. Currently there // is only one reason for this. protected void PostCannotTransmitError() { - string ErrorText = string.Format ( - "Unable to transmit: out of range! Maximum range = {0}m; Current range = {1}m.", - Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2), - Tools.MuMech_ToSI((double)this.transmitDistance, 2) - ); + string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!"); this.ErrorMsg.message = string.Format( "{4}", @@ -283,31 +303,53 @@ // transmission fails (see CanTransmit). protected void PreTransmit_SetPacketResourceCost() { - if (this.transmitDistance <= this.nominalRange) + if (fixedPowerCost || this.transmitDistance <= this.nominalRange) { base.packetResourceCost = this._basepacketResourceCost; } else { + double rangeFactor = (this.transmitDistance / this.nominalRange); + rangeFactor *= rangeFactor; + base.packetResourceCost = this._basepacketResourceCost - * (float)Math.Pow (this.transmitDistance / this.nominalRange, 2); - } + * (float)rangeFactor; + + Tools.PostDebugMessage( + this, + "Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).", + base.packetResourceCost, + rangeFactor); + } + + base.packetResourceCost *= this.packetThrottle / 100f; } // Before transmission, set packetSize. Per above, packet size increases with the inverse square of // distance. packetSize maxes out at _basepacketSize * maxDataFactor. protected void PreTransmit_SetPacketSize() { - if (this.transmitDistance >= this.nominalRange) + if (!fixedPowerCost && this.transmitDistance >= this.nominalRange) { base.packetSize = this._basepacketSize; } else { + double rangeFactor = (this.nominalRange / this.transmitDistance); + rangeFactor *= rangeFactor; + base.packetSize = Math.Min( - this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2), + this._basepacketSize * (float)rangeFactor, this._basepacketSize * this.maxDataFactor); - } + + Tools.PostDebugMessage( + this, + "Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).", + base.packetSize, + rangeFactor); + } + + base.packetSize *= this.packetThrottle / 100f; } // Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description. @@ -322,6 +364,11 @@ // Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible. public new bool CanTransmit() { + if (this.part == null || this.relay == null) + { + return false; + } + PartStates partState = this.part.State; if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED) { @@ -370,6 +417,75 @@ } else { + Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title); + + var logger = Tools.DebugLogger.New(this); + + foreach (ModuleScienceContainer scienceContainer in this.vessel.getModulesOfType()) + { + 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 dataStored = new List(); + + foreach (ScienceData data in dataQueue) + { + 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 msg = new StringBuilder(); + + msg.Append('['); + msg.Append(this.part.partInfo.title); + msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n"); + msg.Append("Data to be discarded:\n"); + + foreach (ScienceData data in dataQueue) + { + msg.AppendFormat("\n{0}\n", data.title); + } + + ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT); + + Tools.PostDebugMessage(msg.ToString()); + } + this.PostCannotTransmitError (); } @@ -427,9 +543,27 @@ { 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"; + if (this.CanTransmit()) + { + this.UIrelayStatus = string.Intern("Connected"); + this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m"; + this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT"; + this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E"; + } + else + { + if (this.relay.firstOccludingBody == null) + { + this.UIrelayStatus = string.Intern("Out of range"); + } + else + { + this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName); + } + this.UImaxTransmitDistance = "N/A"; + this.UIpacketSize = "N/A"; + this.UIpacketCost = "N/A"; + } } } --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -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.0.0.*")] +[assembly: AssemblyVersion("1.6.*")] // 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 @@ -26,8 +26,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +using KSP; using System; using System.Linq; +using ToadicusTools; namespace AntennaRange { --- a/RelayDatabase.cs +++ b/RelayDatabase.cs @@ -29,8 +29,8 @@ using KSP; using System; using System.Collections.Generic; -using System.Linq; using System.Text; +using ToadicusTools; using UnityEngine; namespace AntennaRange --- a/RelayExtensions.cs +++ b/RelayExtensions.cs @@ -36,7 +36,7 @@ /* * A class of utility extensions for Vessels and Relays to help find a relay path back to Kerbin. * */ - public static class Extensions + public static class RelayExtensions { /// /// Returns the distance between this IAntennaRelay and a Vessel @@ -55,7 +55,7 @@ /// A public static double DistanceTo(this AntennaRelay relay, CelestialBody body) { - return relay.vessel.DistanceTo(body); + return relay.vessel.DistanceTo(body) - body.Radius; } /// @@ -76,6 +76,24 @@ { return RelayDatabase.Instance[vessel].Values.ToList(); } + + /// + /// Determines if the specified vessel has a connected relay. + /// + /// true if the specified vessel has a connected relay; otherwise, false. + /// + public static bool HasConnectedRelay(this Vessel vessel) + { + foreach (IAntennaRelay relay in RelayDatabase.Instance[vessel].Values) + { + if (relay.CanTransmit()) + { + return true; + } + } + + return false; + } } }