diff --git a/MicroEngineerProject/MicroEngineer/MicroCelestialBodies.cs b/MicroEngineerProject/MicroEngineer/MicroCelestialBodies.cs new file mode 100644 index 0000000..7fe5a50 --- /dev/null +++ b/MicroEngineerProject/MicroEngineer/MicroCelestialBodies.cs @@ -0,0 +1,61 @@ +using KSP.Game; +using KSP.Sim.Definitions; + +namespace MicroMod +{ + internal class MicroCelestialBodies + { + public List Bodies = new(); + + /// + /// Refreshes the list of all CelestialBodies. Does nothing if list is already populated. + /// + /// True = refresh completed successfully or list is already populated + internal bool GetBodies() + { + if (this.Bodies.Count > 0) + return true; + + Dictionary bodies = GameManager.Instance?.Game?.CelestialBodies?.GetAllBodiesData(); + + if (bodies == null || bodies.Count == 0) + return false; + + foreach (var body in bodies) + { + this.Bodies.Add(new CelestialBody + { + Name = body.Value.data.bodyName, + GravityASL = body.Value.data.gravityASL, + HasAtmosphere = body.Value.data.hasAtmosphere, + IsHomeWorld = body.Value.data.isHomeWorld + }); + } + + return true; + } + + /// + /// Calculates what factor needs to be used for HomeWorld's TWR in order to compensate for gravity of the selected body + /// + /// Name of the CelestialBody for which the TWR factor is calculated + /// TWR factor that needs to be multiplied with HomeWorld's TWR to get TWR at the selected body and information if target body has an atmosphere + internal (double twrFactor, bool hasAtmosphere) GetTwrFactor(string bodyName) + { + if (Bodies.Count == 0) return (0, false); + CelestialBody homeWorld = Bodies.Find(b => b.IsHomeWorld); + CelestialBody targetBody = Bodies.Find(t => t.Name.ToLowerInvariant() == bodyName.ToLowerInvariant()) ?? null; + if (targetBody == null) return (0, false); + + return (homeWorld.GravityASL / targetBody.GravityASL, targetBody.HasAtmosphere); + } + } + + internal class CelestialBody + { + public string Name; + public double GravityASL; + public bool HasAtmosphere; + public bool IsHomeWorld; + } +} diff --git a/MicroEngineerProject/MicroEngineer/MicroEngineerMod.cs b/MicroEngineerProject/MicroEngineer/MicroEngineerMod.cs index 5509a52..fd85b5e 100644 --- a/MicroEngineerProject/MicroEngineer/MicroEngineerMod.cs +++ b/MicroEngineerProject/MicroEngineer/MicroEngineerMod.cs @@ -1,18 +1,13 @@ using BepInEx; using KSP.Game; -using KSP.Sim.impl; using UnityEngine; using SpaceWarp; using SpaceWarp.API.Assets; using SpaceWarp.API.Mods; -using SpaceWarp.API.UI; using SpaceWarp.API.UI.Appbar; -using KSP.Sim.Maneuver; using KSP.UI.Binding; using KSP.Sim.DeltaV; -using KSP.Sim; -using KSP.UI.Flight; -using static KSP.Rendering.Planets.PQSData; +using KSP.Messages; namespace MicroMod { @@ -20,7 +15,8 @@ namespace MicroMod [BepInDependency(SpaceWarpPlugin.ModGuid, SpaceWarpPlugin.ModVer)] public class MicroEngineerMod : BaseSpaceWarpPlugin { - private bool showGUI = false; + private bool _showGuiFlight; + private bool _showGuiOAB; #region Editing window private bool showEditWindow = false; @@ -39,43 +35,167 @@ public class MicroEngineerMod : BaseSpaceWarpPlugin /// private List MicroWindows; + /// + /// Holds data on all bodies for calculating TWR (currently) + /// + private MicroCelestialBodies _celestialBodies = new(); + + // Index of the stage for which user wants to select a different CelestialBody for different TWR calculations. -1 -> no stage is selected + private int _celestialBodySelectionStageIndex = -1; + public override void OnInitialized() { - Appbar.RegisterAppButton( - "Micro Engineer", - "BTN-MicroEngineerBtn", - AssetManager.GetAsset($"{SpaceWarpMetadata.ModID}/images/icon.png"), - delegate { showGUI = !showGUI; } - ); - - MicroStyles.InitializeStyles(); + MicroStyles.InitializeStyles(); InitializeEntries(); InitializeWindows(); + SubscribeToMessages(); + InitializeCelestialBodies(); - // load window positions and states from disk, if file exists + // Load window positions and states from disk, if file exists MicroUtility.LoadLayout(MicroWindows); - } - + + // Preserve backward compatibility with 0.6.0. If user previously saved the layout and then upgraded without deleting the original folder, then StageInfoOAB window will be wiped by LoadLayout(). So, we add it manually now. + if (MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB) == null) + InitializeStageInfoOABWindow(); + + Appbar.RegisterAppButton( + "Micro Engineer", + "BTN-MicroEngineerBtn", + AssetManager.GetAsset($"{SpaceWarpMetadata.ModID}/images/icon.png"), + isOpen => + { + _showGuiFlight = isOpen; + MicroWindows.Find(w => w.MainWindow == MainWindow.MainGui).IsFlightActive = isOpen; + GameObject.Find("BTN-MicroEngineerBtn")?.GetComponent()?.SetValue(isOpen); + }); + + Appbar.RegisterOABAppButton( + "Micro Engineer", + "BTN-MicroEngineerOAB", + AssetManager.GetAsset($"{SpaceWarpMetadata.ModID}/images/icon.png"), + isOpen => + { + _showGuiOAB = isOpen; + MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB).IsEditorActive = isOpen; + GameObject.Find("BTN - MicroEngineerOAB")?.GetComponent()?.SetValue(isOpen); + }); + } + + /// + /// Subscribe to Messages KSP2 is using + /// + private void SubscribeToMessages() + { + MicroUtility.RefreshGameManager(); + + // While in OAB we use the VesselDeltaVCalculationMessage event to refresh data as it's triggered a lot less frequently than Update() + MicroUtility.MessageCenter.Subscribe(new Action(this.RefreshStagingDataOAB)); + + // We are loading layout state when entering Flight or OAB game state + MicroUtility.MessageCenter.Subscribe(new Action(this.GameStateEntered)); + + // We are saving layout state when exiting from Flight or OAB game state + MicroUtility.MessageCenter.Subscribe(new Action(this.GameStateLeft)); + } + + private void GameStateEntered(MessageCenterMessage obj) + { + Logger.LogInfo("Message triggered: GameStateEnteredMessage"); + + MicroUtility.RefreshGameManager(); + if (MicroUtility.GameState.GameState == GameState.FlightView || MicroUtility.GameState.GameState == GameState.VehicleAssemblyBuilder || MicroUtility.GameState.GameState == GameState.Map3DView) + { + MicroUtility.LoadLayout(MicroWindows); + + if(MicroUtility.GameState.GameState == GameState.FlightView || MicroUtility.GameState.GameState == GameState.Map3DView) + _showGuiFlight = MicroWindows.Find(w => w.MainWindow == MainWindow.MainGui).IsFlightActive; + + if(MicroUtility.GameState.GameState == GameState.VehicleAssemblyBuilder) + { + _showGuiOAB = MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB).IsEditorActive; + InitializeCelestialBodies(); + _celestialBodySelectionStageIndex = -1; + } + } + } + + private void GameStateLeft(MessageCenterMessage obj) + { + Logger.LogInfo("Message triggered: GameStateLeftMessage"); + + MicroUtility.RefreshGameManager(); + if (MicroUtility.GameState.GameState == GameState.FlightView || MicroUtility.GameState.GameState == GameState.VehicleAssemblyBuilder || MicroUtility.GameState.GameState == GameState.Map3DView) + { + MicroUtility.SaveLayout(MicroWindows); + + if (MicroUtility.GameState.GameState == GameState.FlightView || MicroUtility.GameState.GameState == GameState.Map3DView) + _showGuiFlight = false; + + if (MicroUtility.GameState.GameState == GameState.VehicleAssemblyBuilder) + _showGuiOAB = false; + } + } + + #region Data refreshing + /// + /// Refresh all staging data while in OAB + /// + private void RefreshStagingDataOAB(MessageCenterMessage obj) + { + MicroUtility.RefreshGameManager(); + if (MicroUtility.GameState.GameState != GameState.VehicleAssemblyBuilder) return; + + MicroUtility.RefreshStagesOAB(); + + MicroWindow stageWindow = MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB); + + if (MicroUtility.VesselDeltaVComponentOAB?.StageInfo == null) + { + stageWindow.Entries.Find(e => e.Name == "Stage Info (OAB)").EntryValue = null; + return; + } + + foreach (var entry in stageWindow.Entries) + entry.RefreshData(); + } + public void Update() { MicroUtility.RefreshActiveVesselAndCurrentManeuver(); - if (MicroUtility.ActiveVessel == null) return; - // Grab all entries from all active windows and refresh their data - foreach (MicroEntry entry in MicroWindows - .Where(w => w.IsFlightActive) - .SelectMany(w => w.Entries ?? Enumerable.Empty()).ToList()) + // Do not perform flight UI updates if we're outside of flight and map scene + if (MicroUtility.GameState == null || (MicroUtility.GameState.GameState != GameState.FlightView && MicroUtility.GameState.GameState != GameState.Map3DView)) + return; + + if (MicroUtility.ActiveVessel == null) + return; + + // Grab all Flight entries from all active windows and refresh their data + foreach (MicroEntry entry in MicroWindows + .Where(w => w.IsFlightActive) + .SelectMany(w => w.Entries ?? Enumerable.Empty()).ToList()) entry.RefreshData(); } - - private void OnGUI() - { + #endregion + + private void OnGUI() + { GUI.skin = MicroStyles.SpaceWarpUISkin; - if (!showGUI || MicroUtility.ActiveVessel == null) return; + MicroUtility.RefreshGameManager(); + if (MicroUtility.GameState.GameState == GameState.VehicleAssemblyBuilder) + OnGUI_OAB(); + else + OnGUI_Flight(); + } + + #region Flight scene UI and logic + private void OnGUI_Flight() + { + if (!_showGuiFlight || MicroUtility.ActiveVessel == null) return; MicroWindow mainGui = MicroWindows.Find(window => window.MainWindow == MainWindow.MainGui); - + // Draw main GUI that contains docked windows mainGui.FlightRect = GUILayout.Window( GUIUtility.GetControlID(FocusType.Passive), @@ -164,11 +284,12 @@ private void OnGUI() MicroStyles.EditWindowRect, DrawEditWindow, "", - MicroStyles.EditWindowStyle - ); + MicroStyles.EditWindowStyle, + GUILayout.Height(0) + ); } } - + /// /// Draws the main GUI with all windows that are toggled and docked /// @@ -177,7 +298,7 @@ private void FillMainGUI(int windowID) { try { - if (CloseButton()) + if (CloseButton(MicroStyles.CloseBtnRect)) { CloseWindow(); } @@ -186,8 +307,8 @@ private void FillMainGUI(int windowID) GUILayout.BeginHorizontal(); - // Draw toggles for all windows except MainGui - foreach (var (window, index) in MicroWindows.Select((window, index) => (window, index)).Where(x => x.window.MainWindow != MainWindow.MainGui)) + // Draw toggles for all windows except MainGui and StageInfoOAB + foreach (var (window, index) in MicroWindows.Select((window, index) => (window, index)).Where(x => x.window.MainWindow != MainWindow.MainGui && x.window.MainWindow != MainWindow.StageInfoOAB)) { // layout can fit 6 toggles, so if all 6 slots are filled then go to a new line. Index == 0 is the MainGUI which isn't rendered if ((index - 1) % 6 == 0 && index > 1) @@ -330,7 +451,7 @@ private void DrawSectionHeader(string sectionName, ref bool isPopout, bool isLoc GUILayout.BeginHorizontal(); // If window is popped out and it's not locked => show the close button. If it's not popped out => show to popup arrow - isPopout = isPopout && !isLocked ? !CloseButton() : !isPopout ? GUILayout.Button("⇖", MicroStyles.PopoutBtnStyle) : isPopout; + isPopout = isPopout && !isLocked ? !CloseButton(MicroStyles.CloseBtnRect) : !isPopout ? GUILayout.Button("⇖", MicroStyles.PopoutBtnStyle) : isPopout; GUILayout.Label($"{sectionName}"); GUILayout.FlexibleSpace(); @@ -343,8 +464,8 @@ private void DrawSectionHeader(string sectionName, ref bool isPopout, bool isLoc private void DrawStagesHeader(MicroWindow stageWindow) { - GUILayout.BeginHorizontal(); - stageWindow.IsFlightPoppedOut = stageWindow.IsFlightPoppedOut ? !CloseButton() : GUILayout.Button("⇖", MicroStyles.PopoutBtnStyle); + GUILayout.BeginHorizontal(); + stageWindow.IsFlightPoppedOut = stageWindow.IsFlightPoppedOut ? !CloseButton(MicroStyles.CloseBtnRect) : GUILayout.Button("⇖", MicroStyles.PopoutBtnStyle); GUILayout.Label($"{stageWindow.Name}"); GUILayout.FlexibleSpace(); @@ -424,10 +545,10 @@ private void DrawSectionEnd(MicroWindow window) /// private void DrawEditWindow(int windowIndex) { - List editableWindows = MicroWindows.FindAll(w => w.IsEditable); // Editable windows are all except MainGUI, Settings and Stage - List entriesByCategory = MicroEntries.FindAll(e => e.Category == selectedCategory); // All window entries belong to a category, but they can still be placed in any window + List editableWindows = MicroWindows.FindAll(w => w.IsEditable); // Editable windows are all except MainGUI, Settings, Stage and StageInfoOAB + List entriesByCategory = MicroEntries.FindAll(e => e.Category == selectedCategory); // All window stageInfoOabEntries belong to a category, but they can still be placed in any window - showEditWindow = !CloseButton(); + showEditWindow = !CloseButton(MicroStyles.CloseBtnRect); #region Selection of window to be edited GUILayout.BeginHorizontal(); @@ -582,7 +703,7 @@ private void CreateCustomWindow(List editableWindows) IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.None, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = new List() }; @@ -593,22 +714,178 @@ private void CreateCustomWindow(List editableWindows) selectedWindowId = editableWindows.Count - 1; } - private bool CloseButton() - { - return GUI.Button(MicroStyles.CloseBtnRect, "X", MicroStyles.CloseBtnStyle); - } - - private void CloseWindow() - { - GameObject.Find("BTN-MicroEngineerBtn")?.GetComponent()?.SetValue(false); - showGUI = false; - } - private void ResetLayout() { InitializeWindows(); } + private void CloseWindow() + { + GameObject.Find("BTN-MicroEngineerBtn")?.GetComponent()?.SetValue(false); + _showGuiFlight = false; + } + + #endregion + + #region OAB scene UI and logic + private void OnGUI_OAB() + { + if (!_showGuiOAB) return; + + MicroWindow stageInfoOAB = MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB); + if (stageInfoOAB.Entries.Find(e => e.Name == "Stage Info (OAB)").EntryValue == null) return; + + stageInfoOAB.EditorRect = GUILayout.Window( + GUIUtility.GetControlID(FocusType.Passive), + stageInfoOAB.EditorRect, + DrawStageInfoOAB, + "", + MicroStyles.StageOABWindowStyle, + GUILayout.Height(0) + ); + stageInfoOAB.EditorRect.position = MicroUtility.ClampToScreen(stageInfoOAB.EditorRect.position, stageInfoOAB.EditorRect.size); + + // Draw window for selecting CelestialBody for a stage + // -1 -> no selection of CelestialBody is taking place + // any other int -> index represents the stage number for which the selection was clicked + if (_celestialBodySelectionStageIndex > -1) + { + Rect stageInfoOabRect = MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB).EditorRect; + Rect celestialBodyRect = new Rect(stageInfoOabRect.x + stageInfoOabRect.width, stageInfoOabRect.y, 0, 0); + + celestialBodyRect = GUILayout.Window( + GUIUtility.GetControlID(FocusType.Passive), + celestialBodyRect, + DrawCelestialBodySelection, + "", + MicroStyles.CelestialSelectionStyle, + GUILayout.Height(0) + ); + } + } + + private void DrawStageInfoOAB(int windowID) + { + MicroWindow stageInfoOabWindow = MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB); + List stageInfoOabEntries = stageInfoOabWindow.Entries; + + GUILayout.BeginHorizontal(); + if (CloseButton(MicroStyles.CloseBtnStagesOABRect)) + { + stageInfoOabWindow.IsEditorActive = false; + _showGuiOAB = false; + } + GUILayout.Label($"Stage Info"); + GUILayout.EndHorizontal(); + + // Draw StageInfo header - Delta V fields + GUILayout.BeginHorizontal(); + GUILayout.Label("Total ∆v (ASL, vacuum)", MicroStyles.NameLabelStyle); + GUILayout.FlexibleSpace(); + GUILayout.Label($"{stageInfoOabEntries.Find(e => e.Name == "Total ∆v Actual (OAB)").ValueDisplay}, {stageInfoOabEntries.Find(e => e.Name == "Total ∆v Vac (OAB)").ValueDisplay}", MicroStyles.ValueLabelStyle); + GUILayout.Space(5); + GUILayout.Label("m/s", MicroStyles.UnitLabelStyle); + GUILayout.EndHorizontal(); + + // Draw Stage table header + GUILayout.BeginHorizontal(); + GUILayout.Label("Stage", MicroStyles.NameLabelStyle, GUILayout.Width(40)); + GUILayout.FlexibleSpace(); + GUILayout.Label("TWR", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(65)); + GUILayout.Label("SLT", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(75)); + GUILayout.Label("", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(30)); + GUILayout.Label("ASL ∆v", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(75)); + GUILayout.Label("", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(30)); + GUILayout.Label("Vac ∆v", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(75)); + GUILayout.Label("Burn Time", MicroStyles.TableHeaderLabelStyle, GUILayout.Width(110)); + GUILayout.Space(20); + GUILayout.Label("Body", MicroStyles.TableHeaderCenteredLabelStyle, GUILayout.Width(80)); + GUILayout.EndHorizontal(); + GUILayout.Space(MicroStyles.SpacingAfterEntry); + + StageInfo_OAB stageInfoOab = (StageInfo_OAB)stageInfoOabWindow.Entries + .Find(e => e.Name == "Stage Info (OAB)"); + + // Draw each stage that has delta v + var stages = ((List)stageInfoOab.EntryValue) + .FindAll(s => s.DeltaVVac > 0.0001 || s.DeltaVASL > 0.0001); + + int celestialIndex = -1; + for (int stageIndex = stages.Count - 1; stageIndex >= 0; stageIndex--) + { + // Check if this stage has a CelestialBody attached. If not, create a new CelestialBody and assign it to HomeWorld (i.e. Kerbin) + if (stageInfoOab.CelestialBodyForStage.Count == ++celestialIndex) + stageInfoOab.AddNewCelestialBody(_celestialBodies); + + GUILayout.BeginHorizontal(); + GUILayout.Label(String.Format("{0:00}", ((List)stageInfoOab.EntryValue).Count - stages[stageIndex].Stage), MicroStyles.NameLabelStyle, GUILayout.Width(40)); + GUILayout.FlexibleSpace(); + + // We calculate what factor needs to be applied to TWR in order to compensate for different gravity of the selected celestial body + // For selected bodies with atmosphere, for ASL TWR we just apply the factor to HomeWorld's (i.e. Kerbin) ASL TWR as it's a good enough approximation for now. + // -> This is just an approximation because ASL TWR depends on Thrust as well which changes depending on atmospheric pressure + (double factor, bool hasAtmosphere) twrFactor = _celestialBodies.GetTwrFactor(stageInfoOab.CelestialBodyForStage[celestialIndex]); + + GUILayout.Label(String.Format("{0:N2}", stages[stageIndex].TWRVac * twrFactor.factor), MicroStyles.ValueLabelStyle, GUILayout.Width(65)); + + // If target body doesn't have an atmosphere, its ASL TWR is the same as Vacuum TWR + GUILayout.Label(String.Format("{0:N2}", twrFactor.hasAtmosphere ? stages[stageIndex].TWRASL * twrFactor.factor : stages[stageIndex].TWRVac * twrFactor.factor), MicroStyles.ValueLabelStyle, GUILayout.Width(75)); + GUILayout.Label(String.Format("{0:N0}", stages[stageIndex].DeltaVASL), MicroStyles.ValueLabelStyle, GUILayout.Width(75)); + GUILayout.Label("m/s", MicroStyles.UnitLabelStyleStageOAB, GUILayout.Width(30)); + GUILayout.Label(String.Format("{0:N0}", stages[stageIndex].DeltaVVac), MicroStyles.ValueLabelStyle, GUILayout.Width(75)); + GUILayout.Label("m/s", MicroStyles.UnitLabelStyleStageOAB, GUILayout.Width(30)); + GUILayout.Label(MicroUtility.SecondsToTimeString(stages[stageIndex].StageBurnTime, true, true), MicroStyles.ValueLabelStyle, GUILayout.Width(110)); + GUILayout.Space(20); + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(stageInfoOab.CelestialBodyForStage[celestialIndex], MicroStyles.CelestialSelectionBtnStyle)) + { + _celestialBodySelectionStageIndex = celestialIndex; + } + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + GUILayout.Space(MicroStyles.SpacingAfterEntry); + } + + GUILayout.Space(MicroStyles.SpacingBelowPopout); + + GUI.DragWindow(new Rect(0, 0, Screen.width, Screen.height)); + } + + /// + /// Opens a window for selecting a CelestialObject for the stage on the given index + /// + private void DrawCelestialBodySelection(int id) + { + GUILayout.BeginVertical(); + + foreach (var body in _celestialBodies.Bodies) + { + if (GUILayout.Button(body.Name)) + { + StageInfo_OAB stageInfoOab = (StageInfo_OAB)MicroWindows.Find(w => w.MainWindow == MainWindow.StageInfoOAB).Entries.Find(e => e.Name == "Stage Info (OAB)"); + stageInfoOab.CelestialBodyForStage[_celestialBodySelectionStageIndex] = body.Name; + + // Hide the selection window + _celestialBodySelectionStageIndex = -1; + } + } + + GUILayout.EndVertical(); + } + #endregion + + /// + /// Draws a close button (X) + /// + /// Where to position the close button + /// + private bool CloseButton(Rect rect) + { + return GUI.Button(rect, "X", MicroStyles.CloseBtnStyle); + } + + #region Window and data initialization /// /// Builds the list of all Entries /// @@ -675,12 +952,19 @@ private void InitializeEntries() #region Misc entries MicroEntries.Add(new Separator()); #endregion + #region OAB entries + MicroEntries.Add(new TotalBurnTime_OAB()); + MicroEntries.Add(new TotalDeltaVASL_OAB()); + MicroEntries.Add(new TotalDeltaVActual_OAB()); + MicroEntries.Add(new TotalDeltaVVac_OAB()); + MicroEntries.Add(new StageInfo_OAB()); + #endregion } - /// - /// Builds the default Windows and fills them with default Entries - /// - private void InitializeWindows() + /// + /// Builds the default Windows and fills them with default Entries + /// + private void InitializeWindows() { MicroWindows = new List(); @@ -692,14 +976,14 @@ private void InitializeWindows() Abbreviation = null, Description = "Main GUI", IsEditorActive = false, - IsFlightActive = true, + IsFlightActive = false, IsMapActive = false, IsEditorPoppedOut = false, // not relevant to Main GUI IsFlightPoppedOut = false, // not relevant to Main GUI IsMapPoppedOut = false, // not relevant to Main GUI IsLocked = false, MainWindow = MainWindow.MainGui, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.MainGuiX, MicroStyles.MainGuiY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = null }); @@ -717,7 +1001,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Settings, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = null }); @@ -735,8 +1019,8 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Vessel, - EditorRect = null, - FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), + //EditorRect = null, + FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Vessel).ToList() }); @@ -753,7 +1037,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Orbital, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Orbital).ToList() }); @@ -771,7 +1055,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Surface, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Surface).ToList() }); @@ -789,7 +1073,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Flight, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Flight).ToList() }); @@ -807,7 +1091,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Target, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Target).ToList() }); @@ -825,7 +1109,7 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Maneuver, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Maneuver).ToList() }); @@ -843,15 +1127,46 @@ private void InitializeWindows() IsMapPoppedOut = false, IsLocked = false, MainWindow = MainWindow.Stage, - EditorRect = null, + //EditorRect = null, FlightRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, MicroStyles.WindowWidth, MicroStyles.WindowHeight), Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.Stage).ToList() - }); + }); + + InitializeStageInfoOABWindow(); } catch (Exception ex) { - Logger.LogError(ex); + Logger.LogError("Error creating a MicroWindow. Full exception: " + ex); } } + + private void InitializeStageInfoOABWindow() + { + MicroWindows.Add(new MicroWindow + { + Name = "Stage (OAB)", + Abbreviation = "SOAB", + Description = "Stage Info window for OAB", + IsEditorActive = false, + IsFlightActive = false, // Not used + IsMapActive = false, // Not used + IsEditorPoppedOut = true, // Not used + IsFlightPoppedOut = false, // Not used + IsMapPoppedOut = false, // Not used + IsLocked = false, // Not used + MainWindow = MainWindow.StageInfoOAB, + EditorRect = new Rect(MicroStyles.PoppedOutX, MicroStyles.PoppedOutY, 0, 0), + Entries = Enumerable.Where(MicroEntries, entry => entry.Category == MicroEntryCategory.OAB).ToList() + }); + } + + private void InitializeCelestialBodies() + { + if (_celestialBodies.Bodies.Count > 0) + return; + + _celestialBodies.GetBodies(); + } + #endregion } } \ No newline at end of file diff --git a/MicroEngineerProject/MicroEngineer/MicroEntries.cs b/MicroEngineerProject/MicroEngineer/MicroEntries.cs index af7de10..ee922fa 100644 --- a/MicroEngineerProject/MicroEngineer/MicroEntries.cs +++ b/MicroEngineerProject/MicroEngineer/MicroEntries.cs @@ -41,6 +41,7 @@ public virtual string ValueDisplay public virtual void RefreshData() { } } + #region Flight scene entries public class Vessel : MicroEntry { public Vessel() @@ -1234,4 +1235,226 @@ public Separator() EntryValue = "---------------"; } } + #endregion + + #region OAB scene entries + public class TotalBurnTime_OAB : MicroEntry + { + public bool UseDHMSFormatting; // TODO: implement + + public TotalBurnTime_OAB() + { + Name = "Total Burn Time (OAB)"; + Description = "Shows the total length of burn the vessel can mantain."; + Category = MicroEntryCategory.OAB; + Unit = "s"; + Formatting = "{0:N1}"; + UseDHMSFormatting = true; + } + + public override void RefreshData() + { + EntryValue = MicroUtility.VesselDeltaVComponentOAB?.TotalBurnTime; + } + + public override string ValueDisplay + { + get + { + if (EntryValue == null) + return "-"; + + if (UseDHMSFormatting) + return MicroUtility.SecondsToTimeString((double)EntryValue); + else + return String.IsNullOrEmpty(this.Formatting) ? EntryValue.ToString() : String.Format(Formatting, EntryValue); + } + } + } + + public class TotalDeltaVASL_OAB : MicroEntry + { + public TotalDeltaVASL_OAB() + { + Name = "Total ∆v ASL (OAB)"; + Description = "Shows the vessel's total delta velocity At Sea Level."; + Category = MicroEntryCategory.OAB; + Unit = "m/s"; + Formatting = "{0:N0}"; + } + public override void RefreshData() + { + EntryValue = MicroUtility.VesselDeltaVComponentOAB?.TotalDeltaVASL; + } + + public override string ValueDisplay + { + get + { + if (EntryValue == null) + return "-"; + + return String.IsNullOrEmpty(this.Formatting) ? EntryValue.ToString() : String.Format(Formatting, EntryValue); + } + } + } + + public class TotalDeltaVActual_OAB : MicroEntry + { + public TotalDeltaVActual_OAB() + { + Name = "Total ∆v Actual (OAB)"; + Description = "Shows the vessel's actual total delta velocity (not used in OAB)."; + Category = MicroEntryCategory.OAB; + Unit = "m/s"; + Formatting = "{0:N0}"; + } + public override void RefreshData() + { + EntryValue = MicroUtility.VesselDeltaVComponentOAB?.TotalDeltaVActual; + } + + public override string ValueDisplay + { + get + { + if (EntryValue == null) + return "-"; + + return String.IsNullOrEmpty(this.Formatting) ? EntryValue.ToString() : String.Format(Formatting, EntryValue); + } + } + } + + public class TotalDeltaVVac_OAB : MicroEntry + { + public TotalDeltaVVac_OAB() + { + Name = "Total ∆v Vac (OAB)"; + Description = "Shows the vessel's total delta velocity in Vacuum."; + Category = MicroEntryCategory.OAB; + Unit = "m/s"; + Formatting = "{0:N0}"; + } + public override void RefreshData() + { + EntryValue = MicroUtility.VesselDeltaVComponentOAB?.TotalDeltaVVac; + } + + public override string ValueDisplay + { + get + { + if (EntryValue == null) + return "-"; + + return String.IsNullOrEmpty(this.Formatting) ? EntryValue.ToString() : String.Format(Formatting, EntryValue); + } + } + } + + /// + /// Holds stage info parameters for each stage. Also keeps information about the celestial body user selected in the window. + /// + public class StageInfo_OAB : MicroEntry + { + public List CelestialBodyForStage = new(); + + public StageInfo_OAB() + { + Name = "Stage Info (OAB)"; + Description = "Holds a list of stage info parameters."; + Category = MicroEntryCategory.OAB; + Unit = null; + Formatting = null; + } + + public override void RefreshData() + { + EntryValue ??= new List(); + + ((List)EntryValue).Clear(); + + if (MicroUtility.VesselDeltaVComponentOAB?.StageInfo == null) return; + + foreach (var stage in MicroUtility.VesselDeltaVComponentOAB.StageInfo) + { + ((List)EntryValue).Add(new DeltaVStageInfo_OAB + { + DeltaVActual = stage.DeltaVActual, + DeltaVASL = stage.DeltaVatASL, + DeltaVVac = stage.DeltaVinVac, + DryMass = stage.DryMass, + EndMass = stage.EndMass, + FuelMass = stage.FuelMass, + IspASL = stage.IspASL, + IspActual = stage.IspActual, + IspVac = stage.IspVac, + SeparationIndex = stage.SeparationIndex, + Stage = stage.Stage, + StageBurnTime = stage.StageBurnTime, + StageMass = stage.StageMass, + StartMass = stage.StartMass, + TWRASL = stage.TWRASL, + TWRActual = stage.TWRActual, + TWRVac = stage.TWRVac, + ThrustASL = stage.ThrustASL, + ThrustActual = stage.ThrustActual, + ThrustVac = stage.ThrustVac, + TotalExhaustVelocityASL = stage.TotalExhaustVelocityASL, + TotalExhaustVelocityActual = stage.TotalExhaustVelocityActual, + TotalExhaustVelocityVAC = stage.TotalExhaustVelocityVAC + }); + } + } + + public override string ValueDisplay + { + get + { + return "-"; + } + } + + /// + /// Adds a new string to the CelestialBodyForStage list that corresponds to the HomeWorld, i.e. Kerbin + /// + /// + internal void AddNewCelestialBody(MicroCelestialBodies celestialBodies) + { + this.CelestialBodyForStage.Add(celestialBodies.Bodies.Find(b => b.IsHomeWorld).Name); + } + } + + /// + /// Parameters for one stage + /// + public class DeltaVStageInfo_OAB + { + public double DeltaVActual; + public double DeltaVASL; + public double DeltaVVac; + public double DryMass; + public double EndMass; + public double FuelMass; + public double IspASL; + public double IspActual; + public double IspVac; + public int SeparationIndex; + public int Stage; + public double StageBurnTime; + public double StageMass; + public double StartMass; + public float TWRASL; + public float TWRActual; + public float TWRVac; + public float ThrustASL; + public float ThrustActual; + public float ThrustVac; + public float TotalExhaustVelocityASL; + public float TotalExhaustVelocityActual; + public float TotalExhaustVelocityVAC; + public string CelestialBody; + } + #endregion } diff --git a/MicroEngineerProject/MicroEngineer/MicroStyles.cs b/MicroEngineerProject/MicroEngineer/MicroStyles.cs index 195737d..88b58bc 100644 --- a/MicroEngineerProject/MicroEngineer/MicroStyles.cs +++ b/MicroEngineerProject/MicroEngineer/MicroStyles.cs @@ -7,26 +7,34 @@ public static class MicroStyles { public static int WindowWidth = 290; public static int WindowHeight = 1440; + public static int WindowWidthStageOAB = 645; + public static int WindowWidthCelestialSelection = 100; public static GUISkin SpaceWarpUISkin; public static GUIStyle MainWindowStyle; public static GUIStyle PopoutWindowStyle; public static GUIStyle EditWindowStyle; + public static GUIStyle StageOABWindowStyle; + public static GUIStyle CelestialSelectionStyle; public static GUIStyle PopoutBtnStyle; public static GUIStyle SectionToggleStyle; public static GUIStyle NameLabelStyle; public static GUIStyle ValueLabelStyle; public static GUIStyle BlueLabelStyle; public static GUIStyle UnitLabelStyle; + public static GUIStyle UnitLabelStyleStageOAB; public static GUIStyle NormalLabelStyle; public static GUIStyle TitleLabelStyle; public static GUIStyle NormalCenteredLabelStyle; public static GUIStyle WindowSelectionTextFieldStyle; public static GUIStyle WindowSelectionAbbrevitionTextFieldStyle; public static GUIStyle CloseBtnStyle; + public static GUIStyle CloseBtnStageOABStyle; public static GUIStyle NormalBtnStyle; + public static GUIStyle CelestialSelectionBtnStyle; public static GUIStyle OneCharacterBtnStyle; public static GUIStyle TableHeaderLabelStyle; + public static GUIStyle TableHeaderCenteredLabelStyle; public static string UnitColorHex { get => ColorUtility.ToHtmlStringRGBA(UnitLabelStyle.normal.textColor); } @@ -41,6 +49,7 @@ public static class MicroStyles public static float MainGuiY = Screen.height * 0.2f; public static Rect CloseBtnRect = new Rect(MicroStyles.WindowWidth - 23, 6, 16, 16); + public static Rect CloseBtnStagesOABRect = new Rect(MicroStyles.WindowWidthStageOAB - 23, 6, 16, 16); public static Rect EditWindowRect = new Rect(Screen.width * 0.5f - MicroStyles.WindowWidth / 2, Screen.height * 0.2f, MicroStyles.WindowWidth, 0); public static void InitializeStyles() @@ -65,6 +74,20 @@ public static void InitializeStyles() padding = new RectOffset(8, 8, 30, 8) }; + StageOABWindowStyle = new GUIStyle(SpaceWarpUISkin.window) + { + padding = new RectOffset(8, 8, 0, 8), + contentOffset = new Vector2(0, -22), + fixedWidth = WindowWidthStageOAB + }; + + CelestialSelectionStyle = new GUIStyle(SpaceWarpUISkin.window) + { + padding = new RectOffset(8, 8, 0, 8), + contentOffset = new Vector2(0, -22), + fixedWidth = WindowWidthCelestialSelection + }; + PopoutBtnStyle = new GUIStyle(SpaceWarpUISkin.button) { alignment = TextAnchor.MiddleCenter, @@ -97,6 +120,12 @@ public static void InitializeStyles() }; UnitLabelStyle.normal.textColor = new Color(.7f, .75f, .75f, 1); + UnitLabelStyleStageOAB = new GUIStyle(SpaceWarpUISkin.label) + { + alignment = TextAnchor.MiddleRight + }; + UnitLabelStyleStageOAB.normal.textColor = new Color(.7f, .75f, .75f, 1); + NormalLabelStyle = new GUIStyle(SpaceWarpUISkin.label) { fixedWidth = 120 @@ -145,6 +174,13 @@ public static void InitializeStyles() alignment = TextAnchor.MiddleCenter }; + CelestialSelectionBtnStyle = new GUIStyle(SpaceWarpUISkin.button) + { + alignment = TextAnchor.MiddleCenter, + fixedWidth = 80, + fixedHeight = 20 + }; + OneCharacterBtnStyle = new GUIStyle(SpaceWarpUISkin.button) { fixedWidth = 20, @@ -155,6 +191,10 @@ public static void InitializeStyles() { alignment = TextAnchor.MiddleRight }; + TableHeaderCenteredLabelStyle = new GUIStyle(NameLabelStyle) + { + alignment = TextAnchor.MiddleCenter + }; } /// diff --git a/MicroEngineerProject/MicroEngineer/MicroUtility.cs b/MicroEngineerProject/MicroEngineer/MicroUtility.cs index 6c388ae..978f333 100644 --- a/MicroEngineerProject/MicroEngineer/MicroUtility.cs +++ b/MicroEngineerProject/MicroEngineer/MicroUtility.cs @@ -8,6 +8,8 @@ using UnityEngine; using static KSP.Rendering.Planets.PQSData; using BepInEx.Logging; +using KSP.Messages; +using KSP.Sim.DeltaV; namespace MicroMod { @@ -17,6 +19,9 @@ public static class MicroUtility public static ManeuverNodeData CurrentManeuver; public static string LayoutPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MicroLayout.json"); private static ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("MicroEngineer.MicroUtility"); + public static GameStateConfiguration GameState; + public static MessageCenter MessageCenter; + public static VesselDeltaVComponent VesselDeltaVComponentOAB; /// /// Refreshes the ActiveVessel and CurrentManeuver @@ -25,7 +30,17 @@ public static void RefreshActiveVesselAndCurrentManeuver() { ActiveVessel = GameManager.Instance?.Game?.ViewController?.GetActiveVehicle(true)?.GetSimVessel(true); CurrentManeuver = ActiveVessel != null ? GameManager.Instance?.Game?.SpaceSimulation.Maneuvers.GetNodesForVessel(ActiveVessel.GlobalId).FirstOrDefault(): null; + } + public static void RefreshGameManager() + { + GameState = GameManager.Instance?.Game?.GlobalGameState?.GetGameState(); + MessageCenter = GameManager.Instance?.Game?.Messages; + } + + public static void RefreshStagesOAB() + { + VesselDeltaVComponentOAB = GameManager.Instance?.Game?.OAB?.Current?.Stats?.MainAssembly?.VesselDeltaV; } public static string DegreesToDMS(double degreeD) @@ -45,7 +60,7 @@ public static string MetersToDistanceString(double heightInMeters) return $"{heightInMeters:N0}"; } - public static string SecondsToTimeString(double seconds, bool addSpacing = true) + public static string SecondsToTimeString(double seconds, bool addSpacing = true, bool returnLastUnit = false) { if (seconds == Double.PositiveInfinity) { @@ -102,11 +117,11 @@ public static string SecondsToTimeString(double seconds, bool addSpacing = true) if (minutes > 0 || hours > 0 || days > 0) { - result += $"{secs:00.}"; + result += returnLastUnit ? $"{secs:00.}{spacing}s" : $"{secs:00.}"; } else { - result += secs; + result += returnLastUnit ? $"{secs}{spacing}s" : $"{secs}"; } return result; diff --git a/MicroEngineerProject/MicroEngineer/MicroWindow.cs b/MicroEngineerProject/MicroEngineer/MicroWindow.cs index c4fd526..91a479d 100644 --- a/MicroEngineerProject/MicroEngineer/MicroWindow.cs +++ b/MicroEngineerProject/MicroEngineer/MicroWindow.cs @@ -17,7 +17,7 @@ public class MicroWindow public string Description; // not used [JsonProperty] - public bool IsEditorActive; // TODO: implement + public bool IsEditorActive; [JsonProperty] public bool IsFlightActive; [JsonProperty] @@ -46,12 +46,12 @@ public class MicroWindow /// Can the window be edited (add, remove & arrange entries) /// [JsonProperty] - public bool IsEditable { get => MainWindow != MainWindow.MainGui && MainWindow != MainWindow.Settings && MainWindow != MainWindow.Stage; } + public bool IsEditable { get => MainWindow != MainWindow.MainGui && MainWindow != MainWindow.Settings && MainWindow != MainWindow.Stage && MainWindow != MainWindow.StageInfoOAB; } [JsonProperty] public MainWindow MainWindow; - - public Rect? EditorRect; // TODO: implement + [JsonProperty] + public Rect EditorRect; [JsonProperty] public Rect FlightRect; [JsonProperty] @@ -125,7 +125,8 @@ public enum MicroEntryCategory Target, Maneuver, Stage, - Misc + Misc, + OAB } /// @@ -142,6 +143,7 @@ public enum MainWindow Flight, Target, Maneuver, - Settings + Settings, + StageInfoOAB } } diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b89e49 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Micro Engineer - a KSP2 plugin +Displays useful in-flight information about your vessel, your orbital and surface parameters and many other things. +![Micro Engineer](https://i.imgur.com/I7mZ4oQ.png) + +# Links +* Spacedock: https://spacedock.info/mod/3282/Micro%20Engineer +* Release: https://github.com/Micrologist/MicroEngineer/releases (grab the .zip file) +* Forum: https://forum.kerbalspaceprogram.com/index.php?/topic/215989-micro-engineer/ +* Contact: [KSP 2 Modding Society](https://discord.com/channels/1078696971088433153/1080340366995239004) + +# Description +A (heavily) KER inspired little mod to give you some additional information on your missions. + +You can enable and disable individual sections and pop them out into their own window or create your own window containing only the information you want to see. + +# Installation +Extract the contents of the .zip into your KSP2 installation folder. + +Mod folder will be placed in ..\Kerbal Space Program 2\BepInEx\plugins\ + +# Usage + +While in flight or VAB, open the mod window by clicking its entry on the APP BAR. + +# Dependency +[Space Warp + BepInEx](https://spacedock.info/mod/3277/Space%20Warp%20+%20BepInEx) + +# Author & Licensing +* Author: [Micrologist](https://github.com/Micrologist) +* Co-author: [Falki](https://github.com/Falki-git) + +Licence diff --git a/Staging/BepInEx/plugins/micro_engineer/swinfo.json b/Staging/BepInEx/plugins/micro_engineer/swinfo.json index 7d3aa57..93332cc 100644 --- a/Staging/BepInEx/plugins/micro_engineer/swinfo.json +++ b/Staging/BepInEx/plugins/micro_engineer/swinfo.json @@ -2,12 +2,20 @@ "mod_id": "micro_engineer", "author": "Micrologist", "name": "Micro Engineer", - "description": "Get in-flight information about your current vessel", + "description": "Get in-flight and VAB information about your current vessel", "source": "https://github.com/Micrologist/MicroEngineer", - "version": "0.6.0", - "dependencies": ["SpaceWarp"], + "version": "0.7.0", + "dependencies": [ + { + "id": "SpaceWarp", + "version": { + "min": "1.0.1", + "max": "*" + } + } + ], "ksp2_version": { "min": "0.1.0", - "max": "0.1.1" + "max": "*" } }