// // Kerbal Engineer Redux // // Copyright (C) 2014 CYBUTEK // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // namespace KerbalEngineer.Editor { #region Using Directives using System; using System.Linq; using Extensions; using Flight; using Helpers; using Settings; using UIControls; using UnityEngine; using VesselSimulator; #endregion [KSPAddon(KSPAddon.Startup.EditorAny, false)] public class BuildAdvanced : MonoBehaviour { #region Fields private GUIStyle areaSettingStyle; private GUIStyle areaStyle; private float atmosphericPercentage = 1.0f; private float atmosphericMach; private GUIStyle bodiesButtonActiveStyle; private GUIStyle bodiesButtonStyle; private DropDown bodiesList; private Rect bodiesListPosition; private GUIStyle buttonStyle; private int compactCheck; private bool compactCollapseRight; private bool compactMode; private float compactRight; private bool hasChanged; private GUIStyle infoStyle; private bool isEditorLocked; private int numberOfStages; private Rect position = new Rect(265.0f, 45.0f, 0, 0); private GUIStyle settingAtmoStyle; private GUIStyle settingStyle; private bool showAllStages; private bool showAtmosphericDetails; private bool showSettings; private Stage[] stages; private GUIStyle titleStyle; private bool visible = true; private GUIStyle windowStyle; #endregion #region Properties /// /// Gets the current instance if started or returns null. /// public static BuildAdvanced Instance { get; private set; } /// /// Gets and sets whether to show in compact mode. /// public bool CompactMode { get { return this.compactMode; } set { this.compactMode = value; } } /// /// Gets and sets whether to show all stages. /// public bool ShowAllStages { get { return this.showAllStages; } set { this.showAllStages = value; } } /// /// Gets and sets whether to use atmospheric details. /// public bool ShowAtmosphericDetails { get { return this.showAtmosphericDetails; } set { this.showAtmosphericDetails = value; } } /// /// Gets and sets whether to show the settings display. /// public bool ShowSettings { get { return this.showSettings; } set { this.showSettings = value; } } /// /// Gets and sets whether the display is enabled. /// public bool Visible { get { return this.visible; } set { this.visible = value; } } #endregion #region Methods protected void Awake() { try { Instance = this; this.bodiesList = this.gameObject.AddComponent(); this.bodiesList.DrawCallback = this.DrawBodiesList; this.Load(); SimManager.UpdateModSettings(); SimManager.OnReady -= this.GetStageInfo; SimManager.OnReady += this.GetStageInfo; } catch (Exception ex) { Logger.Exception(ex); } } /// /// Saves the settings when this object is destroyed. /// protected void OnDestroy() { try { var handler = new SettingHandler(); handler.Set("visible", this.visible); handler.Set("windowPositionX", this.position.x); handler.Set("windowPositionY", this.position.y); handler.Set("compactMode", this.compactMode); handler.Set("compactCollapseRight", this.compactCollapseRight); handler.Set("showAllStages", this.showAllStages); handler.Set("showAtmosphericDetails", this.showAtmosphericDetails); handler.Set("showSettings", this.showSettings); handler.Set("selectedBodyName", CelestialBodies.SelectedBody.Name); handler.Save("BuildAdvanced.xml"); GuiDisplaySize.OnSizeChanged -= this.OnSizeChanged; } catch (Exception ex) { Logger.Exception(ex); } } protected void OnGUI() { try { if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0 || EditorLogic.fetch.editorScreen != EditorScreen.Parts) { return; } if (this.stages == null) { return; } // Change the window title based on whether in compact mode or not. var title = !this.compactMode ? "KERBAL ENGINEER REDUX " + EngineerGlobals.AssemblyVersion : "K.E.R. " + EngineerGlobals.AssemblyVersion + (this.showAtmosphericDetails ? " (ATMOS.)" : String.Empty); // Reset the window size when the staging or something else has changed. var stageCount = this.stages.Count(stage => this.showAllStages || stage.deltaV > 0); if (this.hasChanged || stageCount != this.numberOfStages) { this.hasChanged = false; this.numberOfStages = stageCount; this.position.width = 0; this.position.height = 0; } GUI.skin = null; this.position = GUILayout.Window(this.GetInstanceID(), this.position, this.Window, title, this.windowStyle).ClampToScreen(); if (this.compactCheck > 0 && this.compactCollapseRight) { this.position.x = this.compactRight - this.position.width; this.compactCheck--; } else if (this.compactCheck > 0) { this.compactCheck = 0; } // Check editor lock to manage click-through. this.CheckEditorLock(); } catch (Exception ex) { Logger.Exception(ex); } } protected void Start() { try { this.InitialiseStyles(); GuiDisplaySize.OnSizeChanged += this.OnSizeChanged; } catch (Exception ex) { Logger.Exception(ex); } } protected void Update() { try { if (Input.GetKeyDown(KeyBinder.EditorShowHide)) { this.visible = !this.visible; if (!this.visible) { this.EditorLock(false); } } if (!this.visible || EditorLogic.fetch == null || EditorLogic.fetch.ship.parts.Count == 0) { this.bodiesList.enabled = false; return; } // Configure the simulation parameters based on the selected reference body. SimManager.Gravity = CelestialBodies.SelectedBody.Gravity; if (this.showAtmosphericDetails) { SimManager.Atmosphere = CelestialBodies.SelectedBody.Atmosphere * 0.01d * this.atmosphericPercentage; } else { SimManager.Atmosphere = 0; } SimManager.Mach = this.atmosphericMach; SimManager.RequestSimulation(); SimManager.TryStartSimulation(); } catch (Exception ex) { Logger.Exception(ex, "BuildAdvanced->Update"); } } /// /// Checks whether the editor should be locked to stop click-through. /// private void CheckEditorLock() { if ((this.position.MouseIsOver() || this.bodiesList.Position.MouseIsOver()) && !this.isEditorLocked) { this.EditorLock(true); } else if (!this.position.MouseIsOver() && !this.bodiesList.Position.MouseIsOver() && this.isEditorLocked) { this.EditorLock(false); } } /// /// Draws the atmospheric settings. /// private void DrawAtmosphericDetails() { GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); GUILayout.Label("Pressure: " + (this.atmosphericPercentage * 100.0f).ToString("F1") + "%", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset)); GUI.skin = HighLogic.Skin; this.atmosphericPercentage = GUILayout.HorizontalSlider(this.atmosphericPercentage, 0, 1.0f); GUI.skin = null; GUILayout.EndVertical(); GUILayout.Space(5.0f); GUILayout.BeginVertical(); GUILayout.Label("Mach: " + this.atmosphericMach.ToString("F1") + "m/s", this.settingAtmoStyle, GUILayout.Width(125.0f * GuiDisplaySize.Offset)); GUI.skin = HighLogic.Skin; this.atmosphericMach = GUILayout.HorizontalSlider(this.atmosphericMach, 0, 25f); // the game limits mach to 50 but I did not see curve with more than 25 GUI.skin = null; GUILayout.EndVertical(); GUILayout.EndHorizontal(); } private void DrawBodiesList() { if (CelestialBodies.SystemBody == CelestialBodies.SelectedBody) { this.DrawBody(CelestialBodies.SystemBody); } else { foreach (var body in CelestialBodies.SystemBody.Children) { this.DrawBody(body); } } } private void DrawBody(CelestialBodies.BodyInfo bodyInfo, int depth = 0) { GUILayout.BeginHorizontal(); GUILayout.Space(20.0f * depth); if (GUILayout.Button(bodyInfo.Children.Count > 0 ? bodyInfo.Name + " [" + bodyInfo.Children.Count + "]" : bodyInfo.Name, bodyInfo.Selected && bodyInfo.SelectedDepth == 0 ? this.bodiesButtonActiveStyle : this.bodiesButtonStyle)) { CelestialBodies.SetSelectedBody(bodyInfo.Name); this.bodiesList.Resize = true; } GUILayout.EndHorizontal(); if (bodyInfo.Selected) { foreach (var body in bodyInfo.Children) { this.DrawBody(body, depth + 1); } } } /// /// Draws the burn time column. /// private void DrawBurnTime() { GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.Label("BURN", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(TimeFormatter.ConvertToString(stage.time), this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the cost column. /// private void DrawCost() { GUILayout.BeginVertical(GUILayout.Width(110.0f * GuiDisplaySize.Offset)); GUILayout.Label("COST", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(Units.Cost(stage.cost, stage.totalCost), this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the deltaV column. /// private void DrawDeltaV() { GUILayout.BeginVertical(GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.Label("DELTA-V", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.deltaV.ToString("N0") + " / " + stage.inverseTotalDeltaV.ToString("N0") + "m/s", this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the specific impluse column. /// private void DrawIsp() { GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.Label("ISP", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.isp.ToString("F1") + "s", this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the mass column. /// private void DrawMass() { GUILayout.BeginVertical(GUILayout.Width(110.0f * GuiDisplaySize.Offset)); GUILayout.Label("MASS", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(Units.ToMass(stage.mass, stage.totalMass), this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the part count column. /// private void DrawPartCount() { GUILayout.BeginVertical(GUILayout.Width(50.0f * GuiDisplaySize.Offset)); GUILayout.Label("PARTS", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.partCount + " / " + stage.totalPartCount, this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the settings panel. /// private void DrawSettings() { GUILayout.BeginHorizontal(); GUILayout.Label("Compact mode collapses to the:", this.settingStyle); this.compactCollapseRight = !GUILayout.Toggle(!this.compactCollapseRight, "LEFT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); this.compactCollapseRight = GUILayout.Toggle(this.compactCollapseRight, "RIGHT", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Simulate using vectored thrust values:"); SimManager.vectoredThrust = GUILayout.Toggle(SimManager.vectoredThrust, "ENABLED", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Build Engineer Overlay:", this.settingStyle); BuildOverlay.Visible = GUILayout.Toggle(BuildOverlay.Visible, "VISIBLE", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); BuildOverlayPartInfo.NamesOnly = GUILayout.Toggle(BuildOverlayPartInfo.NamesOnly, "NAMES ONLY", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); BuildOverlayPartInfo.ClickToOpen = GUILayout.Toggle(BuildOverlayPartInfo.ClickToOpen, "CLICK TO OPEN", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Flight Engineer activation mode:", this.settingStyle); FlightEngineerCore.IsCareerMode = GUILayout.Toggle(FlightEngineerCore.IsCareerMode, "CAREER", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsCareerMode = !GUILayout.Toggle(!FlightEngineerCore.IsCareerMode, "PARTLESS", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("Flight Engineer Career Limitations:", this.settingStyle); FlightEngineerCore.IsKerbalLimited = GUILayout.Toggle(FlightEngineerCore.IsKerbalLimited, "KERBAL", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); FlightEngineerCore.IsTrackingStationLimited = GUILayout.Toggle(FlightEngineerCore.IsTrackingStationLimited, "TRACKING", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.Label("GUI Size: " + GuiDisplaySize.Increment, this.settingStyle); if (GUILayout.Button("<", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset))) { GuiDisplaySize.Increment--; } if (GUILayout.Button(">", this.buttonStyle, GUILayout.Width(100.0f * GuiDisplaySize.Offset))) { GuiDisplaySize.Increment++; } GUILayout.EndHorizontal(); GUILayout.Label("Minimum delay between simulations: " + SimManager.minSimTime.Milliseconds + "ms", this.settingStyle); GUI.skin = HighLogic.Skin; SimManager.minSimTime = new TimeSpan(0, 0, 0, 0, (int)GUILayout.HorizontalSlider(SimManager.minSimTime.Milliseconds, 0, 2000.0f)); GUI.skin = null; } /// /// Draws the stage number column. /// private void DrawStageNumbers() { GUILayout.BeginVertical(GUILayout.Width(30.0f * GuiDisplaySize.Offset)); GUILayout.Label(string.Empty, this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label("S" + stage.number, this.titleStyle); } } GUILayout.EndVertical(); } /// /// Draws the thrust column. /// private void DrawThrust() { GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.Label("THRUST", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.thrust.ToForce(), this.infoStyle); } } GUILayout.EndVertical(); } /// /// Draws the torque column. /// private void DrawTorque() { GUILayout.BeginVertical(GUILayout.Width(75.0f * GuiDisplaySize.Offset)); GUILayout.Label("TORQUE", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.maxThrustTorque.ToTorque(), this.infoStyle); } } GUILayout.EndVertical(); } /// /// Drwas the thrust to weight ratio column. /// private void DrawTwr() { GUILayout.BeginVertical(GUILayout.Width(100.0f * GuiDisplaySize.Offset)); GUILayout.Label("TWR (MAX)", this.titleStyle); foreach (var stage in this.stages) { if (this.showAllStages || stage.deltaV > 0) { GUILayout.Label(stage.thrustToWeight.ToString("F2") + " (" + stage.maxThrustToWeight.ToString("F2") + ")", this.infoStyle); } } GUILayout.EndVertical(); } private void EditorLock(bool state) { if (state) { EditorLogic.fetch.Lock(true, true, true, "KER_BuildAdvanced"); BuildOverlayPartInfo.Hidden = true; this.isEditorLocked = true; } else { EditorLogic.fetch.Unlock("KER_BuildAdvanced"); BuildOverlayPartInfo.Hidden = false; this.isEditorLocked = false; } } private void GetStageInfo() { this.stages = SimManager.Stages; } /// /// Initialises all the styles that are required. /// private void InitialiseStyles() { this.windowStyle = new GUIStyle(HighLogic.Skin.window) { alignment = TextAnchor.UpperLeft }; this.areaStyle = new GUIStyle(HighLogic.Skin.box) { padding = new RectOffset(0, 0, 9, 0) }; this.areaSettingStyle = new GUIStyle(HighLogic.Skin.box) { padding = new RectOffset(10, 10, 10, 10) }; this.buttonStyle = new GUIStyle(HighLogic.Skin.button) { normal = { textColor = Color.white }, fontSize = (int)(11 * GuiDisplaySize.Offset), fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter }; this.titleStyle = new GUIStyle(HighLogic.Skin.label) { normal = { textColor = Color.white }, fontSize = (int)(11 * GuiDisplaySize.Offset), fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter, stretchWidth = true, }; this.infoStyle = new GUIStyle(HighLogic.Skin.label) { fontSize = (int)(11 * GuiDisplaySize.Offset), fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter, stretchWidth = true }; this.settingStyle = new GUIStyle(this.titleStyle) { alignment = TextAnchor.MiddleLeft, stretchWidth = true, stretchHeight = true }; this.settingAtmoStyle = new GUIStyle(this.titleStyle) { margin = new RectOffset(), padding = new RectOffset(), alignment = TextAnchor.UpperLeft }; this.bodiesButtonStyle = new GUIStyle(HighLogic.Skin.button) { margin = new RectOffset(0, 0, 2, 0), padding = new RectOffset(5, 5, 5, 5), normal = { textColor = Color.white }, active = { textColor = Color.white }, fontSize = (int)(11 * GuiDisplaySize.Offset), fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter, fixedHeight = 20.0f }; this.bodiesButtonActiveStyle = new GUIStyle(this.bodiesButtonStyle) { normal = this.bodiesButtonStyle.onNormal, hover = this.bodiesButtonStyle.onHover }; } /// /// Loads the settings when this object is created. /// private void Load() { try { var handler = SettingHandler.Load("BuildAdvanced.xml"); handler.Get("visible", ref this.visible); this.position.x = handler.Get("windowPositionX", this.position.x); this.position.y = handler.Get("windowPositionY", this.position.y); handler.Get("compactMode", ref this.compactMode); handler.Get("compactCollapseRight", ref this.compactCollapseRight); handler.Get("showAllStages", ref this.showAllStages); handler.Get("showAtmosphericDetails", ref this.showAtmosphericDetails); handler.Get("showSettings", ref this.showSettings); CelestialBodies.SetSelectedBody(handler.Get("selectedBodyName", CelestialBodies.SelectedBody.Name)); } catch (Exception ex) { Logger.Exception(ex, "BuildAdvanced->Load"); } } private void OnSizeChanged() { this.InitialiseStyles(); this.hasChanged = true; } /// /// Draws the OnGUI window. /// private void Window(int windowId) { try { // Draw the compact mode toggle. if (GUI.Toggle(new Rect(this.position.width - 70.0f * GuiDisplaySize.Offset, 5.0f, 65.0f * GuiDisplaySize.Offset, 20.0f), this.compactMode, "COMPACT", this.buttonStyle) != this.compactMode) { this.hasChanged = true; this.compactCheck = 2; this.compactRight = this.position.xMax; this.compactMode = !this.compactMode; } // When not in compact mode draw the 'All Stages' and 'Atmospheric' toggles. if (!this.compactMode) { if (GUI.Toggle(new Rect(this.position.width - 143.0f * GuiDisplaySize.Offset, 5.0f, 70.0f * GuiDisplaySize.Offset, 20.0f), this.showSettings, "SETTINGS", this.buttonStyle) != this.showSettings) { this.hasChanged = true; this.showSettings = !this.showSettings; } if (GUI.Toggle(new Rect(this.position.width - 226.0f * GuiDisplaySize.Offset, 5.0f, 80.0f * GuiDisplaySize.Offset, 20.0f), this.showAllStages, "ALL STAGES", this.buttonStyle) != this.showAllStages) { this.hasChanged = true; this.showAllStages = !this.showAllStages; } if (GUI.Toggle(new Rect(this.position.width - 324.0f * GuiDisplaySize.Offset, 5.0f, 95.0f * GuiDisplaySize.Offset, 20.0f), this.showAtmosphericDetails, "ATMOSPHERIC", this.buttonStyle) != this.showAtmosphericDetails) { this.hasChanged = true; this.showAtmosphericDetails = !this.showAtmosphericDetails; } this.bodiesListPosition = new Rect(this.position.width - 452.0f * GuiDisplaySize.Offset, 5.0f, 125.0f * GuiDisplaySize.Offset, 20.0f); this.bodiesList.enabled = GUI.Toggle(this.bodiesListPosition, this.bodiesList.enabled, "BODY: " + CelestialBodies.SelectedBody.Name.ToUpper(), this.buttonStyle); this.bodiesList.SetPosition(this.bodiesListPosition.Translate(this.position)); } // Draw the main informational display box. if (!this.compactMode) { GUILayout.BeginHorizontal(this.areaStyle); this.DrawStageNumbers(); this.DrawPartCount(); this.DrawCost(); this.DrawMass(); this.DrawIsp(); this.DrawThrust(); this.DrawTorque(); this.DrawTwr(); this.DrawDeltaV(); this.DrawBurnTime(); GUILayout.EndHorizontal(); if (this.showAtmosphericDetails) { GUILayout.BeginVertical(this.areaSettingStyle); this.DrawAtmosphericDetails(); GUILayout.EndVertical(); } if (this.showSettings) { GUILayout.BeginVertical(this.areaSettingStyle); this.DrawSettings(); GUILayout.EndVertical(); } } else // Draw only a few details when in compact mode. { GUILayout.BeginHorizontal(this.areaStyle); this.DrawStageNumbers(); this.DrawTwr(); this.DrawDeltaV(); GUILayout.EndHorizontal(); } GUI.DragWindow(); } catch (Exception ex) { Logger.Exception(ex); } } #endregion } }