VOID_SurfAtmo: Shortened window title to try to avoid clipping.
VOID_SurfAtmo: Shortened window title to try to avoid clipping.

--- a/API/VOID_Module.cs
+++ b/API/VOID_Module.cs
@@ -456,6 +456,7 @@
 				if (closeRect.Contains(Event.current.mousePosition))
 				{
 					this.Active = false;
+					this.removeUILock();
 				}
 			}
 
@@ -479,61 +480,68 @@
 
 			bool cursorInWindow = _Pos.Contains(Mouse.screenPos);
 
+			if (cursorInWindow)
+			{
+				this.setUILock();
+			}
+			else
+			{
+				this.removeUILock();
+			}
+
+			if (HighLogic.LoadedSceneIsEditor)
+			{
+				_Pos = Tools.ClampRectToEditorPad(_Pos);
+			}
+			else
+			{
+				_Pos = Tools.ClampRectToScreen(_Pos);
+			}
+
+			if (_Pos != this.WindowPos)
+			{
+				this.WindowPos = _Pos;
+				this.core.configDirty = true;
+			}
+		}
+
+		protected void setUILock()
+		{
 			switch (HighLogic.LoadedScene)
 			{
 				case GameScenes.EDITOR:
-					if (cursorInWindow)
-					{
-						InputLockManager.SetControlLock(
-							ControlTypes.EDITOR_ICON_HOVER | ControlTypes.EDITOR_ICON_PICK |
-							ControlTypes.EDITOR_PAD_PICK_COPY | ControlTypes.EDITOR_PAD_PICK_COPY,
-							this.inputLockName
-						);
-						EditorLogic.fetch.Lock(false, false, false, this.inputLockName);
-					}
-					else
-					{
-						EditorLogic.fetch.Unlock(this.inputLockName);
-					}
+					InputLockManager.SetControlLock(
+						ControlTypes.EDITOR_ICON_HOVER | ControlTypes.EDITOR_ICON_PICK |
+						ControlTypes.EDITOR_PAD_PICK_COPY | ControlTypes.EDITOR_PAD_PICK_COPY,
+						this.inputLockName
+					);
+					EditorLogic.fetch.Lock(false, false, false, this.inputLockName);
 					break;
 				case GameScenes.FLIGHT:
-					if (cursorInWindow)
-					{
-						InputLockManager.SetControlLock(ControlTypes.CAMERACONTROLS, this.inputLockName);
-					}
-					else if (InputLockManager.GetControlLock(this.inputLockName) != ControlTypes.None)
-					{
-						InputLockManager.RemoveControlLock(this.inputLockName);
-					}
+					InputLockManager.SetControlLock(ControlTypes.CAMERACONTROLS, this.inputLockName);
 					break;
 				case GameScenes.SPACECENTER:
-					if (cursorInWindow)
-					{
-						InputLockManager.SetControlLock(
-							ControlTypes.KSC_FACILITIES | ControlTypes.CAMERACONTROLS,
-							this.inputLockName
-						);
-					}
-					else if (InputLockManager.GetControlLock(this.inputLockName) != ControlTypes.None)
-					{
-						InputLockManager.RemoveControlLock(this.inputLockName);
-					}
+					InputLockManager.SetControlLock(
+						ControlTypes.KSC_FACILITIES | ControlTypes.CAMERACONTROLS,
+						this.inputLockName
+					);
 					break;
 			}
-
-			if (HighLogic.LoadedSceneIsEditor)
-			{
-				_Pos = Tools.ClampRectToEditorPad(_Pos);
-			}
-			else
-			{
-				_Pos = Tools.ClampRectToScreen(_Pos);
-			}
-
-			if (_Pos != this.WindowPos)
-			{
-				this.WindowPos = _Pos;
-				this.core.configDirty = true;
+		}
+
+		protected void removeUILock()
+		{
+			switch (HighLogic.LoadedScene)
+			{
+				case GameScenes.EDITOR:
+					EditorLogic.fetch.Unlock(this.inputLockName);
+					break;
+				case GameScenes.FLIGHT:
+					InputLockManager.RemoveControlLock(this.inputLockName);
+					break;
+				case GameScenes.SPACECENTER:
+					InputLockManager.RemoveControlLock(this.inputLockName);
+					break;
 			}
 		}
 	}

--- /dev/null
+++ b/API/VOID_SingletonCore.cs
@@ -1,1 +1,69 @@
+// VOID
+//
+// VOID_SingletonModule.cs
+//
+// Copyright © 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.
+using System;
 
+namespace VOID
+{
+	public abstract class VOID_SingletonCore<T> : VOIDCore, IVOID_Module, IDisposable
+		where T : VOID_Module, new()
+	{
+		#region Singleton Members
+		/*
+		 * Static Members
+		 * */
+		protected static bool _initialized = false;
+
+		public static bool Initialized
+		{
+			get
+			{
+				return _initialized;
+			}
+		}
+
+		protected static T _instance;
+
+		public static T Instance
+		{
+			get
+			{
+				if (_instance == null)
+				{
+					_instance = new T();
+					_initialized = true;
+				}
+				return _instance;
+			}
+		}
+
+		public abstract void Dispose();
+		#endregion
+	}
+}
+
+

--- a/API/VOID_SingletonModule.cs
+++ /dev/null
@@ -1,67 +1,1 @@
-// VOID
-//
-// VOID_SingletonModule.cs
-//
-// Copyright © 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.
-using System;
 
-namespace VOID
-{
-	public abstract class VOID_SingletonModule<T> : VOIDCore, IVOID_Module
-		where T : VOID_Module, new()
-	{
-		#region Singleton Members
-		/*
-		 * Static Members
-		 * */
-		protected static bool _initialized = false;
-
-		public static bool Initialized
-		{
-			get
-			{
-				return _initialized;
-			}
-		}
-
-		protected static T _instance;
-
-		public static T Instance
-		{
-			get
-			{
-				if (_instance == null)
-				{
-					_instance = new T();
-					_initialized = true;
-				}
-				return _instance;
-			}
-		}
-		#endregion
-	}
-}
-
-

--- /dev/null
+++ b/API/VOID_SingletonWindow.cs
@@ -1,1 +1,73 @@
+// VOID
+//
+// VOID_SingletonModule.cs
+//
+// Copyright © 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.
+using System;
 
+namespace VOID
+{
+	public abstract class VOID_SingletonWindow<T> : VOID_WindowModule, IVOID_Module, IDisposable
+		where T : VOID_WindowModule, new()
+	{
+		#region Singleton Members
+		/*
+		 * Static Members
+		 * */
+		protected static bool _initialized = false;
+
+		public static bool Initialized
+		{
+			get
+			{
+				return _initialized;
+			}
+		}
+
+		protected static T _instance;
+
+		public static T Instance
+		{
+			get
+			{
+				if (_instance == null)
+				{
+					_instance = new T();
+					_initialized = true;
+				}
+				return _instance;
+			}
+		}
+
+		public virtual void Dispose()
+		{
+			_instance = null;
+			_initialized = false;
+		}
+		#endregion
+	}
+}
+
+

--- /dev/null
+++ b/Tools/VOID_StageExtensions.cs
@@ -1,1 +1,38 @@
+// VOID © 2015 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 KerbalEngineer.VesselSimulator;
+using KSP;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace VOID
+{
+	public static class VOID_StageExtensions
+	{
+		public static double NominalThrust(this Stage stage)
+		{
+			if (stage.actualThrust == 0d)
+			{
+				return stage.thrust;
+			}
+			else
+			{
+				return stage.actualThrust;
+			}
+		}
+
+		public static double MassFlow(this Stage stage)
+		{
+			double stageIsp = VOID_Data.Core.LastStage.isp;
+			double stageThrust = stage.NominalThrust();
+
+			return stageThrust / (stageIsp * VOID_Data.KerbinGee);
+		}
+	}
+}
+
+

--- a/Tools/VOID_Tools.cs
+++ b/Tools/VOID_Tools.cs
@@ -316,6 +316,7 @@
 		}
 		#endregion
 
+		#region WINDOW_UTILS
 		private static Dictionary<int, GUI.WindowFunction> functionCache;
 		public static UnityEngine.GUI.WindowFunction GetWindowHandler(Action<int> func)
 		{
@@ -377,7 +378,9 @@
 				}
 			}
 		}
-
+		#endregion
+
+		#region TIME_UTILS
 		/// <summary>
 		/// Formats the interval given in seconds as a human-friendly
 		/// time period in [[[[years, ]days, ]hours, ]minutes, and ]seconds.
@@ -545,6 +548,7 @@
 
 			public UnpackedTime() : this(0, 0, 0, 0, 0d) {}
 		}
+		#endregion
 
 		public static string UppercaseFirst(string s)
 		{

--- a/VOID.csproj
+++ b/VOID.csproj
@@ -106,13 +106,15 @@
     <Compile Include="Tools\VOID_DataValue.cs" />
     <Compile Include="Tools\VOID_SaveValue.cs" />
     <Compile Include="API\VOID_HUDModule.cs" />
-    <Compile Include="API\VOID_SingletonModule.cs" />
     <Compile Include="API\Attributes\VOID_ScenesAttribute.cs" />
     <Compile Include="API\Attributes\AVOID_SaveValue.cs" />
     <Compile Include="API\VOIDMaster.cs" />
     <Compile Include="API\Attributes\VOID_GameModesAttribute.cs" />
     <Compile Include="VOID_ConfigWindow.cs" />
     <Compile Include="Tools\VOID_Localization.cs" />
+    <Compile Include="Tools\VOID_StageExtensions.cs" />
+    <Compile Include="API\VOID_SingletonCore.cs" />
+    <Compile Include="API\VOID_SingletonWindow.cs" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>

--- a/VOIDCore_Generic.cs
+++ b/VOIDCore_Generic.cs
@@ -37,7 +37,7 @@
 
 namespace VOID
 {
-	public abstract class VOIDCore_Generic<T> : VOID_SingletonModule<T>, IVOID_Module, IDisposable
+	public abstract class VOIDCore_Generic<T> : VOID_SingletonCore<T>, IVOID_Module, IDisposable
 		where T : VOID_Module, new()
 	{
 		/*
@@ -119,14 +119,29 @@
 
 		public override bool configDirty { get; set; }
 
-		internal IButton ToolbarButton;
-
-		internal ApplicationLauncherButton AppLauncherButton;
+		protected IButton ToolbarButton;
+		protected ApplicationLauncherButton AppLauncherButton;
+		protected IconState iconState;
 
 		/*
 		 * Properties
 		 * */
-
+		public override bool Active
+		{
+			get
+			{
+				return base.Active;
+			}
+			set
+			{
+				if (value != base.Active)
+				{
+					this.SetIconTexture(this.powerState | this.activeState);
+				}
+
+				base.Active = value;
+			}
+		}
 		public override IList<CelestialBody> AllBodies
 		{
 			get
@@ -372,6 +387,8 @@
 				);
 				this.InitializeToolbarButton();
 			}
+
+			this.SetIconTexture();
 
 			if (this.Active)
 			{
@@ -470,7 +487,6 @@
 				if (this.powerAvailable != newPowerState)
 				{
 					this.powerAvailable = newPowerState;
-					this.SetIconTexture(this.powerState | this.activeState);
 				}
 			}
 
@@ -541,7 +557,6 @@
 					if (GUILayout.Button("Power " + str))
 					{
 						togglePower.value = !togglePower;
-						this.SetIconTexture(this.powerState | this.activeState);
 					}
 				}
 
@@ -753,7 +768,9 @@
 
 			var InstanceProperty = T.GetProperty(
 				"Instance",
-				System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public
+				System.Reflection.BindingFlags.Static |
+				System.Reflection.BindingFlags.Public |
+				System.Reflection.BindingFlags.FlattenHierarchy
 			);
 
 			object modInstance = null;
@@ -783,6 +800,13 @@
 						this.GetType().Name,
 						T.Name
 					));
+			}
+			else
+			{
+				if (module is IDisposable)
+				{
+					(module as IDisposable).Dispose();
+				}
 			}
 		}
 
@@ -928,7 +952,19 @@
 		protected void ToggleMainWindow()
 		{
 			this.Active = !this.Active;
-			this.SetIconTexture(this.powerState | this.activeState);
+		}
+
+		protected void SetIconTexture()
+		{
+			if (
+				this.iconState != (this.powerState | this.activeState) ||
+				(this.VOIDIconTexture == null && this.AppLauncherButton != null)
+			)
+			{
+				this.iconState = this.powerState | this.activeState;
+
+				this.SetIconTexture(this.iconState);
+			}
 		}
 
 		protected void SetIconTexture(IconState state)
@@ -954,15 +990,20 @@
 
 		protected void SetIconTexture(string texturePath)
 		{
+			if (texturePath == null)
+			{
+				return;
+			}
+
 			if (this.ToolbarButton != null)
 			{
 				this.ToolbarButton.TexturePath = texturePath;
 			}
 
-			this.VOIDIconTexture = GameDatabase.Instance.GetTexture(texturePath.Replace("icon", "appIcon"), false);
-
 			if (this.AppLauncherButton != null)
 			{
+				this.VOIDIconTexture = GameDatabase.Instance.GetTexture(texturePath.Replace("icon", "appIcon"), false);
+
 				this.AppLauncherButton.SetTexture(VOIDIconTexture);
 			}
 		}
@@ -1054,13 +1095,11 @@
 			this.LoadConfig();
 
 			this.configVersion = (VOID_SaveValue<int>)VOIDCore.CONFIG_VERSION;
-			
-			this.SetIconTexture(this.powerState | this.activeState);
 
 			this.FactoryReset = false;
 		}
 
-		public virtual void Dispose()
+		public override void Dispose()
 		{
 			this.StopGUI();
 

--- a/VOID_CareerStatus.cs
+++ b/VOID_CareerStatus.cs
@@ -36,14 +36,8 @@
 {
 	[VOID_Scenes(GameScenes.FLIGHT, GameScenes.EDITOR, GameScenes.SPACECENTER)]
 	[VOID_GameModes(Game.Modes.CAREER, Game.Modes.SCIENCE_SANDBOX)]
-	public class VOID_CareerStatus : VOID_WindowModule
+	public class VOID_CareerStatus : VOID_SingletonWindow<VOID_CareerStatus>
 	{
-		public static VOID_CareerStatus Instance
-		{
-			get;
-			private set;
-		}
-
 		public static string formatDelta(double delta, string numberFormat)
 		{
 			if (delta > 0)
@@ -233,11 +227,6 @@
 		 * */
 		public VOID_CareerStatus() : base()
 		{
-			if (this.InValidGame && this.InValidScene)
-			{
-				VOID_CareerStatus.Instance = this;
-			}
-
 			this.Name = "Career Status";
 
 			GameEvents.OnFundsChanged.Add(this.onFundsChange);
@@ -269,14 +258,19 @@
 			this.currentReputation = float.NaN;
 		}
 
-		~VOID_CareerStatus()
+		public override void Dispose()
 		{
 			GameEvents.OnFundsChanged.Remove(this.onFundsChange);
 			GameEvents.OnReputationChanged.Remove(this.onRepChange);
 			GameEvents.OnScienceChanged.Remove(this.onScienceChange);
 			GameEvents.onGameStateLoad.Remove(this.onGameStateLoad);
 
-			VOID_CareerStatus.Instance = null;
+			base.Dispose();
+		}
+
+		~VOID_CareerStatus()
+		{
+			this.Dispose();
 		}
 	}
 }

--- a/VOID_ConfigWindow.cs
+++ b/VOID_ConfigWindow.cs
@@ -33,14 +33,8 @@
 
 namespace VOID
 {
-	public class VOID_ConfigWindow : VOID_WindowModule
+	public class VOID_ConfigWindow : VOID_SingletonWindow<VOID_ConfigWindow>
 	{
-		public static VOID_ConfigWindow Instance
-		{
-			get;
-			private set;
-		}
-
 		public override bool InValidScene
 		{
 			get
@@ -59,8 +53,6 @@
 
 		public VOID_ConfigWindow() : base()
 		{
-			VOID_ConfigWindow.Instance = this;
-
 			this.Name = "VOID Configuration";
 		}
 
@@ -71,12 +63,13 @@
 			this.core.DrawConfigurables();
 
 			GUILayout.EndVertical();
-			GUI.DragWindow();
+
+			base.ModuleWindow(id);
 		}
 
 		~VOID_ConfigWindow()
 		{
-			VOID_ConfigWindow.Instance = null;
+			this.Dispose();
 		}
 	}
 }

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -38,6 +38,7 @@
 	public static class VOID_Data
 	{
 		private static Dictionary<int, IVOID_DataValue> dataValues = new Dictionary<int, IVOID_DataValue>();
+
 		public static Dictionary<int, IVOID_DataValue> DataValues
 		{
 			get
@@ -380,19 +381,7 @@
 						return double.NaN;
 					}
 
-					double stageIsp = Core.LastStage.isp;
-					double stageThrust = stageNominalThrust;
-
-					Tools.PostDebugMessage(typeof(VOID_Data), "calculating stageMassFlow from:\n" +
-						"\tstageIsp: {0}\n" +
-						"\tstageThrust: {1}\n" +
-						"\tKerbinGee: {2}\n",
-						stageIsp,
-						stageThrust,
-						KerbinGee
-					);
-
-					return stageThrust / (stageIsp * KerbinGee);
+					return Core.LastStage.MassFlow();
 				},
 				"Mg/s"
 			);
@@ -407,14 +396,7 @@
 						return double.NaN;
 					}
 
-					if (Core.LastStage.actualThrust == 0d)
-					{
-						return Core.LastStage.thrust;
-					}
-					else
-					{
-						return Core.LastStage.actualThrust;
-					}
+					return Core.LastStage.NominalThrust();
 				},
 				"kN"
 			);
@@ -481,7 +463,7 @@
 					double maxThrust = Core.LastStage.thrust;
 					double mass = Core.LastStage.totalMass;
 					double gravity = (VOIDCore.Constant_G * Core.Vessel.mainBody.Mass) /
-					               (Core.Vessel.mainBody.Radius * Core.Vessel.mainBody.Radius);
+					                 (Core.Vessel.mainBody.Radius * Core.Vessel.mainBody.Radius);
 					double weight = mass * gravity;
 
 					return maxThrust / weight;
@@ -564,13 +546,13 @@
 					thrustDir = vesselTransform.InverseTransformDirection(thrustDir);
 
 					Vector3d thrustOffset = VectorTools.PointDistanceToLine(
-						                      thrustPos, thrustDir.normalized, Core.Vessel.findLocalCenterOfMass());
+						                        thrustPos, thrustDir.normalized, Core.Vessel.findLocalCenterOfMass());
 
 					Tools.PostDebugMessage(typeof(VOID_Data), "vesselThrustOffset:\n" +
-						"\tthrustPos: {0}\n" +
-						"\tthrustDir: {1}\n" +
-						"\tthrustOffset: {2}\n" +
-						"\tvessel.CoM: {3}",
+					"\tthrustPos: {0}\n" +
+					"\tthrustDir: {1}\n" +
+					"\tthrustOffset: {2}\n" +
+					"\tvessel.CoM: {3}",
 						thrustPos,
 						thrustDir.normalized,
 						thrustOffset,
@@ -610,9 +592,9 @@
 								propellantList = engineModule.propellants;
 							}
 							else if (part.tryGetFirstModuleOfType<ModuleEnginesFX>(out enginesFXModule))
-								{
-									propellantList = enginesFXModule.propellants;
-								}
+							{
+								propellantList = enginesFXModule.propellants;
+							}
 
 							if (propellantList != null)
 							{
@@ -701,8 +683,8 @@
 				{
 
 					if (Core.Vessel == null ||
-					  Planetarium.fetch == null ||
-					  Core.Vessel.mainBody != Planetarium.fetch.Home)
+					    Planetarium.fetch == null ||
+					    Core.Vessel.mainBody != Planetarium.fetch.Home)
 					{
 						return double.NaN;
 					}
@@ -955,14 +937,12 @@
 				"Total Burn Time",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDeltaV.Value);
 				},
 				"s"
 			);
@@ -972,14 +952,12 @@
 				"Burn Time Remaining",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDVRemaining == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDVRemaining.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDVRemaining.Value);
 				},
 				"s"
 			);
@@ -989,14 +967,12 @@
 				"Half Burn Time",
 				delegate()
 				{
-					if (Core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
+					if (currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					return realVesselBurnTime(currManeuverDeltaV.Value / 2d);
 				},
 				"s"
 			);
@@ -1088,7 +1064,7 @@
 				delegate()
 				{
 					double orbitRadius = Core.Vessel.mainBody.Radius +
-					                   Core.Vessel.mainBody.GetAltitude(Core.Vessel.findWorldCenterOfMass());
+					                     Core.Vessel.mainBody.GetAltitude(Core.Vessel.findWorldCenterOfMass());
 					return (VOIDCore.Constant_G * Core.Vessel.mainBody.Mass) /
 					(orbitRadius * orbitRadius);
 				},
@@ -1157,9 +1133,9 @@
 				{
 					double trueAnomalyAscNode = 360d - argumentPeriapsis;
 					double dTAscNode = Core.Vessel.orbit.GetDTforTrueAnomaly(
-						trueAnomalyAscNode * Mathf.Deg2Rad,
-						Core.Vessel.orbit.period
-					);
+						                   trueAnomalyAscNode * Mathf.Deg2Rad,
+						                   Core.Vessel.orbit.period
+					                   );
 
 					dTAscNode %= Core.Vessel.orbit.period;
 
@@ -1179,9 +1155,9 @@
 				{
 					double trueAnomalyAscNode = 180d - argumentPeriapsis;
 					double dTDescNode = Core.Vessel.orbit.GetDTforTrueAnomaly(
-						trueAnomalyAscNode * Mathf.Deg2Rad,
-						Core.Vessel.orbit.period
-					);
+						                    trueAnomalyAscNode * Mathf.Deg2Rad,
+						                    Core.Vessel.orbit.period
+					                    );
 
 					dTDescNode %= Core.Vessel.orbit.period;
 
@@ -1198,7 +1174,7 @@
 			new VOID_DoubleValue(
 				"Local Sidereal Longitude",
 				new Func<double>(() => VOID_Tools.FixDegreeDomain(
-						Core.Vessel.longitude + Core.Vessel.orbit.referenceBody.rotationAngle)),
+					Core.Vessel.longitude + Core.Vessel.orbit.referenceBody.rotationAngle)),
 				"°"
 			);
 
@@ -1244,10 +1220,10 @@
 		private static double burnTime(double deltaV, double initialMass, double massFlow, double thrust)
 		{
 			Tools.PostDebugMessage(typeof(VOID_Data), "calculating burnTime from:\n" +
-				"\tdeltaV: {0}\n" +
-				"\tinitialMass: {1}\n" +
-				"\tmassFlow: {2}\n" +
-				"\tthrust: {3}\n",
+			"\tdeltaV: {0}\n" +
+			"\tinitialMass: {1}\n" +
+			"\tmassFlow: {2}\n" +
+			"\tthrust: {3}\n",
 				deltaV,
 				initialMass,
 				massFlow,
@@ -1255,6 +1231,46 @@
 			);
 			return initialMass / massFlow * (1d - Math.Exp(-deltaV * massFlow / thrust));
 		}
+
+		private static double dVfromBurnTime(double time, double initialMass, double massFlow, double thrust)
+		{
+			return -thrust / massFlow * Math.Log(1d - time * massFlow / initialMass);
+		}
+
+		private static double realVesselBurnTime(double deltaV)
+		{
+			if (Core.Stages == null || Core.Stages.Length < 1)
+			{
+				return double.NaN;
+			}
+
+			double burntime = 0d;
+			double dVRemaining = deltaV;
+
+			int stageIdx = Core.Stages.Length - 1;
+
+			while (dVRemaining > double.Epsilon)
+			{
+				if (stageIdx < 0)
+				{
+					return double.PositiveInfinity;
+				}
+
+				Stage stage = Core.Stages[stageIdx];
+
+				if (stage.deltaV > 0)
+				{
+					double stageDVUsed = Math.Min(stage.deltaV, dVRemaining);
+
+					burntime += burnTime(stageDVUsed, stage.totalMass, stage.MassFlow(), stage.NominalThrust());
+					dVRemaining -= stageDVUsed;
+				}
+
+				stageIdx--;
+			}
+
+			return burntime;
+		}
 	}
 }
 

--- a/VOID_SurfAtmo.cs
+++ b/VOID_SurfAtmo.cs
@@ -41,7 +41,7 @@
 
 		public VOID_SurfAtmo()
 		{
-			this.Name = "Surface & Atmospheric Information";
+			this.Name = "Surface & Atmospheric Info";
 
 			this.WindowPos.x = Screen.width - 260f;
 			this.WindowPos.y = 85;