diff --git a/MechJeb2/MechJebModuleNodeEditor.cs b/MechJeb2/MechJebModuleNodeEditor.cs index fd7d864cc..44d9e0469 100644 --- a/MechJeb2/MechJebModuleNodeEditor.cs +++ b/MechJeb2/MechJebModuleNodeEditor.cs @@ -1,307 +1,307 @@ -using System; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleNodeEditor : DisplayModule - { - EditableDouble prograde = 0; - EditableDouble radialPlus = 0; - EditableDouble normalPlus = 0; - [Persistent(pass = (int)Pass.Global)] - EditableDouble progradeDelta = 0; - [Persistent(pass = (int)Pass.Global)] - EditableDouble radialPlusDelta = 0; - [Persistent(pass = (int)Pass.Global)] - EditableDouble normalPlusDelta = 0; - [Persistent(pass = (int)Pass.Global)] - EditableTime timeOffset = 0; - - ManeuverNode node; - ManeuverGizmo gizmo; - - enum Snap { PERIAPSIS, APOAPSIS, REL_ASCENDING, REL_DESCENDING, EQ_ASCENDING, EQ_DESCENDING }; - static int numSnaps = Enum.GetNames(typeof(Snap)).Length; - Snap snap = Snap.PERIAPSIS; - string[] snapStrings = new string[] { Localizer.Format("#MechJeb_NodeEd_Snap1"), Localizer.Format("#MechJeb_NodeEd_Snap2"), Localizer.Format("#MechJeb_NodeEd_Snap3"), Localizer.Format("#MechJeb_NodeEd_Snap4"), Localizer.Format("#MechJeb_NodeEd_Snap5"), Localizer.Format("#MechJeb_NodeEd_Snap6") };//"periapsis""apoapsis""AN with target""DN with target""equatorial AN""equatorial DN" - - void GizmoUpdateHandler(Vector3d dV, double UT) - { - prograde = dV.z; - radialPlus = dV.x; - normalPlus = dV.y; - } - - void MergeNext(int index) - { - ManeuverNode cur = vessel.patchedConicSolver.maneuverNodes[index]; - ManeuverNode next = vessel.patchedConicSolver.maneuverNodes[index+1]; - - double newUT = (cur.UT + next.UT) / 2; - cur.UpdateNode(cur.patch.DeltaVToManeuverNodeCoordinates(newUT, cur.WorldDeltaV() + next.WorldDeltaV()), newUT); - next.RemoveSelf(); - } - - protected override void WindowGUI(int windowID) - { - if (vessel.patchedConicSolver.maneuverNodes.Count == 0) - { - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label1"));//"No maneuver nodes to edit." - RelativityModeSelectUI(); - base.WindowGUI(windowID); - return; - } - - GUILayout.BeginVertical(); - - ManeuverNode oldNode = node; - - if (vessel.patchedConicSolver.maneuverNodes.Count == 1) - { - node = vessel.patchedConicSolver.maneuverNodes[0]; - } - else - { - if (!vessel.patchedConicSolver.maneuverNodes.Contains(node)) node = vessel.patchedConicSolver.maneuverNodes[0]; - - int nodeIndex = vessel.patchedConicSolver.maneuverNodes.IndexOf(node); - int numNodes = vessel.patchedConicSolver.maneuverNodes.Count; - - nodeIndex = GuiUtils.ArrowSelector(nodeIndex, numNodes, "Maneuver node #" + (nodeIndex + 1)); - - node = vessel.patchedConicSolver.maneuverNodes[nodeIndex]; - if (nodeIndex < (numNodes-1) && GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button1"))) MergeNext(nodeIndex);//"Merge next node" - } - - if (node != oldNode) - { - prograde = node.DeltaV.z; - radialPlus = node.DeltaV.x; - normalPlus = node.DeltaV.y; - } - - if (gizmo != node.attachedGizmo) - { - if (gizmo != null) gizmo.OnGizmoUpdated -= GizmoUpdateHandler; - gizmo = node.attachedGizmo; - if (gizmo != null) gizmo.OnGizmoUpdated += GizmoUpdateHandler; - } - - - GUILayout.BeginHorizontal(); - GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label2"), prograde, "m/s", 60);//"Prograde:" - if (LimitedRepeatButtoon("-")) - { - prograde -= progradeDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - progradeDelta.text = GUILayout.TextField(progradeDelta.text, GUILayout.Width(50)); - if (LimitedRepeatButtoon("+")) - { - prograde += progradeDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label3"), radialPlus, "m/s", 60);//"Radial+:" - if (LimitedRepeatButtoon("-")) - { - radialPlus -= radialPlusDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - radialPlusDelta.text = GUILayout.TextField(radialPlusDelta.text, GUILayout.Width(50)); - if (LimitedRepeatButtoon("+")) - { - radialPlus += radialPlusDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label4"), normalPlus, "m/s", 60);//"Normal+:" - if (LimitedRepeatButtoon("-")) - { - normalPlus -= normalPlusDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - normalPlusDelta.text = GUILayout.TextField(normalPlusDelta.text, GUILayout.Width(50)); - if (LimitedRepeatButtoon("+")) - { - normalPlus += normalPlusDelta; - node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); - } - GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label5"), GUILayout.ExpandWidth(true));//"Set delta to:" - if (GUILayout.Button("0.01", GUILayout.ExpandWidth(true))) - progradeDelta = radialPlusDelta = normalPlusDelta = 0.01; - if (GUILayout.Button("0.1", GUILayout.ExpandWidth(true))) - progradeDelta = radialPlusDelta = normalPlusDelta = 0.1; - if (GUILayout.Button("1", GUILayout.ExpandWidth(true))) - progradeDelta = radialPlusDelta = normalPlusDelta = 1; - if (GUILayout.Button("10", GUILayout.ExpandWidth(true))) - progradeDelta = radialPlusDelta = normalPlusDelta = 10; - if (GUILayout.Button("100", GUILayout.ExpandWidth(true))) - progradeDelta = radialPlusDelta = normalPlusDelta = 100; - GUILayout.EndHorizontal(); - - if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button2"))) node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT);//"Update" - - GUILayout.BeginHorizontal(); - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label6"), GUILayout.ExpandWidth(true));//"Shift time" - if (GUILayout.Button("-o", GUILayout.ExpandWidth(false))) - { - node.UpdateNode(node.DeltaV, node.UT - node.patch.period); - } - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) - { - node.UpdateNode(node.DeltaV, node.UT - timeOffset); - } - timeOffset.text = GUILayout.TextField(timeOffset.text, GUILayout.Width(100)); - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) - { - node.UpdateNode(node.DeltaV, node.UT + timeOffset); - } - if (GUILayout.Button("+o", GUILayout.ExpandWidth(false))) - { - node.UpdateNode(node.DeltaV, node.UT + node.patch.period); - } - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button3"), GUILayout.ExpandWidth(true)))//"Snap node to" - { - Orbit o = node.patch; - double UT = node.UT; - switch (snap) - { - case Snap.PERIAPSIS: - UT = o.NextPeriapsisTime(o.eccentricity < 1 ? UT - o.period / 2 : UT); - break; - - case Snap.APOAPSIS: - if (o.eccentricity < 1) UT = o.NextApoapsisTime(UT - o.period / 2); - break; - - case Snap.EQ_ASCENDING: - if (o.AscendingNodeEquatorialExists()) UT = o.TimeOfAscendingNodeEquatorial(UT - o.period / 2); - break; - - case Snap.EQ_DESCENDING: - if (o.DescendingNodeEquatorialExists()) UT = o.TimeOfDescendingNodeEquatorial(UT - o.period / 2); - break; - - case Snap.REL_ASCENDING: - if (core.target.NormalTargetExists && core.target.TargetOrbit.referenceBody == o.referenceBody) - { - if (o.AscendingNodeExists(core.target.TargetOrbit)) UT = o.TimeOfAscendingNode(core.target.TargetOrbit, UT - o.period / 2); - } - break; - - case Snap.REL_DESCENDING: - if (core.target.NormalTargetExists && core.target.TargetOrbit.referenceBody == o.referenceBody) - { - if (o.DescendingNodeExists(core.target.TargetOrbit)) UT = o.TimeOfDescendingNode(core.target.TargetOrbit, UT - o.period / 2); - } - break; - } - node.UpdateNode(node.DeltaV, UT); - } - - snap = (Snap)GuiUtils.ArrowSelector((int)snap, numSnaps, snapStrings[(int)snap]); - - GUILayout.EndHorizontal(); - - RelativityModeSelectUI(); - - - if (core.node != null) - { - if (vessel.patchedConicSolver.maneuverNodes.Count > 0 && !core.node.enabled) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button4")))//"Execute next node" - { - core.node.ExecuteOneNode(this); - } - - if (vessel.patchedConicSolver.maneuverNodes.Count > 1) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button5")))//"Execute all nodes" - { - core.node.ExecuteAllNodes(this); - } - } - } - else if (core.node.enabled) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button6")))//"Abort node execution" - { - core.node.Abort(); - } - } - - GUILayout.BeginHorizontal(); - core.node.autowarp = GUILayout.Toggle(core.node.autowarp, Localizer.Format("#MechJeb_NodeEd_checkbox1"), GUILayout.ExpandWidth(true));//"Auto-warp" - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label7"), GUILayout.ExpandWidth(false));//"Tolerance:" - core.node.tolerance.text = GUILayout.TextField(core.node.tolerance.text, GUILayout.Width(35), GUILayout.ExpandWidth(false)); - GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - GUILayout.EndVertical(); - - base.WindowGUI(windowID); - } - - private static float nextClick = 0; - - private static bool LimitedRepeatButtoon(string text) - { - if (GUILayout.RepeatButton(text, GUILayout.ExpandWidth(false)) && nextClick < Time.time) - { - nextClick = Time.time + 0.2f; - return true; - } - return false; - } - - static readonly string[] relativityModeStrings = { "0", "1", "2", "3", "4" }; - private void RelativityModeSelectUI() - { - GUILayout.BeginVertical(); - - GUILayout.BeginHorizontal(); - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label8"), GUILayout.ExpandWidth(false));//"Conics mode:" - int newRelativityMode = GUILayout.SelectionGrid((int)vessel.patchedConicRenderer.relativityMode, relativityModeStrings, 5); - vessel.patchedConicRenderer.relativityMode = (PatchRendering.RelativityMode)newRelativityMode; - GUILayout.EndHorizontal(); - - GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label9", vessel.patchedConicRenderer.relativityMode.ToString()));//"Current mode: <<1>>" - - GUILayout.EndVertical(); - } - - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[] { GUILayout.Width(300), GUILayout.Height(150) }; - } - - public override string GetName() - { - return Localizer.Format("#MechJeb_NodeEd_title");//"Maneuver Node Editor" - } - - public override bool IsSpaceCenterUpgradeUnlocked() - { - return vessel.patchedConicsUnlocked(); - } - - public MechJebModuleNodeEditor(MechJebCore core) : base(core) { } - } -} +using System; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleNodeEditor : DisplayModule + { + EditableDouble prograde = 0; + EditableDouble radialPlus = 0; + EditableDouble normalPlus = 0; + [Persistent(pass = (int)Pass.Global)] + EditableDouble progradeDelta = 0; + [Persistent(pass = (int)Pass.Global)] + EditableDouble radialPlusDelta = 0; + [Persistent(pass = (int)Pass.Global)] + EditableDouble normalPlusDelta = 0; + [Persistent(pass = (int)Pass.Global)] + EditableTime timeOffset = 0; + + ManeuverNode node; + ManeuverGizmo gizmo; + + enum Snap { PERIAPSIS, APOAPSIS, REL_ASCENDING, REL_DESCENDING, EQ_ASCENDING, EQ_DESCENDING }; + static int numSnaps = Enum.GetNames(typeof(Snap)).Length; + Snap snap = Snap.PERIAPSIS; + string[] snapStrings = new string[] { Localizer.Format("#MechJeb_NodeEd_Snap1"), Localizer.Format("#MechJeb_NodeEd_Snap2"), Localizer.Format("#MechJeb_NodeEd_Snap3"), Localizer.Format("#MechJeb_NodeEd_Snap4"), Localizer.Format("#MechJeb_NodeEd_Snap5"), Localizer.Format("#MechJeb_NodeEd_Snap6") };//"periapsis""apoapsis""AN with target""DN with target""equatorial AN""equatorial DN" + + void GizmoUpdateHandler(Vector3d dV, double UT) + { + prograde = dV.z; + radialPlus = dV.x; + normalPlus = dV.y; + } + + void MergeNext(int index) + { + ManeuverNode cur = vessel.patchedConicSolver.maneuverNodes[index]; + ManeuverNode next = vessel.patchedConicSolver.maneuverNodes[index+1]; + + double newUT = (cur.UT + next.UT) / 2; + cur.UpdateNode(cur.patch.DeltaVToManeuverNodeCoordinates(newUT, cur.WorldDeltaV() + next.WorldDeltaV()), newUT); + next.RemoveSelf(); + } + + protected override void WindowGUI(int windowID) + { + if (vessel.patchedConicSolver.maneuverNodes.Count == 0) + { + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label1"));//"No maneuver nodes to edit." + RelativityModeSelectUI(); + base.WindowGUI(windowID); + return; + } + + GUILayout.BeginVertical(); + + ManeuverNode oldNode = node; + + if (vessel.patchedConicSolver.maneuverNodes.Count == 1) + { + node = vessel.patchedConicSolver.maneuverNodes[0]; + } + else + { + if (!vessel.patchedConicSolver.maneuverNodes.Contains(node)) node = vessel.patchedConicSolver.maneuverNodes[0]; + + int nodeIndex = vessel.patchedConicSolver.maneuverNodes.IndexOf(node); + int numNodes = vessel.patchedConicSolver.maneuverNodes.Count; + + nodeIndex = GuiUtils.ArrowSelector(nodeIndex, numNodes, "Maneuver node #" + (nodeIndex + 1)); + + node = vessel.patchedConicSolver.maneuverNodes[nodeIndex]; + if (nodeIndex < (numNodes-1) && GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button1"))) MergeNext(nodeIndex);//"Merge next node" + } + + if (node != oldNode) + { + prograde = node.DeltaV.z; + radialPlus = node.DeltaV.x; + normalPlus = node.DeltaV.y; + } + + if (gizmo != node.attachedGizmo) + { + if (gizmo != null) gizmo.OnGizmoUpdated -= GizmoUpdateHandler; + gizmo = node.attachedGizmo; + if (gizmo != null) gizmo.OnGizmoUpdated += GizmoUpdateHandler; + } + + + GUILayout.BeginHorizontal(); + GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label2"), prograde, "m/s", 60);//"Prograde:" + if (LimitedRepeatButtoon("-")) + { + prograde -= progradeDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + progradeDelta.text = GUILayout.TextField(progradeDelta.text, GUILayout.Width(50)); + if (LimitedRepeatButtoon("+")) + { + prograde += progradeDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label3"), radialPlus, "m/s", 60);//"Radial+:" + if (LimitedRepeatButtoon("-")) + { + radialPlus -= radialPlusDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + radialPlusDelta.text = GUILayout.TextField(radialPlusDelta.text, GUILayout.Width(50)); + if (LimitedRepeatButtoon("+")) + { + radialPlus += radialPlusDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_NodeEd_Label4"), normalPlus, "m/s", 60);//"Normal+:" + if (LimitedRepeatButtoon("-")) + { + normalPlus -= normalPlusDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + normalPlusDelta.text = GUILayout.TextField(normalPlusDelta.text, GUILayout.Width(50)); + if (LimitedRepeatButtoon("+")) + { + normalPlus += normalPlusDelta; + node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT); + } + GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label5"), GUILayout.ExpandWidth(true));//"Set delta to:" + if (GUILayout.Button("0.01", GUILayout.ExpandWidth(true))) + progradeDelta = radialPlusDelta = normalPlusDelta = 0.01; + if (GUILayout.Button("0.1", GUILayout.ExpandWidth(true))) + progradeDelta = radialPlusDelta = normalPlusDelta = 0.1; + if (GUILayout.Button("1", GUILayout.ExpandWidth(true))) + progradeDelta = radialPlusDelta = normalPlusDelta = 1; + if (GUILayout.Button("10", GUILayout.ExpandWidth(true))) + progradeDelta = radialPlusDelta = normalPlusDelta = 10; + if (GUILayout.Button("100", GUILayout.ExpandWidth(true))) + progradeDelta = radialPlusDelta = normalPlusDelta = 100; + GUILayout.EndHorizontal(); + + if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button2"))) node.UpdateNode(new Vector3d(radialPlus, normalPlus, prograde), node.UT);//"Update" + + GUILayout.BeginHorizontal(); + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label6"), GUILayout.ExpandWidth(true));//"Shift time" + if (GUILayout.Button("-o", GUILayout.ExpandWidth(false))) + { + node.UpdateNode(node.DeltaV, node.UT - node.patch.period); + } + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) + { + node.UpdateNode(node.DeltaV, node.UT - timeOffset); + } + timeOffset.text = GUILayout.TextField(timeOffset.text, GUILayout.Width(100)); + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) + { + node.UpdateNode(node.DeltaV, node.UT + timeOffset); + } + if (GUILayout.Button("+o", GUILayout.ExpandWidth(false))) + { + node.UpdateNode(node.DeltaV, node.UT + node.patch.period); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button3"), GUILayout.ExpandWidth(true)))//"Snap node to" + { + Orbit o = node.patch; + double UT = node.UT; + switch (snap) + { + case Snap.PERIAPSIS: + UT = o.NextPeriapsisTime(o.eccentricity < 1 ? UT - o.period / 2 : UT); + break; + + case Snap.APOAPSIS: + if (o.eccentricity < 1) UT = o.NextApoapsisTime(UT - o.period / 2); + break; + + case Snap.EQ_ASCENDING: + if (o.AscendingNodeEquatorialExists()) UT = o.TimeOfAscendingNodeEquatorial(UT - o.period / 2); + break; + + case Snap.EQ_DESCENDING: + if (o.DescendingNodeEquatorialExists()) UT = o.TimeOfDescendingNodeEquatorial(UT - o.period / 2); + break; + + case Snap.REL_ASCENDING: + if (core.target.NormalTargetExists && core.target.TargetOrbit.referenceBody == o.referenceBody) + { + if (o.AscendingNodeExists(core.target.TargetOrbit)) UT = o.TimeOfAscendingNode(core.target.TargetOrbit, UT - o.period / 2); + } + break; + + case Snap.REL_DESCENDING: + if (core.target.NormalTargetExists && core.target.TargetOrbit.referenceBody == o.referenceBody) + { + if (o.DescendingNodeExists(core.target.TargetOrbit)) UT = o.TimeOfDescendingNode(core.target.TargetOrbit, UT - o.period / 2); + } + break; + } + node.UpdateNode(node.DeltaV, UT); + } + + snap = (Snap)GuiUtils.ArrowSelector((int)snap, numSnaps, snapStrings[(int)snap]); + + GUILayout.EndHorizontal(); + + RelativityModeSelectUI(); + + + if (core.node != null) + { + if (vessel.patchedConicSolver.maneuverNodes.Count > 0 && !core.node.enabled) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button4")))//"Execute next node" + { + core.node.ExecuteOneNode(this); + } + + if (vessel.patchedConicSolver.maneuverNodes.Count > 1) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button5")))//"Execute all nodes" + { + core.node.ExecuteAllNodes(this); + } + } + } + else if (core.node.enabled) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_NodeEd_button6")))//"Abort node execution" + { + core.node.Abort(); + } + } + + GUILayout.BeginHorizontal(); + core.node.autowarp = GUILayout.Toggle(core.node.autowarp, Localizer.Format("#MechJeb_NodeEd_checkbox1"), GUILayout.ExpandWidth(true));//"Auto-warp" + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label7"), GUILayout.ExpandWidth(false));//"Tolerance:" + core.node.tolerance.text = GUILayout.TextField(core.node.tolerance.text, GUILayout.Width(35), GUILayout.ExpandWidth(false)); + GUILayout.Label("m/s", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + GUILayout.EndVertical(); + + base.WindowGUI(windowID); + } + + private static float nextClick = 0; + + private static bool LimitedRepeatButtoon(string text) + { + if (GUILayout.RepeatButton(text, GUILayout.ExpandWidth(false)) && nextClick < Time.time) + { + nextClick = Time.time + 0.2f; + return true; + } + return false; + } + + static readonly string[] relativityModeStrings = { "0", "1", "2", "3", "4" }; + private void RelativityModeSelectUI() + { + GUILayout.BeginVertical(); + + GUILayout.BeginHorizontal(); + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label8"), GUILayout.ExpandWidth(false));//"Conics mode:" + int newRelativityMode = GUILayout.SelectionGrid((int)vessel.patchedConicRenderer.relativityMode, relativityModeStrings, 5); + vessel.patchedConicRenderer.relativityMode = (PatchRendering.RelativityMode)newRelativityMode; + GUILayout.EndHorizontal(); + + GUILayout.Label(Localizer.Format("#MechJeb_NodeEd_Label9", vessel.patchedConicRenderer.relativityMode.ToString()));//"Current mode: <<1>>" + + GUILayout.EndVertical(); + } + + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[] { GUILayout.Width(300), GUILayout.Height(150) }; + } + + public override string GetName() + { + return Localizer.Format("#MechJeb_NodeEd_title");//"Maneuver Node Editor" + } + + public override bool IsSpaceCenterUpgradeUnlocked() + { + return vessel.patchedConicsUnlocked(); + } + + public MechJebModuleNodeEditor(MechJebCore core) : base(core) { } + } +} diff --git a/MechJeb2/MechJebModuleRCSBalancer.cs b/MechJeb2/MechJebModuleRCSBalancer.cs index c02121864..3742e6f12 100644 --- a/MechJeb2/MechJebModuleRCSBalancer.cs +++ b/MechJeb2/MechJebModuleRCSBalancer.cs @@ -1,282 +1,282 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleRCSBalancer : ComputerModule - { - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - [ToggleInfoItem("#MechJeb_smartTranslation", InfoItem.Category.Thrust)]//Smart RCS translation - public bool smartTranslation = false; - - // Overdrive - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - [EditableInfoItem("#MechJeb_RCSBalancerOverdrive", InfoItem.Category.Thrust, rightLabel = "%")]//RCS balancer overdrive - public EditableDoubleMult overdrive = new EditableDoubleMult(1, 0.01); - - // Advanced options - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - public bool advancedOptions = false; - - // Advanced: overdrive scale. While 'overdrive' will range from 0..1, - // we should reduce it slightly before using it to control the 'waste - // threshold' tuning parameter, because waste thresholds of 1 or above - // cause problems by allowing unhelpful thrusters to fire. - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - public EditableDouble overdriveScale = 0.9; - - // Advanced: tuning parameters - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - public EditableDouble tuningParamFactorTorque = 1; - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - public EditableDouble tuningParamFactorTranslate = 0.005; - [Persistent(pass = (int)(Pass.Type | Pass.Global))] - public EditableDouble tuningParamFactorWaste = 1; - - // Variables for RCS solving. - private RCSSolverThread solverThread = new RCSSolverThread(); - private List thrusters = null; - private double[] throttles = null; - - [EditableInfoItem("#MechJeb_RCSBalancerPrecision", InfoItem.Category.Thrust)]//RCS balancer precision - public EditableInt calcPrecision = 3; - - [GeneralInfoItem("#MechJeb_RCSBalancerInfo", InfoItem.Category.Thrust)]//RCS balancer info - public void RCSBalancerInfoItem() - { - GUILayout.BeginVertical(); - GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label1"), (solverThread.calculationTime * 1000).ToString("F0") + " ms");//"Calculation time" - GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label2"), solverThread.taskCount);//"Pending tasks" - - GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label3"), solverThread.cacheSize);//"Cache size" - GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label4"), solverThread.cacheHits);//"Cache hits" - GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label5"), solverThread.cacheMisses);//"Cache misses" - - GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label6"), MuUtils.ToSI(solverThread.comError) + "m");//"CoM shift" - GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label7"), MuUtils.ToSI(solverThread.comErrorThreshold) + "m");//"CoM recalc" - GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label8"), MuUtils.ToSI(solverThread.maxComError) + "m");//"Max CoM shift" - - GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label9"), solverThread.statusString);//"Status" - - string error = solverThread.errorString; - if (!string.IsNullOrEmpty(error)) - { - GUILayout.Label(error, GUILayout.ExpandWidth(true)); - } - - GUILayout.EndVertical(); - } - - [GeneralInfoItem("#MechJeb_RCSThrusterStates", InfoItem.Category.Thrust)]//RCS thruster states - private void RCSThrusterStateInfoItem() - { - GUILayout.BeginVertical(); - GUILayout.Label(Localizer.Format("#MechJeb_RCSThrusterStates_Label1"));//"RCS thrusters states (scaled to 0-9)" - - bool firstRcsModule = true; - string thrusterStates = ""; - for (int index = 0; index < vessel.parts.Count; index++) - { - Part p = vessel.parts[index]; - foreach (ModuleRCS pm in p.Modules.OfType()) - { - if (!firstRcsModule) - { - thrusterStates += " "; - } - firstRcsModule = false; - thrusterStates += String.Format("({0:F0}:", pm.thrusterPower * 9); - for (int i = 0; i < pm.thrustForces.Length; i++) - { - if (i != 0) - { - thrusterStates += ","; - } - thrusterStates += (pm.thrustForces[i] * 9).ToString("F0"); - } - thrusterStates += ")"; - } - } - - GUILayout.Label(thrusterStates); - GUILayout.EndVertical(); - } - - [GeneralInfoItem("#MechJeb_RCSPartThrottles", InfoItem.Category.Thrust)]//RCS part throttles - private void RCSPartThrottlesInfoItem() - { - GUILayout.BeginVertical(); - - bool firstRcsModule = true; - string thrusterStates = ""; - - for (int index = 0; index < vessel.parts.Count; index++) - { - Part p = vessel.parts[index]; - foreach (ModuleRCS pm in p.Modules.OfType()) - { - if (!firstRcsModule) - { - thrusterStates += " "; - } - firstRcsModule = false; - thrusterStates += pm.thrusterPower.ToString("F1"); - } - } - - GUILayout.Label(thrusterStates); - GUILayout.EndVertical(); - } - - [GeneralInfoItem("#MechJeb_ControlVector", InfoItem.Category.Thrust)]//Control vector - private void ControlVectorInfoItem() - { - FlightCtrlState s = FlightInputHandler.state; - - string xyz = String.Format("{0:F2} {1:F2} {2:F2}", s.X, s.Y, s.Z); - string rpy = String.Format("{0:F2} {1:F2} {2:F2}", s.roll, s.pitch, s.yaw); - GUILayout.BeginVertical(); - GuiUtils.SimpleLabel("X/Y/Z", xyz); - GuiUtils.SimpleLabel("R/P/Y", rpy); - GUILayout.EndVertical(); - } - public MechJebModuleRCSBalancer(MechJebCore core) - : base(core) - { - priority = 700; - } - - public override void OnModuleEnabled() - { - UpdateTuningParameters(); - solverThread.start(); - - base.OnModuleEnabled(); - } - - public override void OnModuleDisabled() - { - solverThread.stop(); - - base.OnModuleDisabled(); - } - - public void ResetThrusterForces() - { - solverThread.ResetThrusterForces(); - } - - public void GetThrottles(Vector3 direction, out double[] throttles, out List thrusters) - { - solverThread.GetThrottles(vessel, vesselState, direction, out throttles, out thrusters); - } - - // Throttles RCS thrusters to keep a vessel balanced during translation. - protected void AdjustRCSThrottles(FlightCtrlState s) - { - bool cutThrottles = false; - - if (s.X == 0 && s.Y == 0 && s.Z == 0) - { - solverThread.ResetThrusterForces(); - } - - // Note that FlightCtrlState doesn't use the same axes as the - // vehicle's reference frame. FlightCtrlState coordinates are right- - // handed, with vessel prograde being -Z. Vessel coordinates - // are left-handed, with vessel prograde being +Y. Here's how - // FlightCtrlState relates to various ship directions (and their - // default keyboard shortcuts): - // up (i): y -1 - // down (k): y +1 - // left (j): x +1 - // right (l): x -1 - // forward (h): z -1 - // backward (n): z +1 - // To turn this vector into a vessel-relative one, we need to negate - // each value and also swap the Y and Z values. - Vector3 direction = new Vector3(-s.X, -s.Z, -s.Y); - - // RCS balancing on rotation isn't supported. - //Vector3 rotation = new Vector3(s.pitch, s.roll, s.yaw); - - RCSSolverKey.SetPrecision(calcPrecision); - GetThrottles(direction, out throttles, out thrusters); - - // If the throttles we got were bad (due to the threaded - // calculation not having completed yet), cut throttles. It's - // better to not move at all than move in the wrong direction. - if (throttles.Length != thrusters.Count) - { - throttles = new double[thrusters.Count]; - cutThrottles = true; - } - - if (cutThrottles) - { - for (int i = 0; i < throttles.Length; i++) - { - throttles[i] = 0; - } - } - - // Apply the calculated throttles to all RCS parts. - for (int i = 0; i < thrusters.Count; i++) - { - thrusters[i].partModule.thrusterPower = (float)throttles[i]; - } - } - - public void UpdateTuningParameters() - { - double wasteThreshold = overdrive * overdriveScale; - RCSSolverTuningParams tuningParams = new RCSSolverTuningParams(); - tuningParams.wasteThreshold = wasteThreshold; - tuningParams.factorTorque = tuningParamFactorTorque; - tuningParams.factorTranslate = tuningParamFactorTranslate; - tuningParams.factorWaste = tuningParamFactorWaste; - solverThread.UpdateTuningParameters(tuningParams); - } - - public double GetCalculationTime() - { - return solverThread.calculationTime; - } - - /* - public override void OnUpdate() - { - // Make thruster exhaust onscreen correspond to actual thrust. - if (smartTranslation && throttles != null) - { - for (int i = 0; i < throttles.Length; i++) - { - // 'throttles' and 'thrusters' are guaranteed to be of the - // same length. - float throttle = (float)throttles[i]; - var tfx = thrusters[i].partModule.thrusterFX; - - for (int j = 0; j < tfx.Count; j++) - { - tfx[j].Power *= throttle; - } - } - } - base.OnUpdate(); - } - */ - - public override void Drive(FlightCtrlState s) - { - if (smartTranslation) - { - AdjustRCSThrottles(s); - } - - base.Drive(s); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleRCSBalancer : ComputerModule + { + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + [ToggleInfoItem("#MechJeb_smartTranslation", InfoItem.Category.Thrust)]//Smart RCS translation + public bool smartTranslation = false; + + // Overdrive + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + [EditableInfoItem("#MechJeb_RCSBalancerOverdrive", InfoItem.Category.Thrust, rightLabel = "%")]//RCS balancer overdrive + public EditableDoubleMult overdrive = new EditableDoubleMult(1, 0.01); + + // Advanced options + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + public bool advancedOptions = false; + + // Advanced: overdrive scale. While 'overdrive' will range from 0..1, + // we should reduce it slightly before using it to control the 'waste + // threshold' tuning parameter, because waste thresholds of 1 or above + // cause problems by allowing unhelpful thrusters to fire. + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + public EditableDouble overdriveScale = 0.9; + + // Advanced: tuning parameters + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + public EditableDouble tuningParamFactorTorque = 1; + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + public EditableDouble tuningParamFactorTranslate = 0.005; + [Persistent(pass = (int)(Pass.Type | Pass.Global))] + public EditableDouble tuningParamFactorWaste = 1; + + // Variables for RCS solving. + private RCSSolverThread solverThread = new RCSSolverThread(); + private List thrusters = null; + private double[] throttles = null; + + [EditableInfoItem("#MechJeb_RCSBalancerPrecision", InfoItem.Category.Thrust)]//RCS balancer precision + public EditableInt calcPrecision = 3; + + [GeneralInfoItem("#MechJeb_RCSBalancerInfo", InfoItem.Category.Thrust)]//RCS balancer info + public void RCSBalancerInfoItem() + { + GUILayout.BeginVertical(); + GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label1"), (solverThread.calculationTime * 1000).ToString("F0") + " ms");//"Calculation time" + GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label2"), solverThread.taskCount);//"Pending tasks" + + GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label3"), solverThread.cacheSize);//"Cache size" + GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label4"), solverThread.cacheHits);//"Cache hits" + GuiUtils.SimpleLabelInt(Localizer.Format("#MechJeb_RCSBalancerInfo_Label5"), solverThread.cacheMisses);//"Cache misses" + + GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label6"), MuUtils.ToSI(solverThread.comError) + "m");//"CoM shift" + GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label7"), MuUtils.ToSI(solverThread.comErrorThreshold) + "m");//"CoM recalc" + GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label8"), MuUtils.ToSI(solverThread.maxComError) + "m");//"Max CoM shift" + + GuiUtils.SimpleLabel(Localizer.Format("#MechJeb_RCSBalancerInfo_Label9"), solverThread.statusString);//"Status" + + string error = solverThread.errorString; + if (!string.IsNullOrEmpty(error)) + { + GUILayout.Label(error, GUILayout.ExpandWidth(true)); + } + + GUILayout.EndVertical(); + } + + [GeneralInfoItem("#MechJeb_RCSThrusterStates", InfoItem.Category.Thrust)]//RCS thruster states + private void RCSThrusterStateInfoItem() + { + GUILayout.BeginVertical(); + GUILayout.Label(Localizer.Format("#MechJeb_RCSThrusterStates_Label1"));//"RCS thrusters states (scaled to 0-9)" + + bool firstRcsModule = true; + string thrusterStates = ""; + for (int index = 0; index < vessel.parts.Count; index++) + { + Part p = vessel.parts[index]; + foreach (ModuleRCS pm in p.Modules.OfType()) + { + if (!firstRcsModule) + { + thrusterStates += " "; + } + firstRcsModule = false; + thrusterStates += String.Format("({0:F0}:", pm.thrusterPower * 9); + for (int i = 0; i < pm.thrustForces.Length; i++) + { + if (i != 0) + { + thrusterStates += ","; + } + thrusterStates += (pm.thrustForces[i] * 9).ToString("F0"); + } + thrusterStates += ")"; + } + } + + GUILayout.Label(thrusterStates); + GUILayout.EndVertical(); + } + + [GeneralInfoItem("#MechJeb_RCSPartThrottles", InfoItem.Category.Thrust)]//RCS part throttles + private void RCSPartThrottlesInfoItem() + { + GUILayout.BeginVertical(); + + bool firstRcsModule = true; + string thrusterStates = ""; + + for (int index = 0; index < vessel.parts.Count; index++) + { + Part p = vessel.parts[index]; + foreach (ModuleRCS pm in p.Modules.OfType()) + { + if (!firstRcsModule) + { + thrusterStates += " "; + } + firstRcsModule = false; + thrusterStates += pm.thrusterPower.ToString("F1"); + } + } + + GUILayout.Label(thrusterStates); + GUILayout.EndVertical(); + } + + [GeneralInfoItem("#MechJeb_ControlVector", InfoItem.Category.Thrust)]//Control vector + private void ControlVectorInfoItem() + { + FlightCtrlState s = FlightInputHandler.state; + + string xyz = String.Format("{0:F2} {1:F2} {2:F2}", s.X, s.Y, s.Z); + string rpy = String.Format("{0:F2} {1:F2} {2:F2}", s.roll, s.pitch, s.yaw); + GUILayout.BeginVertical(); + GuiUtils.SimpleLabel("X/Y/Z", xyz); + GuiUtils.SimpleLabel("R/P/Y", rpy); + GUILayout.EndVertical(); + } + public MechJebModuleRCSBalancer(MechJebCore core) + : base(core) + { + priority = 700; + } + + public override void OnModuleEnabled() + { + UpdateTuningParameters(); + solverThread.start(); + + base.OnModuleEnabled(); + } + + public override void OnModuleDisabled() + { + solverThread.stop(); + + base.OnModuleDisabled(); + } + + public void ResetThrusterForces() + { + solverThread.ResetThrusterForces(); + } + + public void GetThrottles(Vector3 direction, out double[] throttles, out List thrusters) + { + solverThread.GetThrottles(vessel, vesselState, direction, out throttles, out thrusters); + } + + // Throttles RCS thrusters to keep a vessel balanced during translation. + protected void AdjustRCSThrottles(FlightCtrlState s) + { + bool cutThrottles = false; + + if (s.X == 0 && s.Y == 0 && s.Z == 0) + { + solverThread.ResetThrusterForces(); + } + + // Note that FlightCtrlState doesn't use the same axes as the + // vehicle's reference frame. FlightCtrlState coordinates are right- + // handed, with vessel prograde being -Z. Vessel coordinates + // are left-handed, with vessel prograde being +Y. Here's how + // FlightCtrlState relates to various ship directions (and their + // default keyboard shortcuts): + // up (i): y -1 + // down (k): y +1 + // left (j): x +1 + // right (l): x -1 + // forward (h): z -1 + // backward (n): z +1 + // To turn this vector into a vessel-relative one, we need to negate + // each value and also swap the Y and Z values. + Vector3 direction = new Vector3(-s.X, -s.Z, -s.Y); + + // RCS balancing on rotation isn't supported. + //Vector3 rotation = new Vector3(s.pitch, s.roll, s.yaw); + + RCSSolverKey.SetPrecision(calcPrecision); + GetThrottles(direction, out throttles, out thrusters); + + // If the throttles we got were bad (due to the threaded + // calculation not having completed yet), cut throttles. It's + // better to not move at all than move in the wrong direction. + if (throttles.Length != thrusters.Count) + { + throttles = new double[thrusters.Count]; + cutThrottles = true; + } + + if (cutThrottles) + { + for (int i = 0; i < throttles.Length; i++) + { + throttles[i] = 0; + } + } + + // Apply the calculated throttles to all RCS parts. + for (int i = 0; i < thrusters.Count; i++) + { + thrusters[i].partModule.thrusterPower = (float)throttles[i]; + } + } + + public void UpdateTuningParameters() + { + double wasteThreshold = overdrive * overdriveScale; + RCSSolverTuningParams tuningParams = new RCSSolverTuningParams(); + tuningParams.wasteThreshold = wasteThreshold; + tuningParams.factorTorque = tuningParamFactorTorque; + tuningParams.factorTranslate = tuningParamFactorTranslate; + tuningParams.factorWaste = tuningParamFactorWaste; + solverThread.UpdateTuningParameters(tuningParams); + } + + public double GetCalculationTime() + { + return solverThread.calculationTime; + } + + /* + public override void OnUpdate() + { + // Make thruster exhaust onscreen correspond to actual thrust. + if (smartTranslation && throttles != null) + { + for (int i = 0; i < throttles.Length; i++) + { + // 'throttles' and 'thrusters' are guaranteed to be of the + // same length. + float throttle = (float)throttles[i]; + var tfx = thrusters[i].partModule.thrusterFX; + + for (int j = 0; j < tfx.Count; j++) + { + tfx[j].Power *= throttle; + } + } + } + base.OnUpdate(); + } + */ + + public override void Drive(FlightCtrlState s) + { + if (smartTranslation) + { + AdjustRCSThrottles(s); + } + + base.Drive(s); + } + } +} diff --git a/MechJeb2/MechJebModuleRCSController.cs b/MechJeb2/MechJebModuleRCSController.cs index 40d4b1b37..9a270e872 100644 --- a/MechJeb2/MechJebModuleRCSController.cs +++ b/MechJeb2/MechJebModuleRCSController.cs @@ -1,271 +1,271 @@ -using System; -using UnityEngine; - -namespace MuMech -{ - public class MechJebModuleRCSController : ComputerModule - { - public Vector3d targetVelocity = Vector3d.zero; - - public PIDControllerV2 pid; - Vector3d lastAct = Vector3d.zero; - Vector3d worldVelocityDelta = Vector3d.zero; - Vector3d prev_worldVelocityDelta = Vector3d.zero; - - enum ControlType - { - TARGET_VELOCITY, - VELOCITY_ERROR, - VELOCITY_TARGET_REL, - POSITION_TARGET_REL - }; - - ControlType controlType; - - [Persistent(pass = (int)(Pass.Global))] - [ToggleInfoItem("#MechJeb_conserveFuel", InfoItem.Category.Thrust)]//Conserve RCS fuel - public bool conserveFuel = false; - - [EditableInfoItem("#MechJeb_conserveThreshold", InfoItem.Category.Thrust, rightLabel = "m/s")]//Conserve RCS fuel threshold - public EditableDouble conserveThreshold = 0.05; - - [Persistent(pass = (int)(Pass.Local| Pass.Type | Pass.Global))] - [EditableInfoItem("#MechJeb_RCSTf", InfoItem.Category.Thrust)]//RCS Tf - public EditableDouble Tf = 1; - - [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] - public EditableDouble Kp = 0.125; - - [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] - public EditableDouble Ki = 0.07; - - [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] - public EditableDouble Kd = 0.53; - - [Persistent(pass = (int)(Pass.Global))] - public bool rcsManualPID = false; - - [Persistent(pass = (int)(Pass.Global))] - [ToggleInfoItem("#MechJeb_RCSThrottle", InfoItem.Category.Thrust)]//RCS throttle when 0kn thrust - public bool rcsThrottle = true; - - [Persistent(pass = (int)(Pass.Global))] - [ToggleInfoItem("#MechJeb_rcsForRotation", InfoItem.Category.Thrust)]//Use RCS for rotation - public bool rcsForRotation = true; - - public MechJebModuleRCSController(MechJebCore core) - : base(core) - { - priority = 600; - pid = new PIDControllerV2(Kp, Ki, Kd, 1, -1); - } - - public override void OnModuleEnabled() - { - setPIDParameters(); - pid.Reset(); - lastAct = Vector3d.zero; - worldVelocityDelta = Vector3d.zero; - prev_worldVelocityDelta = Vector3d.zero; - controlType = ControlType.VELOCITY_ERROR; - base.OnModuleEnabled(); - } - - public bool rcsDeactivate() - { - users.Clear(); - return true; - } - - public void setPIDParameters() - { - if (rcsManualPID) - { - pid.Kd = Kd; - pid.Kp = Kp; - pid.Ki = Ki; - } - else - { - Tf = Math.Max(Tf, 0.02); - - pid.Kd = 0.53 / Tf; - pid.Kp = pid.Kd / (3 * Math.Sqrt(2) * Tf); - pid.Ki = pid.Kp / (12 * Math.Sqrt(2) * Tf); - - Kd.val = pid.Kd; - Kp.val = pid.Kp; - Ki.val = pid.Ki; - } - } - - [GeneralInfoItem("#MechJeb_RCSPid", InfoItem.Category.Thrust)]//RCS Pid - public void PIDGUI() - { - GUILayout.BeginVertical(); - rcsManualPID = GUILayout.Toggle(rcsManualPID, "RCS Manual Pid"); - if (rcsManualPID) - { - GUILayout.BeginHorizontal(); - GUILayout.Label("P", GUILayout.ExpandWidth(false)); - Kp.text = GUILayout.TextField(Kp.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); - - GUILayout.Label("I", GUILayout.ExpandWidth(false)); - Kd.text = GUILayout.TextField(Kd.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); - - GUILayout.Label("D", GUILayout.ExpandWidth(false)); - Ki.text = GUILayout.TextField(Ki.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); - GUILayout.EndHorizontal(); - } - else - { - GUILayout.BeginHorizontal(); - GUILayout.Label("Tf", GUILayout.ExpandWidth(false)); - Tf.text = GUILayout.TextField(Tf.text, GUILayout.ExpandWidth(true), GUILayout.Width(40)); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("P", GUILayout.ExpandWidth(false)); - GUILayout.Label(Kp.val.ToString("F4"), GUILayout.ExpandWidth(true)); - - GUILayout.Label("I", GUILayout.ExpandWidth(false)); - GUILayout.Label(Ki.val.ToString("F4"), GUILayout.ExpandWidth(true)); - - GUILayout.Label("D", GUILayout.ExpandWidth(false)); - GUILayout.Label(Kd.val.ToString("F4"), GUILayout.ExpandWidth(true)); - GUILayout.EndHorizontal(); - } - GUILayout.EndVertical(); - setPIDParameters(); - } - - - // When evaluating how fast RCS can accelerate and calculate a speed that available thrust should - // be multiplied by that since the PID controller actual lower the used acceleration - public double rcsAccelFactor() - { - return pid.Kp; - } - - public override void OnModuleDisabled() - { - vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, false); - base.OnModuleDisabled(); - } - - public void SetTargetWorldVelocity(Vector3d vel) - { - targetVelocity = vel; - controlType = ControlType.TARGET_VELOCITY; - } - - public void SetWorldVelocityError(Vector3d dv) - { - worldVelocityDelta = -dv; - if (controlType != ControlType.VELOCITY_ERROR) - { - prev_worldVelocityDelta = worldVelocityDelta; - controlType = ControlType.VELOCITY_ERROR; - } - } - - public void SetTargetRelative(Vector3d vel) - { - targetVelocity = vel; - controlType = ControlType.VELOCITY_TARGET_REL; - } - - public override void Drive(FlightCtrlState s) - { - setPIDParameters(); - - switch (controlType) - { - case ControlType.TARGET_VELOCITY: - // Removed the gravity since it also affect the target and we don't know the target pos here. - // Since the difference is negligable for docking it's removed - // TODO : add it back once we use the RCS Controler for other use than docking. Account for current acceleration beside gravity ? - worldVelocityDelta = vesselState.orbitalVelocity - targetVelocity; - //worldVelocityDelta += TimeWarp.fixedDeltaTime * vesselState.gravityForce; //account for one frame's worth of gravity - //worldVelocityDelta -= TimeWarp.fixedDeltaTime * gravityForce = FlightGlobals.getGeeForceAtPosition( Here be the target position ); ; //account for one frame's worth of gravity - break; - - case ControlType.VELOCITY_ERROR: - // worldVelocityDelta already contains the velocity error - break; - - case ControlType.VELOCITY_TARGET_REL: - if (core.target.Target == null) - { - rcsDeactivate(); - return; - } - - worldVelocityDelta = core.target.RelativeVelocity - targetVelocity; - break; - } - - // We work in local vessel coordinate - Vector3d velocityDelta = Quaternion.Inverse(vessel.GetTransform().rotation) * worldVelocityDelta; - - if (!conserveFuel || (velocityDelta.magnitude > conserveThreshold)) - { - if (!vessel.ActionGroups[KSPActionGroup.RCS]) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, true); - } - - Vector3d rcs = new Vector3d(); - - for (int i = 0; i < Vector6.Values.Length; i++) - { - Vector6.Direction dir = Vector6.Values[i]; - double dirDv = Vector3d.Dot(velocityDelta, Vector6.directions[(int)dir]); - double dirAvail = vesselState.rcsThrustAvailable[dir]; - if (dirAvail > 0 && Math.Abs(dirDv) > 0.001) - { - double dirAction = dirDv / (dirAvail * TimeWarp.fixedDeltaTime / vesselState.mass); - if (dirAction > 0) - { - rcs += Vector6.directions[(int)dir] * dirAction; - } - } - } - - Vector3d omega = Vector3d.zero; - - switch (controlType) - { - case ControlType.TARGET_VELOCITY: - omega = Quaternion.Inverse(vessel.GetTransform().rotation) * (vessel.acceleration - vesselState.gravityForce); - break; - - case ControlType.VELOCITY_TARGET_REL: - case ControlType.VELOCITY_ERROR: - omega = (worldVelocityDelta - prev_worldVelocityDelta) / TimeWarp.fixedDeltaTime; - prev_worldVelocityDelta = worldVelocityDelta; - break; - } - - rcs = pid.Compute(rcs, omega); - - // Disabled the low pass filter for now. Was doing more harm than good - //rcs = lastAct + (rcs - lastAct) * (1 / ((Tf / TimeWarp.fixedDeltaTime) + 1)); - lastAct = rcs; - - s.X = Mathf.Clamp((float)rcs.x, -1, 1); - s.Y = Mathf.Clamp((float)rcs.z, -1, 1); //note that z and - s.Z = Mathf.Clamp((float)rcs.y, -1, 1); //y must be swapped - } - else if (conserveFuel) - { - if (vessel.ActionGroups[KSPActionGroup.RCS]) - { - vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, false); - } - } - - base.Drive(s); - } - } -} +using System; +using UnityEngine; + +namespace MuMech +{ + public class MechJebModuleRCSController : ComputerModule + { + public Vector3d targetVelocity = Vector3d.zero; + + public PIDControllerV2 pid; + Vector3d lastAct = Vector3d.zero; + Vector3d worldVelocityDelta = Vector3d.zero; + Vector3d prev_worldVelocityDelta = Vector3d.zero; + + enum ControlType + { + TARGET_VELOCITY, + VELOCITY_ERROR, + VELOCITY_TARGET_REL, + POSITION_TARGET_REL + }; + + ControlType controlType; + + [Persistent(pass = (int)(Pass.Global))] + [ToggleInfoItem("#MechJeb_conserveFuel", InfoItem.Category.Thrust)]//Conserve RCS fuel + public bool conserveFuel = false; + + [EditableInfoItem("#MechJeb_conserveThreshold", InfoItem.Category.Thrust, rightLabel = "m/s")]//Conserve RCS fuel threshold + public EditableDouble conserveThreshold = 0.05; + + [Persistent(pass = (int)(Pass.Local| Pass.Type | Pass.Global))] + [EditableInfoItem("#MechJeb_RCSTf", InfoItem.Category.Thrust)]//RCS Tf + public EditableDouble Tf = 1; + + [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] + public EditableDouble Kp = 0.125; + + [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] + public EditableDouble Ki = 0.07; + + [Persistent(pass = (int)(Pass.Local | Pass.Type | Pass.Global))] + public EditableDouble Kd = 0.53; + + [Persistent(pass = (int)(Pass.Global))] + public bool rcsManualPID = false; + + [Persistent(pass = (int)(Pass.Global))] + [ToggleInfoItem("#MechJeb_RCSThrottle", InfoItem.Category.Thrust)]//RCS throttle when 0kn thrust + public bool rcsThrottle = true; + + [Persistent(pass = (int)(Pass.Global))] + [ToggleInfoItem("#MechJeb_rcsForRotation", InfoItem.Category.Thrust)]//Use RCS for rotation + public bool rcsForRotation = true; + + public MechJebModuleRCSController(MechJebCore core) + : base(core) + { + priority = 600; + pid = new PIDControllerV2(Kp, Ki, Kd, 1, -1); + } + + public override void OnModuleEnabled() + { + setPIDParameters(); + pid.Reset(); + lastAct = Vector3d.zero; + worldVelocityDelta = Vector3d.zero; + prev_worldVelocityDelta = Vector3d.zero; + controlType = ControlType.VELOCITY_ERROR; + base.OnModuleEnabled(); + } + + public bool rcsDeactivate() + { + users.Clear(); + return true; + } + + public void setPIDParameters() + { + if (rcsManualPID) + { + pid.Kd = Kd; + pid.Kp = Kp; + pid.Ki = Ki; + } + else + { + Tf = Math.Max(Tf, 0.02); + + pid.Kd = 0.53 / Tf; + pid.Kp = pid.Kd / (3 * Math.Sqrt(2) * Tf); + pid.Ki = pid.Kp / (12 * Math.Sqrt(2) * Tf); + + Kd.val = pid.Kd; + Kp.val = pid.Kp; + Ki.val = pid.Ki; + } + } + + [GeneralInfoItem("#MechJeb_RCSPid", InfoItem.Category.Thrust)]//RCS Pid + public void PIDGUI() + { + GUILayout.BeginVertical(); + rcsManualPID = GUILayout.Toggle(rcsManualPID, "RCS Manual Pid"); + if (rcsManualPID) + { + GUILayout.BeginHorizontal(); + GUILayout.Label("P", GUILayout.ExpandWidth(false)); + Kp.text = GUILayout.TextField(Kp.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); + + GUILayout.Label("I", GUILayout.ExpandWidth(false)); + Kd.text = GUILayout.TextField(Kd.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); + + GUILayout.Label("D", GUILayout.ExpandWidth(false)); + Ki.text = GUILayout.TextField(Ki.text, GUILayout.ExpandWidth(true), GUILayout.Width(60)); + GUILayout.EndHorizontal(); + } + else + { + GUILayout.BeginHorizontal(); + GUILayout.Label("Tf", GUILayout.ExpandWidth(false)); + Tf.text = GUILayout.TextField(Tf.text, GUILayout.ExpandWidth(true), GUILayout.Width(40)); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("P", GUILayout.ExpandWidth(false)); + GUILayout.Label(Kp.val.ToString("F4"), GUILayout.ExpandWidth(true)); + + GUILayout.Label("I", GUILayout.ExpandWidth(false)); + GUILayout.Label(Ki.val.ToString("F4"), GUILayout.ExpandWidth(true)); + + GUILayout.Label("D", GUILayout.ExpandWidth(false)); + GUILayout.Label(Kd.val.ToString("F4"), GUILayout.ExpandWidth(true)); + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + setPIDParameters(); + } + + + // When evaluating how fast RCS can accelerate and calculate a speed that available thrust should + // be multiplied by that since the PID controller actual lower the used acceleration + public double rcsAccelFactor() + { + return pid.Kp; + } + + public override void OnModuleDisabled() + { + vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, false); + base.OnModuleDisabled(); + } + + public void SetTargetWorldVelocity(Vector3d vel) + { + targetVelocity = vel; + controlType = ControlType.TARGET_VELOCITY; + } + + public void SetWorldVelocityError(Vector3d dv) + { + worldVelocityDelta = -dv; + if (controlType != ControlType.VELOCITY_ERROR) + { + prev_worldVelocityDelta = worldVelocityDelta; + controlType = ControlType.VELOCITY_ERROR; + } + } + + public void SetTargetRelative(Vector3d vel) + { + targetVelocity = vel; + controlType = ControlType.VELOCITY_TARGET_REL; + } + + public override void Drive(FlightCtrlState s) + { + setPIDParameters(); + + switch (controlType) + { + case ControlType.TARGET_VELOCITY: + // Removed the gravity since it also affect the target and we don't know the target pos here. + // Since the difference is negligable for docking it's removed + // TODO : add it back once we use the RCS Controler for other use than docking. Account for current acceleration beside gravity ? + worldVelocityDelta = vesselState.orbitalVelocity - targetVelocity; + //worldVelocityDelta += TimeWarp.fixedDeltaTime * vesselState.gravityForce; //account for one frame's worth of gravity + //worldVelocityDelta -= TimeWarp.fixedDeltaTime * gravityForce = FlightGlobals.getGeeForceAtPosition( Here be the target position ); ; //account for one frame's worth of gravity + break; + + case ControlType.VELOCITY_ERROR: + // worldVelocityDelta already contains the velocity error + break; + + case ControlType.VELOCITY_TARGET_REL: + if (core.target.Target == null) + { + rcsDeactivate(); + return; + } + + worldVelocityDelta = core.target.RelativeVelocity - targetVelocity; + break; + } + + // We work in local vessel coordinate + Vector3d velocityDelta = Quaternion.Inverse(vessel.GetTransform().rotation) * worldVelocityDelta; + + if (!conserveFuel || (velocityDelta.magnitude > conserveThreshold)) + { + if (!vessel.ActionGroups[KSPActionGroup.RCS]) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, true); + } + + Vector3d rcs = new Vector3d(); + + for (int i = 0; i < Vector6.Values.Length; i++) + { + Vector6.Direction dir = Vector6.Values[i]; + double dirDv = Vector3d.Dot(velocityDelta, Vector6.directions[(int)dir]); + double dirAvail = vesselState.rcsThrustAvailable[dir]; + if (dirAvail > 0 && Math.Abs(dirDv) > 0.001) + { + double dirAction = dirDv / (dirAvail * TimeWarp.fixedDeltaTime / vesselState.mass); + if (dirAction > 0) + { + rcs += Vector6.directions[(int)dir] * dirAction; + } + } + } + + Vector3d omega = Vector3d.zero; + + switch (controlType) + { + case ControlType.TARGET_VELOCITY: + omega = Quaternion.Inverse(vessel.GetTransform().rotation) * (vessel.acceleration - vesselState.gravityForce); + break; + + case ControlType.VELOCITY_TARGET_REL: + case ControlType.VELOCITY_ERROR: + omega = (worldVelocityDelta - prev_worldVelocityDelta) / TimeWarp.fixedDeltaTime; + prev_worldVelocityDelta = worldVelocityDelta; + break; + } + + rcs = pid.Compute(rcs, omega); + + // Disabled the low pass filter for now. Was doing more harm than good + //rcs = lastAct + (rcs - lastAct) * (1 / ((Tf / TimeWarp.fixedDeltaTime) + 1)); + lastAct = rcs; + + s.X = Mathf.Clamp((float)rcs.x, -1, 1); + s.Y = Mathf.Clamp((float)rcs.z, -1, 1); //note that z and + s.Z = Mathf.Clamp((float)rcs.y, -1, 1); //y must be swapped + } + else if (conserveFuel) + { + if (vessel.ActionGroups[KSPActionGroup.RCS]) + { + vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, false); + } + } + + base.Drive(s); + } + } +} diff --git a/MechJeb2/MechJebModuleRoverController.cs b/MechJeb2/MechJebModuleRoverController.cs index 9972f7c73..7375ddbcc 100644 --- a/MechJeb2/MechJebModuleRoverController.cs +++ b/MechJeb2/MechJebModuleRoverController.cs @@ -1,586 +1,586 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace MuMech -{ - public class MechJebModuleRoverController : ComputerModule - { - public List Waypoints = new List(); - public int WaypointIndex = -1; - private CelestialBody lastBody = null; - public bool LoopWaypoints = false; - -// protected bool controlHeading; - [ToggleInfoItem("#MechJeb_ControlHeading", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Heading control - public bool ControlHeading; // TODO: change things back to properties when ConfigNodes can save and load these -// { -// get { return controlHeading; } -// set -// { -// controlHeading = value; -// if (controlHeading || controlSpeed || brakeOnEject) -// { -// users.Add(this); -// } -// else -// { -// users.Remove(this); -// } -// } -// } - - [EditableInfoItem("#MechJeb_Heading", InfoItem.Category.Rover, width = 40), Persistent(pass = (int)Pass.Local)]//Heading - public EditableDouble heading = 0; - -// protected bool controlSpeed = false; - [ToggleInfoItem("#MechJeb_ControlSpeed", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Speed control - public bool ControlSpeed = false; -// { -// get { return controlSpeed; } -// set -// { -// controlSpeed = value; -// if (controlHeading || controlSpeed || brakeOnEject) -// { -// users.Add(this); -// } -// else -// { -// users.Remove(this); -// } -// } -// } - - [EditableInfoItem("#MechJeb_Speed", InfoItem.Category.Rover, width = 40), Persistent(pass = (int)Pass.Local)]//Speed - public EditableDouble speed = 10; - - [ToggleInfoItem("#MechJeb_BrakeOnEject", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Brake on Pilot Eject - public bool BrakeOnEject = false; - - [ToggleInfoItem("#MechJeb_BrakeOnEnergyDepletion", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Brake on Energy Depletion - public bool BrakeOnEnergyDepletion = false; - - [ToggleInfoItem("#MechJeb_WarpToDaylight", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Warp until Day if Depleted - public bool WarpToDaylight = false; - public bool waitingForDaylight = false; - - [ToggleInfoItem("#MechJeb_StabilityControl", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Stability Control - public bool StabilityControl = false; - - [ToggleInfoItem("#MechJeb_LimitAcceleration", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local | (int)Pass.Type)]//Limit Acceleration - public bool LimitAcceleration = false; - - public PIDController headingPID; - public PIDController speedPID; - -// private LineRenderer line; - - [EditableInfoItem("#MechJeb_SafeTurnspeed", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Safe turnspeed - public EditableDouble turnSpeed = 3; - [EditableInfoItem("#MechJeb_TerrainLookAhead", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Terrain Look Ahead - public EditableDouble terrainLookAhead = 1.0; - [EditableInfoItem("#MechJeb_BrakeSpeedLimit", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Brake Speed Limit - public EditableDouble brakeSpeedLimit = 0.7; - - [EditableInfoItem("#MechJeb_HeadingPIDP", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID P - public EditableDouble hPIDp = 0.03; // 0.01 - [EditableInfoItem("#MechJeb_HeadingPIDI", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID I - public EditableDouble hPIDi = 0.002; // 0.001 - [EditableInfoItem("#MechJeb_HeadingPIDD", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID D - public EditableDouble hPIDd = 0.005; - - [EditableInfoItem("#MechJeb_SpeedPIDP", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID P - public EditableDouble sPIDp = 2.0; - [EditableInfoItem("#MechJeb_SpeedPIDI", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID I - public EditableDouble sPIDi = 0.1; - [EditableInfoItem("#MechJeb_SpeedPIDD", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID D - public EditableDouble sPIDd = 0.001; - - [ValueInfoItem("#MechJeb_SpeedIntAcc", InfoItem.Category.Rover, format = ValueInfoItem.SI, units = "m/s")]//Speed Int Acc - public double speedIntAcc = 0; - - [ValueInfoItem("#MechJeb_Traction", InfoItem.Category.Rover, format = "F0", units = "%")]//Traction - public float traction = 0; - [EditableInfoItem("#MechJeb_TractionBrakeLimit", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Traction Brake Limit - public EditableDouble tractionLimit = 75; - - public List wheels = new List(); - public List colliders = new List(); - public Vector3 norm = Vector3.zero; - - public override void OnStart(PartModule.StartState state) - { - headingPID = new PIDController(hPIDp, hPIDi, hPIDd); - speedPID = new PIDController(sPIDp, sPIDi, sPIDd); - - if (HighLogic.LoadedSceneIsFlight && orbit != null) - { - lastBody = orbit.referenceBody; - } - -// MechJebRouteRenderer.NewLineRenderer(ref line); -// line.enabled = false; - - GameEvents.onVesselWasModified.Add(OnVesselModified); - - base.OnStart(state); - } - - public void OnVesselModified(Vessel v) - { - - //TODO : need to look into the new ModuleWheelSteering ModuleWheelMotor ModuleWheelBrakes ModuleWheelBase ModuleWheelSuspension and see what they could bring - - try - { - wheels.Clear(); - wheels.AddRange(vessel.Parts.FindAll(p => p.HasModule() /*&& p.FindModelComponent() != null*/ && p.GetModule().wheelType != WheelType.LEG)); - colliders.Clear(); - wheels.ForEach(p => colliders.AddRange(p.FindModelComponents())); - } - catch (Exception) {} - } - - [ValueInfoItem("#MechJeb_Headingerror", InfoItem.Category.Rover, format = "F1", units = "º")]//Heading error - public double headingErr; - [ValueInfoItem("#MechJeb_Speederror", InfoItem.Category.Rover, format = ValueInfoItem.SI, units = "m/s")]//Speed error - public double speedErr; - public double tgtSpeed; - public MuMech.MovingAverage etaSpeed = new MovingAverage(50); - private double lastETA = 0; - private float lastThrottle = 0; - double curSpeed; - - public double HeadingToPos(Vector3 fromPos, Vector3 toPos) - { - // thanks to Cilph who did most of this since I don't understand anything ~ BR2k - var body = vessel.mainBody; - var fromLon = body.GetLongitude(fromPos); - var toLon = body.GetLongitude(toPos); - var diff = toLon - fromLon; - if (diff < -180) { diff += 360; } - if (diff > 180) { diff -= 360; } - Vector3 myPos = fromPos - body.transform.position; - Vector3 north = body.transform.position + ((float)body.Radius * body.transform.up) - fromPos; - Vector3 tgtPos = toPos - fromPos; - return (diff < 0 ? -1 : 1) * Vector3.Angle(Vector3d.Exclude(myPos.normalized, north.normalized), Vector3.ProjectOnPlane(tgtPos.normalized, myPos.normalized)); - } - - public float TurningSpeed(double speed, double error) - { - return (float)Math.Max(speed / (Math.Abs(error) / 3 > 1 ? Math.Abs(error) / 3 : 1), turnSpeed); - } - - public void CalculateTraction() - { - if (wheels.Count == 0 && colliders.Count == 0) { OnVesselModified(vessel); } - RaycastHit hit; - Physics.Raycast(vessel.CoM + vesselState.surfaceVelocity * terrainLookAhead + vesselState.up * 100, -vesselState.up, out hit, 500, 1 << 15); - norm = hit.normal; - traction = 0; -// foreach (var c in colliders) { -// //WheelHit hit; -// //if (c.GetGroundHit(out hit)) { traction += 1; } -// if (Physics.Raycast(c.transform.position + c.center, -(vesselState.up + norm.normalized) / 2, out hit, c.radius + 1.5f, 1 << 15)) { -// traction += (1.5f - (hit.distance - c.radius)) * 100; -// } -// } - - for (int i = 0; i < wheels.Count; i++) - { - var w = wheels[i]; - if (w.GroundContact) - { - traction += 100; - } - } - -// for (int i = 0; i < colliders.Count; i++) -// { -// var c = colliders[i]; -// if (c.isGrounded) -// { -// traction += 100; -// } -// } - - traction /= wheels.Count; - } - - public override void OnModuleDisabled() - { - if (core.attitude.users.Contains(this)) - { -// line.enabled = false; - core.attitude.attitudeDeactivate(); - core.attitude.users.Remove(this); - } - - base.OnModuleDisabled(); - } - - private float Square(float number) { return number * number; } - private double Square(double number) { return number * number; } - - public override void Drive(FlightCtrlState s) // TODO put the brake in when running out of power to prevent nighttime solar failures on hills, or atleast try to - { // TODO make distance calculation for 'reached' determination consider the rover and waypoint on sealevel to prevent height differences from messing it up -- should be done now? - if (orbit.referenceBody != lastBody) { WaypointIndex = -1; Waypoints.Clear(); } - MechJebWaypoint wp = (WaypointIndex > -1 && WaypointIndex < Waypoints.Count ? Waypoints[WaypointIndex] : null); - - var brake = vessel.ActionGroups[KSPActionGroup.Brakes]; // keep brakes locked if they are - curSpeed = Vector3d.Dot(vesselState.surfaceVelocity, vesselState.forward); - - CalculateTraction(); - speedIntAcc = speedPID.intAccum; - - if (wp != null && wp.Body == orbit.referenceBody) - { - if (ControlHeading) - { - heading.val = Math.Round(HeadingToPos(vessel.CoM, wp.Position), 1); - } - if (ControlSpeed) - { - var nextWP = (WaypointIndex < Waypoints.Count - 1 ? Waypoints[WaypointIndex + 1] : (LoopWaypoints ? Waypoints[0] : null)); - var distance = Vector3.Distance(vessel.CoM, wp.Position); - if (wp.Target != null) { distance += (float)(wp.Target.srfSpeed * curSpeed) / 2; } - // var maxSpeed = (wp.MaxSpeed > 0 ? Math.Min((float)speed, wp.MaxSpeed) : speed); // use waypoints maxSpeed if set and smaller than set the speed or just stick with the set speed - var maxSpeed = (wp.MaxSpeed > 0 ? wp.MaxSpeed : speed); // speed used to go towards the waypoint, using the waypoints maxSpeed if set or just stick with the set speed - var minSpeed = (wp.MinSpeed > 0 ? wp.MinSpeed : - (nextWP != null ? TurningSpeed((nextWP.MaxSpeed > 0 ? nextWP.MaxSpeed : speed), heading - HeadingToPos(wp.Position, nextWP.Position)) : - (distance - wp.Radius > 50 ? turnSpeed.val : 1))); - minSpeed = (wp.Quicksave ? 1 : minSpeed); - // ^ speed used to go through the waypoint, using half the set speed or maxSpeed as minSpeed for routing waypoints (all except the last) - var newSpeed = Math.Min(maxSpeed, Math.Max((distance - wp.Radius) / curSpeed, minSpeed)); // brake when getting closer - newSpeed = (newSpeed > turnSpeed ? TurningSpeed(newSpeed, headingErr) : newSpeed); // reduce speed when turning a lot -// if (LimitAcceleration) { newSpeed = curSpeed + Mathf.Clamp((float)(newSpeed - curSpeed), -1.5f, 0.5f); } -// newSpeed = tgtSpeed + Mathf.Clamp((float)(newSpeed - tgtSpeed), -Time.deltaTime * 8f, Time.deltaTime * 2f); - var radius = Math.Max(wp.Radius, 10); - if (distance < radius) - { - if (WaypointIndex + 1 >= Waypoints.Count) // last waypoint - { - newSpeed = new [] { newSpeed, (distance < radius * 0.8 ? 0 : 1) }.Min(); - // ^ limit speed so it'll only go from 1m/s to full stop when braking to prevent accidents on moons - if (LoopWaypoints) - { - WaypointIndex = 0; - } - else - { - newSpeed = 0; - brake = true; -// tgtSpeed.force(newSpeed); - if (curSpeed < brakeSpeedLimit) - { - if (wp.Quicksave) - { - //if (s.mainThrottle > 0) { s.mainThrottle = 0; } - if (FlightGlobals.ClearToSave() == ClearToSaveStatus.CLEAR) - { - WaypointIndex = -1; - ControlHeading = ControlSpeed = false; - QuickSaveLoad.QuickSave(); - } - } - else - { - WaypointIndex = -1; - ControlHeading = ControlSpeed = false; - } - } -// else { -// Debug.Log("Is this even getting called?"); -// WaypointIndex++; -// } - } - } - else - { - if (wp.Quicksave) - { - //if (s.mainThrottle > 0) { s.mainThrottle = 0; } - newSpeed = 0; -// tgtSpeed.force(newSpeed); - if (curSpeed < brakeSpeedLimit) - { - if (FlightGlobals.ClearToSave() == ClearToSaveStatus.CLEAR) - { - WaypointIndex++; - QuickSaveLoad.QuickSave(); - } - } - } - else - { - WaypointIndex++; - } - } - } - brake = brake || ((s.wheelThrottle == 0 || !vessel.isActiveVessel) && curSpeed < brakeSpeedLimit && newSpeed < brakeSpeedLimit); - // ^ brake if needed to prevent rolling, hopefully - tgtSpeed = (newSpeed >= 0 ? newSpeed : 0); - } - } - - if (ControlHeading) - { - headingPID.intAccum = Mathf.Clamp((float)headingPID.intAccum, -1, 1); - - double instantaneousHeading = vesselState.rotationVesselSurface.eulerAngles.y; - headingErr = MuUtils.ClampDegrees180(instantaneousHeading - heading); - if (s.wheelSteer == s.wheelSteerTrim || FlightGlobals.ActiveVessel != vessel) - { - float limit = (Math.Abs(curSpeed) > turnSpeed ? Mathf.Clamp((float)((turnSpeed + 6) / Square(curSpeed)), 0.1f, 1f) : 1f); - // turnSpeed needs to be higher than curSpeed or it will never steer as much as it could even at 0.2m/s above it - // double act = headingPID.Compute(headingErr * headingErr / 10 * Math.Sign(headingErr)); - double act = headingPID.Compute(headingErr); - if (traction >= tractionLimit) { - s.wheelSteer = Mathf.Clamp((float)act, -limit, limit); - // prevents it from flying above a waypoint and landing with steering at max while still going fast - } - } - } - - // Brake if there is no controler (Pilot eject from seat) - if (BrakeOnEject && vessel.GetReferenceTransformPart() == null) - { - s.wheelThrottle = 0; -// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, true); - brake = true; - } - else if (ControlSpeed) - { - speedPID.intAccum = Mathf.Clamp((float)speedPID.intAccum, -5, 5); - - speedErr = (WaypointIndex == -1 ? speed.val : tgtSpeed) - Vector3d.Dot(vesselState.surfaceVelocity, vesselState.forward); - if (s.wheelThrottle == s.wheelThrottleTrim || FlightGlobals.ActiveVessel != vessel) - { - float act = (float)speedPID.Compute(speedErr); - s.wheelThrottle = Mathf.Clamp(act, -1f, 1f); - // s.wheelThrottle = (!LimitAcceleration ? Mathf.Clamp(act, -1, 1) : // I think I'm using these ( ? : ) a bit too much - // (traction == 0 ? 0 : (act < 0 ? Mathf.Clamp(act, -1f, 1f) : (lastThrottle + Mathf.Clamp(act - lastThrottle, -0.01f, 0.01f)) * (traction < tractionLimit ? -1 : 1)))); -// (lastThrottle + Mathf.Clamp(act, -0.01f, 0.01f))); -// Debug.Log(s.wheelThrottle + Mathf.Clamp(act, -0.01f, 0.01f)); - if (curSpeed < 0 & s.wheelThrottle < 0) { s.wheelThrottle = 0; } // don't go backwards - if (Mathf.Sign(act) + Mathf.Sign(s.wheelThrottle) == 0) { s.wheelThrottle = Mathf.Clamp(act, -1f, 1f); } - if (speedErr < -1 && StabilityControl && Mathf.Sign(s.wheelThrottle) + Math.Sign(curSpeed) == 0) { // StabilityControl && traction > 50 && -//// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, true); - brake = true; -// foreach (Part p in wheels) { -// if (p.GetModule().stressPercent >= 0.01) { // #TODO needs adaptive braking -// brake = false; -// break; -// } -// } - } -//// else if (!StabilityControl || traction <= 50 || speedErr > -0.2 || Mathf.Sign(s.wheelThrottle) + Mathf.Sign((float)curSpeed) != 0) { -//// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, (GameSettings.BRAKES.GetKey() && vessel.isActiveVessel)); -//// } - lastThrottle = Mathf.Clamp(s.wheelThrottle, -1, 1); - } - } - - if (StabilityControl) - { - if (!core.attitude.users.Contains(this)) - { - core.attitude.users.Add(this); -// line.enabled = true; - } -// float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, vessel.CoM) / 900f; -// line.SetPosition(0, vessel.CoM); -// line.SetPosition(1, vessel.CoM + hit.normal * 5); -// line.SetWidth(0, scale + 0.1f); - var fSpeed = (float)curSpeed; -// if (Mathf.Abs(fSpeed) >= turnSpeed * 0.75) { - Vector3 fwd = (Vector3)(traction > 0 ? // V when the speed is low go for the vessels forward, else with a bit of velocity -// ((Mathf.Abs(fSpeed) <= turnSpeed ? vesselState.forward : vesselState.surfaceVelocity / 4) - vessel.transform.right * s.wheelSteer) * Mathf.Sign(fSpeed) : -// // ^ and then add the steering - vesselState.forward * 4 - vessel.transform.right * s.wheelSteer * Mathf.Sign(fSpeed) : // and then add the steering - vesselState.surfaceVelocity); // in the air so follow velocity - Vector3.OrthoNormalize(ref norm, ref fwd); - var quat = Quaternion.LookRotation(fwd, norm); - -// if (traction > 0 || speed <= turnSpeed) { -// var u = new Vector3(0, 1, 0); -// -// var q = FlightGlobals.ship_rotation; -// var q_s = quat; -// -// var q_u = new Quaternion(u.x, u.y, u.z, 0); -// var a = Quaternion.Dot(q, q_s * q_u); -// var q_qs = Quaternion.Dot(q, q_s); -// var b = (a == 0) ? Math.Sign(q_qs) : (q_qs / a); -// var g = b / Mathf.Sqrt((b * b) + 1); -// var gu = Mathf.Sqrt(1 - (g * g)) * u; -// var q_d = new Quaternion() { w = g, x = gu.x, y = gu.y, z = gu.z }; -// var n = q_s * q_d; -// -// quat = n; -// } - if (vesselState.torqueAvailable.sqrMagnitude > 0) - core.attitude.attitudeTo(quat, AttitudeReference.INERTIAL, this); -// } - } - - if (BrakeOnEnergyDepletion) - { - var batteries = vessel.Parts.FindAll(p => p.Resources.Contains(PartResourceLibrary.ElectricityHashcode) && p.Resources.Get(PartResourceLibrary.ElectricityHashcode).flowState); - var energyLeft = batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).amount) / batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).maxAmount); - var openSolars = vessel.mainBody.atmosphere && // true if in atmosphere and there are breakable solarpanels that aren't broken nor retracted - vessel.FindPartModulesImplementing().FindAll(p => p.isBreakable && p.deployState != ModuleDeployablePart.DeployState.BROKEN && - p.deployState != ModuleDeployablePart.DeployState.RETRACTED).Count > 0; - - if (openSolars && energyLeft > 0.99) - { - vessel.FindPartModulesImplementing().FindAll(p => p.isBreakable && - p.deployState == ModuleDeployablePart.DeployState.EXTENDED).ForEach(p => p.Retract()); - } - - if (energyLeft < 0.05 && Math.Sign(s.wheelThrottle) + Math.Sign(curSpeed) != 0) { s.wheelThrottle = 0; } // save remaining energy by not using it for acceleration - if (openSolars || energyLeft < 0.03) { tgtSpeed = 0; } - - if (curSpeed < brakeSpeedLimit && (energyLeft < 0.05 || openSolars)) - { - brake = true; - } - - if (curSpeed < 0.1 && energyLeft < 0.05 && !waitingForDaylight && - vessel.FindPartModulesImplementing().FindAll(p => p.deployState == ModuleDeployablePart.DeployState.EXTENDED).Count > 0) - { - waitingForDaylight = true; - } - } - -// brake = brake && (s.wheelThrottle == 0); // release brake if the user or AP want to drive - if (s.wheelThrottle != 0 && (Math.Sign(s.wheelThrottle) + Math.Sign(curSpeed) != 0 || curSpeed < 1)) - { - brake = false; // the AP or user want to drive into the direction of momentum so release the brake - } - - if (vessel.isActiveVessel) - { - if (GameSettings.BRAKES.GetKeyUp()) - { - brake = false; // release the brakes if the user lets go of them - } - if (GameSettings.BRAKES.GetKey()) - { - brake = true; // brake if the user brakes and we aren't about to flip - } - } - - tractionLimit = (double)Mathf.Clamp((float)tractionLimit, 0, 100); - vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, brake && (StabilityControl && (ControlHeading || ControlSpeed) ? traction >= tractionLimit : true)); - // only let go of the brake when losing traction if the AP is driving, otherwise assume the player knows when to let go of it - // also to not constantly turn off the parking brake from going over a small bump - if (brake && curSpeed < 0.1) { s.wheelThrottle = 0; } - } - - public override void OnFixedUpdate() - { - if (!core.GetComputerModule().enabled) - { - Waypoints.ForEach(wp => wp.Update()); // update waypoints unless the waypoint window is (hopefully) doing that already - } - - if (orbit != null && lastBody != orbit.referenceBody) { lastBody = orbit.referenceBody; } - - headingPID.Kp = hPIDp; - headingPID.Ki = hPIDi; - headingPID.Kd = hPIDd; - speedPID.Kp = sPIDp; - speedPID.Ki = sPIDi; - speedPID.Kd = sPIDd; - - if (lastETA + 0.2 < DateTime.Now.TimeOfDay.TotalSeconds) - { - etaSpeed.value = curSpeed; - lastETA = DateTime.Now.TimeOfDay.TotalSeconds; - } - - if (!core.GetComputerModule().enabled) - { - core.GetComputerModule().OnUpdate(); // update users for Stability Control, Brake on Eject and Brake on Energy Depletion - } - } - - public override void OnUpdate() - { - if (WarpToDaylight && waitingForDaylight && vessel.isActiveVessel) - { - var batteries = vessel.Parts.FindAll(p => p.Resources.Contains(PartResourceLibrary.ElectricityHashcode) && p.Resources.Get(PartResourceLibrary.ElectricityHashcode).flowState); - var energyLeft = batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).amount) / batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).maxAmount); - - if (waitingForDaylight) - { - if (vessel.FindPartModulesImplementing().FindAll(p => p.deployState == ModuleDeployablePart.DeployState.EXTENDED).Count == 0) - { - waitingForDaylight = false; - } - core.warp.WarpRegularAtRate(energyLeft < 0.9 ? 1000 : 50); - if (energyLeft > 0.99) - { - waitingForDaylight = false; - core.warp.MinimumWarp(false); - } - } - } - else if (!WarpToDaylight && waitingForDaylight) - { - waitingForDaylight = false; - } - - if (!core.GetComputerModule().enabled) - { - core.GetComputerModule().OnUpdate(); // update users for Stability Control, Brake on Eject and Brake on Energy Depletion - } - - if (!StabilityControl && core.attitude.users.Contains(this)) - { - core.attitude.attitudeDeactivate(); - core.attitude.users.Remove(this); - } - } - - public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnLoad(local, type, global); - - if (local != null) - { - var wps = local.GetNode("Waypoints"); - if (wps != null && wps.HasNode("Waypoint")) - { - int.TryParse(wps.GetValue("Index"), out WaypointIndex); - Waypoints.Clear(); - foreach (ConfigNode cn in wps.GetNodes("Waypoint")) - { - Waypoints.Add(new MechJebWaypoint(cn)); - } - } - } - } - - public override void OnSave(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnSave(local, type, global); - - if (local == null) return; - - if (local.HasNode("Waypoints")) { local.RemoveNode("Waypoints"); } - if (Waypoints.Count > 0) { - ConfigNode cn = local.AddNode("Waypoints"); - cn.AddValue("Index", WaypointIndex); - foreach (MechJebWaypoint wp in Waypoints) { - cn.AddNode(wp.ToConfigNode()); - } - } - } - - public MechJebModuleRoverController(MechJebCore core) : base(core) { } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace MuMech +{ + public class MechJebModuleRoverController : ComputerModule + { + public List Waypoints = new List(); + public int WaypointIndex = -1; + private CelestialBody lastBody = null; + public bool LoopWaypoints = false; + +// protected bool controlHeading; + [ToggleInfoItem("#MechJeb_ControlHeading", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Heading control + public bool ControlHeading; // TODO: change things back to properties when ConfigNodes can save and load these +// { +// get { return controlHeading; } +// set +// { +// controlHeading = value; +// if (controlHeading || controlSpeed || brakeOnEject) +// { +// users.Add(this); +// } +// else +// { +// users.Remove(this); +// } +// } +// } + + [EditableInfoItem("#MechJeb_Heading", InfoItem.Category.Rover, width = 40), Persistent(pass = (int)Pass.Local)]//Heading + public EditableDouble heading = 0; + +// protected bool controlSpeed = false; + [ToggleInfoItem("#MechJeb_ControlSpeed", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Speed control + public bool ControlSpeed = false; +// { +// get { return controlSpeed; } +// set +// { +// controlSpeed = value; +// if (controlHeading || controlSpeed || brakeOnEject) +// { +// users.Add(this); +// } +// else +// { +// users.Remove(this); +// } +// } +// } + + [EditableInfoItem("#MechJeb_Speed", InfoItem.Category.Rover, width = 40), Persistent(pass = (int)Pass.Local)]//Speed + public EditableDouble speed = 10; + + [ToggleInfoItem("#MechJeb_BrakeOnEject", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Brake on Pilot Eject + public bool BrakeOnEject = false; + + [ToggleInfoItem("#MechJeb_BrakeOnEnergyDepletion", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Brake on Energy Depletion + public bool BrakeOnEnergyDepletion = false; + + [ToggleInfoItem("#MechJeb_WarpToDaylight", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Warp until Day if Depleted + public bool WarpToDaylight = false; + public bool waitingForDaylight = false; + + [ToggleInfoItem("#MechJeb_StabilityControl", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local)]//Stability Control + public bool StabilityControl = false; + + [ToggleInfoItem("#MechJeb_LimitAcceleration", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Local | (int)Pass.Type)]//Limit Acceleration + public bool LimitAcceleration = false; + + public PIDController headingPID; + public PIDController speedPID; + +// private LineRenderer line; + + [EditableInfoItem("#MechJeb_SafeTurnspeed", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Safe turnspeed + public EditableDouble turnSpeed = 3; + [EditableInfoItem("#MechJeb_TerrainLookAhead", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Terrain Look Ahead + public EditableDouble terrainLookAhead = 1.0; + [EditableInfoItem("#MechJeb_BrakeSpeedLimit", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Brake Speed Limit + public EditableDouble brakeSpeedLimit = 0.7; + + [EditableInfoItem("#MechJeb_HeadingPIDP", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID P + public EditableDouble hPIDp = 0.03; // 0.01 + [EditableInfoItem("#MechJeb_HeadingPIDI", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID I + public EditableDouble hPIDi = 0.002; // 0.001 + [EditableInfoItem("#MechJeb_HeadingPIDD", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Heading PID D + public EditableDouble hPIDd = 0.005; + + [EditableInfoItem("#MechJeb_SpeedPIDP", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID P + public EditableDouble sPIDp = 2.0; + [EditableInfoItem("#MechJeb_SpeedPIDI", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID I + public EditableDouble sPIDi = 0.1; + [EditableInfoItem("#MechJeb_SpeedPIDD", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Speed PID D + public EditableDouble sPIDd = 0.001; + + [ValueInfoItem("#MechJeb_SpeedIntAcc", InfoItem.Category.Rover, format = ValueInfoItem.SI, units = "m/s")]//Speed Int Acc + public double speedIntAcc = 0; + + [ValueInfoItem("#MechJeb_Traction", InfoItem.Category.Rover, format = "F0", units = "%")]//Traction + public float traction = 0; + [EditableInfoItem("#MechJeb_TractionBrakeLimit", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Type)]//Traction Brake Limit + public EditableDouble tractionLimit = 75; + + public List wheels = new List(); + public List colliders = new List(); + public Vector3 norm = Vector3.zero; + + public override void OnStart(PartModule.StartState state) + { + headingPID = new PIDController(hPIDp, hPIDi, hPIDd); + speedPID = new PIDController(sPIDp, sPIDi, sPIDd); + + if (HighLogic.LoadedSceneIsFlight && orbit != null) + { + lastBody = orbit.referenceBody; + } + +// MechJebRouteRenderer.NewLineRenderer(ref line); +// line.enabled = false; + + GameEvents.onVesselWasModified.Add(OnVesselModified); + + base.OnStart(state); + } + + public void OnVesselModified(Vessel v) + { + + //TODO : need to look into the new ModuleWheelSteering ModuleWheelMotor ModuleWheelBrakes ModuleWheelBase ModuleWheelSuspension and see what they could bring + + try + { + wheels.Clear(); + wheels.AddRange(vessel.Parts.FindAll(p => p.HasModule() /*&& p.FindModelComponent() != null*/ && p.GetModule().wheelType != WheelType.LEG)); + colliders.Clear(); + wheels.ForEach(p => colliders.AddRange(p.FindModelComponents())); + } + catch (Exception) {} + } + + [ValueInfoItem("#MechJeb_Headingerror", InfoItem.Category.Rover, format = "F1", units = "º")]//Heading error + public double headingErr; + [ValueInfoItem("#MechJeb_Speederror", InfoItem.Category.Rover, format = ValueInfoItem.SI, units = "m/s")]//Speed error + public double speedErr; + public double tgtSpeed; + public MuMech.MovingAverage etaSpeed = new MovingAverage(50); + private double lastETA = 0; + private float lastThrottle = 0; + double curSpeed; + + public double HeadingToPos(Vector3 fromPos, Vector3 toPos) + { + // thanks to Cilph who did most of this since I don't understand anything ~ BR2k + var body = vessel.mainBody; + var fromLon = body.GetLongitude(fromPos); + var toLon = body.GetLongitude(toPos); + var diff = toLon - fromLon; + if (diff < -180) { diff += 360; } + if (diff > 180) { diff -= 360; } + Vector3 myPos = fromPos - body.transform.position; + Vector3 north = body.transform.position + ((float)body.Radius * body.transform.up) - fromPos; + Vector3 tgtPos = toPos - fromPos; + return (diff < 0 ? -1 : 1) * Vector3.Angle(Vector3d.Exclude(myPos.normalized, north.normalized), Vector3.ProjectOnPlane(tgtPos.normalized, myPos.normalized)); + } + + public float TurningSpeed(double speed, double error) + { + return (float)Math.Max(speed / (Math.Abs(error) / 3 > 1 ? Math.Abs(error) / 3 : 1), turnSpeed); + } + + public void CalculateTraction() + { + if (wheels.Count == 0 && colliders.Count == 0) { OnVesselModified(vessel); } + RaycastHit hit; + Physics.Raycast(vessel.CoM + vesselState.surfaceVelocity * terrainLookAhead + vesselState.up * 100, -vesselState.up, out hit, 500, 1 << 15); + norm = hit.normal; + traction = 0; +// foreach (var c in colliders) { +// //WheelHit hit; +// //if (c.GetGroundHit(out hit)) { traction += 1; } +// if (Physics.Raycast(c.transform.position + c.center, -(vesselState.up + norm.normalized) / 2, out hit, c.radius + 1.5f, 1 << 15)) { +// traction += (1.5f - (hit.distance - c.radius)) * 100; +// } +// } + + for (int i = 0; i < wheels.Count; i++) + { + var w = wheels[i]; + if (w.GroundContact) + { + traction += 100; + } + } + +// for (int i = 0; i < colliders.Count; i++) +// { +// var c = colliders[i]; +// if (c.isGrounded) +// { +// traction += 100; +// } +// } + + traction /= wheels.Count; + } + + public override void OnModuleDisabled() + { + if (core.attitude.users.Contains(this)) + { +// line.enabled = false; + core.attitude.attitudeDeactivate(); + core.attitude.users.Remove(this); + } + + base.OnModuleDisabled(); + } + + private float Square(float number) { return number * number; } + private double Square(double number) { return number * number; } + + public override void Drive(FlightCtrlState s) // TODO put the brake in when running out of power to prevent nighttime solar failures on hills, or atleast try to + { // TODO make distance calculation for 'reached' determination consider the rover and waypoint on sealevel to prevent height differences from messing it up -- should be done now? + if (orbit.referenceBody != lastBody) { WaypointIndex = -1; Waypoints.Clear(); } + MechJebWaypoint wp = (WaypointIndex > -1 && WaypointIndex < Waypoints.Count ? Waypoints[WaypointIndex] : null); + + var brake = vessel.ActionGroups[KSPActionGroup.Brakes]; // keep brakes locked if they are + curSpeed = Vector3d.Dot(vesselState.surfaceVelocity, vesselState.forward); + + CalculateTraction(); + speedIntAcc = speedPID.intAccum; + + if (wp != null && wp.Body == orbit.referenceBody) + { + if (ControlHeading) + { + heading.val = Math.Round(HeadingToPos(vessel.CoM, wp.Position), 1); + } + if (ControlSpeed) + { + var nextWP = (WaypointIndex < Waypoints.Count - 1 ? Waypoints[WaypointIndex + 1] : (LoopWaypoints ? Waypoints[0] : null)); + var distance = Vector3.Distance(vessel.CoM, wp.Position); + if (wp.Target != null) { distance += (float)(wp.Target.srfSpeed * curSpeed) / 2; } + // var maxSpeed = (wp.MaxSpeed > 0 ? Math.Min((float)speed, wp.MaxSpeed) : speed); // use waypoints maxSpeed if set and smaller than set the speed or just stick with the set speed + var maxSpeed = (wp.MaxSpeed > 0 ? wp.MaxSpeed : speed); // speed used to go towards the waypoint, using the waypoints maxSpeed if set or just stick with the set speed + var minSpeed = (wp.MinSpeed > 0 ? wp.MinSpeed : + (nextWP != null ? TurningSpeed((nextWP.MaxSpeed > 0 ? nextWP.MaxSpeed : speed), heading - HeadingToPos(wp.Position, nextWP.Position)) : + (distance - wp.Radius > 50 ? turnSpeed.val : 1))); + minSpeed = (wp.Quicksave ? 1 : minSpeed); + // ^ speed used to go through the waypoint, using half the set speed or maxSpeed as minSpeed for routing waypoints (all except the last) + var newSpeed = Math.Min(maxSpeed, Math.Max((distance - wp.Radius) / curSpeed, minSpeed)); // brake when getting closer + newSpeed = (newSpeed > turnSpeed ? TurningSpeed(newSpeed, headingErr) : newSpeed); // reduce speed when turning a lot +// if (LimitAcceleration) { newSpeed = curSpeed + Mathf.Clamp((float)(newSpeed - curSpeed), -1.5f, 0.5f); } +// newSpeed = tgtSpeed + Mathf.Clamp((float)(newSpeed - tgtSpeed), -Time.deltaTime * 8f, Time.deltaTime * 2f); + var radius = Math.Max(wp.Radius, 10); + if (distance < radius) + { + if (WaypointIndex + 1 >= Waypoints.Count) // last waypoint + { + newSpeed = new [] { newSpeed, (distance < radius * 0.8 ? 0 : 1) }.Min(); + // ^ limit speed so it'll only go from 1m/s to full stop when braking to prevent accidents on moons + if (LoopWaypoints) + { + WaypointIndex = 0; + } + else + { + newSpeed = 0; + brake = true; +// tgtSpeed.force(newSpeed); + if (curSpeed < brakeSpeedLimit) + { + if (wp.Quicksave) + { + //if (s.mainThrottle > 0) { s.mainThrottle = 0; } + if (FlightGlobals.ClearToSave() == ClearToSaveStatus.CLEAR) + { + WaypointIndex = -1; + ControlHeading = ControlSpeed = false; + QuickSaveLoad.QuickSave(); + } + } + else + { + WaypointIndex = -1; + ControlHeading = ControlSpeed = false; + } + } +// else { +// Debug.Log("Is this even getting called?"); +// WaypointIndex++; +// } + } + } + else + { + if (wp.Quicksave) + { + //if (s.mainThrottle > 0) { s.mainThrottle = 0; } + newSpeed = 0; +// tgtSpeed.force(newSpeed); + if (curSpeed < brakeSpeedLimit) + { + if (FlightGlobals.ClearToSave() == ClearToSaveStatus.CLEAR) + { + WaypointIndex++; + QuickSaveLoad.QuickSave(); + } + } + } + else + { + WaypointIndex++; + } + } + } + brake = brake || ((s.wheelThrottle == 0 || !vessel.isActiveVessel) && curSpeed < brakeSpeedLimit && newSpeed < brakeSpeedLimit); + // ^ brake if needed to prevent rolling, hopefully + tgtSpeed = (newSpeed >= 0 ? newSpeed : 0); + } + } + + if (ControlHeading) + { + headingPID.intAccum = Mathf.Clamp((float)headingPID.intAccum, -1, 1); + + double instantaneousHeading = vesselState.rotationVesselSurface.eulerAngles.y; + headingErr = MuUtils.ClampDegrees180(instantaneousHeading - heading); + if (s.wheelSteer == s.wheelSteerTrim || FlightGlobals.ActiveVessel != vessel) + { + float limit = (Math.Abs(curSpeed) > turnSpeed ? Mathf.Clamp((float)((turnSpeed + 6) / Square(curSpeed)), 0.1f, 1f) : 1f); + // turnSpeed needs to be higher than curSpeed or it will never steer as much as it could even at 0.2m/s above it + // double act = headingPID.Compute(headingErr * headingErr / 10 * Math.Sign(headingErr)); + double act = headingPID.Compute(headingErr); + if (traction >= tractionLimit) { + s.wheelSteer = Mathf.Clamp((float)act, -limit, limit); + // prevents it from flying above a waypoint and landing with steering at max while still going fast + } + } + } + + // Brake if there is no controler (Pilot eject from seat) + if (BrakeOnEject && vessel.GetReferenceTransformPart() == null) + { + s.wheelThrottle = 0; +// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, true); + brake = true; + } + else if (ControlSpeed) + { + speedPID.intAccum = Mathf.Clamp((float)speedPID.intAccum, -5, 5); + + speedErr = (WaypointIndex == -1 ? speed.val : tgtSpeed) - Vector3d.Dot(vesselState.surfaceVelocity, vesselState.forward); + if (s.wheelThrottle == s.wheelThrottleTrim || FlightGlobals.ActiveVessel != vessel) + { + float act = (float)speedPID.Compute(speedErr); + s.wheelThrottle = Mathf.Clamp(act, -1f, 1f); + // s.wheelThrottle = (!LimitAcceleration ? Mathf.Clamp(act, -1, 1) : // I think I'm using these ( ? : ) a bit too much + // (traction == 0 ? 0 : (act < 0 ? Mathf.Clamp(act, -1f, 1f) : (lastThrottle + Mathf.Clamp(act - lastThrottle, -0.01f, 0.01f)) * (traction < tractionLimit ? -1 : 1)))); +// (lastThrottle + Mathf.Clamp(act, -0.01f, 0.01f))); +// Debug.Log(s.wheelThrottle + Mathf.Clamp(act, -0.01f, 0.01f)); + if (curSpeed < 0 & s.wheelThrottle < 0) { s.wheelThrottle = 0; } // don't go backwards + if (Mathf.Sign(act) + Mathf.Sign(s.wheelThrottle) == 0) { s.wheelThrottle = Mathf.Clamp(act, -1f, 1f); } + if (speedErr < -1 && StabilityControl && Mathf.Sign(s.wheelThrottle) + Math.Sign(curSpeed) == 0) { // StabilityControl && traction > 50 && +//// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, true); + brake = true; +// foreach (Part p in wheels) { +// if (p.GetModule().stressPercent >= 0.01) { // #TODO needs adaptive braking +// brake = false; +// break; +// } +// } + } +//// else if (!StabilityControl || traction <= 50 || speedErr > -0.2 || Mathf.Sign(s.wheelThrottle) + Mathf.Sign((float)curSpeed) != 0) { +//// vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, (GameSettings.BRAKES.GetKey() && vessel.isActiveVessel)); +//// } + lastThrottle = Mathf.Clamp(s.wheelThrottle, -1, 1); + } + } + + if (StabilityControl) + { + if (!core.attitude.users.Contains(this)) + { + core.attitude.users.Add(this); +// line.enabled = true; + } +// float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, vessel.CoM) / 900f; +// line.SetPosition(0, vessel.CoM); +// line.SetPosition(1, vessel.CoM + hit.normal * 5); +// line.SetWidth(0, scale + 0.1f); + var fSpeed = (float)curSpeed; +// if (Mathf.Abs(fSpeed) >= turnSpeed * 0.75) { + Vector3 fwd = (Vector3)(traction > 0 ? // V when the speed is low go for the vessels forward, else with a bit of velocity +// ((Mathf.Abs(fSpeed) <= turnSpeed ? vesselState.forward : vesselState.surfaceVelocity / 4) - vessel.transform.right * s.wheelSteer) * Mathf.Sign(fSpeed) : +// // ^ and then add the steering + vesselState.forward * 4 - vessel.transform.right * s.wheelSteer * Mathf.Sign(fSpeed) : // and then add the steering + vesselState.surfaceVelocity); // in the air so follow velocity + Vector3.OrthoNormalize(ref norm, ref fwd); + var quat = Quaternion.LookRotation(fwd, norm); + +// if (traction > 0 || speed <= turnSpeed) { +// var u = new Vector3(0, 1, 0); +// +// var q = FlightGlobals.ship_rotation; +// var q_s = quat; +// +// var q_u = new Quaternion(u.x, u.y, u.z, 0); +// var a = Quaternion.Dot(q, q_s * q_u); +// var q_qs = Quaternion.Dot(q, q_s); +// var b = (a == 0) ? Math.Sign(q_qs) : (q_qs / a); +// var g = b / Mathf.Sqrt((b * b) + 1); +// var gu = Mathf.Sqrt(1 - (g * g)) * u; +// var q_d = new Quaternion() { w = g, x = gu.x, y = gu.y, z = gu.z }; +// var n = q_s * q_d; +// +// quat = n; +// } + if (vesselState.torqueAvailable.sqrMagnitude > 0) + core.attitude.attitudeTo(quat, AttitudeReference.INERTIAL, this); +// } + } + + if (BrakeOnEnergyDepletion) + { + var batteries = vessel.Parts.FindAll(p => p.Resources.Contains(PartResourceLibrary.ElectricityHashcode) && p.Resources.Get(PartResourceLibrary.ElectricityHashcode).flowState); + var energyLeft = batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).amount) / batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).maxAmount); + var openSolars = vessel.mainBody.atmosphere && // true if in atmosphere and there are breakable solarpanels that aren't broken nor retracted + vessel.FindPartModulesImplementing().FindAll(p => p.isBreakable && p.deployState != ModuleDeployablePart.DeployState.BROKEN && + p.deployState != ModuleDeployablePart.DeployState.RETRACTED).Count > 0; + + if (openSolars && energyLeft > 0.99) + { + vessel.FindPartModulesImplementing().FindAll(p => p.isBreakable && + p.deployState == ModuleDeployablePart.DeployState.EXTENDED).ForEach(p => p.Retract()); + } + + if (energyLeft < 0.05 && Math.Sign(s.wheelThrottle) + Math.Sign(curSpeed) != 0) { s.wheelThrottle = 0; } // save remaining energy by not using it for acceleration + if (openSolars || energyLeft < 0.03) { tgtSpeed = 0; } + + if (curSpeed < brakeSpeedLimit && (energyLeft < 0.05 || openSolars)) + { + brake = true; + } + + if (curSpeed < 0.1 && energyLeft < 0.05 && !waitingForDaylight && + vessel.FindPartModulesImplementing().FindAll(p => p.deployState == ModuleDeployablePart.DeployState.EXTENDED).Count > 0) + { + waitingForDaylight = true; + } + } + +// brake = brake && (s.wheelThrottle == 0); // release brake if the user or AP want to drive + if (s.wheelThrottle != 0 && (Math.Sign(s.wheelThrottle) + Math.Sign(curSpeed) != 0 || curSpeed < 1)) + { + brake = false; // the AP or user want to drive into the direction of momentum so release the brake + } + + if (vessel.isActiveVessel) + { + if (GameSettings.BRAKES.GetKeyUp()) + { + brake = false; // release the brakes if the user lets go of them + } + if (GameSettings.BRAKES.GetKey()) + { + brake = true; // brake if the user brakes and we aren't about to flip + } + } + + tractionLimit = (double)Mathf.Clamp((float)tractionLimit, 0, 100); + vessel.ActionGroups.SetGroup(KSPActionGroup.Brakes, brake && (StabilityControl && (ControlHeading || ControlSpeed) ? traction >= tractionLimit : true)); + // only let go of the brake when losing traction if the AP is driving, otherwise assume the player knows when to let go of it + // also to not constantly turn off the parking brake from going over a small bump + if (brake && curSpeed < 0.1) { s.wheelThrottle = 0; } + } + + public override void OnFixedUpdate() + { + if (!core.GetComputerModule().enabled) + { + Waypoints.ForEach(wp => wp.Update()); // update waypoints unless the waypoint window is (hopefully) doing that already + } + + if (orbit != null && lastBody != orbit.referenceBody) { lastBody = orbit.referenceBody; } + + headingPID.Kp = hPIDp; + headingPID.Ki = hPIDi; + headingPID.Kd = hPIDd; + speedPID.Kp = sPIDp; + speedPID.Ki = sPIDi; + speedPID.Kd = sPIDd; + + if (lastETA + 0.2 < DateTime.Now.TimeOfDay.TotalSeconds) + { + etaSpeed.value = curSpeed; + lastETA = DateTime.Now.TimeOfDay.TotalSeconds; + } + + if (!core.GetComputerModule().enabled) + { + core.GetComputerModule().OnUpdate(); // update users for Stability Control, Brake on Eject and Brake on Energy Depletion + } + } + + public override void OnUpdate() + { + if (WarpToDaylight && waitingForDaylight && vessel.isActiveVessel) + { + var batteries = vessel.Parts.FindAll(p => p.Resources.Contains(PartResourceLibrary.ElectricityHashcode) && p.Resources.Get(PartResourceLibrary.ElectricityHashcode).flowState); + var energyLeft = batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).amount) / batteries.Sum(p => p.Resources.Get(PartResourceLibrary.ElectricityHashcode).maxAmount); + + if (waitingForDaylight) + { + if (vessel.FindPartModulesImplementing().FindAll(p => p.deployState == ModuleDeployablePart.DeployState.EXTENDED).Count == 0) + { + waitingForDaylight = false; + } + core.warp.WarpRegularAtRate(energyLeft < 0.9 ? 1000 : 50); + if (energyLeft > 0.99) + { + waitingForDaylight = false; + core.warp.MinimumWarp(false); + } + } + } + else if (!WarpToDaylight && waitingForDaylight) + { + waitingForDaylight = false; + } + + if (!core.GetComputerModule().enabled) + { + core.GetComputerModule().OnUpdate(); // update users for Stability Control, Brake on Eject and Brake on Energy Depletion + } + + if (!StabilityControl && core.attitude.users.Contains(this)) + { + core.attitude.attitudeDeactivate(); + core.attitude.users.Remove(this); + } + } + + public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnLoad(local, type, global); + + if (local != null) + { + var wps = local.GetNode("Waypoints"); + if (wps != null && wps.HasNode("Waypoint")) + { + int.TryParse(wps.GetValue("Index"), out WaypointIndex); + Waypoints.Clear(); + foreach (ConfigNode cn in wps.GetNodes("Waypoint")) + { + Waypoints.Add(new MechJebWaypoint(cn)); + } + } + } + } + + public override void OnSave(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnSave(local, type, global); + + if (local == null) return; + + if (local.HasNode("Waypoints")) { local.RemoveNode("Waypoints"); } + if (Waypoints.Count > 0) { + ConfigNode cn = local.AddNode("Waypoints"); + cn.AddValue("Index", WaypointIndex); + foreach (MechJebWaypoint wp in Waypoints) { + cn.AddNode(wp.ToConfigNode()); + } + } + } + + public MechJebModuleRoverController(MechJebCore core) : base(core) { } + } +} diff --git a/MechJeb2/MechJebModuleSettings.cs b/MechJeb2/MechJebModuleSettings.cs index be7d65d23..8a6860489 100644 --- a/MechJeb2/MechJebModuleSettings.cs +++ b/MechJeb2/MechJebModuleSettings.cs @@ -1,129 +1,129 @@ -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleSettings : DisplayModule - { - public MechJebModuleSettings(MechJebCore core) : base(core) - { - ShowInEditor = true; - ShowInFlight = true; - } - - // Kept for old conf compatibility - [Persistent(pass = (int)Pass.Global)] - public bool useOldSkin = false; - - [Persistent(pass = (int)Pass.Global)] - public int skinId = 0; - - [Persistent(pass = (int)(Pass.Global))] - public EditableDouble UIScale = 1.0; - - [Persistent(pass = (int)Pass.Global)] - public bool dontUseDropDownMenu = false; - - [ToggleInfoItem("#MechJeb_hideBrakeOnEject", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Hide 'Brake on Eject' in Rover Controller - public bool hideBrakeOnEject = false; - - [ToggleInfoItem("#MechJeb_useTitlebarDragging", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Use only the titlebar for window dragging - public bool useTitlebarDragging = false; - - [ToggleInfoItem("#MechJeb_rssMode", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Module disabling does not kill throttle (RSS/RO) - public bool rssMode = false; - - public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnLoad(local, type, global); - - GuiUtils.SetGUIScale(UIScale.val); - GuiUtils.dontUseDropDownMenu = dontUseDropDownMenu; - - if (useOldSkin) - { - skinId = 1; - useOldSkin = false; - } - } - - protected override void WindowGUI(int windowID) - { - GUILayout.BeginVertical(); - - if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button1")))//"\nRestore factory default settings\n" - { - KSP.IO.FileInfo.CreateForType("mechjeb_settings_global.cfg").Delete(); - if (vessel != null && vessel.vesselName != null) - KSP.IO.FileInfo.CreateForType("mechjeb_settings_type_" + vessel.vesselName + ".cfg").Delete(); - core.ReloadAllComputerModules(); - GuiUtils.SetGUIScale(1); - } - - GUILayout.Label(Localizer.Format("#MechJeb_Settings_label1", (GuiUtils.SkinType)skinId));//"Current skin: <<1>>" - if (GuiUtils.skin == null || skinId != 1) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button2")))//"Use MechJeb 1 GUI skin" - { - GuiUtils.LoadSkin(GuiUtils.SkinType.MechJeb1); - skinId = 1; - } - } - if (GuiUtils.skin == null || skinId != 0) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button3")))//"Use MechJeb 2 GUI skin" - { - GuiUtils.LoadSkin(GuiUtils.SkinType.Default); - skinId = 0; - } - } - if (GuiUtils.skin == null || skinId != 2) - { - if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button4")))//"Use MJ2 Compact GUI skin" - { - GuiUtils.LoadSkin(GuiUtils.SkinType.Compact); - skinId = 2; - } - } - - GUILayout.BeginHorizontal(); - GUILayout.Label(Localizer.Format("#MechJeb_Settings_label2"), GUILayout.ExpandWidth(true));//"UI Scale:" - UIScale.text = GUILayout.TextField(UIScale.text, GUILayout.Width(60)); - GUILayout.EndHorizontal(); - - GuiUtils.SetGUIScale(UIScale.val); - - dontUseDropDownMenu = GUILayout.Toggle(dontUseDropDownMenu, Localizer.Format("#MechJeb_Settings_checkbox1"));//"Replace drop down menu with arrow selector" - GuiUtils.dontUseDropDownMenu = dontUseDropDownMenu; - - MechJebModuleCustomWindowEditor ed = core.GetComputerModule(); - ed.registry.Find(i => i.id == "Toggle:Settings.hideBrakeOnEject").DrawItem(); - - ed.registry.Find(i => i.id == "Toggle:Settings.useTitlebarDragging").DrawItem(); - - ed.registry.Find(i => i.id == "Toggle:Menu.useAppLauncher").DrawItem(); - if (ToolbarManager.ToolbarAvailable || core.GetComputerModule().useAppLauncher) - ed.registry.Find(i => i.id == "Toggle:Menu.hideButton").DrawItem(); - - ed.registry.Find(i => i.id == "General:Menu.MenuPosition").DrawItem(); - - ed.registry.Find(i => i.id == "Toggle:Settings.rssMode").DrawItem(); - - core.warp.activateSASOnWarp = GUILayout.Toggle(core.warp.activateSASOnWarp, Localizer.Format("#MechJeb_Settings_checkbox2"));//"Activate SAS on Warp" - - GUILayout.EndVertical(); - - base.WindowGUI(windowID); - } - - public override string GetName() - { - return Localizer.Format("#MechJeb_Settings_title");//"Settings" - } - - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[] { GUILayout.Width(200), GUILayout.Height(100) }; - } - } -} +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleSettings : DisplayModule + { + public MechJebModuleSettings(MechJebCore core) : base(core) + { + ShowInEditor = true; + ShowInFlight = true; + } + + // Kept for old conf compatibility + [Persistent(pass = (int)Pass.Global)] + public bool useOldSkin = false; + + [Persistent(pass = (int)Pass.Global)] + public int skinId = 0; + + [Persistent(pass = (int)(Pass.Global))] + public EditableDouble UIScale = 1.0; + + [Persistent(pass = (int)Pass.Global)] + public bool dontUseDropDownMenu = false; + + [ToggleInfoItem("#MechJeb_hideBrakeOnEject", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Hide 'Brake on Eject' in Rover Controller + public bool hideBrakeOnEject = false; + + [ToggleInfoItem("#MechJeb_useTitlebarDragging", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Use only the titlebar for window dragging + public bool useTitlebarDragging = false; + + [ToggleInfoItem("#MechJeb_rssMode", InfoItem.Category.Misc), Persistent(pass = (int)Pass.Global)]//Module disabling does not kill throttle (RSS/RO) + public bool rssMode = false; + + public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnLoad(local, type, global); + + GuiUtils.SetGUIScale(UIScale.val); + GuiUtils.dontUseDropDownMenu = dontUseDropDownMenu; + + if (useOldSkin) + { + skinId = 1; + useOldSkin = false; + } + } + + protected override void WindowGUI(int windowID) + { + GUILayout.BeginVertical(); + + if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button1")))//"\nRestore factory default settings\n" + { + KSP.IO.FileInfo.CreateForType("mechjeb_settings_global.cfg").Delete(); + if (vessel != null && vessel.vesselName != null) + KSP.IO.FileInfo.CreateForType("mechjeb_settings_type_" + vessel.vesselName + ".cfg").Delete(); + core.ReloadAllComputerModules(); + GuiUtils.SetGUIScale(1); + } + + GUILayout.Label(Localizer.Format("#MechJeb_Settings_label1", (GuiUtils.SkinType)skinId));//"Current skin: <<1>>" + if (GuiUtils.skin == null || skinId != 1) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button2")))//"Use MechJeb 1 GUI skin" + { + GuiUtils.LoadSkin(GuiUtils.SkinType.MechJeb1); + skinId = 1; + } + } + if (GuiUtils.skin == null || skinId != 0) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button3")))//"Use MechJeb 2 GUI skin" + { + GuiUtils.LoadSkin(GuiUtils.SkinType.Default); + skinId = 0; + } + } + if (GuiUtils.skin == null || skinId != 2) + { + if (GUILayout.Button(Localizer.Format("#MechJeb_Settings_button4")))//"Use MJ2 Compact GUI skin" + { + GuiUtils.LoadSkin(GuiUtils.SkinType.Compact); + skinId = 2; + } + } + + GUILayout.BeginHorizontal(); + GUILayout.Label(Localizer.Format("#MechJeb_Settings_label2"), GUILayout.ExpandWidth(true));//"UI Scale:" + UIScale.text = GUILayout.TextField(UIScale.text, GUILayout.Width(60)); + GUILayout.EndHorizontal(); + + GuiUtils.SetGUIScale(UIScale.val); + + dontUseDropDownMenu = GUILayout.Toggle(dontUseDropDownMenu, Localizer.Format("#MechJeb_Settings_checkbox1"));//"Replace drop down menu with arrow selector" + GuiUtils.dontUseDropDownMenu = dontUseDropDownMenu; + + MechJebModuleCustomWindowEditor ed = core.GetComputerModule(); + ed.registry.Find(i => i.id == "Toggle:Settings.hideBrakeOnEject").DrawItem(); + + ed.registry.Find(i => i.id == "Toggle:Settings.useTitlebarDragging").DrawItem(); + + ed.registry.Find(i => i.id == "Toggle:Menu.useAppLauncher").DrawItem(); + if (ToolbarManager.ToolbarAvailable || core.GetComputerModule().useAppLauncher) + ed.registry.Find(i => i.id == "Toggle:Menu.hideButton").DrawItem(); + + ed.registry.Find(i => i.id == "General:Menu.MenuPosition").DrawItem(); + + ed.registry.Find(i => i.id == "Toggle:Settings.rssMode").DrawItem(); + + core.warp.activateSASOnWarp = GUILayout.Toggle(core.warp.activateSASOnWarp, Localizer.Format("#MechJeb_Settings_checkbox2"));//"Activate SAS on Warp" + + GUILayout.EndVertical(); + + base.WindowGUI(windowID); + } + + public override string GetName() + { + return Localizer.Format("#MechJeb_Settings_title");//"Settings" + } + + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[] { GUILayout.Width(200), GUILayout.Height(100) }; + } + } +} diff --git a/MechJeb2/MechJebModuleSmartASS.cs b/MechJeb2/MechJebModuleSmartASS.cs index 2f1fe2278..29f061788 100644 --- a/MechJeb2/MechJebModuleSmartASS.cs +++ b/MechJeb2/MechJebModuleSmartASS.cs @@ -1,548 +1,548 @@ -using System; -using System.Linq; -using UnityEngine; -using KSP.Localization; -namespace MuMech -{ - public class MechJebModuleSmartASS : DisplayModule - { - public enum Mode - { - ORBITAL, - SURFACE, - TARGET, - ADVANCED, - AUTO - } - public enum Target - { - OFF, - KILLROT, - NODE, - SURFACE, - PROGRADE, - RETROGRADE, - NORMAL_PLUS, - NORMAL_MINUS, - RADIAL_PLUS, - RADIAL_MINUS, - RELATIVE_PLUS, - RELATIVE_MINUS, - TARGET_PLUS, - TARGET_MINUS, - PARALLEL_PLUS, - PARALLEL_MINUS, - ADVANCED, - AUTO, - SURFACE_PROGRADE, - SURFACE_RETROGRADE, - HORIZONTAL_PLUS, - HORIZONTAL_MINUS, - VERTICAL_PLUS - } - public static Mode[] Target2Mode = { Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.SURFACE, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.ADVANCED, Mode.AUTO, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE }; - public static bool[] TargetIsMode = { true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false }; - public static string[] ModeTexts = { Localizer.Format("#MechJeb_SmartASS_button1"), Localizer.Format("#MechJeb_SmartASS_button2"), Localizer.Format("#MechJeb_SmartASS_button3"), Localizer.Format("#MechJeb_SmartASS_button4"),Localizer.Format("#MechJeb_SmartASS_button5") };//"OBT""SURF""TGT""ADV""AUTO" - public static string[] ScriptModeTexts = { Localizer.Format("#MechJeb_SmartASS_button6"), Localizer.Format("#MechJeb_SmartASS_button7"), Localizer.Format("#MechJeb_SmartASS_button8"), Localizer.Format("#MechJeb_SmartASS_button9"), Localizer.Format("#MechJeb_SmartASS_button10") };//"Orbit""Surface""Target""Advanced""Auto" - public static string[] TargetTexts = { Localizer.Format("#MechJeb_SmartASS_button11"), Localizer.Format("#MechJeb_SmartASS_button12"), Localizer.Format("#MechJeb_SmartASS_button13"), Localizer.Format("#MechJeb_SmartASS_button14"), Localizer.Format("#MechJeb_SmartASS_button15"), Localizer.Format("#MechJeb_SmartASS_button16"), Localizer.Format("#MechJeb_SmartASS_button17"), Localizer.Format("#MechJeb_SmartASS_button18"), Localizer.Format("#MechJeb_SmartASS_button19"), Localizer.Format("#MechJeb_SmartASS_button20"), Localizer.Format("#MechJeb_SmartASS_button21"), Localizer.Format("#MechJeb_SmartASS_button22"), Localizer.Format("#MechJeb_SmartASS_button23"), Localizer.Format("#MechJeb_SmartASS_button24"), Localizer.Format("#MechJeb_SmartASS_button25"), Localizer.Format("#MechJeb_SmartASS_button26"), Localizer.Format("#MechJeb_SmartASS_button27"), Localizer.Format("#MechJeb_SmartASS_button28"), Localizer.Format("#MechJeb_SmartASS_button29"), Localizer.Format("#MechJeb_SmartASS_button30"), Localizer.Format("#MechJeb_SmartASS_button31"), Localizer.Format("#MechJeb_SmartASS_button32"), Localizer.Format("#MechJeb_SmartASS_button33") };//"OFF""KILL\nROT""NODE""SURF""PRO\nGRAD" "RETR\nGRAD""NML\n+""NML\n-""RAD\n+""RAD\n-""RVEL\n+""RVEL\n-""TGT\n+""TGT\n-""PAR\n+""PAR\n-""ADV""AUTO""SVEL\n+""SVEL\n-""HVEL\n+""HVEL\n-""UP" - public static string[] ScriptTargetTexts = { Localizer.Format("#MechJeb_SmartASS_button34"), Localizer.Format("#MechJeb_SmartASS_button35"), Localizer.Format("#MechJeb_SmartASS_button36"), Localizer.Format("#MechJeb_SmartASS_button37"), Localizer.Format("#MechJeb_SmartASS_button38"), Localizer.Format("#MechJeb_SmartASS_button39"), Localizer.Format("#MechJeb_SmartASS_button40"), Localizer.Format("#MechJeb_SmartASS_button41"), Localizer.Format("#MechJeb_SmartASS_button42"), Localizer.Format("#MechJeb_SmartASS_button43"), Localizer.Format("#MechJeb_SmartASS_button44"), Localizer.Format("#MechJeb_SmartASS_button45"), Localizer.Format("#MechJeb_SmartASS_button46"), Localizer.Format("#MechJeb_SmartASS_button47"), Localizer.Format("#MechJeb_SmartASS_button48"), Localizer.Format("#MechJeb_SmartASS_button49"), Localizer.Format("#MechJeb_SmartASS_button50"), Localizer.Format("#MechJeb_SmartASS_button51"), Localizer.Format("#MechJeb_SmartASS_button52"), Localizer.Format("#MechJeb_SmartASS_button53"), Localizer.Format("#MechJeb_SmartASS_button54"), Localizer.Format("#MechJeb_SmartASS_button55"),Localizer.Format("#MechJeb_SmartASS_button56") };//"Off""Kill Rotation""Node""Surface""Prograde""Retrograde""Normal+""Normal-""Radial+""Radial-""Relative Velocity+""Relative Velocity-""Target+""Target-""Parallel+""Parallel-""Advanced""Auto""Surface Velocity+""Surface Velocity-""Horizontal Velocity+""Horizontal Velocity-""Up" - public static string[] ReferenceTexts = Enum.GetNames(typeof(AttitudeReference)); - public static string[] directionTexts = Enum.GetNames(typeof(Vector6.Direction)); - - public static GUIStyle btNormal, btActive, btAuto; - - [Persistent(pass = (int)Pass.Local)] - public Mode mode = Mode.ORBITAL; - [Persistent(pass = (int)Pass.Local)] - public Target target = Target.OFF; - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfHdg = new EditableDouble(90); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfPit = new EditableDouble(90); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfRol = new EditableDouble(0); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfVelYaw = new EditableDouble(0); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfVelPit = new EditableDouble(0); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble srfVelRol = new EditableDouble(0); - [Persistent(pass = (int)Pass.Local)] - public EditableDouble rol = new EditableDouble(0); - [Persistent(pass = (int)Pass.Local)] - public AttitudeReference advReference = AttitudeReference.INERTIAL; - [Persistent(pass = (int)Pass.Local)] - public Vector6.Direction advDirection = Vector6.Direction.FORWARD; - [Persistent(pass = (int)Pass.Local)] - public Boolean forceRol = false; - - [Persistent(pass = (int)Pass.Local)] - public Boolean forcePitch = true; - - [Persistent(pass = (int)Pass.Local)] - public Boolean forceYaw = true; - - - [Persistent(pass = (int)Pass.Global)] - public bool autoDisableSmartASS = true; - [GeneralInfoItem("#MechJeb_DisableSmartACSAutomatically", InfoItem.Category.Misc)]//Disable SmartACS automatically - public void AutoDisableSmartASS() - { - autoDisableSmartASS = GUILayout.Toggle(autoDisableSmartASS, core.eduMode ? Localizer.Format("#MechJeb_SmartASS_checkbox1") :Localizer.Format("#MechJeb_SmartASS_checkbox2") );//"Disable SmartACS automatically":"Disable SmartASS automatically" - } - - public MechJebModuleSmartASS(MechJebCore core) : base(core) { } - - protected void ModeButton(Mode bt) - { - if (GUILayout.Button(ModeTexts[(int)bt], (mode == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) - { - mode = bt; - } - } - - protected void TargetButton(Target bt) - { - if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) - { - target = bt; - Engage(); - } - } - - protected void TargetButtonNoEngage(Target bt) - { - if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) - { - target = bt; - } - } - - protected void ForceRoll() - { - GUILayout.BeginHorizontal(); - bool _forceRol = forceRol; - forceRol = GUILayout.Toggle(forceRol, Localizer.Format("#MechJeb_SmartASS_checkbox3"), GUILayout.ExpandWidth(false));//"Force Roll :" - if (_forceRol != forceRol) - { - Engage(); - } - rol.text = GUILayout.TextField(rol.text, GUILayout.Width(30)); - GUILayout.Label("°", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnLoad(local, type, global); - if (target != Target.OFF) - Engage(false); - } - - - protected override void WindowGUI(int windowID) - { - if (btNormal == null) - { - btNormal = new GUIStyle(GUI.skin.button); - btNormal.normal.textColor = btNormal.focused.textColor = Color.white; - btNormal.hover.textColor = btNormal.active.textColor = Color.yellow; - btNormal.onNormal.textColor = btNormal.onFocused.textColor = btNormal.onHover.textColor = btNormal.onActive.textColor = Color.green; - btNormal.padding = new RectOffset(8, 8, 8, 8); - - btActive = new GUIStyle(btNormal); - btActive.active = btActive.onActive; - btActive.normal = btActive.onNormal; - btActive.onFocused = btActive.focused; - btActive.hover = btActive.onHover; - - btAuto = new GUIStyle(btNormal); - btAuto.normal.textColor = Color.red; - btAuto.onActive = btAuto.onFocused = btAuto.onHover = btAuto.onNormal = btAuto.active = btAuto.focused = btAuto.hover = btAuto.normal; - } - - // If any other module use the attitude controler then let them do it - if (core.attitude.enabled && core.attitude.users.Count(u => !this.Equals(u)) > 0) - { - if (autoDisableSmartASS) - { - target = Target.OFF; - if (core.attitude.users.Contains(this)) core.attitude.users.Remove(this); // so we don't suddenly turn on when the other autopilot finishes - } - GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button57"), btAuto, GUILayout.ExpandWidth(true));//"AUTO" - } - else - { - GUILayout.BeginVertical(); - - GUILayout.BeginHorizontal(); - TargetButton(Target.OFF); - TargetButton(Target.KILLROT); - if (vessel.patchedConicsUnlocked()) - { - TargetButton(Target.NODE); - } - else - { - GUILayout.Button("-", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); - } - GUILayout.EndHorizontal(); - - GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label1"));//"Mode:" - GUILayout.BeginHorizontal(); - ModeButton(Mode.ORBITAL); - ModeButton(Mode.SURFACE); - ModeButton(Mode.TARGET); - ModeButton(Mode.ADVANCED); - GUILayout.EndHorizontal(); - - switch (mode) - { - case Mode.ORBITAL: - - GUILayout.BeginHorizontal(); - TargetButton(Target.PROGRADE); - TargetButton(Target.NORMAL_PLUS); - TargetButton(Target.RADIAL_PLUS); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - TargetButton(Target.RETROGRADE); - TargetButton(Target.NORMAL_MINUS); - TargetButton(Target.RADIAL_MINUS); - GUILayout.EndHorizontal(); - - ForceRoll(); - - break; - case Mode.SURFACE: - double val = (GameSettings.MODIFIER_KEY.GetKey() ? 5 : 1); // change by 5 if the mod_key is held down, else by 1 - GUILayout.BeginHorizontal(); - TargetButton(Target.SURFACE_PROGRADE); - TargetButton(Target.SURFACE_RETROGRADE); - TargetButtonNoEngage(Target.SURFACE); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - TargetButton(Target.HORIZONTAL_PLUS); - TargetButton(Target.HORIZONTAL_MINUS); - TargetButton(Target.VERTICAL_PLUS); - GUILayout.EndHorizontal(); - if (target == Target.SURFACE) { - bool changed = false; - GUILayout.BeginHorizontal(); - forceYaw = GUILayout.Toggle(forceYaw, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("HDG", srfHdg, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfHdg -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfHdg += val; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfHdg = 0; - changed = true; - } - if (GUILayout.Button("90", GUILayout.Width(35))) { - srfHdg = 90; - changed = true; - } - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - forcePitch = GUILayout.Toggle(forcePitch, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("PIT", srfPit, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfPit -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfPit += val; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfPit = 0; - changed = true; - } - if (GUILayout.Button("90", GUILayout.Width(35))) { - srfPit = 90; - changed = true; - } - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - forceRol = GUILayout.Toggle(forceRol, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("ROL", srfRol, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfRol -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfRol += val; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfRol = 0; - changed = true; - } - if (GUILayout.Button("180", GUILayout.Width(35))) { - srfRol = 180; - changed = true; - } - GUILayout.EndHorizontal(); - if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58"))){// "EXECUTE" - Engage(); - } - if (changed) - { - Engage(false); - } - core.attitude.AxisControl(forcePitch, forceYaw, forceRol); - } else if (target == Target.SURFACE_PROGRADE || target == Target.SURFACE_RETROGRADE) - { - bool changed = false; - GUILayout.BeginHorizontal(); - forceRol = GUILayout.Toggle(forceRol, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("ROL", srfVelRol, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfVelRol -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfVelRol += val; - changed = true; - } - if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { - srfVelRol = -vesselState.vesselRoll.value; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfVelRol = 0; - changed = true; - } - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - forcePitch = GUILayout.Toggle(forcePitch, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("PIT", srfVelPit, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfVelPit -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfVelPit += val; - changed = true; - } - if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { - srfVelPit = vesselState.AoA.value; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfVelPit = 0; - changed = true; - } - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - forceYaw = GUILayout.Toggle(forceYaw, "", GUILayout.ExpandWidth(false)); - GuiUtils.SimpleTextBox("YAW", srfVelYaw, "°", 37); - if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { - srfVelYaw -= val; - changed = true; - } - if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { - srfVelYaw += val; - changed = true; - } - if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { - srfVelYaw = -vesselState.AoS.value; - changed = true; - } - if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { - srfVelYaw = 0; - changed = true; - } - GUILayout.EndHorizontal(); - if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58")))//"EXECUTE" - { - Engage(); - } - if (changed) - { - Engage(false); - } - core.attitude.AxisControl(forcePitch, forceYaw, forceRol); - } - break; - case Mode.TARGET: - if (core.target.NormalTargetExists) - { - GUILayout.BeginHorizontal(); - TargetButton(Target.TARGET_PLUS); - TargetButton(Target.RELATIVE_PLUS); - TargetButton(Target.PARALLEL_PLUS); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - TargetButton(Target.TARGET_MINUS); - TargetButton(Target.RELATIVE_MINUS); - TargetButton(Target.PARALLEL_MINUS); - GUILayout.EndHorizontal(); - - ForceRoll(); - } - else - { - GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label2"));//"Please select a target" - } - break; - case Mode.ADVANCED: - GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label3"));//"Reference:" - advReference = (AttitudeReference)GuiUtils.ComboBox.Box((int)advReference, ReferenceTexts, this); - - GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label4"));//"Direction:" - advDirection = (Vector6.Direction)GuiUtils.ComboBox.Box((int)advDirection, directionTexts, directionTexts); - - ForceRoll(); - - if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58"), btNormal, GUILayout.ExpandWidth(true)))//"EXECUTE" - { - target = Target.ADVANCED; - Engage(); - } - break; - case Mode.AUTO: - break; - } - - GUILayout.EndVertical(); - } - - base.WindowGUI(windowID); - } - - public void Engage(bool resetPID = true) - { - Quaternion attitude = new Quaternion(); - Vector3d direction = Vector3d.zero; - AttitudeReference reference = AttitudeReference.ORBIT; - switch (target) - { - case Target.OFF: - core.attitude.attitudeDeactivate(); - return; - case Target.KILLROT: - core.attitude.attitudeKILLROT = true; - attitude = Quaternion.LookRotation(part.vessel.GetTransform().up, -part.vessel.GetTransform().forward); - reference = AttitudeReference.INERTIAL; - break; - case Target.NODE: - direction = Vector3d.forward; - reference = AttitudeReference.MANEUVER_NODE; - break; - case Target.SURFACE: - attitude = Quaternion.AngleAxis((float)srfHdg, Vector3.up) - * Quaternion.AngleAxis(-(float)srfPit, Vector3.right) - * Quaternion.AngleAxis(-(float)srfRol, Vector3.forward); - reference = AttitudeReference.SURFACE_NORTH; - break; - case Target.PROGRADE: - direction = Vector3d.forward; - reference = AttitudeReference.ORBIT; - break; - case Target.RETROGRADE: - direction = Vector3d.back; - reference = AttitudeReference.ORBIT; - break; - case Target.NORMAL_PLUS: - direction = Vector3d.left; - reference = AttitudeReference.ORBIT; - break; - case Target.NORMAL_MINUS: - direction = Vector3d.right; - reference = AttitudeReference.ORBIT; - break; - case Target.RADIAL_PLUS: - direction = Vector3d.up; - reference = AttitudeReference.ORBIT; - break; - case Target.RADIAL_MINUS: - direction = Vector3d.down; - reference = AttitudeReference.ORBIT; - break; - case Target.RELATIVE_PLUS: - direction = Vector3d.forward; - reference = AttitudeReference.RELATIVE_VELOCITY; - break; - case Target.RELATIVE_MINUS: - direction = Vector3d.back; - reference = AttitudeReference.RELATIVE_VELOCITY; - break; - case Target.TARGET_PLUS: - direction = Vector3d.forward; - reference = AttitudeReference.TARGET; - break; - case Target.TARGET_MINUS: - direction = Vector3d.back; - reference = AttitudeReference.TARGET; - break; - case Target.PARALLEL_PLUS: - direction = Vector3d.forward; - reference = AttitudeReference.TARGET_ORIENTATION; - break; - case Target.PARALLEL_MINUS: - direction = Vector3d.back; - reference = AttitudeReference.TARGET_ORIENTATION; - break; - case Target.ADVANCED: - direction = Vector6.directions[(int)advDirection]; - reference = advReference; - break; - case Target.SURFACE_PROGRADE: - attitude = Quaternion.AngleAxis(-(float)srfVelRol, Vector3.forward) * - Quaternion.AngleAxis(-(float)srfVelPit, Vector3.right) * - Quaternion.AngleAxis((float)srfVelYaw, Vector3.up); - reference = AttitudeReference.SURFACE_VELOCITY; - break; - case Target.SURFACE_RETROGRADE: - attitude = Quaternion.AngleAxis((float)srfVelRol + 180, Vector3.forward) * - Quaternion.AngleAxis(-(float)srfVelPit + 180, Vector3.right) * - Quaternion.AngleAxis((float)srfVelYaw, Vector3.up); - reference = AttitudeReference.SURFACE_VELOCITY; - break; - case Target.HORIZONTAL_PLUS: - direction = Vector3d.forward; - reference = AttitudeReference.SURFACE_HORIZONTAL; - break; - case Target.HORIZONTAL_MINUS: - direction = Vector3d.back; - reference = AttitudeReference.SURFACE_HORIZONTAL; - break; - case Target.VERTICAL_PLUS: - direction = Vector3d.up; - reference = AttitudeReference.SURFACE_NORTH; - break; - default: - return; - } - - if (forceRol && direction != Vector3d.zero) - { - attitude = Quaternion.LookRotation(direction, Vector3d.up) * Quaternion.AngleAxis(-(float)rol, Vector3d.forward); - direction = Vector3d.zero; - } - - if (direction != Vector3d.zero) - core.attitude.attitudeTo(direction, reference, this); - else - core.attitude.attitudeTo(attitude, reference, this); - - if (resetPID) { core.attitude.Controller.Reset(); } - } - - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[] { GUILayout.Width(180), GUILayout.Height(100) }; - } - - public override string GetName() - { - return core.eduMode ? Localizer.Format("#MechJeb_SmartACS_title") : Localizer.Format("#MechJeb_SmartASS_title");//"Smart A.C.S.":"Smart A.S.S." - } - } -} +using System; +using System.Linq; +using UnityEngine; +using KSP.Localization; +namespace MuMech +{ + public class MechJebModuleSmartASS : DisplayModule + { + public enum Mode + { + ORBITAL, + SURFACE, + TARGET, + ADVANCED, + AUTO + } + public enum Target + { + OFF, + KILLROT, + NODE, + SURFACE, + PROGRADE, + RETROGRADE, + NORMAL_PLUS, + NORMAL_MINUS, + RADIAL_PLUS, + RADIAL_MINUS, + RELATIVE_PLUS, + RELATIVE_MINUS, + TARGET_PLUS, + TARGET_MINUS, + PARALLEL_PLUS, + PARALLEL_MINUS, + ADVANCED, + AUTO, + SURFACE_PROGRADE, + SURFACE_RETROGRADE, + HORIZONTAL_PLUS, + HORIZONTAL_MINUS, + VERTICAL_PLUS + } + public static Mode[] Target2Mode = { Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.SURFACE, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.ORBITAL, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.TARGET, Mode.ADVANCED, Mode.AUTO, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE, Mode.SURFACE }; + public static bool[] TargetIsMode = { true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false }; + public static string[] ModeTexts = { Localizer.Format("#MechJeb_SmartASS_button1"), Localizer.Format("#MechJeb_SmartASS_button2"), Localizer.Format("#MechJeb_SmartASS_button3"), Localizer.Format("#MechJeb_SmartASS_button4"),Localizer.Format("#MechJeb_SmartASS_button5") };//"OBT""SURF""TGT""ADV""AUTO" + public static string[] ScriptModeTexts = { Localizer.Format("#MechJeb_SmartASS_button6"), Localizer.Format("#MechJeb_SmartASS_button7"), Localizer.Format("#MechJeb_SmartASS_button8"), Localizer.Format("#MechJeb_SmartASS_button9"), Localizer.Format("#MechJeb_SmartASS_button10") };//"Orbit""Surface""Target""Advanced""Auto" + public static string[] TargetTexts = { Localizer.Format("#MechJeb_SmartASS_button11"), Localizer.Format("#MechJeb_SmartASS_button12"), Localizer.Format("#MechJeb_SmartASS_button13"), Localizer.Format("#MechJeb_SmartASS_button14"), Localizer.Format("#MechJeb_SmartASS_button15"), Localizer.Format("#MechJeb_SmartASS_button16"), Localizer.Format("#MechJeb_SmartASS_button17"), Localizer.Format("#MechJeb_SmartASS_button18"), Localizer.Format("#MechJeb_SmartASS_button19"), Localizer.Format("#MechJeb_SmartASS_button20"), Localizer.Format("#MechJeb_SmartASS_button21"), Localizer.Format("#MechJeb_SmartASS_button22"), Localizer.Format("#MechJeb_SmartASS_button23"), Localizer.Format("#MechJeb_SmartASS_button24"), Localizer.Format("#MechJeb_SmartASS_button25"), Localizer.Format("#MechJeb_SmartASS_button26"), Localizer.Format("#MechJeb_SmartASS_button27"), Localizer.Format("#MechJeb_SmartASS_button28"), Localizer.Format("#MechJeb_SmartASS_button29"), Localizer.Format("#MechJeb_SmartASS_button30"), Localizer.Format("#MechJeb_SmartASS_button31"), Localizer.Format("#MechJeb_SmartASS_button32"), Localizer.Format("#MechJeb_SmartASS_button33") };//"OFF""KILL\nROT""NODE""SURF""PRO\nGRAD" "RETR\nGRAD""NML\n+""NML\n-""RAD\n+""RAD\n-""RVEL\n+""RVEL\n-""TGT\n+""TGT\n-""PAR\n+""PAR\n-""ADV""AUTO""SVEL\n+""SVEL\n-""HVEL\n+""HVEL\n-""UP" + public static string[] ScriptTargetTexts = { Localizer.Format("#MechJeb_SmartASS_button34"), Localizer.Format("#MechJeb_SmartASS_button35"), Localizer.Format("#MechJeb_SmartASS_button36"), Localizer.Format("#MechJeb_SmartASS_button37"), Localizer.Format("#MechJeb_SmartASS_button38"), Localizer.Format("#MechJeb_SmartASS_button39"), Localizer.Format("#MechJeb_SmartASS_button40"), Localizer.Format("#MechJeb_SmartASS_button41"), Localizer.Format("#MechJeb_SmartASS_button42"), Localizer.Format("#MechJeb_SmartASS_button43"), Localizer.Format("#MechJeb_SmartASS_button44"), Localizer.Format("#MechJeb_SmartASS_button45"), Localizer.Format("#MechJeb_SmartASS_button46"), Localizer.Format("#MechJeb_SmartASS_button47"), Localizer.Format("#MechJeb_SmartASS_button48"), Localizer.Format("#MechJeb_SmartASS_button49"), Localizer.Format("#MechJeb_SmartASS_button50"), Localizer.Format("#MechJeb_SmartASS_button51"), Localizer.Format("#MechJeb_SmartASS_button52"), Localizer.Format("#MechJeb_SmartASS_button53"), Localizer.Format("#MechJeb_SmartASS_button54"), Localizer.Format("#MechJeb_SmartASS_button55"),Localizer.Format("#MechJeb_SmartASS_button56") };//"Off""Kill Rotation""Node""Surface""Prograde""Retrograde""Normal+""Normal-""Radial+""Radial-""Relative Velocity+""Relative Velocity-""Target+""Target-""Parallel+""Parallel-""Advanced""Auto""Surface Velocity+""Surface Velocity-""Horizontal Velocity+""Horizontal Velocity-""Up" + public static string[] ReferenceTexts = Enum.GetNames(typeof(AttitudeReference)); + public static string[] directionTexts = Enum.GetNames(typeof(Vector6.Direction)); + + public static GUIStyle btNormal, btActive, btAuto; + + [Persistent(pass = (int)Pass.Local)] + public Mode mode = Mode.ORBITAL; + [Persistent(pass = (int)Pass.Local)] + public Target target = Target.OFF; + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfHdg = new EditableDouble(90); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfPit = new EditableDouble(90); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfRol = new EditableDouble(0); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfVelYaw = new EditableDouble(0); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfVelPit = new EditableDouble(0); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble srfVelRol = new EditableDouble(0); + [Persistent(pass = (int)Pass.Local)] + public EditableDouble rol = new EditableDouble(0); + [Persistent(pass = (int)Pass.Local)] + public AttitudeReference advReference = AttitudeReference.INERTIAL; + [Persistent(pass = (int)Pass.Local)] + public Vector6.Direction advDirection = Vector6.Direction.FORWARD; + [Persistent(pass = (int)Pass.Local)] + public Boolean forceRol = false; + + [Persistent(pass = (int)Pass.Local)] + public Boolean forcePitch = true; + + [Persistent(pass = (int)Pass.Local)] + public Boolean forceYaw = true; + + + [Persistent(pass = (int)Pass.Global)] + public bool autoDisableSmartASS = true; + [GeneralInfoItem("#MechJeb_DisableSmartACSAutomatically", InfoItem.Category.Misc)]//Disable SmartACS automatically + public void AutoDisableSmartASS() + { + autoDisableSmartASS = GUILayout.Toggle(autoDisableSmartASS, core.eduMode ? Localizer.Format("#MechJeb_SmartASS_checkbox1") :Localizer.Format("#MechJeb_SmartASS_checkbox2") );//"Disable SmartACS automatically":"Disable SmartASS automatically" + } + + public MechJebModuleSmartASS(MechJebCore core) : base(core) { } + + protected void ModeButton(Mode bt) + { + if (GUILayout.Button(ModeTexts[(int)bt], (mode == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) + { + mode = bt; + } + } + + protected void TargetButton(Target bt) + { + if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) + { + target = bt; + Engage(); + } + } + + protected void TargetButtonNoEngage(Target bt) + { + if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) + { + target = bt; + } + } + + protected void ForceRoll() + { + GUILayout.BeginHorizontal(); + bool _forceRol = forceRol; + forceRol = GUILayout.Toggle(forceRol, Localizer.Format("#MechJeb_SmartASS_checkbox3"), GUILayout.ExpandWidth(false));//"Force Roll :" + if (_forceRol != forceRol) + { + Engage(); + } + rol.text = GUILayout.TextField(rol.text, GUILayout.Width(30)); + GUILayout.Label("°", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnLoad(local, type, global); + if (target != Target.OFF) + Engage(false); + } + + + protected override void WindowGUI(int windowID) + { + if (btNormal == null) + { + btNormal = new GUIStyle(GUI.skin.button); + btNormal.normal.textColor = btNormal.focused.textColor = Color.white; + btNormal.hover.textColor = btNormal.active.textColor = Color.yellow; + btNormal.onNormal.textColor = btNormal.onFocused.textColor = btNormal.onHover.textColor = btNormal.onActive.textColor = Color.green; + btNormal.padding = new RectOffset(8, 8, 8, 8); + + btActive = new GUIStyle(btNormal); + btActive.active = btActive.onActive; + btActive.normal = btActive.onNormal; + btActive.onFocused = btActive.focused; + btActive.hover = btActive.onHover; + + btAuto = new GUIStyle(btNormal); + btAuto.normal.textColor = Color.red; + btAuto.onActive = btAuto.onFocused = btAuto.onHover = btAuto.onNormal = btAuto.active = btAuto.focused = btAuto.hover = btAuto.normal; + } + + // If any other module use the attitude controler then let them do it + if (core.attitude.enabled && core.attitude.users.Count(u => !this.Equals(u)) > 0) + { + if (autoDisableSmartASS) + { + target = Target.OFF; + if (core.attitude.users.Contains(this)) core.attitude.users.Remove(this); // so we don't suddenly turn on when the other autopilot finishes + } + GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button57"), btAuto, GUILayout.ExpandWidth(true));//"AUTO" + } + else + { + GUILayout.BeginVertical(); + + GUILayout.BeginHorizontal(); + TargetButton(Target.OFF); + TargetButton(Target.KILLROT); + if (vessel.patchedConicsUnlocked()) + { + TargetButton(Target.NODE); + } + else + { + GUILayout.Button("-", GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); + } + GUILayout.EndHorizontal(); + + GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label1"));//"Mode:" + GUILayout.BeginHorizontal(); + ModeButton(Mode.ORBITAL); + ModeButton(Mode.SURFACE); + ModeButton(Mode.TARGET); + ModeButton(Mode.ADVANCED); + GUILayout.EndHorizontal(); + + switch (mode) + { + case Mode.ORBITAL: + + GUILayout.BeginHorizontal(); + TargetButton(Target.PROGRADE); + TargetButton(Target.NORMAL_PLUS); + TargetButton(Target.RADIAL_PLUS); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + TargetButton(Target.RETROGRADE); + TargetButton(Target.NORMAL_MINUS); + TargetButton(Target.RADIAL_MINUS); + GUILayout.EndHorizontal(); + + ForceRoll(); + + break; + case Mode.SURFACE: + double val = (GameSettings.MODIFIER_KEY.GetKey() ? 5 : 1); // change by 5 if the mod_key is held down, else by 1 + GUILayout.BeginHorizontal(); + TargetButton(Target.SURFACE_PROGRADE); + TargetButton(Target.SURFACE_RETROGRADE); + TargetButtonNoEngage(Target.SURFACE); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + TargetButton(Target.HORIZONTAL_PLUS); + TargetButton(Target.HORIZONTAL_MINUS); + TargetButton(Target.VERTICAL_PLUS); + GUILayout.EndHorizontal(); + if (target == Target.SURFACE) { + bool changed = false; + GUILayout.BeginHorizontal(); + forceYaw = GUILayout.Toggle(forceYaw, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("HDG", srfHdg, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfHdg -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfHdg += val; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfHdg = 0; + changed = true; + } + if (GUILayout.Button("90", GUILayout.Width(35))) { + srfHdg = 90; + changed = true; + } + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + forcePitch = GUILayout.Toggle(forcePitch, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("PIT", srfPit, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfPit -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfPit += val; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfPit = 0; + changed = true; + } + if (GUILayout.Button("90", GUILayout.Width(35))) { + srfPit = 90; + changed = true; + } + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + forceRol = GUILayout.Toggle(forceRol, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("ROL", srfRol, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfRol -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfRol += val; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfRol = 0; + changed = true; + } + if (GUILayout.Button("180", GUILayout.Width(35))) { + srfRol = 180; + changed = true; + } + GUILayout.EndHorizontal(); + if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58"))){// "EXECUTE" + Engage(); + } + if (changed) + { + Engage(false); + } + core.attitude.AxisControl(forcePitch, forceYaw, forceRol); + } else if (target == Target.SURFACE_PROGRADE || target == Target.SURFACE_RETROGRADE) + { + bool changed = false; + GUILayout.BeginHorizontal(); + forceRol = GUILayout.Toggle(forceRol, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("ROL", srfVelRol, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfVelRol -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfVelRol += val; + changed = true; + } + if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { + srfVelRol = -vesselState.vesselRoll.value; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfVelRol = 0; + changed = true; + } + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + forcePitch = GUILayout.Toggle(forcePitch, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("PIT", srfVelPit, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfVelPit -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfVelPit += val; + changed = true; + } + if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { + srfVelPit = vesselState.AoA.value; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfVelPit = 0; + changed = true; + } + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + forceYaw = GUILayout.Toggle(forceYaw, "", GUILayout.ExpandWidth(false)); + GuiUtils.SimpleTextBox("YAW", srfVelYaw, "°", 37); + if (GUILayout.Button("-", GUILayout.ExpandWidth(false))) { + srfVelYaw -= val; + changed = true; + } + if (GUILayout.Button("+", GUILayout.ExpandWidth(false))) { + srfVelYaw += val; + changed = true; + } + if (GUILayout.Button("CUR", GUILayout.ExpandWidth(false))) { + srfVelYaw = -vesselState.AoS.value; + changed = true; + } + if (GUILayout.Button("0", GUILayout.ExpandWidth(false))) { + srfVelYaw = 0; + changed = true; + } + GUILayout.EndHorizontal(); + if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58")))//"EXECUTE" + { + Engage(); + } + if (changed) + { + Engage(false); + } + core.attitude.AxisControl(forcePitch, forceYaw, forceRol); + } + break; + case Mode.TARGET: + if (core.target.NormalTargetExists) + { + GUILayout.BeginHorizontal(); + TargetButton(Target.TARGET_PLUS); + TargetButton(Target.RELATIVE_PLUS); + TargetButton(Target.PARALLEL_PLUS); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + TargetButton(Target.TARGET_MINUS); + TargetButton(Target.RELATIVE_MINUS); + TargetButton(Target.PARALLEL_MINUS); + GUILayout.EndHorizontal(); + + ForceRoll(); + } + else + { + GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label2"));//"Please select a target" + } + break; + case Mode.ADVANCED: + GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label3"));//"Reference:" + advReference = (AttitudeReference)GuiUtils.ComboBox.Box((int)advReference, ReferenceTexts, this); + + GUILayout.Label(Localizer.Format("#MechJeb_SmartASS_label4"));//"Direction:" + advDirection = (Vector6.Direction)GuiUtils.ComboBox.Box((int)advDirection, directionTexts, directionTexts); + + ForceRoll(); + + if (GUILayout.Button(Localizer.Format("#MechJeb_SmartASS_button58"), btNormal, GUILayout.ExpandWidth(true)))//"EXECUTE" + { + target = Target.ADVANCED; + Engage(); + } + break; + case Mode.AUTO: + break; + } + + GUILayout.EndVertical(); + } + + base.WindowGUI(windowID); + } + + public void Engage(bool resetPID = true) + { + Quaternion attitude = new Quaternion(); + Vector3d direction = Vector3d.zero; + AttitudeReference reference = AttitudeReference.ORBIT; + switch (target) + { + case Target.OFF: + core.attitude.attitudeDeactivate(); + return; + case Target.KILLROT: + core.attitude.attitudeKILLROT = true; + attitude = Quaternion.LookRotation(part.vessel.GetTransform().up, -part.vessel.GetTransform().forward); + reference = AttitudeReference.INERTIAL; + break; + case Target.NODE: + direction = Vector3d.forward; + reference = AttitudeReference.MANEUVER_NODE; + break; + case Target.SURFACE: + attitude = Quaternion.AngleAxis((float)srfHdg, Vector3.up) + * Quaternion.AngleAxis(-(float)srfPit, Vector3.right) + * Quaternion.AngleAxis(-(float)srfRol, Vector3.forward); + reference = AttitudeReference.SURFACE_NORTH; + break; + case Target.PROGRADE: + direction = Vector3d.forward; + reference = AttitudeReference.ORBIT; + break; + case Target.RETROGRADE: + direction = Vector3d.back; + reference = AttitudeReference.ORBIT; + break; + case Target.NORMAL_PLUS: + direction = Vector3d.left; + reference = AttitudeReference.ORBIT; + break; + case Target.NORMAL_MINUS: + direction = Vector3d.right; + reference = AttitudeReference.ORBIT; + break; + case Target.RADIAL_PLUS: + direction = Vector3d.up; + reference = AttitudeReference.ORBIT; + break; + case Target.RADIAL_MINUS: + direction = Vector3d.down; + reference = AttitudeReference.ORBIT; + break; + case Target.RELATIVE_PLUS: + direction = Vector3d.forward; + reference = AttitudeReference.RELATIVE_VELOCITY; + break; + case Target.RELATIVE_MINUS: + direction = Vector3d.back; + reference = AttitudeReference.RELATIVE_VELOCITY; + break; + case Target.TARGET_PLUS: + direction = Vector3d.forward; + reference = AttitudeReference.TARGET; + break; + case Target.TARGET_MINUS: + direction = Vector3d.back; + reference = AttitudeReference.TARGET; + break; + case Target.PARALLEL_PLUS: + direction = Vector3d.forward; + reference = AttitudeReference.TARGET_ORIENTATION; + break; + case Target.PARALLEL_MINUS: + direction = Vector3d.back; + reference = AttitudeReference.TARGET_ORIENTATION; + break; + case Target.ADVANCED: + direction = Vector6.directions[(int)advDirection]; + reference = advReference; + break; + case Target.SURFACE_PROGRADE: + attitude = Quaternion.AngleAxis(-(float)srfVelRol, Vector3.forward) * + Quaternion.AngleAxis(-(float)srfVelPit, Vector3.right) * + Quaternion.AngleAxis((float)srfVelYaw, Vector3.up); + reference = AttitudeReference.SURFACE_VELOCITY; + break; + case Target.SURFACE_RETROGRADE: + attitude = Quaternion.AngleAxis((float)srfVelRol + 180, Vector3.forward) * + Quaternion.AngleAxis(-(float)srfVelPit + 180, Vector3.right) * + Quaternion.AngleAxis((float)srfVelYaw, Vector3.up); + reference = AttitudeReference.SURFACE_VELOCITY; + break; + case Target.HORIZONTAL_PLUS: + direction = Vector3d.forward; + reference = AttitudeReference.SURFACE_HORIZONTAL; + break; + case Target.HORIZONTAL_MINUS: + direction = Vector3d.back; + reference = AttitudeReference.SURFACE_HORIZONTAL; + break; + case Target.VERTICAL_PLUS: + direction = Vector3d.up; + reference = AttitudeReference.SURFACE_NORTH; + break; + default: + return; + } + + if (forceRol && direction != Vector3d.zero) + { + attitude = Quaternion.LookRotation(direction, Vector3d.up) * Quaternion.AngleAxis(-(float)rol, Vector3d.forward); + direction = Vector3d.zero; + } + + if (direction != Vector3d.zero) + core.attitude.attitudeTo(direction, reference, this); + else + core.attitude.attitudeTo(attitude, reference, this); + + if (resetPID) { core.attitude.Controller.Reset(); } + } + + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[] { GUILayout.Width(180), GUILayout.Height(100) }; + } + + public override string GetName() + { + return core.eduMode ? Localizer.Format("#MechJeb_SmartACS_title") : Localizer.Format("#MechJeb_SmartASS_title");//"Smart A.C.S.":"Smart A.S.S." + } + } +} diff --git a/MechJeb2/MechJebModuleSmartRcs.cs b/MechJeb2/MechJebModuleSmartRcs.cs index 38b7ea7ad..a15108e1d 100644 --- a/MechJeb2/MechJebModuleSmartRcs.cs +++ b/MechJeb2/MechJebModuleSmartRcs.cs @@ -1,123 +1,123 @@ -using System.Linq; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - class MechJebModuleSmartRcs : DisplayModule - { - - public enum Target - { - OFF, - ZERO_RVEL, - } - - public static readonly string[] TargetTexts = { Localizer.Format("#MechJeb_SmartRcs_button1"), Localizer.Format("#MechJeb_SmartRcs_button2") };//"OFF", "ZERO RVEL" - - public Target target; - - private static GUIStyle btNormal, btActive, btAuto; - - [Persistent(pass = (int)Pass.Global)] - public bool autoDisableSmartRCS = true; - [GeneralInfoItem("#MechJeb_DisableSmartRcsAutomatically", InfoItem.Category.Misc)]//Disable SmartRcs automatically - public void AutoDisableSmartRCS() - { - autoDisableSmartRCS = GUILayout.Toggle(autoDisableSmartRCS, Localizer.Format("#MechJeb_SmartRcs_checkbox1 "));//"Disable SmartRcs automatically" - } - - protected void TargetButton(Target bt) - { - if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) - { - target = bt; - Engage(); - } - } - - public MechJebModuleSmartRcs(MechJebCore core) : base(core) - { - } - - protected override void WindowGUI(int windowID) - { - - if (btNormal == null) - { - btNormal = new GUIStyle(GUI.skin.button); - btNormal.normal.textColor = btNormal.focused.textColor = Color.white; - btNormal.hover.textColor = btNormal.active.textColor = Color.yellow; - btNormal.onNormal.textColor = - btNormal.onFocused.textColor = btNormal.onHover.textColor = btNormal.onActive.textColor = Color.green; - btNormal.padding = new RectOffset(8, 8, 8, 8); - - btActive = new GUIStyle(btNormal); - btActive.active = btActive.onActive; - btActive.normal = btActive.onNormal; - btActive.onFocused = btActive.focused; - btActive.hover = btActive.onHover; - - btAuto = new GUIStyle(btNormal); - btAuto.normal.textColor = Color.red; - btAuto.onActive = - btAuto.onFocused = btAuto.onHover = btAuto.onNormal = btAuto.active = btAuto.focused = btAuto.hover = btAuto.normal; - } - - // Disable if RCS is used by an other module - if (core.rcs.enabled && core.rcs.users.Count(u => !this.Equals(u)) > 0) - { - if (autoDisableSmartRCS) - { - target = Target.OFF; - if (core.rcs.users.Contains(this)) - core.rcs.users.Remove(this); // so we don't suddenly turn on when the other autopilot finishes - } - GUILayout.Button(Localizer.Format("#MechJeb_SmartRcs_button3"), btAuto, GUILayout.ExpandWidth(true));//"AUTO" - } - else if (core.target.Target == null) - { - GUILayout.Label(Localizer.Format("#MechJeb_SmartRcs_label1"));//"Choose a target" - } - else - { - GUILayout.BeginVertical(); - - TargetButton(Target.OFF); - TargetButton(Target.ZERO_RVEL); - //TargetButton(Target.HOLD_RPOS); - - GUILayout.EndVertical(); - } - core.rcs.rcsThrottle = GUILayout.Toggle(core.rcs.rcsThrottle, Localizer.Format("#MechJeb_SmartRcs_checkbox2"));//" RCS throttle when engines are offline" - core.rcs.rcsForRotation = GUILayout.Toggle(core.rcs.rcsForRotation, Localizer.Format("#MechJeb_SmartRcs_checkbox3"));// " Use RCS for rotation" - base.WindowGUI(windowID); - } - - - public void Engage() - { - switch (target) - { - case Target.OFF: - core.rcs.users.Remove(this); - return; - case Target.ZERO_RVEL: - core.rcs.users.Add(this); - core.rcs.SetTargetRelative(Vector3d.zero); - break; - } - } - - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[] { GUILayout.Width(180), GUILayout.Height(100) }; - } - - public override string GetName() - { - return Localizer.Format("#MechJeb_SmartRcs_title");//"SmartRcs" - } - - } -} +using System.Linq; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + class MechJebModuleSmartRcs : DisplayModule + { + + public enum Target + { + OFF, + ZERO_RVEL, + } + + public static readonly string[] TargetTexts = { Localizer.Format("#MechJeb_SmartRcs_button1"), Localizer.Format("#MechJeb_SmartRcs_button2") };//"OFF", "ZERO RVEL" + + public Target target; + + private static GUIStyle btNormal, btActive, btAuto; + + [Persistent(pass = (int)Pass.Global)] + public bool autoDisableSmartRCS = true; + [GeneralInfoItem("#MechJeb_DisableSmartRcsAutomatically", InfoItem.Category.Misc)]//Disable SmartRcs automatically + public void AutoDisableSmartRCS() + { + autoDisableSmartRCS = GUILayout.Toggle(autoDisableSmartRCS, Localizer.Format("#MechJeb_SmartRcs_checkbox1 "));//"Disable SmartRcs automatically" + } + + protected void TargetButton(Target bt) + { + if (GUILayout.Button(TargetTexts[(int)bt], (target == bt) ? btActive : btNormal, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) + { + target = bt; + Engage(); + } + } + + public MechJebModuleSmartRcs(MechJebCore core) : base(core) + { + } + + protected override void WindowGUI(int windowID) + { + + if (btNormal == null) + { + btNormal = new GUIStyle(GUI.skin.button); + btNormal.normal.textColor = btNormal.focused.textColor = Color.white; + btNormal.hover.textColor = btNormal.active.textColor = Color.yellow; + btNormal.onNormal.textColor = + btNormal.onFocused.textColor = btNormal.onHover.textColor = btNormal.onActive.textColor = Color.green; + btNormal.padding = new RectOffset(8, 8, 8, 8); + + btActive = new GUIStyle(btNormal); + btActive.active = btActive.onActive; + btActive.normal = btActive.onNormal; + btActive.onFocused = btActive.focused; + btActive.hover = btActive.onHover; + + btAuto = new GUIStyle(btNormal); + btAuto.normal.textColor = Color.red; + btAuto.onActive = + btAuto.onFocused = btAuto.onHover = btAuto.onNormal = btAuto.active = btAuto.focused = btAuto.hover = btAuto.normal; + } + + // Disable if RCS is used by an other module + if (core.rcs.enabled && core.rcs.users.Count(u => !this.Equals(u)) > 0) + { + if (autoDisableSmartRCS) + { + target = Target.OFF; + if (core.rcs.users.Contains(this)) + core.rcs.users.Remove(this); // so we don't suddenly turn on when the other autopilot finishes + } + GUILayout.Button(Localizer.Format("#MechJeb_SmartRcs_button3"), btAuto, GUILayout.ExpandWidth(true));//"AUTO" + } + else if (core.target.Target == null) + { + GUILayout.Label(Localizer.Format("#MechJeb_SmartRcs_label1"));//"Choose a target" + } + else + { + GUILayout.BeginVertical(); + + TargetButton(Target.OFF); + TargetButton(Target.ZERO_RVEL); + //TargetButton(Target.HOLD_RPOS); + + GUILayout.EndVertical(); + } + core.rcs.rcsThrottle = GUILayout.Toggle(core.rcs.rcsThrottle, Localizer.Format("#MechJeb_SmartRcs_checkbox2"));//" RCS throttle when engines are offline" + core.rcs.rcsForRotation = GUILayout.Toggle(core.rcs.rcsForRotation, Localizer.Format("#MechJeb_SmartRcs_checkbox3"));// " Use RCS for rotation" + base.WindowGUI(windowID); + } + + + public void Engage() + { + switch (target) + { + case Target.OFF: + core.rcs.users.Remove(this); + return; + case Target.ZERO_RVEL: + core.rcs.users.Add(this); + core.rcs.SetTargetRelative(Vector3d.zero); + break; + } + } + + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[] { GUILayout.Width(180), GUILayout.Height(100) }; + } + + public override string GetName() + { + return Localizer.Format("#MechJeb_SmartRcs_title");//"SmartRcs" + } + + } +} diff --git a/MechJeb2/MechJebModuleSolarPanelController.cs b/MechJeb2/MechJebModuleSolarPanelController.cs index faea2edda..4e4a1ae61 100644 --- a/MechJeb2/MechJebModuleSolarPanelController.cs +++ b/MechJeb2/MechJebModuleSolarPanelController.cs @@ -1,48 +1,48 @@ -using System.Collections.Generic; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleSolarPanelController : MechJebModuleDeployableController - { - public MechJebModuleSolarPanelController(MechJebCore core) - : base(core) - { } - - [GeneralInfoItem("#MechJeb_ToggleSolarPanels", InfoItem.Category.Misc, showInEditor = false)]//Toggle solar panels - public void SolarPanelDeployButton() - { - autoDeploy = GUILayout.Toggle(autoDeploy, Localizer.Format("#MechJeb_SolarPanelDeployButton"));//"Auto-deploy solar panels" - - if (GUILayout.Button(buttonText)) - { - if (ExtendingOrRetracting()) - return; - - if (!extended) - ExtendAll(); - else - RetractAll(); - } - } - - protected override bool isModules(ModuleDeployablePart p) - { - return p is ModuleDeployableSolarPanel; - } - - protected override string getButtonText(DeployablePartState deployablePartState) - { - switch (deployablePartState) - { - case DeployablePartState.EXTENDED: - return Localizer.Format("#MechJeb_SolarPanelDeploy");//"Toggle solar panels (currently extended)" - case DeployablePartState.RETRACTED: - return Localizer.Format("#MechJeb_SolarPanelRetracted");//"Toggle solar panels (currently retracted)" - default: - return Localizer.Format("#MechJeb_SolarPanelToggle");//"Toggle solar panels" - } - } - } -} +using System.Collections.Generic; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleSolarPanelController : MechJebModuleDeployableController + { + public MechJebModuleSolarPanelController(MechJebCore core) + : base(core) + { } + + [GeneralInfoItem("#MechJeb_ToggleSolarPanels", InfoItem.Category.Misc, showInEditor = false)]//Toggle solar panels + public void SolarPanelDeployButton() + { + autoDeploy = GUILayout.Toggle(autoDeploy, Localizer.Format("#MechJeb_SolarPanelDeployButton"));//"Auto-deploy solar panels" + + if (GUILayout.Button(buttonText)) + { + if (ExtendingOrRetracting()) + return; + + if (!extended) + ExtendAll(); + else + RetractAll(); + } + } + + protected override bool isModules(ModuleDeployablePart p) + { + return p is ModuleDeployableSolarPanel; + } + + protected override string getButtonText(DeployablePartState deployablePartState) + { + switch (deployablePartState) + { + case DeployablePartState.EXTENDED: + return Localizer.Format("#MechJeb_SolarPanelDeploy");//"Toggle solar panels (currently extended)" + case DeployablePartState.RETRACTED: + return Localizer.Format("#MechJeb_SolarPanelRetracted");//"Toggle solar panels (currently retracted)" + default: + return Localizer.Format("#MechJeb_SolarPanelToggle");//"Toggle solar panels" + } + } + } +} diff --git a/MechJeb2/MechJebModuleStageStats.cs b/MechJeb2/MechJebModuleStageStats.cs index 5277c5abf..7002a0605 100644 --- a/MechJeb2/MechJebModuleStageStats.cs +++ b/MechJeb2/MechJebModuleStageStats.cs @@ -1,256 +1,256 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using UnityEngine; -using UnityEngine.Profiling; - -namespace MuMech -{ - //Other modules can request that the stage stats be computed by calling RequestUpdate - //This module will then run the stage stats computation in a separate thread, update - //the publicly available atmoStats and vacStats. Then it will disable itself unless - //it got another RequestUpdate in the meantime. - public class MechJebModuleStageStats : ComputerModule - { - public MechJebModuleStageStats(MechJebCore core) : base(core) { } - - [ToggleInfoItem("#MechJeb_DVincludecosinelosses", InfoItem.Category.Thrust, showInEditor = true)]//ΔV include cosine losses - public bool dVLinearThrust = true; - - public FuelFlowSimulation.Stats[] atmoStats = { }; - public FuelFlowSimulation.Stats[] vacStats = { }; - - - // Those are used to store the next result from the thread since we must move result - // to atmoStats/vacStats only in the main thread. - private FuelFlowSimulation.Stats[] newAtmoStats; - private FuelFlowSimulation.Stats[] newVacStats; - private bool resultReady = false; - - public void RequestUpdate(object controller, bool wait = false) - { - users.Add(controller); - updateRequested = true; - - IsResultReady(); - - // In the editor this is our only entry point - if (HighLogic.LoadedSceneIsEditor) - { - TryStartSimulation(); - } - - // wait means the code needs some result to run so we wait if we do not have any result yet - if (wait && atmoStats.Length == 0 && (simulationRunning || TryStartSimulation())) - { - while (simulationRunning) - { - // wait for a sim to be ready. Risked ? - Thread.Sleep(1); - } - IsResultReady(); - } - } - - public CelestialBody editorBody; - public bool liveSLT = true; - public double altSLT = 0; - public double mach = 0; - - protected bool updateRequested = false; - protected bool simulationRunning = false; - protected System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); - - private int needRebuild = 1; - - private FuelFlowSimulation[] sims = { new FuelFlowSimulation(), new FuelFlowSimulation() }; - - long millisecondsBetweenSimulations; - - public override void OnStart(PartModule.StartState state) - { - if (HighLogic.LoadedSceneIsEditor) - { - GameEvents.onEditorShipModified.Add(onEditorShipModified); - GameEvents.onPartCrossfeedStateChange.Add(onPartCrossfeedStateChange); - } - } - - public override void OnDestroy() - { - GameEvents.onEditorShipModified.Remove(onEditorShipModified); - GameEvents.onPartCrossfeedStateChange.Remove(onPartCrossfeedStateChange); - } - - private void onPartCrossfeedStateChange(Part data) - { - setDirty(); - } - - void onEditorShipModified(ShipConstruct data) - { - setDirty(); - } - - void setDirty() - { - // The ship is not really ready in the first frame following the event so we wait 2 - needRebuild = 2; - } - - public override void OnModuleEnabled() - { - millisecondsBetweenSimulations = 0; - stopwatch.Start(); - } - - public override void OnModuleDisabled() - { - stopwatch.Stop(); - stopwatch.Reset(); - } - - public override void OnFixedUpdate() - { - // Check if we have a result ready from the previous physic frame - IsResultReady(); - - TryStartSimulation(); - } - - public override void OnWaitForFixedUpdate() - { - // Check if we managed to get a result while the physic frame was running - IsResultReady(); - } - - public override void OnUpdate() - { - IsResultReady(); - } - - private void IsResultReady() - { - if (resultReady) - { - atmoStats = newAtmoStats; - vacStats = newVacStats; - resultReady = false; - } - } - - private bool TryStartSimulation() - { - if (!simulationRunning && ((HighLogic.LoadedSceneIsEditor && editorBody != null) || (vessel != null))) - { - //We should be running simulations periodically, but one is not running right now. - //Check if enough time has passed since the last one to start a new one: - if (stopwatch.ElapsedMilliseconds > millisecondsBetweenSimulations) - { - if (updateRequested) - { - updateRequested = false; - - stopwatch.Stop(); - stopwatch.Reset(); - - StartSimulation(); - return true; - } - else - { - users.Clear(); - } - } - } - return false; - } - - protected void StartSimulation() - { - Profiler.BeginSample("StartSimulation"); - try - { - simulationRunning = true; - resultReady = false; - stopwatch.Start(); //starts a timer that times how long the simulation takes - - //Create two FuelFlowSimulations, one for vacuum and one for atmosphere - List parts = (HighLogic.LoadedSceneIsEditor ? EditorLogic.fetch.ship.parts : vessel.parts); - - if (HighLogic.LoadedSceneIsEditor) - { - if (needRebuild > 0) - { - PartSet.BuildPartSets(parts, null); - needRebuild--; - } - } - else - { - vessel.UpdateResourceSetsIfDirty(); - } - - Profiler.BeginSample("StartSimulation_Init"); - - sims[0].Init(parts, dVLinearThrust); - sims[1].Init(parts, dVLinearThrust); - - Profiler.EndSample(); - - //Run the simulation in a separate thread - ThreadPool.QueueUserWorkItem(RunSimulation, sims); - //Profiler.BeginSample("StartSimulation_Run"); - //RunSimulation(sims); - //Profiler.EndSample(); - } - catch (Exception e) - { - print("Exception in MechJebModuleStageStats.StartSimulation(): " + e + "\n" + e.StackTrace); - - // Stop timing the simulation - stopwatch.Stop(); - millisecondsBetweenSimulations = 500; - stopwatch.Reset(); - - // Start counting down the time to the next simulation - stopwatch.Start(); - simulationRunning = false; - } - Profiler.EndSample(); - } - - protected void RunSimulation(object o) - { - try - { - CelestialBody simBody = HighLogic.LoadedSceneIsEditor ? editorBody : vessel.mainBody; - - double staticPressureKpa = (HighLogic.LoadedSceneIsEditor || !liveSLT ? (simBody.atmosphere ? simBody.GetPressure(altSLT) : 0) : vessel.staticPressurekPa); - double atmDensity = (HighLogic.LoadedSceneIsEditor || !liveSLT ? simBody.GetDensity(simBody.GetPressure(altSLT), simBody.GetTemperature(0)) : vessel.atmDensity) / 1.225; - double mach = HighLogic.LoadedSceneIsEditor ? this.mach : vessel.mach; - - //Run the simulation - newAtmoStats = sims[0].SimulateAllStages(1.0f, staticPressureKpa, atmDensity, mach); - newVacStats = sims[1].SimulateAllStages(1.0f, 0.0, 0.0, mach); - } - catch (Exception e) - { - print("Exception in MechJebModuleStageStats.RunSimulation(): " + e); - } - - //see how long the simulation took - stopwatch.Stop(); - long millisecondsToCompletion = stopwatch.ElapsedMilliseconds; - stopwatch.Reset(); - - //set the delay before the next simulation - millisecondsBetweenSimulations = 2 * millisecondsToCompletion; - - //start the stopwatch that will count off this delay - stopwatch.Start(); - resultReady = true; - simulationRunning = false; - } - } -} +using System; +using System.Collections.Generic; +using System.Threading; +using UnityEngine; +using UnityEngine.Profiling; + +namespace MuMech +{ + //Other modules can request that the stage stats be computed by calling RequestUpdate + //This module will then run the stage stats computation in a separate thread, update + //the publicly available atmoStats and vacStats. Then it will disable itself unless + //it got another RequestUpdate in the meantime. + public class MechJebModuleStageStats : ComputerModule + { + public MechJebModuleStageStats(MechJebCore core) : base(core) { } + + [ToggleInfoItem("#MechJeb_DVincludecosinelosses", InfoItem.Category.Thrust, showInEditor = true)]//ΔV include cosine losses + public bool dVLinearThrust = true; + + public FuelFlowSimulation.Stats[] atmoStats = { }; + public FuelFlowSimulation.Stats[] vacStats = { }; + + + // Those are used to store the next result from the thread since we must move result + // to atmoStats/vacStats only in the main thread. + private FuelFlowSimulation.Stats[] newAtmoStats; + private FuelFlowSimulation.Stats[] newVacStats; + private bool resultReady = false; + + public void RequestUpdate(object controller, bool wait = false) + { + users.Add(controller); + updateRequested = true; + + IsResultReady(); + + // In the editor this is our only entry point + if (HighLogic.LoadedSceneIsEditor) + { + TryStartSimulation(); + } + + // wait means the code needs some result to run so we wait if we do not have any result yet + if (wait && atmoStats.Length == 0 && (simulationRunning || TryStartSimulation())) + { + while (simulationRunning) + { + // wait for a sim to be ready. Risked ? + Thread.Sleep(1); + } + IsResultReady(); + } + } + + public CelestialBody editorBody; + public bool liveSLT = true; + public double altSLT = 0; + public double mach = 0; + + protected bool updateRequested = false; + protected bool simulationRunning = false; + protected System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + + private int needRebuild = 1; + + private FuelFlowSimulation[] sims = { new FuelFlowSimulation(), new FuelFlowSimulation() }; + + long millisecondsBetweenSimulations; + + public override void OnStart(PartModule.StartState state) + { + if (HighLogic.LoadedSceneIsEditor) + { + GameEvents.onEditorShipModified.Add(onEditorShipModified); + GameEvents.onPartCrossfeedStateChange.Add(onPartCrossfeedStateChange); + } + } + + public override void OnDestroy() + { + GameEvents.onEditorShipModified.Remove(onEditorShipModified); + GameEvents.onPartCrossfeedStateChange.Remove(onPartCrossfeedStateChange); + } + + private void onPartCrossfeedStateChange(Part data) + { + setDirty(); + } + + void onEditorShipModified(ShipConstruct data) + { + setDirty(); + } + + void setDirty() + { + // The ship is not really ready in the first frame following the event so we wait 2 + needRebuild = 2; + } + + public override void OnModuleEnabled() + { + millisecondsBetweenSimulations = 0; + stopwatch.Start(); + } + + public override void OnModuleDisabled() + { + stopwatch.Stop(); + stopwatch.Reset(); + } + + public override void OnFixedUpdate() + { + // Check if we have a result ready from the previous physic frame + IsResultReady(); + + TryStartSimulation(); + } + + public override void OnWaitForFixedUpdate() + { + // Check if we managed to get a result while the physic frame was running + IsResultReady(); + } + + public override void OnUpdate() + { + IsResultReady(); + } + + private void IsResultReady() + { + if (resultReady) + { + atmoStats = newAtmoStats; + vacStats = newVacStats; + resultReady = false; + } + } + + private bool TryStartSimulation() + { + if (!simulationRunning && ((HighLogic.LoadedSceneIsEditor && editorBody != null) || (vessel != null))) + { + //We should be running simulations periodically, but one is not running right now. + //Check if enough time has passed since the last one to start a new one: + if (stopwatch.ElapsedMilliseconds > millisecondsBetweenSimulations) + { + if (updateRequested) + { + updateRequested = false; + + stopwatch.Stop(); + stopwatch.Reset(); + + StartSimulation(); + return true; + } + else + { + users.Clear(); + } + } + } + return false; + } + + protected void StartSimulation() + { + Profiler.BeginSample("StartSimulation"); + try + { + simulationRunning = true; + resultReady = false; + stopwatch.Start(); //starts a timer that times how long the simulation takes + + //Create two FuelFlowSimulations, one for vacuum and one for atmosphere + List parts = (HighLogic.LoadedSceneIsEditor ? EditorLogic.fetch.ship.parts : vessel.parts); + + if (HighLogic.LoadedSceneIsEditor) + { + if (needRebuild > 0) + { + PartSet.BuildPartSets(parts, null); + needRebuild--; + } + } + else + { + vessel.UpdateResourceSetsIfDirty(); + } + + Profiler.BeginSample("StartSimulation_Init"); + + sims[0].Init(parts, dVLinearThrust); + sims[1].Init(parts, dVLinearThrust); + + Profiler.EndSample(); + + //Run the simulation in a separate thread + ThreadPool.QueueUserWorkItem(RunSimulation, sims); + //Profiler.BeginSample("StartSimulation_Run"); + //RunSimulation(sims); + //Profiler.EndSample(); + } + catch (Exception e) + { + print("Exception in MechJebModuleStageStats.StartSimulation(): " + e + "\n" + e.StackTrace); + + // Stop timing the simulation + stopwatch.Stop(); + millisecondsBetweenSimulations = 500; + stopwatch.Reset(); + + // Start counting down the time to the next simulation + stopwatch.Start(); + simulationRunning = false; + } + Profiler.EndSample(); + } + + protected void RunSimulation(object o) + { + try + { + CelestialBody simBody = HighLogic.LoadedSceneIsEditor ? editorBody : vessel.mainBody; + + double staticPressureKpa = (HighLogic.LoadedSceneIsEditor || !liveSLT ? (simBody.atmosphere ? simBody.GetPressure(altSLT) : 0) : vessel.staticPressurekPa); + double atmDensity = (HighLogic.LoadedSceneIsEditor || !liveSLT ? simBody.GetDensity(simBody.GetPressure(altSLT), simBody.GetTemperature(0)) : vessel.atmDensity) / 1.225; + double mach = HighLogic.LoadedSceneIsEditor ? this.mach : vessel.mach; + + //Run the simulation + newAtmoStats = sims[0].SimulateAllStages(1.0f, staticPressureKpa, atmDensity, mach); + newVacStats = sims[1].SimulateAllStages(1.0f, 0.0, 0.0, mach); + } + catch (Exception e) + { + print("Exception in MechJebModuleStageStats.RunSimulation(): " + e); + } + + //see how long the simulation took + stopwatch.Stop(); + long millisecondsToCompletion = stopwatch.ElapsedMilliseconds; + stopwatch.Reset(); + + //set the delay before the next simulation + millisecondsBetweenSimulations = 2 * millisecondsToCompletion; + + //start the stopwatch that will count off this delay + stopwatch.Start(); + resultReady = true; + simulationRunning = false; + } + } +} diff --git a/MechJeb2/MechJebModuleTargetController.cs b/MechJeb2/MechJebModuleTargetController.cs index 9f565c9c3..e75e8a7e4 100644 --- a/MechJeb2/MechJebModuleTargetController.cs +++ b/MechJeb2/MechJebModuleTargetController.cs @@ -1,292 +1,292 @@ -using System; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - //The target controller provides a nicer interface to access info about the player's currently selected target, - //and to set the target to something new. - // - //It also provides the ability to set position and direction targets. Position targets make the navball - //target indicator point to a give set of coordinates on a celestial body. The target is also shown - //in the map view. Direction targets make the navball target indicator point in a certain direction. - // - //Finally, it makes the target persistent in the following sense: when you switch away from a vessel, it - //will keep the same target, independent of whatever targets you set on your new vessel. When you switch - //back to the original vessel, your old target will be restored. - // - //Todo: Make target persistence work even when the original vessel gets unloaded and reloaded. - public class MechJebModuleTargetController : ComputerModule - { - public MechJebModuleTargetController(MechJebCore core) : base(core) { } - - ITargetable target; - - public CelestialBody targetBody; - [Persistent(pass = (int)Pass.Global)] - public EditableAngle targetLatitude = new EditableAngle(0); - [Persistent(pass = (int)Pass.Global)] - public EditableAngle targetLongitude = new EditableAngle(0); - - Vector3d targetDirection; - - bool wasActiveVessel = false; - - public bool pickingPositionTarget = false; - - //////////////////////// - // EXTERNAL INTERFACE // - //////////////////////// - - public void Set(ITargetable t) - { - target = t; - if (vessel != null) - { - vessel.targetObject = target; - } - } - - public void SetPositionTarget(CelestialBody body, double latitude, double longitude) - { - targetBody = body; - targetLatitude = latitude; - targetLongitude = longitude; - - Set(new PositionTarget(String.Format(GetPositionTargetString(), latitude, longitude))); - } - - - [ValueInfoItem("#MechJeb_Targetcoordinates", InfoItem.Category.Target)]//Target coordinates - public string GetPositionTargetString() - { - if (target is PositionTarget) return Coordinates.ToStringDMS(targetLatitude, targetLongitude, true); - - if (NormalTargetExists) return Coordinates.ToStringDMS(TargetOrbit.referenceBody.GetLatitude(Position), TargetOrbit.referenceBody.GetLongitude(Position), true); - - return "N/A"; - } - - public Vector3d GetPositionTargetPosition() - { - return targetBody.GetWorldSurfacePosition(targetLatitude, targetLongitude, targetBody.TerrainAltitude(targetLatitude, targetLongitude))-targetBody.position; - } - - public void SetDirectionTarget(string name) - { - Set(new DirectionTarget(name)); - } - - [ActionInfoItem("#MechJeb_Pickpositiontarget", InfoItem.Category.Target)]//Pick position target - public void PickPositionTargetOnMap() - { - pickingPositionTarget = true; - MapView.EnterMapView(); - string message = Localizer.Format("#MechJeb_pickingPositionMsg",mainBody.displayName);//"Click to select a target on " + + "'s surface.\n(Leave map view to cancel.)" - ScreenMessages.PostScreenMessage(message, 3.0f, ScreenMessageStyle.UPPER_CENTER); - } - - public void StopPickPositionTargetOnMap() - { - pickingPositionTarget = false; - Cursor.visible = true; - } - - public void Unset() - { - Set(null); - } - - public void UpdateDirectionTarget(Vector3d direction) - { - targetDirection = direction; - } - - public bool NormalTargetExists - { - get - { - return (target != null && (target is Vessel || target is CelestialBody || CanAlign)); - } - } - - public bool PositionTargetExists - { - get - { - return (target != null && ((target is PositionTarget) || (target is Vessel)) && !(target is DirectionTarget)); - } - } - - public bool CanAlign - { - get { return target.GetTargetingMode() == VesselTargetModes.DirectionVelocityAndOrientation; } - } - - public ITargetable Target - { - get { return target; } - } - - public Orbit TargetOrbit - { - get { - if (target == null) - return null; - return target.GetOrbit(); - } - } - - public Vector3 Position - { - get { return Transform.position; } - } - - public float Distance - { - get { return Vector3.Distance(Position, vessel.GetTransform().position); } - } - - public Vector3d RelativeVelocity - { - get { return (vessel.orbit.GetVel() - TargetOrbit.GetVel()); } - } - - public Vector3d RelativePosition - { - get { return (vessel.GetTransform().position - Position); } - } - - public Transform Transform - { - get { return target.GetTransform(); } - } - - //which way your vessel should be pointing to dock with the target - public Vector3 DockingAxis - { - get - { - if (CanAlign) return -Transform.forward; - return -Transform.up; - } - } - - public string Name - { - get { return target.GetName(); } - } - - //////////////////////// - // Internal functions // - //////////////////////// - - public override void OnStart(PartModule.StartState state) - { - core.AddToPostDrawQueue(DoMapView); - - users.Add(this); //TargetController should always be running - } - - public override void OnFixedUpdate() - { - //Restore the saved target when we are made active vessel - if (!wasActiveVessel && vessel.isActiveVessel) - { - if (target != null && target.GetVessel() != null) - { - vessel.targetObject = target; - } - } - - //notice when the user switches targets - if (target != vessel.targetObject) - { - target = vessel.targetObject; - if (target is Vessel && ((Vessel)target).LandedOrSplashed && (((Vessel)target).mainBody == vessel.mainBody)) - { - targetBody = vessel.mainBody; - targetLatitude = vessel.mainBody.GetLatitude(target.GetTransform().position); - targetLongitude = vessel.mainBody.GetLongitude(target.GetTransform().position); - } - if (target is CelestialBody) - { - targetBody = (CelestialBody) target; - } - } - - // .23 temp fix until I understand better what's going on - if (targetBody == null) - targetBody = vessel.mainBody; - - //Update targets that need updating: - if (target is DirectionTarget) ((DirectionTarget)target).Update(targetDirection); - else if (target is PositionTarget) ((PositionTarget)target).Update(targetBody, targetLatitude, targetLongitude); - - wasActiveVessel = vessel.isActiveVessel; - } - - public override void OnUpdate() - { - if (MapView.MapIsEnabled && pickingPositionTarget) - { - if (!GuiUtils.MouseIsOverWindow(core) && GuiUtils.GetMouseCoordinates(mainBody) != null) - Cursor.visible = false; - else - Cursor.visible = true; - } - } - - void DoMapView() - { - DoCoordinatePicking(); - - DrawMapViewTarget(); - } - - void DoCoordinatePicking() - { - if (pickingPositionTarget && !MapView.MapIsEnabled) - StopPickPositionTargetOnMap(); //stop picking on leaving map view - - if (!pickingPositionTarget) - return; - - if (MapView.MapIsEnabled && vessel.isActiveVessel) - { - if (!GuiUtils.MouseIsOverWindow(core)) - { - Coordinates mouseCoords = GuiUtils.GetMouseCoordinates(mainBody); - - if (mouseCoords != null) - { - GLUtils.DrawGroundMarker(mainBody, mouseCoords.latitude, mouseCoords.longitude, new Color(1.0f, 0.56f, 0.0f), true, 60); - - string biome = mainBody.GetExperimentBiomeSafe(mouseCoords.latitude, mouseCoords.longitude); - GUI.Label(new Rect(Input.mousePosition.x + 15, Screen.height - Input.mousePosition.y, 200, 50), mouseCoords.ToStringDecimal() + "\n" + biome); - - if (Input.GetMouseButtonDown(0)) - { - SetPositionTarget(mainBody, mouseCoords.latitude, mouseCoords.longitude); - StopPickPositionTargetOnMap(); - } - } - } - } - } - - void DrawMapViewTarget() - { - if (HighLogic.LoadedSceneIsEditor) return; - if (!MapView.MapIsEnabled) return; - if (!vessel.isActiveVessel || vessel.GetMasterMechJeb() != core) return; - - if (target == null) return; - if (!(target is PositionTarget) && !(target is Vessel)) return; - if ((target is Vessel) && (!((Vessel)target).LandedOrSplashed || (((Vessel)target).mainBody != vessel.mainBody))) return; - if (target is DirectionTarget) return; - - GLUtils.DrawGroundMarker(targetBody, targetLatitude, targetLongitude, Color.red, true); - } - } -} +using System; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + //The target controller provides a nicer interface to access info about the player's currently selected target, + //and to set the target to something new. + // + //It also provides the ability to set position and direction targets. Position targets make the navball + //target indicator point to a give set of coordinates on a celestial body. The target is also shown + //in the map view. Direction targets make the navball target indicator point in a certain direction. + // + //Finally, it makes the target persistent in the following sense: when you switch away from a vessel, it + //will keep the same target, independent of whatever targets you set on your new vessel. When you switch + //back to the original vessel, your old target will be restored. + // + //Todo: Make target persistence work even when the original vessel gets unloaded and reloaded. + public class MechJebModuleTargetController : ComputerModule + { + public MechJebModuleTargetController(MechJebCore core) : base(core) { } + + ITargetable target; + + public CelestialBody targetBody; + [Persistent(pass = (int)Pass.Global)] + public EditableAngle targetLatitude = new EditableAngle(0); + [Persistent(pass = (int)Pass.Global)] + public EditableAngle targetLongitude = new EditableAngle(0); + + Vector3d targetDirection; + + bool wasActiveVessel = false; + + public bool pickingPositionTarget = false; + + //////////////////////// + // EXTERNAL INTERFACE // + //////////////////////// + + public void Set(ITargetable t) + { + target = t; + if (vessel != null) + { + vessel.targetObject = target; + } + } + + public void SetPositionTarget(CelestialBody body, double latitude, double longitude) + { + targetBody = body; + targetLatitude = latitude; + targetLongitude = longitude; + + Set(new PositionTarget(String.Format(GetPositionTargetString(), latitude, longitude))); + } + + + [ValueInfoItem("#MechJeb_Targetcoordinates", InfoItem.Category.Target)]//Target coordinates + public string GetPositionTargetString() + { + if (target is PositionTarget) return Coordinates.ToStringDMS(targetLatitude, targetLongitude, true); + + if (NormalTargetExists) return Coordinates.ToStringDMS(TargetOrbit.referenceBody.GetLatitude(Position), TargetOrbit.referenceBody.GetLongitude(Position), true); + + return "N/A"; + } + + public Vector3d GetPositionTargetPosition() + { + return targetBody.GetWorldSurfacePosition(targetLatitude, targetLongitude, targetBody.TerrainAltitude(targetLatitude, targetLongitude))-targetBody.position; + } + + public void SetDirectionTarget(string name) + { + Set(new DirectionTarget(name)); + } + + [ActionInfoItem("#MechJeb_Pickpositiontarget", InfoItem.Category.Target)]//Pick position target + public void PickPositionTargetOnMap() + { + pickingPositionTarget = true; + MapView.EnterMapView(); + string message = Localizer.Format("#MechJeb_pickingPositionMsg",mainBody.displayName);//"Click to select a target on " + + "'s surface.\n(Leave map view to cancel.)" + ScreenMessages.PostScreenMessage(message, 3.0f, ScreenMessageStyle.UPPER_CENTER); + } + + public void StopPickPositionTargetOnMap() + { + pickingPositionTarget = false; + Cursor.visible = true; + } + + public void Unset() + { + Set(null); + } + + public void UpdateDirectionTarget(Vector3d direction) + { + targetDirection = direction; + } + + public bool NormalTargetExists + { + get + { + return (target != null && (target is Vessel || target is CelestialBody || CanAlign)); + } + } + + public bool PositionTargetExists + { + get + { + return (target != null && ((target is PositionTarget) || (target is Vessel)) && !(target is DirectionTarget)); + } + } + + public bool CanAlign + { + get { return target.GetTargetingMode() == VesselTargetModes.DirectionVelocityAndOrientation; } + } + + public ITargetable Target + { + get { return target; } + } + + public Orbit TargetOrbit + { + get { + if (target == null) + return null; + return target.GetOrbit(); + } + } + + public Vector3 Position + { + get { return Transform.position; } + } + + public float Distance + { + get { return Vector3.Distance(Position, vessel.GetTransform().position); } + } + + public Vector3d RelativeVelocity + { + get { return (vessel.orbit.GetVel() - TargetOrbit.GetVel()); } + } + + public Vector3d RelativePosition + { + get { return (vessel.GetTransform().position - Position); } + } + + public Transform Transform + { + get { return target.GetTransform(); } + } + + //which way your vessel should be pointing to dock with the target + public Vector3 DockingAxis + { + get + { + if (CanAlign) return -Transform.forward; + return -Transform.up; + } + } + + public string Name + { + get { return target.GetName(); } + } + + //////////////////////// + // Internal functions // + //////////////////////// + + public override void OnStart(PartModule.StartState state) + { + core.AddToPostDrawQueue(DoMapView); + + users.Add(this); //TargetController should always be running + } + + public override void OnFixedUpdate() + { + //Restore the saved target when we are made active vessel + if (!wasActiveVessel && vessel.isActiveVessel) + { + if (target != null && target.GetVessel() != null) + { + vessel.targetObject = target; + } + } + + //notice when the user switches targets + if (target != vessel.targetObject) + { + target = vessel.targetObject; + if (target is Vessel && ((Vessel)target).LandedOrSplashed && (((Vessel)target).mainBody == vessel.mainBody)) + { + targetBody = vessel.mainBody; + targetLatitude = vessel.mainBody.GetLatitude(target.GetTransform().position); + targetLongitude = vessel.mainBody.GetLongitude(target.GetTransform().position); + } + if (target is CelestialBody) + { + targetBody = (CelestialBody) target; + } + } + + // .23 temp fix until I understand better what's going on + if (targetBody == null) + targetBody = vessel.mainBody; + + //Update targets that need updating: + if (target is DirectionTarget) ((DirectionTarget)target).Update(targetDirection); + else if (target is PositionTarget) ((PositionTarget)target).Update(targetBody, targetLatitude, targetLongitude); + + wasActiveVessel = vessel.isActiveVessel; + } + + public override void OnUpdate() + { + if (MapView.MapIsEnabled && pickingPositionTarget) + { + if (!GuiUtils.MouseIsOverWindow(core) && GuiUtils.GetMouseCoordinates(mainBody) != null) + Cursor.visible = false; + else + Cursor.visible = true; + } + } + + void DoMapView() + { + DoCoordinatePicking(); + + DrawMapViewTarget(); + } + + void DoCoordinatePicking() + { + if (pickingPositionTarget && !MapView.MapIsEnabled) + StopPickPositionTargetOnMap(); //stop picking on leaving map view + + if (!pickingPositionTarget) + return; + + if (MapView.MapIsEnabled && vessel.isActiveVessel) + { + if (!GuiUtils.MouseIsOverWindow(core)) + { + Coordinates mouseCoords = GuiUtils.GetMouseCoordinates(mainBody); + + if (mouseCoords != null) + { + GLUtils.DrawGroundMarker(mainBody, mouseCoords.latitude, mouseCoords.longitude, new Color(1.0f, 0.56f, 0.0f), true, 60); + + string biome = mainBody.GetExperimentBiomeSafe(mouseCoords.latitude, mouseCoords.longitude); + GUI.Label(new Rect(Input.mousePosition.x + 15, Screen.height - Input.mousePosition.y, 200, 50), mouseCoords.ToStringDecimal() + "\n" + biome); + + if (Input.GetMouseButtonDown(0)) + { + SetPositionTarget(mainBody, mouseCoords.latitude, mouseCoords.longitude); + StopPickPositionTargetOnMap(); + } + } + } + } + } + + void DrawMapViewTarget() + { + if (HighLogic.LoadedSceneIsEditor) return; + if (!MapView.MapIsEnabled) return; + if (!vessel.isActiveVessel || vessel.GetMasterMechJeb() != core) return; + + if (target == null) return; + if (!(target is PositionTarget) && !(target is Vessel)) return; + if ((target is Vessel) && (!((Vessel)target).LandedOrSplashed || (((Vessel)target).mainBody != vessel.mainBody))) return; + if (target is DirectionTarget) return; + + GLUtils.DrawGroundMarker(targetBody, targetLatitude, targetLongitude, Color.red, true); + } + } +} diff --git a/MechJeb2/MechJebModuleThrustController.cs b/MechJeb2/MechJebModuleThrustController.cs index 7d1864a92..68793cab2 100644 --- a/MechJeb2/MechJebModuleThrustController.cs +++ b/MechJeb2/MechJebModuleThrustController.cs @@ -1,992 +1,992 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using KSP.UI.Screens; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleThrustController : ComputerModule - { - public enum DifferentialThrottleStatus - { - Success, - AllEnginesOff, - MoreEnginesRequired, - SolverFailed - } - - public MechJebModuleThrustController(MechJebCore core) - : base(core) - { - priority = 200; - } - - public float trans_spd_act = 0; - public float trans_prev_thrust = 0; - public bool trans_kill_h = false; - - - // The Terminal Velocity limiter is removed to not have to deal with users who - // think that seeing the aerodynamic FX means they reached it. - // And it s really high since 1.0.x anyway so the Dynamic Pressure limiter is better now - //[Persistent(pass = (int)Pass.Global)] - public bool limitToTerminalVelocity = false; - - //[GeneralInfoItem("Limit to terminal velocity", InfoItem.Category.Thrust)] - //public void LimitToTerminalVelocityInfoItem() - //{ - // GUIStyle s = new GUIStyle(GUI.skin.toggle); - // if (limiter == LimitMode.TerminalVelocity) s.onHover.textColor = s.onNormal.textColor = Color.green; - // limitToTerminalVelocity = GUILayout.Toggle(limitToTerminalVelocity, "Limit to terminal velocity", s); - //} - - [Persistent(pass = (int)Pass.Global)] - public bool limitDynamicPressure = false; - - [Persistent(pass = (int)Pass.Global)] - public EditableDouble maxDynamicPressure = 20000; - - [GeneralInfoItem("#MechJeb_LimittoMaxQ", InfoItem.Category.Thrust)]//Limit to Max Q - public void LimitToMaxDynamicPressureInfoItem() - { - GUILayout.BeginHorizontal(); - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.DynamicPressure) s.onHover.textColor = s.onNormal.textColor = Color.green; - limitDynamicPressure = GUILayout.Toggle(limitDynamicPressure, Localizer.Format("#MechJeb_Ascent_checkbox11"), s, GUILayout.Width(140));//"Limit Q to" - maxDynamicPressure.text = GUILayout.TextField(maxDynamicPressure.text, GUILayout.Width(80)); - GUILayout.Label("pa", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - [Persistent(pass = (int)Pass.Global)] - public bool limitToPreventOverheats = false; - - [GeneralInfoItem("#MechJeb_PreventEngineOverheats", InfoItem.Category.Thrust)]//Prevent engine overheats - public void LimitToPreventOverheatsInfoItem() - { - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.Temperature) s.onHover.textColor = s.onNormal.textColor = Color.green; - limitToPreventOverheats = GUILayout.Toggle(limitToPreventOverheats, Localizer.Format("#MechJeb_Ascent_checkbox12"), s);//"Prevent engine overheats" - } - - [ToggleInfoItem("#MechJeb_SmoothThrottle", InfoItem.Category.Thrust)]//Smooth throttle - [Persistent(pass = (int)Pass.Global)] - public bool smoothThrottle = false; - - [Persistent(pass = (int)Pass.Global)] - public double throttleSmoothingTime = 1.0; - - [Persistent(pass = (int)Pass.Global)] - public bool limitToPreventFlameout = false; - - [GeneralInfoItem("#MechJeb_PreventJetFlameout", InfoItem.Category.Thrust)]//Prevent jet flameout - public void LimitToPreventFlameoutInfoItem() - { - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.Flameout) s.onHover.textColor = s.onNormal.textColor = Color.green; - limitToPreventFlameout = GUILayout.Toggle(limitToPreventFlameout, Localizer.Format("#MechJeb_Ascent_checkbox13"), s);//"Prevent jet flameout" - } - - [Persistent(pass = (int)Pass.Global)] - public bool limitToPreventUnstableIgnition = false; - - [GeneralInfoItem("#MechJeb_PreventUnstableIgnition", InfoItem.Category.Thrust)]//Prevent unstable ignition - public void LimitToPreventUnstableIgnitionInfoItem() - { - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.UnstableIgnition) s.onHover.textColor = s.onNormal.textColor = Color.green; - limitToPreventUnstableIgnition = GUILayout.Toggle(limitToPreventUnstableIgnition, Localizer.Format("#MechJeb_Ascent_checkbox14"), s);//"Prevent unstable ignition" - } - - [Persistent(pass = (int)Pass.Global)] - public bool autoRCSUllaging = true; - - [GeneralInfoItem("#MechJeb_UseRCStoullage", InfoItem.Category.Thrust)]//Use RCS to ullage - public void AutoRCsUllageInfoItem() - { - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.AutoRCSUllage) s.onHover.textColor = s.onNormal.textColor = Color.green; - autoRCSUllaging = GUILayout.Toggle(autoRCSUllaging, Localizer.Format("#MechJeb_Ascent_checkbox15"), s);//"Use RCS to ullage" - } - - // 5% safety margin on flameouts - [Persistent(pass = (int)Pass.Global)] - public EditableDouble flameoutSafetyPct = 5; - - [ToggleInfoItem("#MechJeb_ManageAirIntakes", InfoItem.Category.Thrust)]//Manage air intakes - [Persistent(pass = (int)Pass.Global)] - public bool manageIntakes = false; - - [Persistent(pass = (int)Pass.Global)] - public bool limitAcceleration = false; - - [Persistent(pass = (int)Pass.Global)] - public EditableDouble maxAcceleration = 40; - - [GeneralInfoItem("#MechJeb_LimitAcceleration", InfoItem.Category.Thrust)]//Limit acceleration - public void LimitAccelerationInfoItem() - { - GUILayout.BeginHorizontal(); - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.Acceleration) s.onHover.textColor = s.onNormal.textColor = Color.green; - limitAcceleration = GUILayout.Toggle(limitAcceleration, Localizer.Format("#MechJeb_Ascent_checkbox16"), s, GUILayout.Width(140));//"Limit acceleration to" - maxAcceleration.text = GUILayout.TextField(maxAcceleration.text, GUILayout.Width(30)); - GUILayout.Label("m/s²", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - [Persistent(pass = (int)Pass.Local)] - public bool limitThrottle = false; - - [Persistent(pass = (int)Pass.Local)] - public EditableDoubleMult maxThrottle = new EditableDoubleMult(1, 0.01); - - [GeneralInfoItem("#MechJeb_LimitThrottle", InfoItem.Category.Thrust)]//Limit throttle - public void LimitThrottleInfoItem() - { - GUILayout.BeginHorizontal(); - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.Throttle) s.onHover.textColor = s.onNormal.textColor = maxThrottle > 0d ? Color.green : Color.red; - limitThrottle = GUILayout.Toggle(limitThrottle, Localizer.Format("#MechJeb_Ascent_checkbox17"), s, GUILayout.Width(110));//"Limit throttle to" - maxThrottle.text = GUILayout.TextField(maxThrottle.text, GUILayout.Width(30)); - GUILayout.Label("%", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - [Persistent(pass = (int) (Pass.Local | Pass.Type | Pass.Global))] - public bool limiterMinThrottle = false; - - [Persistent(pass = (int) (Pass.Local | Pass.Type | Pass.Global))] - public EditableDoubleMult minThrottle = new EditableDoubleMult(0.05, 0.01); - - [GeneralInfoItem("#MechJeb_LowerThrottleLimit", InfoItem.Category.Thrust)]//Lower throttle limit - public void LimiterMinThrottleInfoItem() - { - GUILayout.BeginHorizontal(); - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.MinThrottle) s.onHover.textColor = s.onNormal.textColor = Color.green; - limiterMinThrottle = GUILayout.Toggle(limiterMinThrottle, Localizer.Format("#MechJeb_Ascent_checkbox18"), s, GUILayout.Width(160));//"Keep limited throttle over" - minThrottle.text = GUILayout.TextField(minThrottle.text, GUILayout.Width(30)); - GUILayout.Label("%", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - [Persistent(pass = (int)Pass.Type)] - public bool differentialThrottle = false; - - [GeneralInfoItem("#MechJeb_DifferentialThrottle", InfoItem.Category.Thrust)]//Differential throttle - public void DifferentialThrottle() - { - bool oldDifferentialThrottle = core.thrust.differentialThrottle; - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (differentialThrottle && vessel.LiftedOff()) - { - s.onHover.textColor = s.onNormal.textColor = core.thrust.differentialThrottleSuccess == DifferentialThrottleStatus.Success ? Color.green : Color.yellow; - } - differentialThrottle = GUILayout.Toggle(differentialThrottle, Localizer.Format("#MechJeb_Ascent_checkbox19"), s);//"Differential throttle" - - if (oldDifferentialThrottle && !core.thrust.differentialThrottle) - core.thrust.DisableDifferentialThrottle(); - } - - public Vector3d differentialThrottleDemandedTorque = new Vector3d(); - - public DifferentialThrottleStatus differentialThrottleSuccess = DifferentialThrottleStatus.Success; - - [Persistent(pass = (int)Pass.Local)] - public bool electricThrottle = false; - - [Persistent(pass = (int)Pass.Local)] - public EditableDoubleMult electricThrottleLo = new EditableDoubleMult(0.05, 0.01); - [Persistent(pass = (int)Pass.Local)] - public EditableDoubleMult electricThrottleHi = new EditableDoubleMult(0.15, 0.01); - - [GeneralInfoItem("#MechJeb_ElectricLimit", InfoItem.Category.Thrust)]//Electric limit - public void LimitElectricInfoItem() - { - GUILayout.BeginHorizontal(); - GUIStyle s = new GUIStyle(GUI.skin.toggle); - if (limiter == LimitMode.Electric) - { - if (vesselState.throttleLimit <0.001) - s.onHover.textColor = s.onNormal.textColor = Color.red; - else - s.onHover.textColor = s.onNormal.textColor = Color.yellow; - } - else if (ElectricEngineRunning()) s.onHover.textColor = s.onNormal.textColor = Color.green; - - electricThrottle = GUILayout.Toggle(electricThrottle, Localizer.Format("#MechJeb_Ascent_checkbox20"), s, GUILayout.Width(110));//"Electric limit Lo" - electricThrottleLo.text = GUILayout.TextField(electricThrottleLo.text, GUILayout.Width(30)); - GUILayout.Label("% Hi", GUILayout.ExpandWidth(false)); - electricThrottleHi.text = GUILayout.TextField(electricThrottleHi.text, GUILayout.Width(30)); - GUILayout.Label("%", GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - } - - public enum LimitMode { None, TerminalVelocity, Temperature, Flameout, Acceleration, Throttle, DynamicPressure, MinThrottle, Electric, UnstableIgnition, AutoRCSUllage } - public LimitMode limiter = LimitMode.None; - - public float targetThrottle = 0; - - protected bool tmode_changed = false; - - - public PIDController pid; - - float lastThrottle = 0; - bool userCommandingRotation { get { return userCommandingRotationSmoothed > 0; } } - int userCommandingRotationSmoothed = 0; - bool lastDisableThrusters = false; - - public enum TMode - { - OFF, - KEEP_ORBITAL, - KEEP_SURFACE, - KEEP_VERTICAL, - KEEP_RELATIVE, - DIRECT - } - - private TMode prev_tmode = TMode.OFF; - private TMode _tmode = TMode.OFF; - public TMode tmode - { - get - { - return _tmode; - } - set - { - if (_tmode != value) - { - prev_tmode = _tmode; - _tmode = value; - tmode_changed = true; - } - } - } - - ScreenMessage preventingUnstableIgnitionsMessage; - - public override void OnStart(PartModule.StartState state) - { - preventingUnstableIgnitionsMessage = new ScreenMessage(Localizer.Format("#MechJeb_Ascent_srcmsg1"), 2f, ScreenMessageStyle.UPPER_CENTER);//"[MechJeb]: Killing throttle to prevent unstable ignition" - pid = new PIDController(0.05, 0.000001, 0.05); - users.Add(this); - - base.OnStart(state); - } - - public void ThrustOff() - { - if (vessel == null || vessel.ctrlState == null) - return; - - targetThrottle = 0; - vessel.ctrlState.mainThrottle = 0; - tmode = TMode.OFF; - SetFlightGlobals(0); - } - - private void SetFlightGlobals(double throttle) - { - if (FlightGlobals.ActiveVessel != null && vessel == FlightGlobals.ActiveVessel) - { - FlightInputHandler.state.mainThrottle = (float) throttle; //so that the on-screen throttle gauge reflects the autopilot throttle - } - } - - // Call this function to set the throttle for a burn with dV delta-V remaining. - // timeConstant controls how quickly we throttle down toward the end of the burn. - // This function is nice because it will correctly handle engine spool-up/down times. - public void ThrustForDV(double dV, double timeConstant) - { - timeConstant += vesselState.maxEngineResponseTime; - double spooldownDV = vesselState.currentThrustAccel * vesselState.maxEngineResponseTime; - double desiredAcceleration = (dV - spooldownDV) / timeConstant; - - targetThrottle = Mathf.Clamp01((float)(desiredAcceleration / vesselState.maxThrustAccel)); - } - - /* the current throttle limit, this may include transient condition such as limiting to zero due to unstable propellants in RF */ - public float throttleLimit { get; private set; } - /* the fixed throttle limit (i.e. user limited in the GUI), does not include transient conditions as limiting to zero due to unstable propellants in RF */ - public float throttleFixedLimit { get; private set; } - - /* This is an API for limits which are "temporary" or "conditional" (things like ullage status which will change very soon). - This will often be temporarily zero, which means the computed accelleration of the ship will be zero, which would cause - consumers (like the NodeExecutor) to compute infinite burntime, so this value should not be used by those consumers */ - private void setTempLimit(float limit, LimitMode mode) - { - throttleLimit = limit; - limiter = mode; - } - - /* This is an API for limits which are not temporary (like the throttle limit set in the GUI) - The throttleFixedLimit is what consumers like the NodeExecutor should use to compute acceleration and burntime. - This deliberately sets both values. The actually applied throttle limit may be lower than thottleFixedLimit - (i.e. there may be a more limiting temp limit) */ - private void setFixedLimit(float limit, LimitMode mode) - { - if (throttleLimit > limit) { - throttleLimit = limit; - } - throttleFixedLimit = limit; - limiter = mode; - } - - public override void Drive(FlightCtrlState s) - { - float threshold = 0.1F; - bool _userCommandingRotation = !(Mathfx.Approx(s.pitch, s.pitchTrim, threshold) - && Mathfx.Approx(s.yaw, s.yawTrim, threshold) - && Mathfx.Approx(s.roll, s.rollTrim, threshold)); - bool _userCommandingTranslation = !(Math.Abs(s.X) < threshold - && Math.Abs(s.Y) < threshold - && Math.Abs(s.Z) < threshold); - - if (_userCommandingRotation && !_userCommandingTranslation) - { - userCommandingRotationSmoothed = 2; - } - else if (userCommandingRotationSmoothed > 0) - { - userCommandingRotationSmoothed--; - } - - if (core.GetComputerModule().hidden && core.GetComputerModule().hidden) { return; } - - if ((tmode != TMode.OFF) && (vesselState.thrustAvailable > 0)) - { - double spd = 0; - - switch (tmode) - { - case TMode.KEEP_ORBITAL: - spd = vesselState.speedOrbital; - break; - case TMode.KEEP_SURFACE: - spd = vesselState.speedSurface; - break; - case TMode.KEEP_VERTICAL: - spd = vesselState.speedVertical; - Vector3d rot = Vector3d.up; - if (trans_kill_h) - { - Vector3 hsdir = Vector3.ProjectOnPlane(vesselState.surfaceVelocity, vesselState.up); - Vector3 dir = -hsdir + vesselState.up * Math.Max(Math.Abs(spd), 20 * mainBody.GeeASL); - if ((Math.Min(vesselState.altitudeASL, vesselState.altitudeTrue) > 5000) && (hsdir.magnitude > Math.Max(Math.Abs(spd), 100 * mainBody.GeeASL) * 2)) - { - tmode = TMode.DIRECT; - trans_spd_act = 100; - rot = -hsdir; - } - else - { - rot = dir.normalized; - } - core.attitude.attitudeTo(rot, AttitudeReference.INERTIAL, null); - } - break; - } - - double t_err = (trans_spd_act - spd) / vesselState.maxThrustAccel; - if ((tmode == TMode.KEEP_ORBITAL && Vector3d.Dot(vesselState.forward, vesselState.orbitalVelocity) < 0) || - (tmode == TMode.KEEP_SURFACE && Vector3d.Dot(vesselState.forward, vesselState.surfaceVelocity) < 0)) - { - //allow thrust to declerate - t_err *= -1; - } - - double t_act = pid.Compute(t_err); - - if ((tmode != TMode.KEEP_VERTICAL) - || !trans_kill_h - || (core.attitude.attitudeError < 2) - || ((Math.Min(vesselState.altitudeASL, vesselState.altitudeTrue) < 1000) && (core.attitude.attitudeError < 90))) - { - if (tmode == TMode.DIRECT) - { - trans_prev_thrust = targetThrottle = trans_spd_act / 100.0F; - } - else - { - trans_prev_thrust = targetThrottle = Mathf.Clamp01(trans_prev_thrust + (float)t_act); - } - } - else - { - bool useGimbal = (vesselState.torqueGimbal.positive.x > vesselState.torqueAvailable.x * 10) || - (vesselState.torqueGimbal.positive.z > vesselState.torqueAvailable.z * 10); - - bool useDiffThrottle = (vesselState.torqueDiffThrottle.x > vesselState.torqueAvailable.x * 10) || - (vesselState.torqueDiffThrottle.z > vesselState.torqueAvailable.z * 10); - - if ((core.attitude.attitudeError >= 2) && (useGimbal || (useDiffThrottle && core.thrust.differentialThrottle))) - { - trans_prev_thrust = targetThrottle = 0.1F; - print(" targetThrottle = 0.1F"); - } - else - { - trans_prev_thrust = targetThrottle = 0; - } - } - } - - // Only set throttle if a module need it. Otherwise let the user or other mods set it - // There is always at least 1 user : the module itself (why ?) - if (users.Count > 1) - s.mainThrottle = targetThrottle; - - throttleLimit = 1; - throttleFixedLimit = 1; - - limiter = LimitMode.None; - - if (limitThrottle) - { - if (maxThrottle < throttleLimit) - { - setFixedLimit((float)maxThrottle, LimitMode.Throttle); - } - } - - if (limitToTerminalVelocity) - { - float limit = TerminalVelocityThrottle(); - if (limit < throttleLimit) - { - setFixedLimit(limit, LimitMode.TerminalVelocity); - } - } - - if (limitDynamicPressure) - { - float limit = MaximumDynamicPressureThrottle(); - if (limit < throttleLimit) - { - setFixedLimit(limit, LimitMode.DynamicPressure); - } - } - - if (limitToPreventOverheats) - { - float limit = (float)TemperatureSafetyThrottle(); - if (limit < throttleLimit) { - setFixedLimit(limit, LimitMode.Temperature); - } - } - - if (limitAcceleration) - { - float limit = AccelerationLimitedThrottle(); - if (limit < throttleLimit) { - setFixedLimit(limit, LimitMode.Acceleration); - } - } - - if (electricThrottle && ElectricEngineRunning()) - { - float limit = ElectricThrottle(); - if (limit < throttleLimit) { - setFixedLimit(limit, LimitMode.Electric); - } - } - - if (limitToPreventFlameout) - { - // This clause benefits being last: if we don't need much air - // due to prior limits, we can close some intakes. - float limit = FlameoutSafetyThrottle(); - if (limit < throttleLimit) { - setFixedLimit(limit, LimitMode.Flameout); - } - } - - // Any limiters which can limit to non-zero values must come before this, any - // limiters (like ullage) which enforce zero throttle should come after. The - // minThrottle setting has authority over any other limiter that sets non-zero throttle. - if (limiterMinThrottle && limiter != LimitMode.None) - { - if (minThrottle > throttleFixedLimit) - { - setFixedLimit((float) minThrottle, LimitMode.MinThrottle); - } - if (minThrottle > throttleLimit) - { - setTempLimit((float) minThrottle, LimitMode.MinThrottle); - } - } - - /* auto-RCS ullaging up to very stable */ - if (autoRCSUllaging && s.mainThrottle > 0.0F && throttleLimit > 0.0F ) - { - if (vesselState.lowestUllage < VesselState.UllageState.VeryStable) - { - Debug.Log("MechJeb RCS auto-ullaging: found state below very stable: " + vesselState.lowestUllage); - if (vessel.hasEnabledRCSModules()) - { - if (!vessel.ActionGroups[KSPActionGroup.RCS]) - { - Debug.Log("MechJeb RCS auto-ullaging: enabling RCS action group for automatic ullaging"); - vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, true); - } - Debug.Log("MechJeb RCS auto-ullaging: firing RCS to stabilize ulllage"); - setTempLimit(0.0F, LimitMode.UnstableIgnition); - s.Z = -1.0F; - } else { - Debug.Log("MechJeb RCS auto-ullaging: vessel has no enabled/staged RCS modules"); - } - } - } - - /* prevent unstable ignitions */ - if (limitToPreventUnstableIgnition && s.mainThrottle > 0.0F && throttleLimit > 0.0F ) - { - if (vesselState.lowestUllage < VesselState.UllageState.Stable) - { - ScreenMessages.PostScreenMessage(preventingUnstableIgnitionsMessage); - Debug.Log("MechJeb Unstable Ignitions: preventing ignition in state: " + vesselState.lowestUllage); - setTempLimit(0.0F, LimitMode.UnstableIgnition); - } - } - - // we have to force the throttle here so that rssMode can work, otherwise we don't get our last throttle command - // back on the next tick after disabling. we save this before applying the throttle limits so that we preserve - // the requested throttle, and not the limited throttle. - if (core.rssMode) - { - SetFlightGlobals(s.mainThrottle); - } - - if (double.IsNaN(throttleLimit)) throttleLimit = 1.0F; - throttleLimit = Mathf.Clamp01(throttleLimit); - - /* we do not _apply_ the "fixed" limit, the actual throttleLimit should always be the more limited and lower one */ - /* the purpose of the "fixed" limit is for external consumers like the node executor to consume */ - if (double.IsNaN(throttleFixedLimit)) throttleFixedLimit = 1.0F; - throttleFixedLimit = Mathf.Clamp01(throttleFixedLimit); - - vesselState.throttleLimit = throttleLimit; - vesselState.throttleFixedLimit = throttleFixedLimit; - - if (s.mainThrottle < throttleLimit) limiter = LimitMode.None; - - s.mainThrottle = Mathf.Min(s.mainThrottle, throttleLimit); - - if (smoothThrottle) - { - s.mainThrottle = SmoothThrottle(s.mainThrottle); - } - - if (double.IsNaN(s.mainThrottle)) s.mainThrottle = 0; - - s.mainThrottle = Mathf.Clamp01(s.mainThrottle); - - - if (s.Z == 0 && core.rcs.rcsThrottle && vesselState.rcsThrust) s.Z = -s.mainThrottle; - - lastThrottle = s.mainThrottle; - - if (!core.attitude.enabled) - { - Vector3d act = new Vector3d(s.pitch, s.yaw, s.roll); - differentialThrottleDemandedTorque = -Vector3d.Scale(act.xzy, vesselState.torqueDiffThrottle * s.mainThrottle * 0.5f); - } - } - - public override void OnFixedUpdate() - { - if (differentialThrottle) - { - differentialThrottleSuccess = ComputeDifferentialThrottle(differentialThrottleDemandedTorque); - } - else - { - differentialThrottleSuccess = DifferentialThrottleStatus.Success; - } - } - - //A throttle setting that throttles down when the vertical velocity of the ship exceeds terminal velocity - float TerminalVelocityThrottle() - { - if (vesselState.altitudeASL > mainBody.RealMaxAtmosphereAltitude()) return 1.0F; - - double velocityRatio = Vector3d.Dot(vesselState.surfaceVelocity, vesselState.up) / vesselState.TerminalVelocity(); - - if (velocityRatio < 1.0) return 1.0F; //full throttle if under terminal velocity - - //throttle down quickly as we exceed terminal velocity: - const double falloff = 15.0; - return Mathf.Clamp((float)(1.0 - falloff * (velocityRatio - 1.0)), 0.0F, 1.0F); - } - - //A throttle setting that throttles down when the dynamic pressure exceed a set value - float MaximumDynamicPressureThrottle() - { - if (maxDynamicPressure <= 0) return 1.0F; - - double pressureRatio = vesselState.dynamicPressure / maxDynamicPressure; - - if (pressureRatio < 1.0) return 1.0F; //full throttle if under maximum dynamic pressure - - //throttle down quickly as we exceed maximum dynamic pressure: - const double falloff = 15.0; - return Mathf.Clamp((float)(1.0 - falloff * (pressureRatio - 1.0)), 0.0F, 1.0F); - } - - //a throttle setting that throttles down if something is close to overheating - double TemperatureSafetyThrottle() - { - double maxTempRatio = vessel.parts.Max(p => p.temperature / p.maxTemp); - - //reduce throttle as the max temp. ratio approaches 1 within the safety margin - const double tempSafetyMargin = 0.05f; - if (maxTempRatio < 1 - tempSafetyMargin) return 1.0F; - else return (1 - maxTempRatio) / tempSafetyMargin; - } - - float SmoothThrottle(float mainThrottle) - { - return Mathf.Clamp(mainThrottle, - (float)(lastThrottle - vesselState.deltaT / throttleSmoothingTime), - (float)(lastThrottle + vesselState.deltaT / throttleSmoothingTime)); - } - - float FlameoutSafetyThrottle() - { - float throttle = 1; - - // For every resource that is provided by intakes, find how - // much we need at full throttle, add a safety margin, and - // that's the max throttle for that resource. Take the min of - // the max throttles. - foreach (var resource in vesselState.resources.Values) - { - if (resource.intakes.Count == 0) - { - // No intakes provide this resource; not our problem. - continue; - } - - // Compute the amount of air we will need, plus a safety - // margin. Make sure it's at least enough for last frame's - // requirement, since it takes time for engines to spin down. - // Note: this could be zero, e.g. if we have intakes but no - // jets. - - double margin = (1 + 0.01 * flameoutSafetyPct); - double safeRequirement = margin * resource.requiredAtMaxThrottle; - safeRequirement = Math.Max(safeRequirement, resource.required); - - // Open the right number of intakes. - if (manageIntakes) - { - OptimizeIntakes(resource, safeRequirement); - } - - double provided = resource.intakeProvided; - if (resource.required >= provided) - { - // We must cut throttle immediately, otherwise we are - // flaming out immediately. Continue doing the rest of the - // loop anyway, but we'll be at zero throttle. - throttle = 0; - } - else - { - double maxthrottle = provided / safeRequirement; - throttle = Mathf.Min(throttle, (float)maxthrottle); - } - } - return throttle; - } - - // Open and close intakes so that they provide the required flow but no - // more. - void OptimizeIntakes(VesselState.ResourceInfo info, double requiredFlow) - { - // The highly imperfect algorithm here is: - // - group intakes by symmetry - // - sort the groups by their size - // - open a group at a time until we have enough airflow - // - close the remaining groups - - // TODO: improve the algorithm: - // 1. Take cost-benefit into account. We want to open ram intakes - // before any others, and we probably never want to open - // nacelles. We need more info to know how much thrust we lose - // for losing airflow, versus how much drag we gain for opening - // an intake. - // 2. Take the center of drag into account. We don't need to open - // symmetry groups all at once; we just need the center of drag - // to be in a good place. Symmetry in the SPH also doesn't - // guarantee anything; we could be opening all pairs that are - // above the plane through the center of mass and none below, - // which makes control harder. - // Even just point (1) means we want to solve knapsack. - - // Group intakes by symmetry, and collect the information by intake. - var groups = new List>(); - var groupIds = new Dictionary(); - var data = new Dictionary(); - foreach (var intakeData in info.intakes) - { - ModuleResourceIntake intake = intakeData.intake; - data[intake] = intakeData; - if (groupIds.ContainsKey(intake)) { continue; } - - // Create a group for this symmetry - int grpId = groups.Count; - var intakes = new List(); - groups.Add(intakes); - - // In DFS order, get all the symmetric parts. - // We can't rely on symmetryCounterparts; see bug #52 by tavert: - // https://github.com/MuMech/MechJeb2/issues/52 - var stack = new Stack(); - stack.Push(intake.part); - while(stack.Count > 0) { - var part = stack.Pop(); - var partIntake = part.Modules.OfType().FirstOrDefault(); - if (partIntake == null || groupIds.ContainsKey(partIntake)) { - continue; - } - - groupIds[partIntake] = grpId; - intakes.Add(partIntake); - - for (int i = 0; i < part.symmetryCounterparts.Count; i++) - { - var sympart = part.symmetryCounterparts[i]; - stack.Push(sympart); - } - } - } - - // For each group in sorted order, if we need more air, open any - // closed intakes. If we have enough air, close any open intakes. - // OrderBy is stable, so we'll always be opening the same intakes. - double airFlowSoFar = 0; - KSPActionParam param = new KSPActionParam(KSPActionGroup.None, - KSPActionType.Activate); - foreach (var grp in groups.OrderBy(grp => grp.Count)) - { - if (airFlowSoFar < requiredFlow) - { - for (int i = 0; i < grp.Count; i++) - { - var intake = grp[i]; - double airFlowThisIntake = data[intake].predictedMassFlow; - if (!intake.intakeEnabled) - { - intake.ToggleAction(param); - } - airFlowSoFar += airFlowThisIntake; - } - } - else - { - for (int j = 0; j < grp.Count; j++) - { - var intake = grp[j]; - if (intake.intakeEnabled) - { - intake.ToggleAction(param); - } - } - } - } - } - - bool ElectricEngineRunning() { - var activeEngines = vessel.parts.Where(p => p.inverseStage >= vessel.currentStage && p.IsEngine() && !p.IsSepratron()); - var engineModules = activeEngines.Select(p => p.Modules.OfType().First(e => e.isEnabled)); - - return engineModules.SelectMany(eng => eng.propellants).Any(p => p.id == PartResourceLibrary.ElectricityHashcode); - } - - float ElectricThrottle() - { - double totalElectric = vessel.MaxResourceAmount(PartResourceLibrary.ElectricityHashcode); - double lowJuice = totalElectric * electricThrottleLo; - double highJuice = totalElectric * electricThrottleHi; - double curElectric = vessel.TotalResourceAmount(PartResourceLibrary.ElectricityHashcode); - - if (curElectric <= lowJuice) - return 0; - if (curElectric >= highJuice) - return 1; - // Avoid divide by zero - if (Math.Abs(highJuice - lowJuice) < 0.01) - return 1; - return Mathf.Clamp((float) ((curElectric - lowJuice) / (highJuice - lowJuice)), 0, 1); - } - - //The throttle setting that will give an acceleration of maxAcceleration - float AccelerationLimitedThrottle() - { - double throttleForMaxAccel = (maxAcceleration - vesselState.minThrustAccel) / (vesselState.maxThrustAccel - vesselState.minThrustAccel); - return Mathf.Clamp((float)throttleForMaxAccel, 0, 1); - } - - public override void OnUpdate() - { - if (core.GetComputerModule().hidden && core.GetComputerModule().hidden) - { - return; - } - - if (tmode_changed) - { - if (trans_kill_h && (tmode == TMode.OFF)) - { - core.attitude.attitudeDeactivate(); - } - pid.Reset(); - tmode_changed = false; - FlightInputHandler.SetNeutralControls(); - } - - bool disableThrusters = (userCommandingRotation && !core.rcs.rcsForRotation); - if (disableThrusters != lastDisableThrusters) - { - lastDisableThrusters = disableThrusters; - var rcsModules = vessel.FindPartModulesImplementing(); - foreach (var pm in rcsModules) - { - if (disableThrusters) - { - pm.enablePitch = pm.enableRoll = pm.enableYaw = false; - } - else - { - // TODO : Check the protopart for the original values (slow) ? Or use a dict to save them (hard with save) ? - pm.enablePitch = pm.enableRoll = pm.enableYaw = true; - } - } - } - } - - static void MaxThrust(double[] x, ref double func, double[] grad, object obj) - { - List el = (List)obj; - - func = 0; - - for (int i = 0, j = 0; j < el.Count; j++) - { - VesselState.EngineWrapper e = el[j]; - if (!e.engine.throttleLocked) - { - func -= el[j].maxVariableForce.y * x[i]; - grad[i] = -el[j].maxVariableForce.y; - i++; - } - } - } - - private DifferentialThrottleStatus ComputeDifferentialThrottle(Vector3d torque) - { - //var stopwatch = new Stopwatch(); - //stopwatch.Start(); - - float mainThrottle = vessel.ctrlState.mainThrottle; - - if (mainThrottle == 0) - { - torque = Vector3d.zero; - mainThrottle = 1; - } - - int nb_engines = vesselState.enginesWrappers.Count; - - double torqueScale = 0; - double forceScale = 0; - Vector3d force = new Vector3d(); - - for (int i = 0; i < nb_engines; i++) - { - torque -= vesselState.enginesWrappers[i].constantTorque; - torqueScale += vesselState.enginesWrappers[i].maxVariableTorque.magnitude; - - force += Vector3d.Dot(mainThrottle * vesselState.enginesWrappers[i].maxVariableForce, Vector3d.up) * Vector3d.up; - forceScale += vesselState.enginesWrappers[i].maxVariableForce.magnitude * 10; - } - - var engines = vesselState.enginesWrappers.Where(eng => !eng.engine.throttleLocked).ToList(); - var n = engines.Count; - - if (nb_engines == 0) - return DifferentialThrottleStatus.AllEnginesOff; - - if (nb_engines == 1 || n == 0) - return DifferentialThrottleStatus.MoreEnginesRequired; - - - double[,] a = new double[n, n]; - double[] b = new double[n]; - double[] boundL = new double[n]; - double[] boundU = new double[n]; - - for (int i = 0; i < n; i++) - { - for (int j = 0; j < n; j++) - { - a[i, j] = Vector3d.Dot(engines[i].maxVariableTorque, engines[j].maxVariableTorque) / (torqueScale * torqueScale) - + Vector3d.Dot(engines[i].maxVariableForce, engines[j].maxVariableForce) / (forceScale * forceScale); - } - b[i] = -Vector3d.Dot(engines[i].maxVariableTorque, torque) / (torqueScale * torqueScale) - - Vector3d.Dot(engines[i].maxVariableForce, force) / (forceScale * forceScale); - - boundL[i] = 0; - boundU[i] = mainThrottle; - } - alglib.minqpstate state; - alglib.minqpcreate(n, out state); - alglib.minqpsetquadraticterm(state, a); - alglib.minqpsetlinearterm(state, b); - alglib.minqpsetbc(state, boundL, boundU); - alglib.minqpsetalgobleic(state, 0.0, 0.0, 0.0, 0); - //var t1 = stopwatch.ElapsedMilliseconds; - alglib.minqpoptimize(state); - //var t2 = stopwatch.ElapsedMilliseconds; - double[] x; - alglib.minqpreport report; - alglib.minqpresults(state, out x, out report); - //var t3 = stopwatch.ElapsedMilliseconds; - //UnityEngine.Debug.LogFormat("[DiffThrottle] t1: {0}, t2: {1}, t3: {2}", t1, t2 - t1, t3 - t2); - - if (x.Any(val => double.IsNaN(val))) - return DifferentialThrottleStatus.SolverFailed; - - for (int i = 0; i < n; i++) - { - engines[i].thrustRatio = (float)(x[i] / mainThrottle); - } - - return DifferentialThrottleStatus.Success; - } - - public void DisableDifferentialThrottle() - { - for (int i = 0; i < vessel.parts.Count; i++) - { - Part p = vessel.parts[i]; - for (int j = 0; j < p.Modules.Count; j++) - { - PartModule pm = p.Modules[j]; - ModuleEngines engine = pm as ModuleEngines; - if (engine != null) - { - engine.thrustPercentage = 100; - } - } - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using KSP.UI.Screens; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleThrustController : ComputerModule + { + public enum DifferentialThrottleStatus + { + Success, + AllEnginesOff, + MoreEnginesRequired, + SolverFailed + } + + public MechJebModuleThrustController(MechJebCore core) + : base(core) + { + priority = 200; + } + + public float trans_spd_act = 0; + public float trans_prev_thrust = 0; + public bool trans_kill_h = false; + + + // The Terminal Velocity limiter is removed to not have to deal with users who + // think that seeing the aerodynamic FX means they reached it. + // And it s really high since 1.0.x anyway so the Dynamic Pressure limiter is better now + //[Persistent(pass = (int)Pass.Global)] + public bool limitToTerminalVelocity = false; + + //[GeneralInfoItem("Limit to terminal velocity", InfoItem.Category.Thrust)] + //public void LimitToTerminalVelocityInfoItem() + //{ + // GUIStyle s = new GUIStyle(GUI.skin.toggle); + // if (limiter == LimitMode.TerminalVelocity) s.onHover.textColor = s.onNormal.textColor = Color.green; + // limitToTerminalVelocity = GUILayout.Toggle(limitToTerminalVelocity, "Limit to terminal velocity", s); + //} + + [Persistent(pass = (int)Pass.Global)] + public bool limitDynamicPressure = false; + + [Persistent(pass = (int)Pass.Global)] + public EditableDouble maxDynamicPressure = 20000; + + [GeneralInfoItem("#MechJeb_LimittoMaxQ", InfoItem.Category.Thrust)]//Limit to Max Q + public void LimitToMaxDynamicPressureInfoItem() + { + GUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.DynamicPressure) s.onHover.textColor = s.onNormal.textColor = Color.green; + limitDynamicPressure = GUILayout.Toggle(limitDynamicPressure, Localizer.Format("#MechJeb_Ascent_checkbox11"), s, GUILayout.Width(140));//"Limit Q to" + maxDynamicPressure.text = GUILayout.TextField(maxDynamicPressure.text, GUILayout.Width(80)); + GUILayout.Label("pa", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + [Persistent(pass = (int)Pass.Global)] + public bool limitToPreventOverheats = false; + + [GeneralInfoItem("#MechJeb_PreventEngineOverheats", InfoItem.Category.Thrust)]//Prevent engine overheats + public void LimitToPreventOverheatsInfoItem() + { + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.Temperature) s.onHover.textColor = s.onNormal.textColor = Color.green; + limitToPreventOverheats = GUILayout.Toggle(limitToPreventOverheats, Localizer.Format("#MechJeb_Ascent_checkbox12"), s);//"Prevent engine overheats" + } + + [ToggleInfoItem("#MechJeb_SmoothThrottle", InfoItem.Category.Thrust)]//Smooth throttle + [Persistent(pass = (int)Pass.Global)] + public bool smoothThrottle = false; + + [Persistent(pass = (int)Pass.Global)] + public double throttleSmoothingTime = 1.0; + + [Persistent(pass = (int)Pass.Global)] + public bool limitToPreventFlameout = false; + + [GeneralInfoItem("#MechJeb_PreventJetFlameout", InfoItem.Category.Thrust)]//Prevent jet flameout + public void LimitToPreventFlameoutInfoItem() + { + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.Flameout) s.onHover.textColor = s.onNormal.textColor = Color.green; + limitToPreventFlameout = GUILayout.Toggle(limitToPreventFlameout, Localizer.Format("#MechJeb_Ascent_checkbox13"), s);//"Prevent jet flameout" + } + + [Persistent(pass = (int)Pass.Global)] + public bool limitToPreventUnstableIgnition = false; + + [GeneralInfoItem("#MechJeb_PreventUnstableIgnition", InfoItem.Category.Thrust)]//Prevent unstable ignition + public void LimitToPreventUnstableIgnitionInfoItem() + { + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.UnstableIgnition) s.onHover.textColor = s.onNormal.textColor = Color.green; + limitToPreventUnstableIgnition = GUILayout.Toggle(limitToPreventUnstableIgnition, Localizer.Format("#MechJeb_Ascent_checkbox14"), s);//"Prevent unstable ignition" + } + + [Persistent(pass = (int)Pass.Global)] + public bool autoRCSUllaging = true; + + [GeneralInfoItem("#MechJeb_UseRCStoullage", InfoItem.Category.Thrust)]//Use RCS to ullage + public void AutoRCsUllageInfoItem() + { + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.AutoRCSUllage) s.onHover.textColor = s.onNormal.textColor = Color.green; + autoRCSUllaging = GUILayout.Toggle(autoRCSUllaging, Localizer.Format("#MechJeb_Ascent_checkbox15"), s);//"Use RCS to ullage" + } + + // 5% safety margin on flameouts + [Persistent(pass = (int)Pass.Global)] + public EditableDouble flameoutSafetyPct = 5; + + [ToggleInfoItem("#MechJeb_ManageAirIntakes", InfoItem.Category.Thrust)]//Manage air intakes + [Persistent(pass = (int)Pass.Global)] + public bool manageIntakes = false; + + [Persistent(pass = (int)Pass.Global)] + public bool limitAcceleration = false; + + [Persistent(pass = (int)Pass.Global)] + public EditableDouble maxAcceleration = 40; + + [GeneralInfoItem("#MechJeb_LimitAcceleration", InfoItem.Category.Thrust)]//Limit acceleration + public void LimitAccelerationInfoItem() + { + GUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.Acceleration) s.onHover.textColor = s.onNormal.textColor = Color.green; + limitAcceleration = GUILayout.Toggle(limitAcceleration, Localizer.Format("#MechJeb_Ascent_checkbox16"), s, GUILayout.Width(140));//"Limit acceleration to" + maxAcceleration.text = GUILayout.TextField(maxAcceleration.text, GUILayout.Width(30)); + GUILayout.Label("m/s²", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + [Persistent(pass = (int)Pass.Local)] + public bool limitThrottle = false; + + [Persistent(pass = (int)Pass.Local)] + public EditableDoubleMult maxThrottle = new EditableDoubleMult(1, 0.01); + + [GeneralInfoItem("#MechJeb_LimitThrottle", InfoItem.Category.Thrust)]//Limit throttle + public void LimitThrottleInfoItem() + { + GUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.Throttle) s.onHover.textColor = s.onNormal.textColor = maxThrottle > 0d ? Color.green : Color.red; + limitThrottle = GUILayout.Toggle(limitThrottle, Localizer.Format("#MechJeb_Ascent_checkbox17"), s, GUILayout.Width(110));//"Limit throttle to" + maxThrottle.text = GUILayout.TextField(maxThrottle.text, GUILayout.Width(30)); + GUILayout.Label("%", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + [Persistent(pass = (int) (Pass.Local | Pass.Type | Pass.Global))] + public bool limiterMinThrottle = false; + + [Persistent(pass = (int) (Pass.Local | Pass.Type | Pass.Global))] + public EditableDoubleMult minThrottle = new EditableDoubleMult(0.05, 0.01); + + [GeneralInfoItem("#MechJeb_LowerThrottleLimit", InfoItem.Category.Thrust)]//Lower throttle limit + public void LimiterMinThrottleInfoItem() + { + GUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.MinThrottle) s.onHover.textColor = s.onNormal.textColor = Color.green; + limiterMinThrottle = GUILayout.Toggle(limiterMinThrottle, Localizer.Format("#MechJeb_Ascent_checkbox18"), s, GUILayout.Width(160));//"Keep limited throttle over" + minThrottle.text = GUILayout.TextField(minThrottle.text, GUILayout.Width(30)); + GUILayout.Label("%", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + [Persistent(pass = (int)Pass.Type)] + public bool differentialThrottle = false; + + [GeneralInfoItem("#MechJeb_DifferentialThrottle", InfoItem.Category.Thrust)]//Differential throttle + public void DifferentialThrottle() + { + bool oldDifferentialThrottle = core.thrust.differentialThrottle; + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (differentialThrottle && vessel.LiftedOff()) + { + s.onHover.textColor = s.onNormal.textColor = core.thrust.differentialThrottleSuccess == DifferentialThrottleStatus.Success ? Color.green : Color.yellow; + } + differentialThrottle = GUILayout.Toggle(differentialThrottle, Localizer.Format("#MechJeb_Ascent_checkbox19"), s);//"Differential throttle" + + if (oldDifferentialThrottle && !core.thrust.differentialThrottle) + core.thrust.DisableDifferentialThrottle(); + } + + public Vector3d differentialThrottleDemandedTorque = new Vector3d(); + + public DifferentialThrottleStatus differentialThrottleSuccess = DifferentialThrottleStatus.Success; + + [Persistent(pass = (int)Pass.Local)] + public bool electricThrottle = false; + + [Persistent(pass = (int)Pass.Local)] + public EditableDoubleMult electricThrottleLo = new EditableDoubleMult(0.05, 0.01); + [Persistent(pass = (int)Pass.Local)] + public EditableDoubleMult electricThrottleHi = new EditableDoubleMult(0.15, 0.01); + + [GeneralInfoItem("#MechJeb_ElectricLimit", InfoItem.Category.Thrust)]//Electric limit + public void LimitElectricInfoItem() + { + GUILayout.BeginHorizontal(); + GUIStyle s = new GUIStyle(GUI.skin.toggle); + if (limiter == LimitMode.Electric) + { + if (vesselState.throttleLimit <0.001) + s.onHover.textColor = s.onNormal.textColor = Color.red; + else + s.onHover.textColor = s.onNormal.textColor = Color.yellow; + } + else if (ElectricEngineRunning()) s.onHover.textColor = s.onNormal.textColor = Color.green; + + electricThrottle = GUILayout.Toggle(electricThrottle, Localizer.Format("#MechJeb_Ascent_checkbox20"), s, GUILayout.Width(110));//"Electric limit Lo" + electricThrottleLo.text = GUILayout.TextField(electricThrottleLo.text, GUILayout.Width(30)); + GUILayout.Label("% Hi", GUILayout.ExpandWidth(false)); + electricThrottleHi.text = GUILayout.TextField(electricThrottleHi.text, GUILayout.Width(30)); + GUILayout.Label("%", GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + public enum LimitMode { None, TerminalVelocity, Temperature, Flameout, Acceleration, Throttle, DynamicPressure, MinThrottle, Electric, UnstableIgnition, AutoRCSUllage } + public LimitMode limiter = LimitMode.None; + + public float targetThrottle = 0; + + protected bool tmode_changed = false; + + + public PIDController pid; + + float lastThrottle = 0; + bool userCommandingRotation { get { return userCommandingRotationSmoothed > 0; } } + int userCommandingRotationSmoothed = 0; + bool lastDisableThrusters = false; + + public enum TMode + { + OFF, + KEEP_ORBITAL, + KEEP_SURFACE, + KEEP_VERTICAL, + KEEP_RELATIVE, + DIRECT + } + + private TMode prev_tmode = TMode.OFF; + private TMode _tmode = TMode.OFF; + public TMode tmode + { + get + { + return _tmode; + } + set + { + if (_tmode != value) + { + prev_tmode = _tmode; + _tmode = value; + tmode_changed = true; + } + } + } + + ScreenMessage preventingUnstableIgnitionsMessage; + + public override void OnStart(PartModule.StartState state) + { + preventingUnstableIgnitionsMessage = new ScreenMessage(Localizer.Format("#MechJeb_Ascent_srcmsg1"), 2f, ScreenMessageStyle.UPPER_CENTER);//"[MechJeb]: Killing throttle to prevent unstable ignition" + pid = new PIDController(0.05, 0.000001, 0.05); + users.Add(this); + + base.OnStart(state); + } + + public void ThrustOff() + { + if (vessel == null || vessel.ctrlState == null) + return; + + targetThrottle = 0; + vessel.ctrlState.mainThrottle = 0; + tmode = TMode.OFF; + SetFlightGlobals(0); + } + + private void SetFlightGlobals(double throttle) + { + if (FlightGlobals.ActiveVessel != null && vessel == FlightGlobals.ActiveVessel) + { + FlightInputHandler.state.mainThrottle = (float) throttle; //so that the on-screen throttle gauge reflects the autopilot throttle + } + } + + // Call this function to set the throttle for a burn with dV delta-V remaining. + // timeConstant controls how quickly we throttle down toward the end of the burn. + // This function is nice because it will correctly handle engine spool-up/down times. + public void ThrustForDV(double dV, double timeConstant) + { + timeConstant += vesselState.maxEngineResponseTime; + double spooldownDV = vesselState.currentThrustAccel * vesselState.maxEngineResponseTime; + double desiredAcceleration = (dV - spooldownDV) / timeConstant; + + targetThrottle = Mathf.Clamp01((float)(desiredAcceleration / vesselState.maxThrustAccel)); + } + + /* the current throttle limit, this may include transient condition such as limiting to zero due to unstable propellants in RF */ + public float throttleLimit { get; private set; } + /* the fixed throttle limit (i.e. user limited in the GUI), does not include transient conditions as limiting to zero due to unstable propellants in RF */ + public float throttleFixedLimit { get; private set; } + + /* This is an API for limits which are "temporary" or "conditional" (things like ullage status which will change very soon). + This will often be temporarily zero, which means the computed accelleration of the ship will be zero, which would cause + consumers (like the NodeExecutor) to compute infinite burntime, so this value should not be used by those consumers */ + private void setTempLimit(float limit, LimitMode mode) + { + throttleLimit = limit; + limiter = mode; + } + + /* This is an API for limits which are not temporary (like the throttle limit set in the GUI) + The throttleFixedLimit is what consumers like the NodeExecutor should use to compute acceleration and burntime. + This deliberately sets both values. The actually applied throttle limit may be lower than thottleFixedLimit + (i.e. there may be a more limiting temp limit) */ + private void setFixedLimit(float limit, LimitMode mode) + { + if (throttleLimit > limit) { + throttleLimit = limit; + } + throttleFixedLimit = limit; + limiter = mode; + } + + public override void Drive(FlightCtrlState s) + { + float threshold = 0.1F; + bool _userCommandingRotation = !(Mathfx.Approx(s.pitch, s.pitchTrim, threshold) + && Mathfx.Approx(s.yaw, s.yawTrim, threshold) + && Mathfx.Approx(s.roll, s.rollTrim, threshold)); + bool _userCommandingTranslation = !(Math.Abs(s.X) < threshold + && Math.Abs(s.Y) < threshold + && Math.Abs(s.Z) < threshold); + + if (_userCommandingRotation && !_userCommandingTranslation) + { + userCommandingRotationSmoothed = 2; + } + else if (userCommandingRotationSmoothed > 0) + { + userCommandingRotationSmoothed--; + } + + if (core.GetComputerModule().hidden && core.GetComputerModule().hidden) { return; } + + if ((tmode != TMode.OFF) && (vesselState.thrustAvailable > 0)) + { + double spd = 0; + + switch (tmode) + { + case TMode.KEEP_ORBITAL: + spd = vesselState.speedOrbital; + break; + case TMode.KEEP_SURFACE: + spd = vesselState.speedSurface; + break; + case TMode.KEEP_VERTICAL: + spd = vesselState.speedVertical; + Vector3d rot = Vector3d.up; + if (trans_kill_h) + { + Vector3 hsdir = Vector3.ProjectOnPlane(vesselState.surfaceVelocity, vesselState.up); + Vector3 dir = -hsdir + vesselState.up * Math.Max(Math.Abs(spd), 20 * mainBody.GeeASL); + if ((Math.Min(vesselState.altitudeASL, vesselState.altitudeTrue) > 5000) && (hsdir.magnitude > Math.Max(Math.Abs(spd), 100 * mainBody.GeeASL) * 2)) + { + tmode = TMode.DIRECT; + trans_spd_act = 100; + rot = -hsdir; + } + else + { + rot = dir.normalized; + } + core.attitude.attitudeTo(rot, AttitudeReference.INERTIAL, null); + } + break; + } + + double t_err = (trans_spd_act - spd) / vesselState.maxThrustAccel; + if ((tmode == TMode.KEEP_ORBITAL && Vector3d.Dot(vesselState.forward, vesselState.orbitalVelocity) < 0) || + (tmode == TMode.KEEP_SURFACE && Vector3d.Dot(vesselState.forward, vesselState.surfaceVelocity) < 0)) + { + //allow thrust to declerate + t_err *= -1; + } + + double t_act = pid.Compute(t_err); + + if ((tmode != TMode.KEEP_VERTICAL) + || !trans_kill_h + || (core.attitude.attitudeError < 2) + || ((Math.Min(vesselState.altitudeASL, vesselState.altitudeTrue) < 1000) && (core.attitude.attitudeError < 90))) + { + if (tmode == TMode.DIRECT) + { + trans_prev_thrust = targetThrottle = trans_spd_act / 100.0F; + } + else + { + trans_prev_thrust = targetThrottle = Mathf.Clamp01(trans_prev_thrust + (float)t_act); + } + } + else + { + bool useGimbal = (vesselState.torqueGimbal.positive.x > vesselState.torqueAvailable.x * 10) || + (vesselState.torqueGimbal.positive.z > vesselState.torqueAvailable.z * 10); + + bool useDiffThrottle = (vesselState.torqueDiffThrottle.x > vesselState.torqueAvailable.x * 10) || + (vesselState.torqueDiffThrottle.z > vesselState.torqueAvailable.z * 10); + + if ((core.attitude.attitudeError >= 2) && (useGimbal || (useDiffThrottle && core.thrust.differentialThrottle))) + { + trans_prev_thrust = targetThrottle = 0.1F; + print(" targetThrottle = 0.1F"); + } + else + { + trans_prev_thrust = targetThrottle = 0; + } + } + } + + // Only set throttle if a module need it. Otherwise let the user or other mods set it + // There is always at least 1 user : the module itself (why ?) + if (users.Count > 1) + s.mainThrottle = targetThrottle; + + throttleLimit = 1; + throttleFixedLimit = 1; + + limiter = LimitMode.None; + + if (limitThrottle) + { + if (maxThrottle < throttleLimit) + { + setFixedLimit((float)maxThrottle, LimitMode.Throttle); + } + } + + if (limitToTerminalVelocity) + { + float limit = TerminalVelocityThrottle(); + if (limit < throttleLimit) + { + setFixedLimit(limit, LimitMode.TerminalVelocity); + } + } + + if (limitDynamicPressure) + { + float limit = MaximumDynamicPressureThrottle(); + if (limit < throttleLimit) + { + setFixedLimit(limit, LimitMode.DynamicPressure); + } + } + + if (limitToPreventOverheats) + { + float limit = (float)TemperatureSafetyThrottle(); + if (limit < throttleLimit) { + setFixedLimit(limit, LimitMode.Temperature); + } + } + + if (limitAcceleration) + { + float limit = AccelerationLimitedThrottle(); + if (limit < throttleLimit) { + setFixedLimit(limit, LimitMode.Acceleration); + } + } + + if (electricThrottle && ElectricEngineRunning()) + { + float limit = ElectricThrottle(); + if (limit < throttleLimit) { + setFixedLimit(limit, LimitMode.Electric); + } + } + + if (limitToPreventFlameout) + { + // This clause benefits being last: if we don't need much air + // due to prior limits, we can close some intakes. + float limit = FlameoutSafetyThrottle(); + if (limit < throttleLimit) { + setFixedLimit(limit, LimitMode.Flameout); + } + } + + // Any limiters which can limit to non-zero values must come before this, any + // limiters (like ullage) which enforce zero throttle should come after. The + // minThrottle setting has authority over any other limiter that sets non-zero throttle. + if (limiterMinThrottle && limiter != LimitMode.None) + { + if (minThrottle > throttleFixedLimit) + { + setFixedLimit((float) minThrottle, LimitMode.MinThrottle); + } + if (minThrottle > throttleLimit) + { + setTempLimit((float) minThrottle, LimitMode.MinThrottle); + } + } + + /* auto-RCS ullaging up to very stable */ + if (autoRCSUllaging && s.mainThrottle > 0.0F && throttleLimit > 0.0F ) + { + if (vesselState.lowestUllage < VesselState.UllageState.VeryStable) + { + Debug.Log("MechJeb RCS auto-ullaging: found state below very stable: " + vesselState.lowestUllage); + if (vessel.hasEnabledRCSModules()) + { + if (!vessel.ActionGroups[KSPActionGroup.RCS]) + { + Debug.Log("MechJeb RCS auto-ullaging: enabling RCS action group for automatic ullaging"); + vessel.ActionGroups.SetGroup(KSPActionGroup.RCS, true); + } + Debug.Log("MechJeb RCS auto-ullaging: firing RCS to stabilize ulllage"); + setTempLimit(0.0F, LimitMode.UnstableIgnition); + s.Z = -1.0F; + } else { + Debug.Log("MechJeb RCS auto-ullaging: vessel has no enabled/staged RCS modules"); + } + } + } + + /* prevent unstable ignitions */ + if (limitToPreventUnstableIgnition && s.mainThrottle > 0.0F && throttleLimit > 0.0F ) + { + if (vesselState.lowestUllage < VesselState.UllageState.Stable) + { + ScreenMessages.PostScreenMessage(preventingUnstableIgnitionsMessage); + Debug.Log("MechJeb Unstable Ignitions: preventing ignition in state: " + vesselState.lowestUllage); + setTempLimit(0.0F, LimitMode.UnstableIgnition); + } + } + + // we have to force the throttle here so that rssMode can work, otherwise we don't get our last throttle command + // back on the next tick after disabling. we save this before applying the throttle limits so that we preserve + // the requested throttle, and not the limited throttle. + if (core.rssMode) + { + SetFlightGlobals(s.mainThrottle); + } + + if (double.IsNaN(throttleLimit)) throttleLimit = 1.0F; + throttleLimit = Mathf.Clamp01(throttleLimit); + + /* we do not _apply_ the "fixed" limit, the actual throttleLimit should always be the more limited and lower one */ + /* the purpose of the "fixed" limit is for external consumers like the node executor to consume */ + if (double.IsNaN(throttleFixedLimit)) throttleFixedLimit = 1.0F; + throttleFixedLimit = Mathf.Clamp01(throttleFixedLimit); + + vesselState.throttleLimit = throttleLimit; + vesselState.throttleFixedLimit = throttleFixedLimit; + + if (s.mainThrottle < throttleLimit) limiter = LimitMode.None; + + s.mainThrottle = Mathf.Min(s.mainThrottle, throttleLimit); + + if (smoothThrottle) + { + s.mainThrottle = SmoothThrottle(s.mainThrottle); + } + + if (double.IsNaN(s.mainThrottle)) s.mainThrottle = 0; + + s.mainThrottle = Mathf.Clamp01(s.mainThrottle); + + + if (s.Z == 0 && core.rcs.rcsThrottle && vesselState.rcsThrust) s.Z = -s.mainThrottle; + + lastThrottle = s.mainThrottle; + + if (!core.attitude.enabled) + { + Vector3d act = new Vector3d(s.pitch, s.yaw, s.roll); + differentialThrottleDemandedTorque = -Vector3d.Scale(act.xzy, vesselState.torqueDiffThrottle * s.mainThrottle * 0.5f); + } + } + + public override void OnFixedUpdate() + { + if (differentialThrottle) + { + differentialThrottleSuccess = ComputeDifferentialThrottle(differentialThrottleDemandedTorque); + } + else + { + differentialThrottleSuccess = DifferentialThrottleStatus.Success; + } + } + + //A throttle setting that throttles down when the vertical velocity of the ship exceeds terminal velocity + float TerminalVelocityThrottle() + { + if (vesselState.altitudeASL > mainBody.RealMaxAtmosphereAltitude()) return 1.0F; + + double velocityRatio = Vector3d.Dot(vesselState.surfaceVelocity, vesselState.up) / vesselState.TerminalVelocity(); + + if (velocityRatio < 1.0) return 1.0F; //full throttle if under terminal velocity + + //throttle down quickly as we exceed terminal velocity: + const double falloff = 15.0; + return Mathf.Clamp((float)(1.0 - falloff * (velocityRatio - 1.0)), 0.0F, 1.0F); + } + + //A throttle setting that throttles down when the dynamic pressure exceed a set value + float MaximumDynamicPressureThrottle() + { + if (maxDynamicPressure <= 0) return 1.0F; + + double pressureRatio = vesselState.dynamicPressure / maxDynamicPressure; + + if (pressureRatio < 1.0) return 1.0F; //full throttle if under maximum dynamic pressure + + //throttle down quickly as we exceed maximum dynamic pressure: + const double falloff = 15.0; + return Mathf.Clamp((float)(1.0 - falloff * (pressureRatio - 1.0)), 0.0F, 1.0F); + } + + //a throttle setting that throttles down if something is close to overheating + double TemperatureSafetyThrottle() + { + double maxTempRatio = vessel.parts.Max(p => p.temperature / p.maxTemp); + + //reduce throttle as the max temp. ratio approaches 1 within the safety margin + const double tempSafetyMargin = 0.05f; + if (maxTempRatio < 1 - tempSafetyMargin) return 1.0F; + else return (1 - maxTempRatio) / tempSafetyMargin; + } + + float SmoothThrottle(float mainThrottle) + { + return Mathf.Clamp(mainThrottle, + (float)(lastThrottle - vesselState.deltaT / throttleSmoothingTime), + (float)(lastThrottle + vesselState.deltaT / throttleSmoothingTime)); + } + + float FlameoutSafetyThrottle() + { + float throttle = 1; + + // For every resource that is provided by intakes, find how + // much we need at full throttle, add a safety margin, and + // that's the max throttle for that resource. Take the min of + // the max throttles. + foreach (var resource in vesselState.resources.Values) + { + if (resource.intakes.Count == 0) + { + // No intakes provide this resource; not our problem. + continue; + } + + // Compute the amount of air we will need, plus a safety + // margin. Make sure it's at least enough for last frame's + // requirement, since it takes time for engines to spin down. + // Note: this could be zero, e.g. if we have intakes but no + // jets. + + double margin = (1 + 0.01 * flameoutSafetyPct); + double safeRequirement = margin * resource.requiredAtMaxThrottle; + safeRequirement = Math.Max(safeRequirement, resource.required); + + // Open the right number of intakes. + if (manageIntakes) + { + OptimizeIntakes(resource, safeRequirement); + } + + double provided = resource.intakeProvided; + if (resource.required >= provided) + { + // We must cut throttle immediately, otherwise we are + // flaming out immediately. Continue doing the rest of the + // loop anyway, but we'll be at zero throttle. + throttle = 0; + } + else + { + double maxthrottle = provided / safeRequirement; + throttle = Mathf.Min(throttle, (float)maxthrottle); + } + } + return throttle; + } + + // Open and close intakes so that they provide the required flow but no + // more. + void OptimizeIntakes(VesselState.ResourceInfo info, double requiredFlow) + { + // The highly imperfect algorithm here is: + // - group intakes by symmetry + // - sort the groups by their size + // - open a group at a time until we have enough airflow + // - close the remaining groups + + // TODO: improve the algorithm: + // 1. Take cost-benefit into account. We want to open ram intakes + // before any others, and we probably never want to open + // nacelles. We need more info to know how much thrust we lose + // for losing airflow, versus how much drag we gain for opening + // an intake. + // 2. Take the center of drag into account. We don't need to open + // symmetry groups all at once; we just need the center of drag + // to be in a good place. Symmetry in the SPH also doesn't + // guarantee anything; we could be opening all pairs that are + // above the plane through the center of mass and none below, + // which makes control harder. + // Even just point (1) means we want to solve knapsack. + + // Group intakes by symmetry, and collect the information by intake. + var groups = new List>(); + var groupIds = new Dictionary(); + var data = new Dictionary(); + foreach (var intakeData in info.intakes) + { + ModuleResourceIntake intake = intakeData.intake; + data[intake] = intakeData; + if (groupIds.ContainsKey(intake)) { continue; } + + // Create a group for this symmetry + int grpId = groups.Count; + var intakes = new List(); + groups.Add(intakes); + + // In DFS order, get all the symmetric parts. + // We can't rely on symmetryCounterparts; see bug #52 by tavert: + // https://github.com/MuMech/MechJeb2/issues/52 + var stack = new Stack(); + stack.Push(intake.part); + while(stack.Count > 0) { + var part = stack.Pop(); + var partIntake = part.Modules.OfType().FirstOrDefault(); + if (partIntake == null || groupIds.ContainsKey(partIntake)) { + continue; + } + + groupIds[partIntake] = grpId; + intakes.Add(partIntake); + + for (int i = 0; i < part.symmetryCounterparts.Count; i++) + { + var sympart = part.symmetryCounterparts[i]; + stack.Push(sympart); + } + } + } + + // For each group in sorted order, if we need more air, open any + // closed intakes. If we have enough air, close any open intakes. + // OrderBy is stable, so we'll always be opening the same intakes. + double airFlowSoFar = 0; + KSPActionParam param = new KSPActionParam(KSPActionGroup.None, + KSPActionType.Activate); + foreach (var grp in groups.OrderBy(grp => grp.Count)) + { + if (airFlowSoFar < requiredFlow) + { + for (int i = 0; i < grp.Count; i++) + { + var intake = grp[i]; + double airFlowThisIntake = data[intake].predictedMassFlow; + if (!intake.intakeEnabled) + { + intake.ToggleAction(param); + } + airFlowSoFar += airFlowThisIntake; + } + } + else + { + for (int j = 0; j < grp.Count; j++) + { + var intake = grp[j]; + if (intake.intakeEnabled) + { + intake.ToggleAction(param); + } + } + } + } + } + + bool ElectricEngineRunning() { + var activeEngines = vessel.parts.Where(p => p.inverseStage >= vessel.currentStage && p.IsEngine() && !p.IsSepratron()); + var engineModules = activeEngines.Select(p => p.Modules.OfType().First(e => e.isEnabled)); + + return engineModules.SelectMany(eng => eng.propellants).Any(p => p.id == PartResourceLibrary.ElectricityHashcode); + } + + float ElectricThrottle() + { + double totalElectric = vessel.MaxResourceAmount(PartResourceLibrary.ElectricityHashcode); + double lowJuice = totalElectric * electricThrottleLo; + double highJuice = totalElectric * electricThrottleHi; + double curElectric = vessel.TotalResourceAmount(PartResourceLibrary.ElectricityHashcode); + + if (curElectric <= lowJuice) + return 0; + if (curElectric >= highJuice) + return 1; + // Avoid divide by zero + if (Math.Abs(highJuice - lowJuice) < 0.01) + return 1; + return Mathf.Clamp((float) ((curElectric - lowJuice) / (highJuice - lowJuice)), 0, 1); + } + + //The throttle setting that will give an acceleration of maxAcceleration + float AccelerationLimitedThrottle() + { + double throttleForMaxAccel = (maxAcceleration - vesselState.minThrustAccel) / (vesselState.maxThrustAccel - vesselState.minThrustAccel); + return Mathf.Clamp((float)throttleForMaxAccel, 0, 1); + } + + public override void OnUpdate() + { + if (core.GetComputerModule().hidden && core.GetComputerModule().hidden) + { + return; + } + + if (tmode_changed) + { + if (trans_kill_h && (tmode == TMode.OFF)) + { + core.attitude.attitudeDeactivate(); + } + pid.Reset(); + tmode_changed = false; + FlightInputHandler.SetNeutralControls(); + } + + bool disableThrusters = (userCommandingRotation && !core.rcs.rcsForRotation); + if (disableThrusters != lastDisableThrusters) + { + lastDisableThrusters = disableThrusters; + var rcsModules = vessel.FindPartModulesImplementing(); + foreach (var pm in rcsModules) + { + if (disableThrusters) + { + pm.enablePitch = pm.enableRoll = pm.enableYaw = false; + } + else + { + // TODO : Check the protopart for the original values (slow) ? Or use a dict to save them (hard with save) ? + pm.enablePitch = pm.enableRoll = pm.enableYaw = true; + } + } + } + } + + static void MaxThrust(double[] x, ref double func, double[] grad, object obj) + { + List el = (List)obj; + + func = 0; + + for (int i = 0, j = 0; j < el.Count; j++) + { + VesselState.EngineWrapper e = el[j]; + if (!e.engine.throttleLocked) + { + func -= el[j].maxVariableForce.y * x[i]; + grad[i] = -el[j].maxVariableForce.y; + i++; + } + } + } + + private DifferentialThrottleStatus ComputeDifferentialThrottle(Vector3d torque) + { + //var stopwatch = new Stopwatch(); + //stopwatch.Start(); + + float mainThrottle = vessel.ctrlState.mainThrottle; + + if (mainThrottle == 0) + { + torque = Vector3d.zero; + mainThrottle = 1; + } + + int nb_engines = vesselState.enginesWrappers.Count; + + double torqueScale = 0; + double forceScale = 0; + Vector3d force = new Vector3d(); + + for (int i = 0; i < nb_engines; i++) + { + torque -= vesselState.enginesWrappers[i].constantTorque; + torqueScale += vesselState.enginesWrappers[i].maxVariableTorque.magnitude; + + force += Vector3d.Dot(mainThrottle * vesselState.enginesWrappers[i].maxVariableForce, Vector3d.up) * Vector3d.up; + forceScale += vesselState.enginesWrappers[i].maxVariableForce.magnitude * 10; + } + + var engines = vesselState.enginesWrappers.Where(eng => !eng.engine.throttleLocked).ToList(); + var n = engines.Count; + + if (nb_engines == 0) + return DifferentialThrottleStatus.AllEnginesOff; + + if (nb_engines == 1 || n == 0) + return DifferentialThrottleStatus.MoreEnginesRequired; + + + double[,] a = new double[n, n]; + double[] b = new double[n]; + double[] boundL = new double[n]; + double[] boundU = new double[n]; + + for (int i = 0; i < n; i++) + { + for (int j = 0; j < n; j++) + { + a[i, j] = Vector3d.Dot(engines[i].maxVariableTorque, engines[j].maxVariableTorque) / (torqueScale * torqueScale) + + Vector3d.Dot(engines[i].maxVariableForce, engines[j].maxVariableForce) / (forceScale * forceScale); + } + b[i] = -Vector3d.Dot(engines[i].maxVariableTorque, torque) / (torqueScale * torqueScale) + - Vector3d.Dot(engines[i].maxVariableForce, force) / (forceScale * forceScale); + + boundL[i] = 0; + boundU[i] = mainThrottle; + } + alglib.minqpstate state; + alglib.minqpcreate(n, out state); + alglib.minqpsetquadraticterm(state, a); + alglib.minqpsetlinearterm(state, b); + alglib.minqpsetbc(state, boundL, boundU); + alglib.minqpsetalgobleic(state, 0.0, 0.0, 0.0, 0); + //var t1 = stopwatch.ElapsedMilliseconds; + alglib.minqpoptimize(state); + //var t2 = stopwatch.ElapsedMilliseconds; + double[] x; + alglib.minqpreport report; + alglib.minqpresults(state, out x, out report); + //var t3 = stopwatch.ElapsedMilliseconds; + //UnityEngine.Debug.LogFormat("[DiffThrottle] t1: {0}, t2: {1}, t3: {2}", t1, t2 - t1, t3 - t2); + + if (x.Any(val => double.IsNaN(val))) + return DifferentialThrottleStatus.SolverFailed; + + for (int i = 0; i < n; i++) + { + engines[i].thrustRatio = (float)(x[i] / mainThrottle); + } + + return DifferentialThrottleStatus.Success; + } + + public void DisableDifferentialThrottle() + { + for (int i = 0; i < vessel.parts.Count; i++) + { + Part p = vessel.parts[i]; + for (int j = 0; j < p.Modules.Count; j++) + { + PartModule pm = p.Modules[j]; + ModuleEngines engine = pm as ModuleEngines; + if (engine != null) + { + engine.thrustPercentage = 100; + } + } + } + } + } +} diff --git a/MechJeb2/MechJebModuleThrustWindow.cs b/MechJeb2/MechJebModuleThrustWindow.cs index 21c009bf9..9723051dd 100644 --- a/MechJeb2/MechJebModuleThrustWindow.cs +++ b/MechJeb2/MechJebModuleThrustWindow.cs @@ -1,132 +1,132 @@ -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleThrustWindow : DisplayModule - { - - [Persistent(pass = (int)Pass.Local)] - public bool autostageSavedState = false; - - public MechJebModuleThrustWindow(MechJebCore core) : base(core) { } - - public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnLoad(local, type, global); - - if (autostageSavedState && !core.staging.users.Contains(this)) - { - core.staging.users.Add(this); - } - } - - [GeneralInfoItem("#MechJeb_AutostageOnce", InfoItem.Category.Misc)]//Autostage Once - public void AutostageOnceItem() - { - if (core.staging.enabled) - { - GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label1", (core.staging.autostagingOnce ? Localizer.Format("#MechJeb_Utilities_label1_1") : " ")));//"Autostaging"<<1>>"Active" -------<<1>>" once ":"" - } - if (!core.staging.enabled && GUILayout.Button(Localizer.Format("#MechJeb_Utilities_button1")))//"Autostage once" - { - core.staging.AutostageOnce(this); - } - } - - [GeneralInfoItem("#MechJeb_Autostage", InfoItem.Category.Misc)]//Autostage - public void Autostage() - { - bool oldAutostage = core.staging.users.Contains(this); - bool newAutostage = GUILayout.Toggle(oldAutostage, Localizer.Format("#MechJeb_Utilities_checkbox1"));//"Autostage" - if (newAutostage && !oldAutostage) core.staging.users.Add(this); - if (!newAutostage && oldAutostage) core.staging.users.Remove(this); - autostageSavedState = newAutostage; - } - - // UI stuff - protected override void WindowGUI(int windowID) - { - GUILayout.BeginVertical(); - - //core.thrust.LimitToTerminalVelocityInfoItem(); - core.thrust.LimitToMaxDynamicPressureInfoItem(); - core.thrust.LimitToPreventOverheatsInfoItem(); - core.thrust.LimitAccelerationInfoItem(); - core.thrust.LimitThrottleInfoItem(); - core.thrust.LimiterMinThrottleInfoItem(); - core.thrust.LimitElectricInfoItem(); - core.thrust.LimitToPreventFlameoutInfoItem(); - if (VesselState.isLoadedRealFuels) - { - // does nothing in stock, so we suppress displaying it if RF is not loaded - core.thrust.LimitToPreventUnstableIgnitionInfoItem(); - core.thrust.AutoRCsUllageInfoItem(); - } - core.thrust.smoothThrottle = GUILayout.Toggle(core.thrust.smoothThrottle, Localizer.Format("#MechJeb_Utilities_checkbox2"));//"Smooth throttle" - core.thrust.manageIntakes = GUILayout.Toggle(core.thrust.manageIntakes, Localizer.Format("#MechJeb_Utilities_checkbox3"));//"Manage air intakes" - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); - try - { - GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label2"));//"Jet safety margin" - core.thrust.flameoutSafetyPct.text = GUILayout.TextField(core.thrust.flameoutSafetyPct.text, 5); - GUILayout.Label("%"); - } - finally - { - GUILayout.EndHorizontal(); - } - - core.thrust.DifferentialThrottle(); - - if (core.thrust.differentialThrottle && vessel.LiftedOff()) - { - switch (core.thrust.differentialThrottleSuccess) - { - case MechJebModuleThrustController.DifferentialThrottleStatus.MoreEnginesRequired: - GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label3"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nMore engines required" - break; - case MechJebModuleThrustController.DifferentialThrottleStatus.AllEnginesOff: - GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label4"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nNo active engine" - break; - case MechJebModuleThrustController.DifferentialThrottleStatus.SolverFailed: - GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label5"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nCannot find solution" - break; - case MechJebModuleThrustController.DifferentialThrottleStatus.Success: - break; - } - } - - core.solarpanel.SolarPanelDeployButton(); - core.antennaControl.AntennaDeployButton(); - - Autostage(); - - if (!core.staging.enabled && GUILayout.Button(Localizer.Format("#MechJeb_Utilities_button1"))) core.staging.AutostageOnce(this);//"Autostage once" - - if (core.staging.enabled) core.staging.AutostageSettingsInfoItem(); - - if (core.staging.enabled) GUILayout.Label(core.staging.AutostageStatus()); - - GUILayout.EndVertical(); - - base.WindowGUI(windowID); - } - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[]{ - GUILayout.Width(250), GUILayout.Height(30) - }; - } - - public override bool isActive() - { - return core.thrust.limiter != MechJebModuleThrustController.LimitMode.None; - } - - public override string GetName() - { - return Localizer.Format("#MechJeb_Utilities_title");//"Utilities" - } - } -} +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleThrustWindow : DisplayModule + { + + [Persistent(pass = (int)Pass.Local)] + public bool autostageSavedState = false; + + public MechJebModuleThrustWindow(MechJebCore core) : base(core) { } + + public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnLoad(local, type, global); + + if (autostageSavedState && !core.staging.users.Contains(this)) + { + core.staging.users.Add(this); + } + } + + [GeneralInfoItem("#MechJeb_AutostageOnce", InfoItem.Category.Misc)]//Autostage Once + public void AutostageOnceItem() + { + if (core.staging.enabled) + { + GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label1", (core.staging.autostagingOnce ? Localizer.Format("#MechJeb_Utilities_label1_1") : " ")));//"Autostaging"<<1>>"Active" -------<<1>>" once ":"" + } + if (!core.staging.enabled && GUILayout.Button(Localizer.Format("#MechJeb_Utilities_button1")))//"Autostage once" + { + core.staging.AutostageOnce(this); + } + } + + [GeneralInfoItem("#MechJeb_Autostage", InfoItem.Category.Misc)]//Autostage + public void Autostage() + { + bool oldAutostage = core.staging.users.Contains(this); + bool newAutostage = GUILayout.Toggle(oldAutostage, Localizer.Format("#MechJeb_Utilities_checkbox1"));//"Autostage" + if (newAutostage && !oldAutostage) core.staging.users.Add(this); + if (!newAutostage && oldAutostage) core.staging.users.Remove(this); + autostageSavedState = newAutostage; + } + + // UI stuff + protected override void WindowGUI(int windowID) + { + GUILayout.BeginVertical(); + + //core.thrust.LimitToTerminalVelocityInfoItem(); + core.thrust.LimitToMaxDynamicPressureInfoItem(); + core.thrust.LimitToPreventOverheatsInfoItem(); + core.thrust.LimitAccelerationInfoItem(); + core.thrust.LimitThrottleInfoItem(); + core.thrust.LimiterMinThrottleInfoItem(); + core.thrust.LimitElectricInfoItem(); + core.thrust.LimitToPreventFlameoutInfoItem(); + if (VesselState.isLoadedRealFuels) + { + // does nothing in stock, so we suppress displaying it if RF is not loaded + core.thrust.LimitToPreventUnstableIgnitionInfoItem(); + core.thrust.AutoRCsUllageInfoItem(); + } + core.thrust.smoothThrottle = GUILayout.Toggle(core.thrust.smoothThrottle, Localizer.Format("#MechJeb_Utilities_checkbox2"));//"Smooth throttle" + core.thrust.manageIntakes = GUILayout.Toggle(core.thrust.manageIntakes, Localizer.Format("#MechJeb_Utilities_checkbox3"));//"Manage air intakes" + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + try + { + GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label2"));//"Jet safety margin" + core.thrust.flameoutSafetyPct.text = GUILayout.TextField(core.thrust.flameoutSafetyPct.text, 5); + GUILayout.Label("%"); + } + finally + { + GUILayout.EndHorizontal(); + } + + core.thrust.DifferentialThrottle(); + + if (core.thrust.differentialThrottle && vessel.LiftedOff()) + { + switch (core.thrust.differentialThrottleSuccess) + { + case MechJebModuleThrustController.DifferentialThrottleStatus.MoreEnginesRequired: + GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label3"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nMore engines required" + break; + case MechJebModuleThrustController.DifferentialThrottleStatus.AllEnginesOff: + GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label4"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nNo active engine" + break; + case MechJebModuleThrustController.DifferentialThrottleStatus.SolverFailed: + GUILayout.Label(Localizer.Format("#MechJeb_Utilities_label5"), new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } });//"Differential throttle failed\nCannot find solution" + break; + case MechJebModuleThrustController.DifferentialThrottleStatus.Success: + break; + } + } + + core.solarpanel.SolarPanelDeployButton(); + core.antennaControl.AntennaDeployButton(); + + Autostage(); + + if (!core.staging.enabled && GUILayout.Button(Localizer.Format("#MechJeb_Utilities_button1"))) core.staging.AutostageOnce(this);//"Autostage once" + + if (core.staging.enabled) core.staging.AutostageSettingsInfoItem(); + + if (core.staging.enabled) GUILayout.Label(core.staging.AutostageStatus()); + + GUILayout.EndVertical(); + + base.WindowGUI(windowID); + } + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[]{ + GUILayout.Width(250), GUILayout.Height(30) + }; + } + + public override bool isActive() + { + return core.thrust.limiter != MechJebModuleThrustController.LimitMode.None; + } + + public override string GetName() + { + return Localizer.Format("#MechJeb_Utilities_title");//"Utilities" + } + } +} diff --git a/MechJeb2/MechJebModuleWarpController.cs b/MechJeb2/MechJebModuleWarpController.cs index 6992cee53..e98660cf2 100644 --- a/MechJeb2/MechJebModuleWarpController.cs +++ b/MechJeb2/MechJebModuleWarpController.cs @@ -1,277 +1,277 @@ -using System; -using System.Linq; -using UnityEngine; -using KSP.Localization; - -namespace MuMech -{ - public class MechJebModuleWarpController : ComputerModule - { - public MechJebModuleWarpController(MechJebCore core) - : base(core) - { - WarpPaused = false; - priority = 100; - enabled = true; - } - - double warpIncreaseAttemptTime = 0; - - private int lastAskedIndex = 0; - - public double warpToUT { get; private set; } - public bool WarpPaused { get; private set; } - - [Persistent(pass = (int)Pass.Global)] - public bool activateSASOnWarp = true; - - [Persistent(pass = (int)Pass.Global)] - public bool useQuickWarp = false; - - public void useQuickWarpInfoItem() - { - useQuickWarp = GUILayout.Toggle(useQuickWarp, Localizer.Format("#MechJeb_WarpHelper_checkbox1"));//"Quick warp" - } - - [GeneralInfoItem("#MechJeb_MJWarpControl", InfoItem.Category.Misc)]//MJ Warp Control - public void ControlWarpButton() - { - if (WarpPaused && GUILayout.Button(Localizer.Format("#MechJeb_WarpHelper_button3")))//"Resume MJ Warp" - { - ResumeWarp(); - } - if (!WarpPaused && GUILayout.Button(Localizer.Format("#MechJeb_WarpHelper_button4")))//"Pause MJ Warp" - { - PauseWarp(); - } - } - - public override void OnUpdate() - { - if (!WarpPaused && lastAskedIndex > 0 && lastAskedIndex != TimeWarp.CurrentRateIndex) - { - // Rate limited by the altitude so we should not care - if (!vessel.LandedOrSplashed && TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRateIndex == TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody)) - return; - - //print("Warppause : lastAskedIndex=" + lastAskedIndex + " CurrentRateIndex=" + TimeWarp.CurrentRateIndex + " WarpMode=" + TimeWarp.WarpMode + " MaxCurrentRate=" + TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody)); - WarpPaused = false; - //PauseWarp(); - - //ScreenMessages.PostScreenMessage("MJ : Warp canceled by user or an other mod"); - } - } - - public override void OnFixedUpdate() { - if (warpToUT > 0) - WarpToUT(warpToUT); - } - - private void PauseWarp() - { - WarpPaused = true; - - if (activateSASOnWarp && TimeWarp.CurrentRateIndex == 0) - part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, false); - } - - private void ResumeWarp() - { - if (!WarpPaused) - return; - - WarpPaused = false; - SetTimeWarpRate(lastAskedIndex, false); - } - - // Turn SAS on during regular warp for compatibility with PersistentRotation - private void SetTimeWarpRate(int rateIndex, bool instant) - { - if (rateIndex != TimeWarp.CurrentRateIndex) - { - if (activateSASOnWarp && TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRateIndex == 0) - part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, true); - - lastAskedIndex = rateIndex; - if (WarpPaused) - { - ScreenMessages.PostScreenMessage(Localizer.Format("#MechJeb_WarpHelper_scrmsg"));//"MJ : Warp paused - resume in the Warp Helper menu" - } - else - { - TimeWarp.SetRate(rateIndex, instant); - } - - if (activateSASOnWarp && rateIndex == 0) - part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, false); - } - } - - public void WarpToUT(double UT, double maxRate = -1) - { - if (UT <= vesselState.time) { - warpToUT = 0.0; - return; - } - - if (maxRate < 0) - maxRate = TimeWarp.fetch.warpRates[TimeWarp.fetch.warpRates.Length - 1]; - - double desiredRate; - if (useQuickWarp) { - desiredRate = 1; - if (orbit.patchEndTransition != Orbit.PatchTransitionType.FINAL && orbit.EndUT < UT) { - for(int i=0; i maxRate) - { - DecreaseRegularWarp(instantOnDecrease); - } - else if (TimeWarp.CurrentRateIndex + 1 < TimeWarp.fetch.warpRates.Length && TimeWarp.fetch.warpRates[TimeWarp.CurrentRateIndex + 1] <= maxRate) - { - IncreaseRegularWarp(instantOnIncrease); - } - } - - //warp at the highest regular warp rate that is <= maxRate - public void WarpPhysicsAtRate(float maxRate, bool instantOnIncrease = false, bool instantOnDecrease = true) - { - if (!CheckPhysicsWarp()) return; - - if (TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex] > maxRate) - { - DecreasePhysicsWarp(instantOnDecrease); - } - else if (TimeWarp.CurrentRateIndex + 1 < TimeWarp.fetch.physicsWarpRates.Length && TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex + 1] <= maxRate) - { - IncreasePhysicsWarp(instantOnIncrease); - } - } - - //If the warp mode is regular warp, returns true - //If the warp mode is physics warp, switches it to regular warp and returns false - private bool CheckRegularWarp() - { - if (TimeWarp.WarpMode != TimeWarp.Modes.HIGH) - { - double instantAltitudeASL = (vesselState.CoM - mainBody.position).magnitude - mainBody.Radius; - if (instantAltitudeASL > mainBody.RealMaxAtmosphereAltitude()) - { - TimeWarp.fetch.Mode = TimeWarp.Modes.HIGH; - SetTimeWarpRate(0, true); - } - return false; - } - return true; - } - - //If the warp mode is physics warp, returns true - //If the warp mode is regular warp, switches it to physics warp and returns false - private bool CheckPhysicsWarp() - { - if (TimeWarp.WarpMode != TimeWarp.Modes.LOW) - { - TimeWarp.fetch.Mode = TimeWarp.Modes.LOW; - SetTimeWarpRate(0, true); - return false; - } - return true; - } - - private bool IncreaseRegularWarp(bool instant = false) - { - if (!CheckRegularWarp()) return false; //make sure we are in regular warp - - //do a bunch of checks to see if we can increase the warp rate: - if (TimeWarp.CurrentRateIndex + 1 == TimeWarp.fetch.warpRates.Length) return false; //already at max warp - if (!vessel.LandedOrSplashed) - { - double instantAltitudeASL = (vesselState.CoM - mainBody.position).magnitude - mainBody.Radius; - if (TimeWarp.fetch.GetAltitudeLimit(TimeWarp.CurrentRateIndex + 1, mainBody) > instantAltitudeASL) return false; - //altitude too low to increase warp - } - if (TimeWarp.fetch.warpRates[TimeWarp.CurrentRateIndex] != TimeWarp.CurrentRate) return false; //most recent warp change is not yet complete - if (vesselState.time - warpIncreaseAttemptTime < 2) return false; //we increased warp too recently - - warpIncreaseAttemptTime = vesselState.time; - SetTimeWarpRate(TimeWarp.CurrentRateIndex + 1, instant); - return true; - } - - private bool IncreasePhysicsWarp(bool instant = false) - { - if (!CheckPhysicsWarp()) return false; //make sure we are in regular warp - - //do a bunch of checks to see if we can increase the warp rate: - if (TimeWarp.CurrentRateIndex + 1 == TimeWarp.fetch.physicsWarpRates.Length) return false; //already at max warp - if (TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex] != TimeWarp.CurrentRate) return false; //most recent warp change is not yet complete - if (vesselState.time - warpIncreaseAttemptTime < 2) return false; //we increased warp too recently - - warpIncreaseAttemptTime = vesselState.time; - SetTimeWarpRate(TimeWarp.CurrentRateIndex + 1, instant); - return true; - } - - private bool DecreaseRegularWarp(bool instant = false) - { - if (!CheckRegularWarp()) return false; - - if (TimeWarp.CurrentRateIndex == 0) return false; //already at minimum warp - - SetTimeWarpRate(TimeWarp.CurrentRateIndex - 1, instant); - return true; - } - - private bool DecreasePhysicsWarp(bool instant = false) - { - if (!CheckPhysicsWarp()) return false; - - if (TimeWarp.CurrentRateIndex == 0) return false; //already at minimum warp - - SetTimeWarpRate(TimeWarp.CurrentRateIndex - 1, instant); - return true; - } - - public bool MinimumWarp(bool instant = false) - { - warpToUT = 0.0; - if (TimeWarp.CurrentRateIndex == 0) return false; //Somehow setting TimeWarp.SetRate to 0 when already at 0 causes unexpected rapid separation (Kraken) - SetTimeWarpRate(0, instant); - return true; - } - } -} +using System; +using System.Linq; +using UnityEngine; +using KSP.Localization; + +namespace MuMech +{ + public class MechJebModuleWarpController : ComputerModule + { + public MechJebModuleWarpController(MechJebCore core) + : base(core) + { + WarpPaused = false; + priority = 100; + enabled = true; + } + + double warpIncreaseAttemptTime = 0; + + private int lastAskedIndex = 0; + + public double warpToUT { get; private set; } + public bool WarpPaused { get; private set; } + + [Persistent(pass = (int)Pass.Global)] + public bool activateSASOnWarp = true; + + [Persistent(pass = (int)Pass.Global)] + public bool useQuickWarp = false; + + public void useQuickWarpInfoItem() + { + useQuickWarp = GUILayout.Toggle(useQuickWarp, Localizer.Format("#MechJeb_WarpHelper_checkbox1"));//"Quick warp" + } + + [GeneralInfoItem("#MechJeb_MJWarpControl", InfoItem.Category.Misc)]//MJ Warp Control + public void ControlWarpButton() + { + if (WarpPaused && GUILayout.Button(Localizer.Format("#MechJeb_WarpHelper_button3")))//"Resume MJ Warp" + { + ResumeWarp(); + } + if (!WarpPaused && GUILayout.Button(Localizer.Format("#MechJeb_WarpHelper_button4")))//"Pause MJ Warp" + { + PauseWarp(); + } + } + + public override void OnUpdate() + { + if (!WarpPaused && lastAskedIndex > 0 && lastAskedIndex != TimeWarp.CurrentRateIndex) + { + // Rate limited by the altitude so we should not care + if (!vessel.LandedOrSplashed && TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRateIndex == TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody)) + return; + + //print("Warppause : lastAskedIndex=" + lastAskedIndex + " CurrentRateIndex=" + TimeWarp.CurrentRateIndex + " WarpMode=" + TimeWarp.WarpMode + " MaxCurrentRate=" + TimeWarp.fetch.GetMaxRateForAltitude(vessel.altitude, vessel.mainBody)); + WarpPaused = false; + //PauseWarp(); + + //ScreenMessages.PostScreenMessage("MJ : Warp canceled by user or an other mod"); + } + } + + public override void OnFixedUpdate() { + if (warpToUT > 0) + WarpToUT(warpToUT); + } + + private void PauseWarp() + { + WarpPaused = true; + + if (activateSASOnWarp && TimeWarp.CurrentRateIndex == 0) + part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, false); + } + + private void ResumeWarp() + { + if (!WarpPaused) + return; + + WarpPaused = false; + SetTimeWarpRate(lastAskedIndex, false); + } + + // Turn SAS on during regular warp for compatibility with PersistentRotation + private void SetTimeWarpRate(int rateIndex, bool instant) + { + if (rateIndex != TimeWarp.CurrentRateIndex) + { + if (activateSASOnWarp && TimeWarp.WarpMode == TimeWarp.Modes.HIGH && TimeWarp.CurrentRateIndex == 0) + part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, true); + + lastAskedIndex = rateIndex; + if (WarpPaused) + { + ScreenMessages.PostScreenMessage(Localizer.Format("#MechJeb_WarpHelper_scrmsg"));//"MJ : Warp paused - resume in the Warp Helper menu" + } + else + { + TimeWarp.SetRate(rateIndex, instant); + } + + if (activateSASOnWarp && rateIndex == 0) + part.vessel.ActionGroups.SetGroup(KSPActionGroup.SAS, false); + } + } + + public void WarpToUT(double UT, double maxRate = -1) + { + if (UT <= vesselState.time) { + warpToUT = 0.0; + return; + } + + if (maxRate < 0) + maxRate = TimeWarp.fetch.warpRates[TimeWarp.fetch.warpRates.Length - 1]; + + double desiredRate; + if (useQuickWarp) { + desiredRate = 1; + if (orbit.patchEndTransition != Orbit.PatchTransitionType.FINAL && orbit.EndUT < UT) { + for(int i=0; i maxRate) + { + DecreaseRegularWarp(instantOnDecrease); + } + else if (TimeWarp.CurrentRateIndex + 1 < TimeWarp.fetch.warpRates.Length && TimeWarp.fetch.warpRates[TimeWarp.CurrentRateIndex + 1] <= maxRate) + { + IncreaseRegularWarp(instantOnIncrease); + } + } + + //warp at the highest regular warp rate that is <= maxRate + public void WarpPhysicsAtRate(float maxRate, bool instantOnIncrease = false, bool instantOnDecrease = true) + { + if (!CheckPhysicsWarp()) return; + + if (TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex] > maxRate) + { + DecreasePhysicsWarp(instantOnDecrease); + } + else if (TimeWarp.CurrentRateIndex + 1 < TimeWarp.fetch.physicsWarpRates.Length && TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex + 1] <= maxRate) + { + IncreasePhysicsWarp(instantOnIncrease); + } + } + + //If the warp mode is regular warp, returns true + //If the warp mode is physics warp, switches it to regular warp and returns false + private bool CheckRegularWarp() + { + if (TimeWarp.WarpMode != TimeWarp.Modes.HIGH) + { + double instantAltitudeASL = (vesselState.CoM - mainBody.position).magnitude - mainBody.Radius; + if (instantAltitudeASL > mainBody.RealMaxAtmosphereAltitude()) + { + TimeWarp.fetch.Mode = TimeWarp.Modes.HIGH; + SetTimeWarpRate(0, true); + } + return false; + } + return true; + } + + //If the warp mode is physics warp, returns true + //If the warp mode is regular warp, switches it to physics warp and returns false + private bool CheckPhysicsWarp() + { + if (TimeWarp.WarpMode != TimeWarp.Modes.LOW) + { + TimeWarp.fetch.Mode = TimeWarp.Modes.LOW; + SetTimeWarpRate(0, true); + return false; + } + return true; + } + + private bool IncreaseRegularWarp(bool instant = false) + { + if (!CheckRegularWarp()) return false; //make sure we are in regular warp + + //do a bunch of checks to see if we can increase the warp rate: + if (TimeWarp.CurrentRateIndex + 1 == TimeWarp.fetch.warpRates.Length) return false; //already at max warp + if (!vessel.LandedOrSplashed) + { + double instantAltitudeASL = (vesselState.CoM - mainBody.position).magnitude - mainBody.Radius; + if (TimeWarp.fetch.GetAltitudeLimit(TimeWarp.CurrentRateIndex + 1, mainBody) > instantAltitudeASL) return false; + //altitude too low to increase warp + } + if (TimeWarp.fetch.warpRates[TimeWarp.CurrentRateIndex] != TimeWarp.CurrentRate) return false; //most recent warp change is not yet complete + if (vesselState.time - warpIncreaseAttemptTime < 2) return false; //we increased warp too recently + + warpIncreaseAttemptTime = vesselState.time; + SetTimeWarpRate(TimeWarp.CurrentRateIndex + 1, instant); + return true; + } + + private bool IncreasePhysicsWarp(bool instant = false) + { + if (!CheckPhysicsWarp()) return false; //make sure we are in regular warp + + //do a bunch of checks to see if we can increase the warp rate: + if (TimeWarp.CurrentRateIndex + 1 == TimeWarp.fetch.physicsWarpRates.Length) return false; //already at max warp + if (TimeWarp.fetch.physicsWarpRates[TimeWarp.CurrentRateIndex] != TimeWarp.CurrentRate) return false; //most recent warp change is not yet complete + if (vesselState.time - warpIncreaseAttemptTime < 2) return false; //we increased warp too recently + + warpIncreaseAttemptTime = vesselState.time; + SetTimeWarpRate(TimeWarp.CurrentRateIndex + 1, instant); + return true; + } + + private bool DecreaseRegularWarp(bool instant = false) + { + if (!CheckRegularWarp()) return false; + + if (TimeWarp.CurrentRateIndex == 0) return false; //already at minimum warp + + SetTimeWarpRate(TimeWarp.CurrentRateIndex - 1, instant); + return true; + } + + private bool DecreasePhysicsWarp(bool instant = false) + { + if (!CheckPhysicsWarp()) return false; + + if (TimeWarp.CurrentRateIndex == 0) return false; //already at minimum warp + + SetTimeWarpRate(TimeWarp.CurrentRateIndex - 1, instant); + return true; + } + + public bool MinimumWarp(bool instant = false) + { + warpToUT = 0.0; + if (TimeWarp.CurrentRateIndex == 0) return false; //Somehow setting TimeWarp.SetRate to 0 when already at 0 causes unexpected rapid separation (Kraken) + SetTimeWarpRate(0, instant); + return true; + } + } +} diff --git a/MechJeb2/MechJebModuleWaypointWindow.cs b/MechJeb2/MechJebModuleWaypointWindow.cs index 1515e99a2..9a5548e70 100644 --- a/MechJeb2/MechJebModuleWaypointWindow.cs +++ b/MechJeb2/MechJebModuleWaypointWindow.cs @@ -1,1324 +1,1324 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using UnityEngine; -using KSP.Localization; -namespace MuMech -{ - public class MechJebWaypoint { - public const float defaultRadius = 5; - public double Latitude; - public double Longitude; - public double Altitude; - public Vector3d Position; - public float Radius; - public string Name; - public Vessel Target; - public float MinSpeed; - public float MaxSpeed; - public bool Quicksave; - - public CelestialBody Body { - get { return (Target != null ? Target.mainBody : FlightGlobals.ActiveVessel.mainBody); } - } - - public MechJebWaypoint(double Latitude, double Longitude, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { //, CelestialBody Body = null) { - this.Latitude = Latitude; - this.Longitude = Longitude; - this.Radius = Radius; - this.Name = (Name == null ? "" : Name); - this.MinSpeed = MinSpeed; - this.MaxSpeed = MaxSpeed; - Update(); - } - - public MechJebWaypoint(Vector3d Position, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { //, CelestialBody Body = null) { - this.Latitude = Body.GetLatitude(Position); - this.Longitude = Body.GetLongitude(Position); - this.Radius = Radius; - this.Name = (Name == null ? "" : Name); - this.MinSpeed = MinSpeed; - this.MaxSpeed = MaxSpeed; - Update(); - } - - public MechJebWaypoint(Vessel Target, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { - this.Target = Target; - this.Radius = Radius; - this.Name = (Name == null ? "" : Name); - this.MinSpeed = MinSpeed; - this.MaxSpeed = MaxSpeed; - Update(); - } - - public MechJebWaypoint(ConfigNode Node) { - if (Node.HasValue("Latitude")) { double.TryParse(Node.GetValue("Latitude"), out this.Latitude); } - if (Node.HasValue("Longitude")) { double.TryParse(Node.GetValue("Longitude"), out this.Longitude); } - this.Target = (Node.HasValue("Target") ? FlightGlobals.Vessels.Find(v => v.id.ToString() == Node.GetValue("Target")) : null); - if (Node.HasValue("Radius")) { float.TryParse(Node.GetValue("Radius"), out this.Radius); } else { this.Radius = defaultRadius; } - this.Name = (Node.HasValue("Name") ? Node.GetValue("Name") : ""); - if (Node.HasValue("MinSpeed")) { float.TryParse(Node.GetValue("MinSpeed"), out this.MinSpeed); } - if (Node.HasValue("MaxSpeed")) { float.TryParse(Node.GetValue("MaxSpeed"), out this.MaxSpeed); } - if (Node.HasValue("Quicksave")) { bool.TryParse(Node.GetValue("Quicksave"), out this.Quicksave); } - Update(); - } - - public ConfigNode ToConfigNode() { - ConfigNode cn = new ConfigNode("Waypoint"); - if (Target != null) { - cn.AddValue("Target", Target.id); - } - if (Name != "") { cn.AddValue("Name", Name); } - cn.AddValue("Latitude", Latitude); - cn.AddValue("Longitude", Longitude); - cn.AddValue("Radius", Radius); - cn.AddValue("MinSpeed", MinSpeed); - cn.AddValue("MaxSpeed", MaxSpeed); - cn.AddValue("Quicksave", Quicksave); - return cn; - } - - public string GetNameWithCoords() { - return (Name != "" ? Name : (Target != null ? Target.vesselName : "Waypoint")) + " - " + Coordinates.ToStringDMS(Latitude, Longitude, false); -// ((Latitude >= 0 ? "N " : "S ") + Math.Abs(Math.Round(Latitude, 3)) + ", " + (Longitude >= 0 ? "E " : "W ") + Math.Abs(Math.Round(Longitude, 3))); - } - - public void Update() { - if (Target != null) { - Position = Target.CoM; - Latitude = Body.GetLatitude(Position); - Longitude = Body.GetLongitude(Position); - } - else { - Position = Body.GetWorldSurfacePosition(Latitude, Longitude, Body.TerrainAltitude(Latitude, Longitude)); - if (Vector3d.Distance(Position, FlightGlobals.ActiveVessel.CoM) < 200) { - var dir = (Position - Body.position).normalized; - var rayPos = Body.position + dir * (Body.Radius + 50000); - dir = (Vector3d)(Body.position - rayPos).normalized; - RaycastHit hit; - var raycast = Physics.Raycast(rayPos, dir, out hit, (float)Body.Radius, 1 << 15); - if (raycast) { - dir = (hit.point - Body.position); - Position = Body.position + dir.normalized * (dir.magnitude + 0.5); -// Latitude = Body.GetLatitude(Position); -// Longitude = Body.GetLongitude(Position); - } - } - } - if (MinSpeed > 0 && MaxSpeed > 0 && MinSpeed > MaxSpeed) { MinSpeed = MaxSpeed; } - else if (MinSpeed > 0 && MaxSpeed > 0 && MaxSpeed < MinSpeed) { MaxSpeed = MinSpeed; } - } - } - - public class MechJebWaypointRoute : List { - public string Name; - - private CelestialBody body; - public CelestialBody Body { - get { return body; } - } - - public string Mode { get; private set; } - - private string stats; - public string Stats { - get { - updateStats(); // recalculation of the stats all the time are fine according to Majiir and Fractal_UK :3 - return stats; - } - } - - public ConfigNode ToConfigNode() { - ConfigNode cn = new ConfigNode("Waypoints"); - cn.AddValue("Name", Name); - cn.AddValue("Body", body.bodyName); - cn.AddValue("Mode", Mode); - this.ForEach(wp => cn.AddNode(wp.ToConfigNode())); - return cn; - } - - private void updateStats() { - float distance = 0; - if (Count > 1) { - for (int i = 1; i < Count; i++) { - distance += Vector3.Distance(this[i - 1].Position, this[i].Position); - } - } - stats = string.Format("{0} waypoints over {1}m", Count, MuUtils.ToSI(distance, -1)); - } - - public MechJebWaypointRoute(string Name = "", CelestialBody Body = null, string Mode = "Rover") { - this.Name = Name; - this.body = (Body != null ? Body : FlightGlobals.currentMainBody); - this.Mode = Mode; - } - - public MechJebWaypointRoute(ConfigNode Node) { // feed this "Waypoints" nodes, just not the local ones of a ship - if (Node == null) { return; } - this.Name = (Node.HasValue("Name") ? Node.GetValue("Name") : ""); - this.body = (Node.HasValue("Body") ? FlightGlobals.Bodies.Find(b => b.bodyName == Node.GetValue("Body")) : null); - this.Mode = (Node.HasValue("Mode") ? Node.GetValue("Mode") : "Rover"); - if (Node.HasNode("Waypoint")) { - foreach (ConfigNode cn in Node.GetNodes("Waypoint")) { - this.Add(new MechJebWaypoint(cn)); - } - } - } - -// public new void Add(MechJebWaypoint Waypoint) { -// this.Add(Waypoint); -// updateStats(); -// } -// -// public new void Insert(int Index, MechJebWaypoint Waypoint) { -// this.Insert(Index, Waypoint); -// updateStats(); -// } -// -// public new void Remove(MechJebWaypoint Waypoint) { -// this.Remove(Waypoint); -// updateStats(); -// } -// -// public new void RemoveAt(int Index) { -// this.RemoveAt(Index); -// updateStats(); -// } -// -// public new void RemoveRange() { -// -// } - } - - public class MechJebModuleWaypointWindow : DisplayModule { - public enum WaypointMode { - Rover, - Plane - } - - public WaypointMode Mode = WaypointMode.Rover; - public MechJebModuleRoverController ap; - public static List Routes = new List(); - [EditableInfoItem("#MechJeb_MohoMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Moho Mapdist - public EditableDouble MohoMapdist = 5000; - [EditableInfoItem("#MechJeb_EveMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Eve Mapdist - public EditableDouble EveMapdist = 5000; - [EditableInfoItem("#MechJeb_GillyMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Gilly Mapdist - public EditableDouble GillyMapdist = -500; - [EditableInfoItem("#MechJeb_KerbinMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Kerbin Mapdist - public EditableDouble KerbinMapdist = 500; - [EditableInfoItem("#MechJeb_MunMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Mun Mapdist - public EditableDouble MunMapdist = 4000; - [EditableInfoItem("#MechJeb_MinmusMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Minmus Mapdist - public EditableDouble MinmusMapdist = 3500; - [EditableInfoItem("#MechJeb_DunaMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Duna Mapdist - public EditableDouble DunaMapdist = 5000; - [EditableInfoItem("#MechJeb_IkeMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Ike Mapdist - public EditableDouble IkeMapdist = 4000; - [EditableInfoItem("#MechJeb_DresMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Dres Mapdist - public EditableDouble DresMapdist = 1500; - [EditableInfoItem("#MechJeb_EelooMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Eeloo Mapdist - public EditableDouble EelooMapdist = 2000; - [EditableInfoItem("#MechJeb_JoolMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Jool Mapdist - public EditableDouble JoolMapdist = 30000; - [EditableInfoItem("#MechJeb_TyloMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Tylo Mapdist - public EditableDouble TyloMapdist = 5000; - [EditableInfoItem("#MechJeb_LaytheMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Laythe Mapdist - public EditableDouble LaytheMapdist = 1000; - [EditableInfoItem("#MechJeb_PolMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Pol Mapdist - public EditableDouble PolMapdist = 500; - [EditableInfoItem("#MechJeb_BopMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Bop Mapdist - public EditableDouble BopMapdist = 1000; - [EditableInfoItem("#MechJeb_VallMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Vall Mapdist - public EditableDouble VallMapdist = 5000; - - internal int selIndex = -1; - internal int saveIndex = -1; - internal string tmpRadius = ""; - internal string tmpMinSpeed = ""; - internal string tmpMaxSpeed = ""; - internal string tmpLat = ""; - internal string tmpLon = ""; - internal const string coordRegEx = @"^([nsew])?\s*(-?\d+(?:\.\d+)?)(?:[°:\s]+(-?\d+(?:\.\d+)?))?(?:[':\s]+(-?\d+(?:\.\d+)?))?(?:[^nsew]*([nsew])?)?$"; - - private Vector2 scroll; - private GUIStyle styleActive; - private GUIStyle styleInactive; - private GUIStyle styleQuicksave; - private string titleAdd = ""; - private string saveName = ""; - private bool waitingForPick = false; - private pages showPage = pages.waypoints; - private enum pages { waypoints, settings, routes }; - private static MechJebRouteRenderer renderer; - private Rect[] waypointRects = new Rect[0]; - private int lastIndex = -1; - private int settingPageIndex = 0; - private string[] settingPages = new string[] { "Rover", "Waypoints" }; -// private static LineRenderer redLine; -// private static LineRenderer greenLine; - - public MechJebModuleWaypointWindow(MechJebCore core) : base(core) { } - - public override void OnStart(PartModule.StartState state) - { - hidden = true; - ap = core.GetComputerModule(); - if (HighLogic.LoadedSceneIsFlight && vessel.isActiveVessel) { - renderer = MechJebRouteRenderer.AttachToMapView(core); - renderer.enabled = enabled; - } - -// GameObject obj = new GameObject("LineRenderer"); -// redLine = obj.AddComponent(); -// redLine.useWorldSpace = true; -// redLine.material = renderer.material; -// redLine.SetWidth(10.0f, 10.0f); -// redLine.SetColors(Color.red, Color.red); -// redLine.SetVertexCount(2); -// GameObject obj2 = new GameObject("LineRenderer"); -// greenLine = obj2.AddComponent(); -// greenLine.useWorldSpace = true; -// greenLine.material = renderer.material; -// greenLine.SetWidth(10.0f, 10.0f); -// greenLine.SetColors(Color.green, Color.green); -// greenLine.SetVertexCount(2); -// MechJebRouteRenderer.NewLineRenderer(ref greenLine); - base.OnStart(state); - } - - public override void OnModuleEnabled() - { - if (renderer != null) { renderer.enabled = true; } - base.OnModuleEnabled(); - } - - public override void OnModuleDisabled() - { - if (renderer != null) { renderer.enabled = false; } - base.OnModuleDisabled(); - } - - public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) - { - base.OnLoad(local, type, global); - - ConfigNode wps = new ConfigNode("Routes"); - if (KSP.IO.File.Exists("mechjeb_routes.cfg")) - { - try - { - wps = ConfigNode.Load(KSP.IO.IOUtils.GetFilePathFor(core.GetType(), "mechjeb_routes.cfg")); - } - catch (Exception e) - { - Debug.LogError("MechJebModuleWaypointWindow.OnLoad caught an exception trying to load mechjeb_routes.cfg: " + e); - } - } - - if (wps.HasNode("Waypoints")) - { - Routes.Clear(); - foreach (ConfigNode cn in wps.GetNodes("Waypoints")) - { - Routes.Add(new MechJebWaypointRoute(cn)); - } - Routes.Sort(SortRoutes); - } - } - - public void SaveRoutes() - { - var cn = new ConfigNode("Routes"); - - if (Routes.Count > 0) { - Routes.Sort(SortRoutes); - foreach (MechJebWaypointRoute r in Routes) { - cn.AddNode(r.ToConfigNode()); - } - } - - cn.Save(KSP.IO.IOUtils.GetFilePathFor(core.GetType(), "mechjeb_routes.cfg")); - } - - public override string GetName() - { - return Mode.ToString() + " Waypoints" + (titleAdd != "" ? " - " + titleAdd : ""); - } - - public static Coordinates GetMouseFlightCoordinates() - { - CelestialBody body = FlightGlobals.currentMainBody; - Ray mouseRay = FlightCamera.fetch.mainCamera.ScreenPointToRay(Input.mousePosition); - RaycastHit raycast; -// mouseRay.origin = ScaledSpace.ScaledToLocalSpace(mouseRay.origin); - Vector3d relOrigin = mouseRay.origin - body.position; - Vector3d relSurfacePosition; - if (Physics.Raycast(mouseRay, out raycast, (float)body.Radius * 4f, 1 << 15)) - { - return new Coordinates(body.GetLatitude(raycast.point), MuUtils.ClampDegrees180(body.GetLongitude(raycast.point))); - } - else - { - double curRadius = body.pqsController.radiusMax; - double lastRadius = 0; - double error = 0; - int loops = 0; - float st = Time.time; - while (loops < 50) - { - if (PQS.LineSphereIntersection(relOrigin, mouseRay.direction, curRadius, out relSurfacePosition)) - { - Vector3d surfacePoint = body.position + relSurfacePosition; - double alt = body.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(body.GetLongitude(surfacePoint), Vector3d.down) * QuaternionD.AngleAxis(body.GetLatitude(surfacePoint), Vector3d.forward) * Vector3d.right); - error = Math.Abs(curRadius - alt); - if (error < (body.pqsController.radiusMax - body.pqsController.radiusMin) / 100) - { - return new Coordinates(body.GetLatitude(surfacePoint), MuUtils.ClampDegrees180(body.GetLongitude(surfacePoint))); - } - else - { - lastRadius = curRadius; - curRadius = alt; - loops++; - } - } - else - { - if (loops == 0) - { - break; - } - else - { // Went too low, needs to try higher - curRadius = (lastRadius * 9 + curRadius) / 10; - loops++; - } - } - } - } - - return null; - -// var cam = FlightCamera.fetch.mainCamera; -// Ray ray = cam.ScreenPointToRay(Input.mousePosition); -//// greenLine.SetPosition(0, ray.origin); -//// greenLine.SetPosition(1, (Vector3d)ray.direction * body.Radius / 2); -//// if (Physics.Raycast(ray, out raycast, (float)body.Radius * 4f, ~(1 << 1))) { -// Vector3d hit; -// //body.pqsController.RayIntersection(ray.origin, ray.direction, out hit); -// PQS.LineSphereIntersection(ray.origin - body.position, ray.direction, body.Radius, out hit); -// if (hit != Vector3d.zero) { -// hit = body.position + hit; -// Vector3d start = ray.origin; -// Vector3d end = hit; -// Vector3d point = Vector3d.zero; -// for (int i = 0; i < 16; i++) { -// point = (start + end) / 2; -// //var lat = body.GetLatitude(point); -// //var lon = body.GetLongitude(point); -// //var surf = body.GetWorldSurfacePosition(lat, lon, body.TerrainAltitude(lat, lon)); -// var alt = body.GetAltitude(point) - body.TerrainAltitude(point); -// //Debug.Log(alt); -// if (alt > 0) { -// start = point; -// } -// else if (alt < 0) { -// end = point; -// } -// else { -// break; -// } -// } -// hit = point; -//// redLine.SetPosition(0, ray.origin); -//// redLine.SetPosition(1, hit); -// return new Coordinates(body.GetLatitude(hit), MuUtils.ClampDegrees180(body.GetLongitude(hit))); -// } -// else { -// return null; -// } - } - - public static string LatToString(double Lat) - { - while (Lat > 90) { Lat -= 180; } - while (Lat < -90) { Lat += 180; } - - string ns = (Lat >= 0 ? "N" : "S"); - Lat = Math.Abs(Lat); - - int h = (int)Lat; - Lat -= h; Lat *= 60; - - int m = (int)Lat; - Lat -= m; Lat *= 60; - - float s = (float)Lat; - - return string.Format("{0} {1}° {2}' {3:F3}\"", ns, h, m, s); - } - - public static string LonToString(double Lon) - { - while (Lon > 180) { Lon -= 360; } - while (Lon < -180) { Lon += 360; } - - string ew = (Lon >= 0 ? "E" : "W"); - Lon = Math.Abs(Lon); - - int h = (int)Lon; - Lon -= h; Lon *= 60; - - int m = (int)Lon; - Lon -= m; Lon *= 60; - - float s = (float)Lon; - - return string.Format("{0} {1}° {2}' {3:F3}\"", ew, h, m, s); - } - - public static double ParseCoord(string LatLon, bool IsLongitute = false) - { - var match = new Regex(coordRegEx, RegexOptions.IgnoreCase).Match(LatLon); - var range = (IsLongitute ? 180 : 90); - - float nsew = 1; - if (match.Groups[5] != null) - { - if (match.Groups[5].Value.ToUpper() == "N" || match.Groups[5].Value.ToUpper() == "E") { nsew = 1; } - else if (match.Groups[5].Value.ToUpper() == "S" || match.Groups[5].Value.ToUpper() == "W") { nsew = -1; } - else if (match.Groups[1] != null) { - if (match.Groups[1].Value.ToUpper() == "N" || match.Groups[1].Value.ToUpper() == "E") { nsew = 1; } - else if (match.Groups[1].Value.ToUpper() == "S" || match.Groups[1].Value.ToUpper() == "W") { nsew = -1; } - } - } - - float h = 0; - if (match.Groups[2] != null) { float.TryParse(match.Groups[2].Value, out h); } - if (h < 0) { nsew *= -1; h *= -1; } - - float m = 0; - if (match.Groups[3] != null) { float.TryParse(match.Groups[3].Value, out m); } - - float s = 0; - if (match.Groups[4] != null) { float.TryParse(match.Groups[4].Value, out s); } - - h = (h + (m / 60) + (s / 3600)) * nsew; - - while (h > range) { h -= range * 2; } - while (h < -range) { h += range * 2; } - - return h; - } - - private int SortRoutes(MechJebWaypointRoute a, MechJebWaypointRoute b) - { - var bn = string.Compare(a.Body.bodyName, b.Body.bodyName, true); - if (bn != 0) - { - return bn; - } - else - { - return string.Compare(a.Name, b.Name, true); - } - } - - public MechJebWaypoint SelectedWaypoint() { - return (selIndex > -1 ? ap.Waypoints[selIndex] : null); - } - - public int SelectedWaypointIndex { - get { return selIndex; } - set { selIndex = value; } - } - - public override GUILayoutOption[] WindowOptions() - { - return new GUILayoutOption[] { GUILayout.Width(500), GUILayout.Height(400) }; - } - - public void DrawPageWaypoints() - { - bool alt = Input.GetKey(KeyCode.LeftAlt); - scroll = GUILayout.BeginScrollView(scroll); - if (ap.Waypoints.Count > 0) { - waypointRects = new Rect[ap.Waypoints.Count]; - GUILayout.BeginVertical(); - double eta = 0; - double dist = 0; - for (int i = 0; i < ap.Waypoints.Count; i++) - { - var wp = ap.Waypoints[i]; - var maxSpeed = (wp.MaxSpeed > 0 ? wp.MaxSpeed : ap.speed.val); - var minSpeed = (wp.MinSpeed > 0 ? wp.MinSpeed : 0); - if (MapView.MapIsEnabled && i == selIndex) - { - MuMech.GLUtils.DrawGroundMarker(mainBody, wp.Latitude, wp.Longitude, Color.red, true, (DateTime.Now.Second + DateTime.Now.Millisecond / 1000f) * 6, mainBody.Radius / 100); - } - if (i >= ap.WaypointIndex) - { - if (ap.WaypointIndex > -1) - { - eta += GuiUtils.FromToETA((i == ap.WaypointIndex ? vessel.CoM : (Vector3)ap.Waypoints[i - 1].Position), (Vector3)wp.Position, (ap.etaSpeed > 0.1 && ap.etaSpeed < maxSpeed ? (float)Math.Round(ap.etaSpeed, 1) : maxSpeed)); - } - dist += Vector3.Distance((i == ap.WaypointIndex || (ap.WaypointIndex == -1 && i == 0) ? vessel.CoM : (Vector3)ap.Waypoints[i - 1].Position), (Vector3)wp.Position); - } - string str = string.Format("[{0}] - {1} - R: {2:F1} m\n S: {3:F0} ~ {4:F0} - D: {5}m - ETA: {6}", i + 1, wp.GetNameWithCoords(), wp.Radius, - minSpeed, maxSpeed, MuUtils.ToSI(dist, -1), GuiUtils.TimeToDHMS(eta)); - GUI.backgroundColor = (i == ap.WaypointIndex ? new Color(0.5f, 1f, 0.5f) : Color.white); - if (GUILayout.Button(str, (i == selIndex ? styleActive : (wp.Quicksave ? styleQuicksave : styleInactive)))) - { - if (alt) - { - ap.WaypointIndex = (ap.WaypointIndex == i ? -1 : i); - // set current waypoint or unset it if it's already the current one - } - else - { - if (selIndex == i) - { - selIndex = -1; - } - else - { - selIndex = i; - tmpRadius = wp.Radius.ToString(); - tmpMinSpeed = wp.MinSpeed.ToString(); - tmpMaxSpeed = wp.MaxSpeed.ToString(); - tmpLat = LatToString(wp.Latitude); - tmpLon = LonToString(wp.Longitude); - } - } - } - - if (Event.current.type == EventType.Repaint) - { - waypointRects[i] = GUILayoutUtility.GetLastRect(); - //if (i == ap.WaypointIndex) { Debug.Log(Event.current.type.ToString() + " - " + waypointRects[i].ToString() + " - " + scroll.ToString()); } - } - GUI.backgroundColor = Color.white; - - if (selIndex > -1 && selIndex == i) - { - GUILayout.BeginHorizontal(); - - GUILayout.Label(" Radius: ", GUILayout.ExpandWidth(false)); - tmpRadius = GUILayout.TextField(tmpRadius, GUILayout.Width(50)); - float.TryParse(tmpRadius, out wp.Radius); - if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.Radius = wp.Radius); } - - GUILayout.Label("- Speed: ", GUILayout.ExpandWidth(false)); - tmpMinSpeed = GUILayout.TextField(tmpMinSpeed, GUILayout.Width(40)); - float.TryParse(tmpMinSpeed, out wp.MinSpeed); - if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.MinSpeed = wp.MinSpeed); } - - GUILayout.Label(" - ", GUILayout.ExpandWidth(false)); - tmpMaxSpeed = GUILayout.TextField(tmpMaxSpeed, GUILayout.Width(40)); - float.TryParse(tmpMaxSpeed, out wp.MaxSpeed); - if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.MaxSpeed = wp.MaxSpeed); } - - GUILayout.FlexibleSpace(); - if (GUILayout.Button("QS", (wp.Quicksave ? styleQuicksave : styleInactive), GUILayout.ExpandWidth(false))) - { - if (alt) - { - ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.Quicksave = !fewp.Quicksave); - } - else - { - wp.Quicksave = !wp.Quicksave; - } - } - - GUILayout.EndHorizontal(); - - - GUILayout.BeginHorizontal(); - - GUILayout.Label("Lat ", GUILayout.ExpandWidth(false)); - tmpLat = GUILayout.TextField(tmpLat, GUILayout.Width(125)); - wp.Latitude = ParseCoord(tmpLat); - - GUILayout.Label(" - Lon ", GUILayout.ExpandWidth(false)); - tmpLon = GUILayout.TextField(tmpLon, GUILayout.Width(125)); - wp.Longitude = ParseCoord(tmpLon, true); - - GUILayout.EndHorizontal(); - } - } - titleAdd = "Distance: " + MuUtils.ToSI(dist, -1) + "m - ETA: " + GuiUtils.TimeToDHMS(eta); - GUILayout.EndVertical(); - } - else - { - titleAdd = ""; - } - GUILayout.EndScrollView(); - - GUILayout.BeginHorizontal(); - if (GUILayout.Button(alt ? "Reverse" : (!waitingForPick ? "Add Waypoint" : "Abort Adding"), GUILayout.Width(110))) - { - if (alt) - { - ap.Waypoints.Reverse(); - if (ap.WaypointIndex > -1) { ap.WaypointIndex = ap.Waypoints.Count - 1 - ap.WaypointIndex; } - if (selIndex > -1) { selIndex = ap.Waypoints.Count - 1 - selIndex; } - } - else - { - if (!waitingForPick) - { - waitingForPick = true; - if (MapView.MapIsEnabled) - { - core.target.Unset(); - core.target.PickPositionTargetOnMap(); - } - } - else - { - waitingForPick = false; - } - } - } - if (GUILayout.Button((alt ? "Clear" : "Remove"), GUILayout.Width(65)) && selIndex >= 0 && ap.Waypoints.Count > 0) - { - if (alt) - { - ap.WaypointIndex = -1; - ap.Waypoints.Clear(); - } - else - { - ap.Waypoints.RemoveAt(selIndex); - if (ap.WaypointIndex > selIndex) { ap.WaypointIndex--; } - } - selIndex = -1; - //if (ap.WaypointIndex >= ap.Waypoints.Count) { ap.WaypointIndex = ap.Waypoints.Count - 1; } - } - if (GUILayout.Button((alt ? "Top" : "Up"), GUILayout.Width(57)) && selIndex > 0 && selIndex < ap.Waypoints.Count && ap.Waypoints.Count >= 2) - { - if (alt) - { - var t = ap.Waypoints[selIndex]; - ap.Waypoints.RemoveAt(selIndex); - ap.Waypoints.Insert(0, t); - selIndex = 0; - } - else - { - ap.Waypoints.Swap(selIndex, --selIndex); - } - } - if (GUILayout.Button((alt ? "Bottom" : "Down"), GUILayout.Width(57)) && selIndex >= 0 && selIndex < ap.Waypoints.Count - 1 && ap.Waypoints.Count >= 2) - { - if (alt) - { - var t = ap.Waypoints[selIndex]; - ap.Waypoints.RemoveAt(selIndex); - ap.Waypoints.Add(t); - selIndex = ap.Waypoints.Count - 1; - } - else - { - ap.Waypoints.Swap(selIndex, ++selIndex); - } - } - if (GUILayout.Button("Routes")) - { - showPage = pages.routes; - scroll = Vector2.zero; - } - if (GUILayout.Button("Settings")) - { - showPage = pages.settings; - scroll = Vector2.zero; - } - GUILayout.EndHorizontal(); - - if (selIndex >= ap.Waypoints.Count) { selIndex = -1; } - if (selIndex == -1 && ap.WaypointIndex > -1 && lastIndex != ap.WaypointIndex && waypointRects.Length > 0) - { - scroll.y = waypointRects[ap.WaypointIndex].y - 160; - } - lastIndex = ap.WaypointIndex; - } - - public void DrawPageSettings() - { - bool alt = Input.GetKey(KeyCode.LeftAlt); - titleAdd = "Settings"; - MechJebModuleCustomWindowEditor ed = core.GetComputerModule(); - if (!ap.enabled) { ap.CalculateTraction(); } // keep calculating traction just for displaying it - - scroll = GUILayout.BeginScrollView(scroll); - - settingPageIndex = GUILayout.SelectionGrid(settingPageIndex, settingPages, settingPages.Length); - - switch (settingPageIndex) - { - case 0: - GUILayout.BeginHorizontal(); - - GUILayout.BeginVertical(); - ed.registry.Find(i => i.id == "Editable:RoverController.hPIDp").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.hPIDi").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.hPIDd").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.terrainLookAhead").DrawItem(); - // ed.registry.Find(i => i.id == "Value:RoverController.speedIntAcc").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.tractionLimit").DrawItem(); - ed.registry.Find(i => i.id == "Toggle:RoverController.LimitAcceleration").DrawItem(); - GUILayout.EndVertical(); - - GUILayout.BeginVertical(); - ed.registry.Find(i => i.id == "Editable:RoverController.sPIDp").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.sPIDi").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.sPIDd").DrawItem(); - ed.registry.Find(i => i.id == "Editable:RoverController.turnSpeed").DrawItem(); - ed.registry.Find(i => i.id == "Value:RoverController.traction").DrawItem(); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - break; - - case 1: - GUILayout.BeginHorizontal(); - - GUILayout.BeginVertical(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.MohoMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.EveMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.GillyMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.KerbinMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.MunMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.MinmusMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.DunaMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.IkeMapdist").DrawItem(); - GUILayout.EndVertical(); - - GUILayout.BeginVertical(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.DresMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.JoolMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.LaytheMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.VallMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.TyloMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.BopMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.PolMapdist").DrawItem(); - ed.registry.Find(i => i.id == "Editable:WaypointWindow.EelooMapdist").DrawItem(); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - break; - } - - GUILayout.EndScrollView(); - - GUILayout.BeginHorizontal(); - if (GUILayout.Button("Waypoints")) - { - showPage = pages.waypoints; - scroll = Vector2.zero; - lastIndex = -1; - } - if (GUILayout.Button("Routes")) - { - showPage = pages.routes; - scroll = Vector2.zero; - } - if (GUILayout.Button("Help")) - { - core.GetComputerModule().enabled = !core.GetComputerModule().enabled; - } - GUILayout.EndHorizontal(); - } - - public void DrawPageRoutes() - { - bool alt = Input.GetKey(KeyCode.LeftAlt); - titleAdd = "Routes for " + vessel.mainBody.bodyName; - - scroll = GUILayout.BeginScrollView(scroll); - var bodyWPs = Routes.FindAll(r => r.Body == vessel.mainBody && r.Mode == Mode.ToString()); - for (int i = 0; i < bodyWPs.Count; i++) - { - GUILayout.BeginHorizontal(); - var str = bodyWPs[i].Name + " - " + bodyWPs[i].Stats; - if (GUILayout.Button(str, (i == saveIndex ? styleActive : styleInactive))) - { - saveIndex = (saveIndex == i ? -1 : i); - } - if (i == saveIndex) - { - if (GUILayout.Button("Delete", GUILayout.Width(70))) - { - Routes.RemoveAll(r => r.Name == bodyWPs[i].Name && r.Body == vessel.mainBody && r.Mode == Mode.ToString()); - saveIndex = -1; - } - } - GUILayout.EndHorizontal(); - } - GUILayout.EndScrollView(); - - GUILayout.BeginHorizontal(); - saveName = GUILayout.TextField(saveName, GUILayout.Width(150)); - if (GUILayout.Button("Save", GUILayout.Width(50))) - { - if (saveName != "" && ap.Waypoints.Count > 0) - { - var old = Routes.Find(r => r.Name == saveName && r.Body == vessel.mainBody && r.Mode == Mode.ToString()); - var wps = new MechJebWaypointRoute(saveName, vessel.mainBody); - ap.Waypoints.ForEach(wp => wps.Add(wp)); - if (old == null) - { - Routes.Add(wps); - } - else - { - Routes[Routes.IndexOf(old)] = wps; - } - Routes.Sort(SortRoutes); - SaveRoutes(); - } - } - if (GUILayout.Button((alt ? "Add" : "Load"), GUILayout.Width(50))) - { - if (saveIndex > -1) - { - if (!alt) - { - ap.WaypointIndex = -1; - ap.Waypoints.Clear(); - } - Routes[saveIndex].ForEach(wp => ap.Waypoints.Add(wp)); - } - } - if (GUILayout.Button("Waypoints")) - { - showPage = pages.waypoints; - scroll = Vector2.zero; - lastIndex = -1; - } - if (GUILayout.Button("Settings")) - { - showPage = pages.settings; - scroll = Vector2.zero; - } - GUILayout.EndHorizontal(); - } - - protected override void WindowGUI(int windowID) - { - if (GUI.Button(new Rect(windowPos.width - 48, 0, 13, 20), "?", GuiUtils.yellowOnHover)) - { - var help = core.GetComputerModule(); - switch (showPage) - { - case pages.waypoints: help.selTopic = ((IList)help.topics).IndexOf("Waypoints"); break; - case pages.settings: help.selTopic = ((IList)help.topics).IndexOf("Settings"); break; - case pages.routes: help.selTopic = ((IList)help.topics).IndexOf("Routes"); break; - } - help.enabled = help.selTopic > -1 || help.enabled; - } - - if (styleInactive == null) - { - styleInactive = new GUIStyle(GuiUtils.skin != null ? GuiUtils.skin.button : GuiUtils.defaultSkin.button); - styleInactive.alignment = TextAnchor.UpperLeft; - } - if (styleActive == null) - { - styleActive = new GUIStyle(styleInactive); - styleActive.active.textColor = styleActive.focused.textColor = styleActive.hover.textColor = styleActive.normal.textColor = Color.green; - } // for some reason MJ's skin sometimes isn't loaded at OnStart so this has to be done here - if (styleQuicksave == null) - { - styleQuicksave = new GUIStyle(styleActive); - styleQuicksave.active.textColor = styleQuicksave.focused.textColor = styleQuicksave.hover.textColor = styleQuicksave.normal.textColor = Color.yellow; - } - - bool alt = Input.GetKey(KeyCode.LeftAlt); - - switch (showPage) - { - case pages.waypoints: DrawPageWaypoints(); break; - case pages.settings: DrawPageSettings(); break; - case pages.routes: DrawPageRoutes(); break; - } - - if (waitingForPick && vessel.isActiveVessel && Event.current.type == EventType.Repaint) - { - if (MapView.MapIsEnabled) - { - if (core.target.pickingPositionTarget == false) - { - if (core.target.PositionTargetExists) - { - if (selIndex > -1 && selIndex < ap.Waypoints.Count) - { - ap.Waypoints.Insert(selIndex, new MechJebWaypoint(core.target.GetPositionTargetPosition())); - tmpRadius = ap.Waypoints[selIndex].Radius.ToString(); - tmpLat = LatToString(ap.Waypoints[selIndex].Latitude); - tmpLon = LonToString(ap.Waypoints[selIndex].Longitude); - } - else - { - ap.Waypoints.Add(new MechJebWaypoint(core.target.GetPositionTargetPosition())); - } - core.target.Unset(); - waitingForPick = alt; - } - else - { - core.target.PickPositionTargetOnMap(); - } - } - } - else - { - if (!GuiUtils.MouseIsOverWindow(core)) - { - Coordinates mouseCoords = GetMouseFlightCoordinates(); - if (mouseCoords != null) - { - if (Input.GetMouseButtonDown(0)) - { - if (selIndex > -1 && selIndex < ap.Waypoints.Count) - { - ap.Waypoints.Insert(selIndex, new MechJebWaypoint(mouseCoords.latitude, mouseCoords.longitude)); - tmpRadius = ap.Waypoints[selIndex].Radius.ToString(); - tmpLat = LatToString(ap.Waypoints[selIndex].Latitude); - tmpLon = LonToString(ap.Waypoints[selIndex].Longitude); - } - else - { - ap.Waypoints.Add(new MechJebWaypoint(mouseCoords.latitude, mouseCoords.longitude)); - } - waitingForPick = alt; - } - } - } - } - } - - base.WindowGUI(windowID); - } - - public override void OnFixedUpdate() - { - if (vessel.isActiveVessel && (renderer == null || renderer.ap != ap)) { MechJebRouteRenderer.AttachToMapView(core); } //MechJebRouteRenderer.AttachToMapView(core); } - ap.Waypoints.ForEach(wp => wp.Update()); -// float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, vessel.CoM) / 900f; -// greenLine.SetPosition(0, vessel.CoM); -// greenLine.SetPosition(1, vessel.CoM + ap.norm * 5); -// greenLine.SetWidth(scale + 0.1f, scale + 0.1f); - base.OnFixedUpdate(); - } - } - - public class MechJebModuleWaypointHelpWindow : DisplayModule - { - public int selTopic = 0; - public string[] topics = {"Rover Controller", "Waypoints", "Routes", "Settings"}; - string selSubTopic = ""; - GUIStyle btnActive; - GUIStyle btnInactive; - - void HelpTopic(string title, string text) - { - GUILayout.BeginVertical(); - if (GUILayout.Button(title, (selSubTopic == title ? btnActive : btnInactive))) - { - selSubTopic = (selSubTopic != title ? title : ""); - windowPos = new Rect(windowPos.x, windowPos.y, windowPos.width, 0); - } - if (selSubTopic == title) - { - GUILayout.Label(text); - } - GUILayout.EndVertical(); - } - - public MechJebModuleWaypointHelpWindow(MechJebCore core) : base(core) { } - - public override string GetName() - { - return Localizer.Format("#MechJeb_Waypointhelper_title");//"Waypoint Help" - } - - public override void OnStart(PartModule.StartState state) - { - hidden = true; - base.OnStart(state); - } - - protected override void WindowGUI(int windowID) - { - if (btnInactive == null) - { - btnInactive = new GUIStyle(GuiUtils.skin.button); - btnInactive.alignment = TextAnchor.MiddleLeft; - } - - if (btnActive == null) - { - btnActive = new GUIStyle(btnInactive); - btnActive.active.textColor = btnActive.hover.textColor = btnActive.focused.textColor = btnActive.normal.textColor = Color.green; - } - - selTopic = GUILayout.SelectionGrid(selTopic, topics, topics.Length); - - switch (topics[selTopic]) - { - case "Rover Controller": - HelpTopic("Holding a set Heading", "To hold a specific heading just tick the box next to 'Heading control' and the autopilot will try to keep going for the entered heading." + - "\nThis also needs to be enabled when the autopilot is supposed to drive to a waypoint" + - "'Heading Error' simply shows the error between current heading and target heading."); - HelpTopic("Holding a set Speed", "To hold a specific speed just tick the box next to 'Speed control' and the autopilot will try to keep going at the entered speed." + - "\nThis also needs to be enabled when the autopilot is supposed to drive to a waypoint" + - "'Speed Error' simply shows the error between current speed and target speed."); - HelpTopic("More stability while driving and nice landings", "If you turn on 'Stability Control' then the autopilot will use the reaction wheel's torque to keep the rover aligned with the surface." + - "\nThis means that if you make a jump the autopilot will try to align the rover in the best possible way to land as straight and flat as possible given the available time and torque." + - "\nBe aware that this doesn't make your rover indestructible, only relatively unlikely to land in a bad way." + - "\n\n'Stability Control' will also limit the brake to reduce the chances of flipping over from too much braking power." + - "\nSee 'Settings' -> 'Traction and Braking'. This setting is also saved per vessel."); - HelpTopic("Brake on Pilot Eject", "With this option enabled the rover will stop if the pilot (on manned rovers) should get thrown out of his seat."); - HelpTopic("Target Speed", "Current speed the autopilot tries to achieve."); - HelpTopic("Waypoint Index", "Overview of waypoints and which the autopilot is currently driving to."); - HelpTopic("Button 'Waypoints'", "Opens the waypoint list to set up a route."); - HelpTopic("Button 'Follow' / 'Stop'", "This sets the autopilot to drive along the set route starting at the first waypoint. Only visible when atleast one waypoint is set." + - "\n\nAlt click will set the autopilot to 'Loop Mode' which will make it go for the first waypoint again after reaching the last." + - "If the only waypoint happens to be a target it will keep following that instead of only going to it once." + - "\n\nIf the autopilot is already active the 'Follow' button will turn into the 'Stop' button which will obviously stop it when pressed."); - HelpTopic("Button 'To Target'", "Clears the route, adds the target as only waypoint and starts the autopilot. Only visible with a selected target." + - "\n\nAlt click will set the autopilot to 'Loop Mode' which will make it continue to follow the target, pausing when near it instead of turning off then."); - HelpTopic("Button 'Add Target'", "Adds the selected target as a waypoint either at the end of the route or before the selected waypoint. Only visible with a selected target."); - break; - - case "Waypoints": - HelpTopic("Adding Waypoints", "Adds a new waypoint to the route at the end or before the currently selected waypoint, " + - "simply click the terrain or somewhere on the body in Mapview." + - "\n\nAlt clicking will reverse the route for easier going back and holding Alt while clicking the terrain or body in Mapview will allow to add more waypoints without having to click the button again."); - HelpTopic("Removing Waypoints", "Removes the currently selected waypoint." + - "\n\nAlt clicking will remove all waypoints."); - HelpTopic("Reordering Waypoints", "'Up' and 'Down' will move the selected waypoint up or down in the list, Alt clicking will move it to the top or bottom respectively."); - HelpTopic("Waypoint Radius", "Radius defines the distance to the center of the waypoint after which the waypoint will be considered 'reached'." + - "\n\nA radius of 5m (default) simply means that when you're 5m from the waypoint away the autopilot will jump to the next or turn off if it was the last." + - "\n\nThe 'A' button behind the textfield will set the entered radius for all waypoints."); - HelpTopic("Speedlimits", "The two speed textfields represent the minimum and maximum speed for the waypoint." + - "\n\nThe maximum speed is the speed the autopilot tries to reach to get to the waypoint." + - "\n\nThe minimum speed was before used to set the speed with which the autopilot will go through the waypoint, but that got reworked now to be based on the next waypoint's max. speed and the turn needed at the waypoint." + - "\n\nI have no idea what this will currently do if set so better just leave it at 0..." + - "\n\nThe 'A' buttons set their respective speed for all waypoints."); - HelpTopic("Quicksaving at a Waypoint", "Clicking the 'QS' button will turn on QuickSave for that waypoint." + - "\n\nThis will make the autopilot stop and try to quicksave at that waypoint and then continue. A QuickSave waypoint has yellow text instead of white." + - "\n\nSmall sideeffect: leaving the throttle up will prevent the saving from occurring effectively pausing the autopilot at that point until interefered with. (Discovered by Greys)" + - "\n\nAlt clicking will toggle QS for all waypoints including the clicked one."); - HelpTopic("Changing the current target Waypoint", "Alt clicking a waypoint will mark it as the current target waypoint. The active waypoint has a green tinted background."); - break; - - case "Routes": - HelpTopic("Routes Help", "The empty textfield is for saving routes, enter a name there before clicking 'Save'." + - "\n\nTo load a route simply select one from the list and click 'Load'." + - "\n\nTo delete a route simply select it and a 'Delete' button will appear right of it."); - break; - - case "Settings": - HelpTopic("Heading / Speed PID", "These parameters control the behaviour of the heading's / speed's PID. Saved globally so NO TOUCHING unless you know what you're doing (or atleast know how to write down numbers to restore it if you mess up)"); - HelpTopic("Safe Turn Speed", "'Safe Turn Speed' tells the autopilot which speed the rover can usually go full turn through corners without tipping over." + - "\n\nGiven how differently terrain can be and other influences you can just leave it at 3 m/s but if you're impatient or just want to experiment feel free to test around. Saved per vessel type (same named vessels will share the setting)."); - HelpTopic("Traction and Braking", "'Traction' shows in % how many wheels have ground contact." + - "\n'Traction Brake Limit' defines what traction is atleast needed for the autopilot to still apply the brakes (given 'Stability Control' is active) even if you hold the brake down." + - "\nThis means the default setting of 75 will make it brake only if atleast 3 wheels have ground contact." + - "\n'Traction Brake Limit' is saved per vessel type." + - "\n\nIf you have 'Stability Control' off then it won't take care of your brake and you can flip as much as you want."); - HelpTopic("Changing the route height in Mapview", "These values define offsets for the route height in Mapview. Given how weird it's set up it can be that they are too high or too low so I added these for easier adjusting. Saved globally, I think."); - break; - } - - base.WindowGUI(windowID); - } - } - - public class MechJebRouteRenderer : MonoBehaviour - { - public static readonly Material material = new Material (Shader.Find ("Legacy Shaders/Particles/Additive")); - public MechJebModuleRoverController ap; - private LineRenderer pastPath; - private LineRenderer currPath; - private LineRenderer nextPath; - private LineRenderer selWP; - private Color pastPathColor = new Color(0f, 0f, 1f, 0.5f); - private Color currPathColor = new Color(0f, 1f, 0f, 0.5f); - private Color nextPathColor = new Color(1f, 1f, 0f, 0.5f); - private Color selWPColor = new Color(1f, 0f, 0f, 0.5f); - private double addHeight; - - public static MechJebRouteRenderer AttachToMapView(MechJebCore Core) - { - var renderer = MapView.MapCamera.gameObject.GetComponent(); - if (!renderer) - { - renderer = MapView.MapCamera.gameObject.AddComponent(); - } - renderer.ap = Core.GetComputerModule(); - return renderer; - } - - public static bool NewLineRenderer(ref LineRenderer line) - { - if (line != null) { return false; } - GameObject obj = new GameObject("LineRenderer"); - line = obj.AddComponent(); - line.useWorldSpace = true; - line.material = material; - line.startWidth = 10.0f; - line.endWidth = 10.0f; - line.positionCount = 2; - return true; - } - - public static Vector3 RaisePositionOverTerrain(Vector3 Position, float HeightOffset) - { - var body = FlightGlobals.ActiveVessel.mainBody; - if (MapView.MapIsEnabled) - { - var lat = body.GetLatitude(Position); - var lon = body.GetLongitude(Position); - return ScaledSpace.LocalToScaledSpace(body.position + (body.Radius + HeightOffset + body.TerrainAltitude(lat, lon)) * body.GetSurfaceNVector(lat, lon)); - } - else { - return body.GetWorldSurfacePosition(body.GetLatitude(Position), body.GetLongitude(Position), body.GetAltitude(Position) + HeightOffset); - } - } - - public new bool enabled - { - get { return base.enabled; } - set - { - base.enabled = value; - if (pastPath != null) { pastPath.enabled = value; } - if (currPath != null) { currPath.enabled = value; } - if (nextPath != null) { nextPath.enabled = value; } - } - } - - public void OnPreRender() - { - if (NewLineRenderer(ref pastPath)) { pastPath.startColor = pastPathColor; pastPath.endColor=pastPathColor; } - if (NewLineRenderer(ref currPath)) { currPath.startColor = currPathColor; currPath.endColor=currPathColor; } - if (NewLineRenderer(ref nextPath)) { nextPath.startColor = nextPathColor; nextPath.endColor=nextPathColor; } - if (NewLineRenderer(ref selWP)) { selWP.startColor = selWPColor; selWP.endColor=selWPColor; } - - //Debug.Log(ap.vessel.vesselName); - var window = ap.core.GetComputerModule(); - switch (ap.vessel.mainBody.bodyName) - { - case "Moho" : addHeight = window.MohoMapdist; break; - case "Eve" : addHeight = window.EveMapdist; break; - case "Gilly" : addHeight = window.GillyMapdist; break; - case "Kerbin" : addHeight = window.KerbinMapdist; break; - case "Mun" : addHeight = window.MunMapdist; break; - case "Minmus" : addHeight = window.MinmusMapdist; break; - case "Duna" : addHeight = window.DunaMapdist; break; - case "Ike" : addHeight = window.IkeMapdist; break; - case "Dres" : addHeight = window.DresMapdist; break; - case "Jool" : addHeight = window.JoolMapdist; break; - case "Laythe" : addHeight = window.LaytheMapdist; break; - case "Vall" : addHeight = window.VallMapdist; break; - case "Tylo" : addHeight = window.TyloMapdist; break; - case "Bop" : addHeight = window.BopMapdist; break; - case "Pol" : addHeight = window.PolMapdist; break; - case "Eeloo" : addHeight = window.EelooMapdist; break; - default: addHeight = window.KerbinMapdist; break; - } - - if (ap != null && ap.Waypoints.Count > 0 && ap.vessel.isActiveVessel && HighLogic.LoadedSceneIsFlight) - { - float targetHeight = (MapView.MapIsEnabled ? (float)addHeight : 3f); - float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, ap.vessel.CoM) / 700f; - float width = (MapView.MapIsEnabled ? (float)(0.005 * PlanetariumCamera.fetch.Distance) : scale + 0.1f); - //float width = (MapView.MapIsEnabled ? (float)mainBody.Radius / 10000 : 1); - - pastPath.startWidth = width; - pastPath.endWidth = width; - currPath.startWidth = width; - currPath.endWidth = width; - nextPath.startWidth = width; - nextPath.endWidth = width; - selWP.gameObject.layer = pastPath.gameObject.layer = currPath.gameObject.layer = nextPath.gameObject.layer = (MapView.MapIsEnabled ? 9 : 0); - - int sel = ap.core.GetComputerModule().selIndex; - selWP.enabled = sel > -1 && !MapView.MapIsEnabled; - if (selWP.enabled) - { - float w = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, ap.Waypoints[sel].Position) / 600f + 0.1f; - selWP.startWidth = 0; - selWP.endWidth = w * 10f; - selWP.SetPosition(0, RaisePositionOverTerrain(ap.Waypoints[sel].Position, targetHeight + 3f)); - selWP.SetPosition(1, RaisePositionOverTerrain(ap.Waypoints[sel].Position, targetHeight + 3f + w * 15f)); - } - - if (ap.WaypointIndex > 0) - { -// Debug.Log("drawing pastPath"); - pastPath.enabled = true; - pastPath.positionCount = ap.WaypointIndex + 1; - for (int i = 0; i < ap.WaypointIndex; i++) - { -// Debug.Log("vert " + i.ToString()); - pastPath.SetPosition(i, RaisePositionOverTerrain(ap.Waypoints[i].Position, targetHeight)); - } - pastPath.SetPosition(ap.WaypointIndex, RaisePositionOverTerrain(ap.vessel.CoM, targetHeight)); -// Debug.Log("pastPath drawn"); - } - else - { -// Debug.Log("no pastPath"); - pastPath.enabled = false; - } - - if (ap.WaypointIndex > -1) - { -// Debug.Log("drawing currPath"); - currPath.enabled = true; - currPath.SetPosition(0, RaisePositionOverTerrain(ap.vessel.CoM, targetHeight)); - currPath.SetPosition(1, RaisePositionOverTerrain(ap.Waypoints[ap.WaypointIndex].Position, targetHeight)); -// Debug.Log("currPath drawn"); - } - else - { -// Debug.Log("no currPath"); - currPath.enabled = false; - } - - var nextCount = ap.Waypoints.Count - ap.WaypointIndex; - if (nextCount > 1) - { -// Debug.Log("drawing nextPath of " + nextCount + " verts"); - nextPath.enabled = true; - nextPath.positionCount = nextCount; - nextPath.SetPosition(0, RaisePositionOverTerrain((ap.WaypointIndex == -1 ? ap.vessel.CoM : (Vector3)ap.Waypoints[ap.WaypointIndex].Position), targetHeight)); - for (int i = 0; i < nextCount - 1; i++) - { -// Debug.Log("vert " + i.ToString() + " (" + (ap.WaypointIndex + 1 + i).ToString() + ")"); - nextPath.SetPosition(i + 1, RaisePositionOverTerrain(ap.Waypoints[ap.WaypointIndex + 1 + i].Position, targetHeight)); - } -// Debug.Log("nextPath drawn"); - } - else - { -// Debug.Log("no nextPath"); - nextPath.enabled = false; - } - } - else - { - //Debug.Log("moo"); - selWP.enabled = pastPath.enabled = currPath.enabled = nextPath.enabled = false; - } - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using UnityEngine; +using KSP.Localization; +namespace MuMech +{ + public class MechJebWaypoint { + public const float defaultRadius = 5; + public double Latitude; + public double Longitude; + public double Altitude; + public Vector3d Position; + public float Radius; + public string Name; + public Vessel Target; + public float MinSpeed; + public float MaxSpeed; + public bool Quicksave; + + public CelestialBody Body { + get { return (Target != null ? Target.mainBody : FlightGlobals.ActiveVessel.mainBody); } + } + + public MechJebWaypoint(double Latitude, double Longitude, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { //, CelestialBody Body = null) { + this.Latitude = Latitude; + this.Longitude = Longitude; + this.Radius = Radius; + this.Name = (Name == null ? "" : Name); + this.MinSpeed = MinSpeed; + this.MaxSpeed = MaxSpeed; + Update(); + } + + public MechJebWaypoint(Vector3d Position, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { //, CelestialBody Body = null) { + this.Latitude = Body.GetLatitude(Position); + this.Longitude = Body.GetLongitude(Position); + this.Radius = Radius; + this.Name = (Name == null ? "" : Name); + this.MinSpeed = MinSpeed; + this.MaxSpeed = MaxSpeed; + Update(); + } + + public MechJebWaypoint(Vessel Target, float Radius = defaultRadius, string Name = "", float MinSpeed = 0, float MaxSpeed = 0) { + this.Target = Target; + this.Radius = Radius; + this.Name = (Name == null ? "" : Name); + this.MinSpeed = MinSpeed; + this.MaxSpeed = MaxSpeed; + Update(); + } + + public MechJebWaypoint(ConfigNode Node) { + if (Node.HasValue("Latitude")) { double.TryParse(Node.GetValue("Latitude"), out this.Latitude); } + if (Node.HasValue("Longitude")) { double.TryParse(Node.GetValue("Longitude"), out this.Longitude); } + this.Target = (Node.HasValue("Target") ? FlightGlobals.Vessels.Find(v => v.id.ToString() == Node.GetValue("Target")) : null); + if (Node.HasValue("Radius")) { float.TryParse(Node.GetValue("Radius"), out this.Radius); } else { this.Radius = defaultRadius; } + this.Name = (Node.HasValue("Name") ? Node.GetValue("Name") : ""); + if (Node.HasValue("MinSpeed")) { float.TryParse(Node.GetValue("MinSpeed"), out this.MinSpeed); } + if (Node.HasValue("MaxSpeed")) { float.TryParse(Node.GetValue("MaxSpeed"), out this.MaxSpeed); } + if (Node.HasValue("Quicksave")) { bool.TryParse(Node.GetValue("Quicksave"), out this.Quicksave); } + Update(); + } + + public ConfigNode ToConfigNode() { + ConfigNode cn = new ConfigNode("Waypoint"); + if (Target != null) { + cn.AddValue("Target", Target.id); + } + if (Name != "") { cn.AddValue("Name", Name); } + cn.AddValue("Latitude", Latitude); + cn.AddValue("Longitude", Longitude); + cn.AddValue("Radius", Radius); + cn.AddValue("MinSpeed", MinSpeed); + cn.AddValue("MaxSpeed", MaxSpeed); + cn.AddValue("Quicksave", Quicksave); + return cn; + } + + public string GetNameWithCoords() { + return (Name != "" ? Name : (Target != null ? Target.vesselName : "Waypoint")) + " - " + Coordinates.ToStringDMS(Latitude, Longitude, false); +// ((Latitude >= 0 ? "N " : "S ") + Math.Abs(Math.Round(Latitude, 3)) + ", " + (Longitude >= 0 ? "E " : "W ") + Math.Abs(Math.Round(Longitude, 3))); + } + + public void Update() { + if (Target != null) { + Position = Target.CoM; + Latitude = Body.GetLatitude(Position); + Longitude = Body.GetLongitude(Position); + } + else { + Position = Body.GetWorldSurfacePosition(Latitude, Longitude, Body.TerrainAltitude(Latitude, Longitude)); + if (Vector3d.Distance(Position, FlightGlobals.ActiveVessel.CoM) < 200) { + var dir = (Position - Body.position).normalized; + var rayPos = Body.position + dir * (Body.Radius + 50000); + dir = (Vector3d)(Body.position - rayPos).normalized; + RaycastHit hit; + var raycast = Physics.Raycast(rayPos, dir, out hit, (float)Body.Radius, 1 << 15); + if (raycast) { + dir = (hit.point - Body.position); + Position = Body.position + dir.normalized * (dir.magnitude + 0.5); +// Latitude = Body.GetLatitude(Position); +// Longitude = Body.GetLongitude(Position); + } + } + } + if (MinSpeed > 0 && MaxSpeed > 0 && MinSpeed > MaxSpeed) { MinSpeed = MaxSpeed; } + else if (MinSpeed > 0 && MaxSpeed > 0 && MaxSpeed < MinSpeed) { MaxSpeed = MinSpeed; } + } + } + + public class MechJebWaypointRoute : List { + public string Name; + + private CelestialBody body; + public CelestialBody Body { + get { return body; } + } + + public string Mode { get; private set; } + + private string stats; + public string Stats { + get { + updateStats(); // recalculation of the stats all the time are fine according to Majiir and Fractal_UK :3 + return stats; + } + } + + public ConfigNode ToConfigNode() { + ConfigNode cn = new ConfigNode("Waypoints"); + cn.AddValue("Name", Name); + cn.AddValue("Body", body.bodyName); + cn.AddValue("Mode", Mode); + this.ForEach(wp => cn.AddNode(wp.ToConfigNode())); + return cn; + } + + private void updateStats() { + float distance = 0; + if (Count > 1) { + for (int i = 1; i < Count; i++) { + distance += Vector3.Distance(this[i - 1].Position, this[i].Position); + } + } + stats = string.Format("{0} waypoints over {1}m", Count, MuUtils.ToSI(distance, -1)); + } + + public MechJebWaypointRoute(string Name = "", CelestialBody Body = null, string Mode = "Rover") { + this.Name = Name; + this.body = (Body != null ? Body : FlightGlobals.currentMainBody); + this.Mode = Mode; + } + + public MechJebWaypointRoute(ConfigNode Node) { // feed this "Waypoints" nodes, just not the local ones of a ship + if (Node == null) { return; } + this.Name = (Node.HasValue("Name") ? Node.GetValue("Name") : ""); + this.body = (Node.HasValue("Body") ? FlightGlobals.Bodies.Find(b => b.bodyName == Node.GetValue("Body")) : null); + this.Mode = (Node.HasValue("Mode") ? Node.GetValue("Mode") : "Rover"); + if (Node.HasNode("Waypoint")) { + foreach (ConfigNode cn in Node.GetNodes("Waypoint")) { + this.Add(new MechJebWaypoint(cn)); + } + } + } + +// public new void Add(MechJebWaypoint Waypoint) { +// this.Add(Waypoint); +// updateStats(); +// } +// +// public new void Insert(int Index, MechJebWaypoint Waypoint) { +// this.Insert(Index, Waypoint); +// updateStats(); +// } +// +// public new void Remove(MechJebWaypoint Waypoint) { +// this.Remove(Waypoint); +// updateStats(); +// } +// +// public new void RemoveAt(int Index) { +// this.RemoveAt(Index); +// updateStats(); +// } +// +// public new void RemoveRange() { +// +// } + } + + public class MechJebModuleWaypointWindow : DisplayModule { + public enum WaypointMode { + Rover, + Plane + } + + public WaypointMode Mode = WaypointMode.Rover; + public MechJebModuleRoverController ap; + public static List Routes = new List(); + [EditableInfoItem("#MechJeb_MohoMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Moho Mapdist + public EditableDouble MohoMapdist = 5000; + [EditableInfoItem("#MechJeb_EveMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Eve Mapdist + public EditableDouble EveMapdist = 5000; + [EditableInfoItem("#MechJeb_GillyMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Gilly Mapdist + public EditableDouble GillyMapdist = -500; + [EditableInfoItem("#MechJeb_KerbinMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Kerbin Mapdist + public EditableDouble KerbinMapdist = 500; + [EditableInfoItem("#MechJeb_MunMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Mun Mapdist + public EditableDouble MunMapdist = 4000; + [EditableInfoItem("#MechJeb_MinmusMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Minmus Mapdist + public EditableDouble MinmusMapdist = 3500; + [EditableInfoItem("#MechJeb_DunaMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Duna Mapdist + public EditableDouble DunaMapdist = 5000; + [EditableInfoItem("#MechJeb_IkeMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Ike Mapdist + public EditableDouble IkeMapdist = 4000; + [EditableInfoItem("#MechJeb_DresMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Dres Mapdist + public EditableDouble DresMapdist = 1500; + [EditableInfoItem("#MechJeb_EelooMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Eeloo Mapdist + public EditableDouble EelooMapdist = 2000; + [EditableInfoItem("#MechJeb_JoolMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Jool Mapdist + public EditableDouble JoolMapdist = 30000; + [EditableInfoItem("#MechJeb_TyloMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Tylo Mapdist + public EditableDouble TyloMapdist = 5000; + [EditableInfoItem("#MechJeb_LaytheMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Laythe Mapdist + public EditableDouble LaytheMapdist = 1000; + [EditableInfoItem("#MechJeb_PolMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Pol Mapdist + public EditableDouble PolMapdist = 500; + [EditableInfoItem("#MechJeb_BopMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Bop Mapdist + public EditableDouble BopMapdist = 1000; + [EditableInfoItem("#MechJeb_VallMapdist", InfoItem.Category.Rover), Persistent(pass = (int)Pass.Global)]//Vall Mapdist + public EditableDouble VallMapdist = 5000; + + internal int selIndex = -1; + internal int saveIndex = -1; + internal string tmpRadius = ""; + internal string tmpMinSpeed = ""; + internal string tmpMaxSpeed = ""; + internal string tmpLat = ""; + internal string tmpLon = ""; + internal const string coordRegEx = @"^([nsew])?\s*(-?\d+(?:\.\d+)?)(?:[°:\s]+(-?\d+(?:\.\d+)?))?(?:[':\s]+(-?\d+(?:\.\d+)?))?(?:[^nsew]*([nsew])?)?$"; + + private Vector2 scroll; + private GUIStyle styleActive; + private GUIStyle styleInactive; + private GUIStyle styleQuicksave; + private string titleAdd = ""; + private string saveName = ""; + private bool waitingForPick = false; + private pages showPage = pages.waypoints; + private enum pages { waypoints, settings, routes }; + private static MechJebRouteRenderer renderer; + private Rect[] waypointRects = new Rect[0]; + private int lastIndex = -1; + private int settingPageIndex = 0; + private string[] settingPages = new string[] { "Rover", "Waypoints" }; +// private static LineRenderer redLine; +// private static LineRenderer greenLine; + + public MechJebModuleWaypointWindow(MechJebCore core) : base(core) { } + + public override void OnStart(PartModule.StartState state) + { + hidden = true; + ap = core.GetComputerModule(); + if (HighLogic.LoadedSceneIsFlight && vessel.isActiveVessel) { + renderer = MechJebRouteRenderer.AttachToMapView(core); + renderer.enabled = enabled; + } + +// GameObject obj = new GameObject("LineRenderer"); +// redLine = obj.AddComponent(); +// redLine.useWorldSpace = true; +// redLine.material = renderer.material; +// redLine.SetWidth(10.0f, 10.0f); +// redLine.SetColors(Color.red, Color.red); +// redLine.SetVertexCount(2); +// GameObject obj2 = new GameObject("LineRenderer"); +// greenLine = obj2.AddComponent(); +// greenLine.useWorldSpace = true; +// greenLine.material = renderer.material; +// greenLine.SetWidth(10.0f, 10.0f); +// greenLine.SetColors(Color.green, Color.green); +// greenLine.SetVertexCount(2); +// MechJebRouteRenderer.NewLineRenderer(ref greenLine); + base.OnStart(state); + } + + public override void OnModuleEnabled() + { + if (renderer != null) { renderer.enabled = true; } + base.OnModuleEnabled(); + } + + public override void OnModuleDisabled() + { + if (renderer != null) { renderer.enabled = false; } + base.OnModuleDisabled(); + } + + public override void OnLoad(ConfigNode local, ConfigNode type, ConfigNode global) + { + base.OnLoad(local, type, global); + + ConfigNode wps = new ConfigNode("Routes"); + if (KSP.IO.File.Exists("mechjeb_routes.cfg")) + { + try + { + wps = ConfigNode.Load(KSP.IO.IOUtils.GetFilePathFor(core.GetType(), "mechjeb_routes.cfg")); + } + catch (Exception e) + { + Debug.LogError("MechJebModuleWaypointWindow.OnLoad caught an exception trying to load mechjeb_routes.cfg: " + e); + } + } + + if (wps.HasNode("Waypoints")) + { + Routes.Clear(); + foreach (ConfigNode cn in wps.GetNodes("Waypoints")) + { + Routes.Add(new MechJebWaypointRoute(cn)); + } + Routes.Sort(SortRoutes); + } + } + + public void SaveRoutes() + { + var cn = new ConfigNode("Routes"); + + if (Routes.Count > 0) { + Routes.Sort(SortRoutes); + foreach (MechJebWaypointRoute r in Routes) { + cn.AddNode(r.ToConfigNode()); + } + } + + cn.Save(KSP.IO.IOUtils.GetFilePathFor(core.GetType(), "mechjeb_routes.cfg")); + } + + public override string GetName() + { + return Mode.ToString() + " Waypoints" + (titleAdd != "" ? " - " + titleAdd : ""); + } + + public static Coordinates GetMouseFlightCoordinates() + { + CelestialBody body = FlightGlobals.currentMainBody; + Ray mouseRay = FlightCamera.fetch.mainCamera.ScreenPointToRay(Input.mousePosition); + RaycastHit raycast; +// mouseRay.origin = ScaledSpace.ScaledToLocalSpace(mouseRay.origin); + Vector3d relOrigin = mouseRay.origin - body.position; + Vector3d relSurfacePosition; + if (Physics.Raycast(mouseRay, out raycast, (float)body.Radius * 4f, 1 << 15)) + { + return new Coordinates(body.GetLatitude(raycast.point), MuUtils.ClampDegrees180(body.GetLongitude(raycast.point))); + } + else + { + double curRadius = body.pqsController.radiusMax; + double lastRadius = 0; + double error = 0; + int loops = 0; + float st = Time.time; + while (loops < 50) + { + if (PQS.LineSphereIntersection(relOrigin, mouseRay.direction, curRadius, out relSurfacePosition)) + { + Vector3d surfacePoint = body.position + relSurfacePosition; + double alt = body.pqsController.GetSurfaceHeight(QuaternionD.AngleAxis(body.GetLongitude(surfacePoint), Vector3d.down) * QuaternionD.AngleAxis(body.GetLatitude(surfacePoint), Vector3d.forward) * Vector3d.right); + error = Math.Abs(curRadius - alt); + if (error < (body.pqsController.radiusMax - body.pqsController.radiusMin) / 100) + { + return new Coordinates(body.GetLatitude(surfacePoint), MuUtils.ClampDegrees180(body.GetLongitude(surfacePoint))); + } + else + { + lastRadius = curRadius; + curRadius = alt; + loops++; + } + } + else + { + if (loops == 0) + { + break; + } + else + { // Went too low, needs to try higher + curRadius = (lastRadius * 9 + curRadius) / 10; + loops++; + } + } + } + } + + return null; + +// var cam = FlightCamera.fetch.mainCamera; +// Ray ray = cam.ScreenPointToRay(Input.mousePosition); +//// greenLine.SetPosition(0, ray.origin); +//// greenLine.SetPosition(1, (Vector3d)ray.direction * body.Radius / 2); +//// if (Physics.Raycast(ray, out raycast, (float)body.Radius * 4f, ~(1 << 1))) { +// Vector3d hit; +// //body.pqsController.RayIntersection(ray.origin, ray.direction, out hit); +// PQS.LineSphereIntersection(ray.origin - body.position, ray.direction, body.Radius, out hit); +// if (hit != Vector3d.zero) { +// hit = body.position + hit; +// Vector3d start = ray.origin; +// Vector3d end = hit; +// Vector3d point = Vector3d.zero; +// for (int i = 0; i < 16; i++) { +// point = (start + end) / 2; +// //var lat = body.GetLatitude(point); +// //var lon = body.GetLongitude(point); +// //var surf = body.GetWorldSurfacePosition(lat, lon, body.TerrainAltitude(lat, lon)); +// var alt = body.GetAltitude(point) - body.TerrainAltitude(point); +// //Debug.Log(alt); +// if (alt > 0) { +// start = point; +// } +// else if (alt < 0) { +// end = point; +// } +// else { +// break; +// } +// } +// hit = point; +//// redLine.SetPosition(0, ray.origin); +//// redLine.SetPosition(1, hit); +// return new Coordinates(body.GetLatitude(hit), MuUtils.ClampDegrees180(body.GetLongitude(hit))); +// } +// else { +// return null; +// } + } + + public static string LatToString(double Lat) + { + while (Lat > 90) { Lat -= 180; } + while (Lat < -90) { Lat += 180; } + + string ns = (Lat >= 0 ? "N" : "S"); + Lat = Math.Abs(Lat); + + int h = (int)Lat; + Lat -= h; Lat *= 60; + + int m = (int)Lat; + Lat -= m; Lat *= 60; + + float s = (float)Lat; + + return string.Format("{0} {1}° {2}' {3:F3}\"", ns, h, m, s); + } + + public static string LonToString(double Lon) + { + while (Lon > 180) { Lon -= 360; } + while (Lon < -180) { Lon += 360; } + + string ew = (Lon >= 0 ? "E" : "W"); + Lon = Math.Abs(Lon); + + int h = (int)Lon; + Lon -= h; Lon *= 60; + + int m = (int)Lon; + Lon -= m; Lon *= 60; + + float s = (float)Lon; + + return string.Format("{0} {1}° {2}' {3:F3}\"", ew, h, m, s); + } + + public static double ParseCoord(string LatLon, bool IsLongitute = false) + { + var match = new Regex(coordRegEx, RegexOptions.IgnoreCase).Match(LatLon); + var range = (IsLongitute ? 180 : 90); + + float nsew = 1; + if (match.Groups[5] != null) + { + if (match.Groups[5].Value.ToUpper() == "N" || match.Groups[5].Value.ToUpper() == "E") { nsew = 1; } + else if (match.Groups[5].Value.ToUpper() == "S" || match.Groups[5].Value.ToUpper() == "W") { nsew = -1; } + else if (match.Groups[1] != null) { + if (match.Groups[1].Value.ToUpper() == "N" || match.Groups[1].Value.ToUpper() == "E") { nsew = 1; } + else if (match.Groups[1].Value.ToUpper() == "S" || match.Groups[1].Value.ToUpper() == "W") { nsew = -1; } + } + } + + float h = 0; + if (match.Groups[2] != null) { float.TryParse(match.Groups[2].Value, out h); } + if (h < 0) { nsew *= -1; h *= -1; } + + float m = 0; + if (match.Groups[3] != null) { float.TryParse(match.Groups[3].Value, out m); } + + float s = 0; + if (match.Groups[4] != null) { float.TryParse(match.Groups[4].Value, out s); } + + h = (h + (m / 60) + (s / 3600)) * nsew; + + while (h > range) { h -= range * 2; } + while (h < -range) { h += range * 2; } + + return h; + } + + private int SortRoutes(MechJebWaypointRoute a, MechJebWaypointRoute b) + { + var bn = string.Compare(a.Body.bodyName, b.Body.bodyName, true); + if (bn != 0) + { + return bn; + } + else + { + return string.Compare(a.Name, b.Name, true); + } + } + + public MechJebWaypoint SelectedWaypoint() { + return (selIndex > -1 ? ap.Waypoints[selIndex] : null); + } + + public int SelectedWaypointIndex { + get { return selIndex; } + set { selIndex = value; } + } + + public override GUILayoutOption[] WindowOptions() + { + return new GUILayoutOption[] { GUILayout.Width(500), GUILayout.Height(400) }; + } + + public void DrawPageWaypoints() + { + bool alt = Input.GetKey(KeyCode.LeftAlt); + scroll = GUILayout.BeginScrollView(scroll); + if (ap.Waypoints.Count > 0) { + waypointRects = new Rect[ap.Waypoints.Count]; + GUILayout.BeginVertical(); + double eta = 0; + double dist = 0; + for (int i = 0; i < ap.Waypoints.Count; i++) + { + var wp = ap.Waypoints[i]; + var maxSpeed = (wp.MaxSpeed > 0 ? wp.MaxSpeed : ap.speed.val); + var minSpeed = (wp.MinSpeed > 0 ? wp.MinSpeed : 0); + if (MapView.MapIsEnabled && i == selIndex) + { + MuMech.GLUtils.DrawGroundMarker(mainBody, wp.Latitude, wp.Longitude, Color.red, true, (DateTime.Now.Second + DateTime.Now.Millisecond / 1000f) * 6, mainBody.Radius / 100); + } + if (i >= ap.WaypointIndex) + { + if (ap.WaypointIndex > -1) + { + eta += GuiUtils.FromToETA((i == ap.WaypointIndex ? vessel.CoM : (Vector3)ap.Waypoints[i - 1].Position), (Vector3)wp.Position, (ap.etaSpeed > 0.1 && ap.etaSpeed < maxSpeed ? (float)Math.Round(ap.etaSpeed, 1) : maxSpeed)); + } + dist += Vector3.Distance((i == ap.WaypointIndex || (ap.WaypointIndex == -1 && i == 0) ? vessel.CoM : (Vector3)ap.Waypoints[i - 1].Position), (Vector3)wp.Position); + } + string str = string.Format("[{0}] - {1} - R: {2:F1} m\n S: {3:F0} ~ {4:F0} - D: {5}m - ETA: {6}", i + 1, wp.GetNameWithCoords(), wp.Radius, + minSpeed, maxSpeed, MuUtils.ToSI(dist, -1), GuiUtils.TimeToDHMS(eta)); + GUI.backgroundColor = (i == ap.WaypointIndex ? new Color(0.5f, 1f, 0.5f) : Color.white); + if (GUILayout.Button(str, (i == selIndex ? styleActive : (wp.Quicksave ? styleQuicksave : styleInactive)))) + { + if (alt) + { + ap.WaypointIndex = (ap.WaypointIndex == i ? -1 : i); + // set current waypoint or unset it if it's already the current one + } + else + { + if (selIndex == i) + { + selIndex = -1; + } + else + { + selIndex = i; + tmpRadius = wp.Radius.ToString(); + tmpMinSpeed = wp.MinSpeed.ToString(); + tmpMaxSpeed = wp.MaxSpeed.ToString(); + tmpLat = LatToString(wp.Latitude); + tmpLon = LonToString(wp.Longitude); + } + } + } + + if (Event.current.type == EventType.Repaint) + { + waypointRects[i] = GUILayoutUtility.GetLastRect(); + //if (i == ap.WaypointIndex) { Debug.Log(Event.current.type.ToString() + " - " + waypointRects[i].ToString() + " - " + scroll.ToString()); } + } + GUI.backgroundColor = Color.white; + + if (selIndex > -1 && selIndex == i) + { + GUILayout.BeginHorizontal(); + + GUILayout.Label(" Radius: ", GUILayout.ExpandWidth(false)); + tmpRadius = GUILayout.TextField(tmpRadius, GUILayout.Width(50)); + float.TryParse(tmpRadius, out wp.Radius); + if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.Radius = wp.Radius); } + + GUILayout.Label("- Speed: ", GUILayout.ExpandWidth(false)); + tmpMinSpeed = GUILayout.TextField(tmpMinSpeed, GUILayout.Width(40)); + float.TryParse(tmpMinSpeed, out wp.MinSpeed); + if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.MinSpeed = wp.MinSpeed); } + + GUILayout.Label(" - ", GUILayout.ExpandWidth(false)); + tmpMaxSpeed = GUILayout.TextField(tmpMaxSpeed, GUILayout.Width(40)); + float.TryParse(tmpMaxSpeed, out wp.MaxSpeed); + if (GUILayout.Button("A", GUILayout.ExpandWidth(false))) { ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.MaxSpeed = wp.MaxSpeed); } + + GUILayout.FlexibleSpace(); + if (GUILayout.Button("QS", (wp.Quicksave ? styleQuicksave : styleInactive), GUILayout.ExpandWidth(false))) + { + if (alt) + { + ap.Waypoints.GetRange(i, ap.Waypoints.Count).ForEach(fewp => fewp.Quicksave = !fewp.Quicksave); + } + else + { + wp.Quicksave = !wp.Quicksave; + } + } + + GUILayout.EndHorizontal(); + + + GUILayout.BeginHorizontal(); + + GUILayout.Label("Lat ", GUILayout.ExpandWidth(false)); + tmpLat = GUILayout.TextField(tmpLat, GUILayout.Width(125)); + wp.Latitude = ParseCoord(tmpLat); + + GUILayout.Label(" - Lon ", GUILayout.ExpandWidth(false)); + tmpLon = GUILayout.TextField(tmpLon, GUILayout.Width(125)); + wp.Longitude = ParseCoord(tmpLon, true); + + GUILayout.EndHorizontal(); + } + } + titleAdd = "Distance: " + MuUtils.ToSI(dist, -1) + "m - ETA: " + GuiUtils.TimeToDHMS(eta); + GUILayout.EndVertical(); + } + else + { + titleAdd = ""; + } + GUILayout.EndScrollView(); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button(alt ? "Reverse" : (!waitingForPick ? "Add Waypoint" : "Abort Adding"), GUILayout.Width(110))) + { + if (alt) + { + ap.Waypoints.Reverse(); + if (ap.WaypointIndex > -1) { ap.WaypointIndex = ap.Waypoints.Count - 1 - ap.WaypointIndex; } + if (selIndex > -1) { selIndex = ap.Waypoints.Count - 1 - selIndex; } + } + else + { + if (!waitingForPick) + { + waitingForPick = true; + if (MapView.MapIsEnabled) + { + core.target.Unset(); + core.target.PickPositionTargetOnMap(); + } + } + else + { + waitingForPick = false; + } + } + } + if (GUILayout.Button((alt ? "Clear" : "Remove"), GUILayout.Width(65)) && selIndex >= 0 && ap.Waypoints.Count > 0) + { + if (alt) + { + ap.WaypointIndex = -1; + ap.Waypoints.Clear(); + } + else + { + ap.Waypoints.RemoveAt(selIndex); + if (ap.WaypointIndex > selIndex) { ap.WaypointIndex--; } + } + selIndex = -1; + //if (ap.WaypointIndex >= ap.Waypoints.Count) { ap.WaypointIndex = ap.Waypoints.Count - 1; } + } + if (GUILayout.Button((alt ? "Top" : "Up"), GUILayout.Width(57)) && selIndex > 0 && selIndex < ap.Waypoints.Count && ap.Waypoints.Count >= 2) + { + if (alt) + { + var t = ap.Waypoints[selIndex]; + ap.Waypoints.RemoveAt(selIndex); + ap.Waypoints.Insert(0, t); + selIndex = 0; + } + else + { + ap.Waypoints.Swap(selIndex, --selIndex); + } + } + if (GUILayout.Button((alt ? "Bottom" : "Down"), GUILayout.Width(57)) && selIndex >= 0 && selIndex < ap.Waypoints.Count - 1 && ap.Waypoints.Count >= 2) + { + if (alt) + { + var t = ap.Waypoints[selIndex]; + ap.Waypoints.RemoveAt(selIndex); + ap.Waypoints.Add(t); + selIndex = ap.Waypoints.Count - 1; + } + else + { + ap.Waypoints.Swap(selIndex, ++selIndex); + } + } + if (GUILayout.Button("Routes")) + { + showPage = pages.routes; + scroll = Vector2.zero; + } + if (GUILayout.Button("Settings")) + { + showPage = pages.settings; + scroll = Vector2.zero; + } + GUILayout.EndHorizontal(); + + if (selIndex >= ap.Waypoints.Count) { selIndex = -1; } + if (selIndex == -1 && ap.WaypointIndex > -1 && lastIndex != ap.WaypointIndex && waypointRects.Length > 0) + { + scroll.y = waypointRects[ap.WaypointIndex].y - 160; + } + lastIndex = ap.WaypointIndex; + } + + public void DrawPageSettings() + { + bool alt = Input.GetKey(KeyCode.LeftAlt); + titleAdd = "Settings"; + MechJebModuleCustomWindowEditor ed = core.GetComputerModule(); + if (!ap.enabled) { ap.CalculateTraction(); } // keep calculating traction just for displaying it + + scroll = GUILayout.BeginScrollView(scroll); + + settingPageIndex = GUILayout.SelectionGrid(settingPageIndex, settingPages, settingPages.Length); + + switch (settingPageIndex) + { + case 0: + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(); + ed.registry.Find(i => i.id == "Editable:RoverController.hPIDp").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.hPIDi").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.hPIDd").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.terrainLookAhead").DrawItem(); + // ed.registry.Find(i => i.id == "Value:RoverController.speedIntAcc").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.tractionLimit").DrawItem(); + ed.registry.Find(i => i.id == "Toggle:RoverController.LimitAcceleration").DrawItem(); + GUILayout.EndVertical(); + + GUILayout.BeginVertical(); + ed.registry.Find(i => i.id == "Editable:RoverController.sPIDp").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.sPIDi").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.sPIDd").DrawItem(); + ed.registry.Find(i => i.id == "Editable:RoverController.turnSpeed").DrawItem(); + ed.registry.Find(i => i.id == "Value:RoverController.traction").DrawItem(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + break; + + case 1: + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.MohoMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.EveMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.GillyMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.KerbinMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.MunMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.MinmusMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.DunaMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.IkeMapdist").DrawItem(); + GUILayout.EndVertical(); + + GUILayout.BeginVertical(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.DresMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.JoolMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.LaytheMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.VallMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.TyloMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.BopMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.PolMapdist").DrawItem(); + ed.registry.Find(i => i.id == "Editable:WaypointWindow.EelooMapdist").DrawItem(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + break; + } + + GUILayout.EndScrollView(); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Waypoints")) + { + showPage = pages.waypoints; + scroll = Vector2.zero; + lastIndex = -1; + } + if (GUILayout.Button("Routes")) + { + showPage = pages.routes; + scroll = Vector2.zero; + } + if (GUILayout.Button("Help")) + { + core.GetComputerModule().enabled = !core.GetComputerModule().enabled; + } + GUILayout.EndHorizontal(); + } + + public void DrawPageRoutes() + { + bool alt = Input.GetKey(KeyCode.LeftAlt); + titleAdd = "Routes for " + vessel.mainBody.bodyName; + + scroll = GUILayout.BeginScrollView(scroll); + var bodyWPs = Routes.FindAll(r => r.Body == vessel.mainBody && r.Mode == Mode.ToString()); + for (int i = 0; i < bodyWPs.Count; i++) + { + GUILayout.BeginHorizontal(); + var str = bodyWPs[i].Name + " - " + bodyWPs[i].Stats; + if (GUILayout.Button(str, (i == saveIndex ? styleActive : styleInactive))) + { + saveIndex = (saveIndex == i ? -1 : i); + } + if (i == saveIndex) + { + if (GUILayout.Button("Delete", GUILayout.Width(70))) + { + Routes.RemoveAll(r => r.Name == bodyWPs[i].Name && r.Body == vessel.mainBody && r.Mode == Mode.ToString()); + saveIndex = -1; + } + } + GUILayout.EndHorizontal(); + } + GUILayout.EndScrollView(); + + GUILayout.BeginHorizontal(); + saveName = GUILayout.TextField(saveName, GUILayout.Width(150)); + if (GUILayout.Button("Save", GUILayout.Width(50))) + { + if (saveName != "" && ap.Waypoints.Count > 0) + { + var old = Routes.Find(r => r.Name == saveName && r.Body == vessel.mainBody && r.Mode == Mode.ToString()); + var wps = new MechJebWaypointRoute(saveName, vessel.mainBody); + ap.Waypoints.ForEach(wp => wps.Add(wp)); + if (old == null) + { + Routes.Add(wps); + } + else + { + Routes[Routes.IndexOf(old)] = wps; + } + Routes.Sort(SortRoutes); + SaveRoutes(); + } + } + if (GUILayout.Button((alt ? "Add" : "Load"), GUILayout.Width(50))) + { + if (saveIndex > -1) + { + if (!alt) + { + ap.WaypointIndex = -1; + ap.Waypoints.Clear(); + } + Routes[saveIndex].ForEach(wp => ap.Waypoints.Add(wp)); + } + } + if (GUILayout.Button("Waypoints")) + { + showPage = pages.waypoints; + scroll = Vector2.zero; + lastIndex = -1; + } + if (GUILayout.Button("Settings")) + { + showPage = pages.settings; + scroll = Vector2.zero; + } + GUILayout.EndHorizontal(); + } + + protected override void WindowGUI(int windowID) + { + if (GUI.Button(new Rect(windowPos.width - 48, 0, 13, 20), "?", GuiUtils.yellowOnHover)) + { + var help = core.GetComputerModule(); + switch (showPage) + { + case pages.waypoints: help.selTopic = ((IList)help.topics).IndexOf("Waypoints"); break; + case pages.settings: help.selTopic = ((IList)help.topics).IndexOf("Settings"); break; + case pages.routes: help.selTopic = ((IList)help.topics).IndexOf("Routes"); break; + } + help.enabled = help.selTopic > -1 || help.enabled; + } + + if (styleInactive == null) + { + styleInactive = new GUIStyle(GuiUtils.skin != null ? GuiUtils.skin.button : GuiUtils.defaultSkin.button); + styleInactive.alignment = TextAnchor.UpperLeft; + } + if (styleActive == null) + { + styleActive = new GUIStyle(styleInactive); + styleActive.active.textColor = styleActive.focused.textColor = styleActive.hover.textColor = styleActive.normal.textColor = Color.green; + } // for some reason MJ's skin sometimes isn't loaded at OnStart so this has to be done here + if (styleQuicksave == null) + { + styleQuicksave = new GUIStyle(styleActive); + styleQuicksave.active.textColor = styleQuicksave.focused.textColor = styleQuicksave.hover.textColor = styleQuicksave.normal.textColor = Color.yellow; + } + + bool alt = Input.GetKey(KeyCode.LeftAlt); + + switch (showPage) + { + case pages.waypoints: DrawPageWaypoints(); break; + case pages.settings: DrawPageSettings(); break; + case pages.routes: DrawPageRoutes(); break; + } + + if (waitingForPick && vessel.isActiveVessel && Event.current.type == EventType.Repaint) + { + if (MapView.MapIsEnabled) + { + if (core.target.pickingPositionTarget == false) + { + if (core.target.PositionTargetExists) + { + if (selIndex > -1 && selIndex < ap.Waypoints.Count) + { + ap.Waypoints.Insert(selIndex, new MechJebWaypoint(core.target.GetPositionTargetPosition())); + tmpRadius = ap.Waypoints[selIndex].Radius.ToString(); + tmpLat = LatToString(ap.Waypoints[selIndex].Latitude); + tmpLon = LonToString(ap.Waypoints[selIndex].Longitude); + } + else + { + ap.Waypoints.Add(new MechJebWaypoint(core.target.GetPositionTargetPosition())); + } + core.target.Unset(); + waitingForPick = alt; + } + else + { + core.target.PickPositionTargetOnMap(); + } + } + } + else + { + if (!GuiUtils.MouseIsOverWindow(core)) + { + Coordinates mouseCoords = GetMouseFlightCoordinates(); + if (mouseCoords != null) + { + if (Input.GetMouseButtonDown(0)) + { + if (selIndex > -1 && selIndex < ap.Waypoints.Count) + { + ap.Waypoints.Insert(selIndex, new MechJebWaypoint(mouseCoords.latitude, mouseCoords.longitude)); + tmpRadius = ap.Waypoints[selIndex].Radius.ToString(); + tmpLat = LatToString(ap.Waypoints[selIndex].Latitude); + tmpLon = LonToString(ap.Waypoints[selIndex].Longitude); + } + else + { + ap.Waypoints.Add(new MechJebWaypoint(mouseCoords.latitude, mouseCoords.longitude)); + } + waitingForPick = alt; + } + } + } + } + } + + base.WindowGUI(windowID); + } + + public override void OnFixedUpdate() + { + if (vessel.isActiveVessel && (renderer == null || renderer.ap != ap)) { MechJebRouteRenderer.AttachToMapView(core); } //MechJebRouteRenderer.AttachToMapView(core); } + ap.Waypoints.ForEach(wp => wp.Update()); +// float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, vessel.CoM) / 900f; +// greenLine.SetPosition(0, vessel.CoM); +// greenLine.SetPosition(1, vessel.CoM + ap.norm * 5); +// greenLine.SetWidth(scale + 0.1f, scale + 0.1f); + base.OnFixedUpdate(); + } + } + + public class MechJebModuleWaypointHelpWindow : DisplayModule + { + public int selTopic = 0; + public string[] topics = {"Rover Controller", "Waypoints", "Routes", "Settings"}; + string selSubTopic = ""; + GUIStyle btnActive; + GUIStyle btnInactive; + + void HelpTopic(string title, string text) + { + GUILayout.BeginVertical(); + if (GUILayout.Button(title, (selSubTopic == title ? btnActive : btnInactive))) + { + selSubTopic = (selSubTopic != title ? title : ""); + windowPos = new Rect(windowPos.x, windowPos.y, windowPos.width, 0); + } + if (selSubTopic == title) + { + GUILayout.Label(text); + } + GUILayout.EndVertical(); + } + + public MechJebModuleWaypointHelpWindow(MechJebCore core) : base(core) { } + + public override string GetName() + { + return Localizer.Format("#MechJeb_Waypointhelper_title");//"Waypoint Help" + } + + public override void OnStart(PartModule.StartState state) + { + hidden = true; + base.OnStart(state); + } + + protected override void WindowGUI(int windowID) + { + if (btnInactive == null) + { + btnInactive = new GUIStyle(GuiUtils.skin.button); + btnInactive.alignment = TextAnchor.MiddleLeft; + } + + if (btnActive == null) + { + btnActive = new GUIStyle(btnInactive); + btnActive.active.textColor = btnActive.hover.textColor = btnActive.focused.textColor = btnActive.normal.textColor = Color.green; + } + + selTopic = GUILayout.SelectionGrid(selTopic, topics, topics.Length); + + switch (topics[selTopic]) + { + case "Rover Controller": + HelpTopic("Holding a set Heading", "To hold a specific heading just tick the box next to 'Heading control' and the autopilot will try to keep going for the entered heading." + + "\nThis also needs to be enabled when the autopilot is supposed to drive to a waypoint" + + "'Heading Error' simply shows the error between current heading and target heading."); + HelpTopic("Holding a set Speed", "To hold a specific speed just tick the box next to 'Speed control' and the autopilot will try to keep going at the entered speed." + + "\nThis also needs to be enabled when the autopilot is supposed to drive to a waypoint" + + "'Speed Error' simply shows the error between current speed and target speed."); + HelpTopic("More stability while driving and nice landings", "If you turn on 'Stability Control' then the autopilot will use the reaction wheel's torque to keep the rover aligned with the surface." + + "\nThis means that if you make a jump the autopilot will try to align the rover in the best possible way to land as straight and flat as possible given the available time and torque." + + "\nBe aware that this doesn't make your rover indestructible, only relatively unlikely to land in a bad way." + + "\n\n'Stability Control' will also limit the brake to reduce the chances of flipping over from too much braking power." + + "\nSee 'Settings' -> 'Traction and Braking'. This setting is also saved per vessel."); + HelpTopic("Brake on Pilot Eject", "With this option enabled the rover will stop if the pilot (on manned rovers) should get thrown out of his seat."); + HelpTopic("Target Speed", "Current speed the autopilot tries to achieve."); + HelpTopic("Waypoint Index", "Overview of waypoints and which the autopilot is currently driving to."); + HelpTopic("Button 'Waypoints'", "Opens the waypoint list to set up a route."); + HelpTopic("Button 'Follow' / 'Stop'", "This sets the autopilot to drive along the set route starting at the first waypoint. Only visible when atleast one waypoint is set." + + "\n\nAlt click will set the autopilot to 'Loop Mode' which will make it go for the first waypoint again after reaching the last." + + "If the only waypoint happens to be a target it will keep following that instead of only going to it once." + + "\n\nIf the autopilot is already active the 'Follow' button will turn into the 'Stop' button which will obviously stop it when pressed."); + HelpTopic("Button 'To Target'", "Clears the route, adds the target as only waypoint and starts the autopilot. Only visible with a selected target." + + "\n\nAlt click will set the autopilot to 'Loop Mode' which will make it continue to follow the target, pausing when near it instead of turning off then."); + HelpTopic("Button 'Add Target'", "Adds the selected target as a waypoint either at the end of the route or before the selected waypoint. Only visible with a selected target."); + break; + + case "Waypoints": + HelpTopic("Adding Waypoints", "Adds a new waypoint to the route at the end or before the currently selected waypoint, " + + "simply click the terrain or somewhere on the body in Mapview." + + "\n\nAlt clicking will reverse the route for easier going back and holding Alt while clicking the terrain or body in Mapview will allow to add more waypoints without having to click the button again."); + HelpTopic("Removing Waypoints", "Removes the currently selected waypoint." + + "\n\nAlt clicking will remove all waypoints."); + HelpTopic("Reordering Waypoints", "'Up' and 'Down' will move the selected waypoint up or down in the list, Alt clicking will move it to the top or bottom respectively."); + HelpTopic("Waypoint Radius", "Radius defines the distance to the center of the waypoint after which the waypoint will be considered 'reached'." + + "\n\nA radius of 5m (default) simply means that when you're 5m from the waypoint away the autopilot will jump to the next or turn off if it was the last." + + "\n\nThe 'A' button behind the textfield will set the entered radius for all waypoints."); + HelpTopic("Speedlimits", "The two speed textfields represent the minimum and maximum speed for the waypoint." + + "\n\nThe maximum speed is the speed the autopilot tries to reach to get to the waypoint." + + "\n\nThe minimum speed was before used to set the speed with which the autopilot will go through the waypoint, but that got reworked now to be based on the next waypoint's max. speed and the turn needed at the waypoint." + + "\n\nI have no idea what this will currently do if set so better just leave it at 0..." + + "\n\nThe 'A' buttons set their respective speed for all waypoints."); + HelpTopic("Quicksaving at a Waypoint", "Clicking the 'QS' button will turn on QuickSave for that waypoint." + + "\n\nThis will make the autopilot stop and try to quicksave at that waypoint and then continue. A QuickSave waypoint has yellow text instead of white." + + "\n\nSmall sideeffect: leaving the throttle up will prevent the saving from occurring effectively pausing the autopilot at that point until interefered with. (Discovered by Greys)" + + "\n\nAlt clicking will toggle QS for all waypoints including the clicked one."); + HelpTopic("Changing the current target Waypoint", "Alt clicking a waypoint will mark it as the current target waypoint. The active waypoint has a green tinted background."); + break; + + case "Routes": + HelpTopic("Routes Help", "The empty textfield is for saving routes, enter a name there before clicking 'Save'." + + "\n\nTo load a route simply select one from the list and click 'Load'." + + "\n\nTo delete a route simply select it and a 'Delete' button will appear right of it."); + break; + + case "Settings": + HelpTopic("Heading / Speed PID", "These parameters control the behaviour of the heading's / speed's PID. Saved globally so NO TOUCHING unless you know what you're doing (or atleast know how to write down numbers to restore it if you mess up)"); + HelpTopic("Safe Turn Speed", "'Safe Turn Speed' tells the autopilot which speed the rover can usually go full turn through corners without tipping over." + + "\n\nGiven how differently terrain can be and other influences you can just leave it at 3 m/s but if you're impatient or just want to experiment feel free to test around. Saved per vessel type (same named vessels will share the setting)."); + HelpTopic("Traction and Braking", "'Traction' shows in % how many wheels have ground contact." + + "\n'Traction Brake Limit' defines what traction is atleast needed for the autopilot to still apply the brakes (given 'Stability Control' is active) even if you hold the brake down." + + "\nThis means the default setting of 75 will make it brake only if atleast 3 wheels have ground contact." + + "\n'Traction Brake Limit' is saved per vessel type." + + "\n\nIf you have 'Stability Control' off then it won't take care of your brake and you can flip as much as you want."); + HelpTopic("Changing the route height in Mapview", "These values define offsets for the route height in Mapview. Given how weird it's set up it can be that they are too high or too low so I added these for easier adjusting. Saved globally, I think."); + break; + } + + base.WindowGUI(windowID); + } + } + + public class MechJebRouteRenderer : MonoBehaviour + { + public static readonly Material material = new Material (Shader.Find ("Legacy Shaders/Particles/Additive")); + public MechJebModuleRoverController ap; + private LineRenderer pastPath; + private LineRenderer currPath; + private LineRenderer nextPath; + private LineRenderer selWP; + private Color pastPathColor = new Color(0f, 0f, 1f, 0.5f); + private Color currPathColor = new Color(0f, 1f, 0f, 0.5f); + private Color nextPathColor = new Color(1f, 1f, 0f, 0.5f); + private Color selWPColor = new Color(1f, 0f, 0f, 0.5f); + private double addHeight; + + public static MechJebRouteRenderer AttachToMapView(MechJebCore Core) + { + var renderer = MapView.MapCamera.gameObject.GetComponent(); + if (!renderer) + { + renderer = MapView.MapCamera.gameObject.AddComponent(); + } + renderer.ap = Core.GetComputerModule(); + return renderer; + } + + public static bool NewLineRenderer(ref LineRenderer line) + { + if (line != null) { return false; } + GameObject obj = new GameObject("LineRenderer"); + line = obj.AddComponent(); + line.useWorldSpace = true; + line.material = material; + line.startWidth = 10.0f; + line.endWidth = 10.0f; + line.positionCount = 2; + return true; + } + + public static Vector3 RaisePositionOverTerrain(Vector3 Position, float HeightOffset) + { + var body = FlightGlobals.ActiveVessel.mainBody; + if (MapView.MapIsEnabled) + { + var lat = body.GetLatitude(Position); + var lon = body.GetLongitude(Position); + return ScaledSpace.LocalToScaledSpace(body.position + (body.Radius + HeightOffset + body.TerrainAltitude(lat, lon)) * body.GetSurfaceNVector(lat, lon)); + } + else { + return body.GetWorldSurfacePosition(body.GetLatitude(Position), body.GetLongitude(Position), body.GetAltitude(Position) + HeightOffset); + } + } + + public new bool enabled + { + get { return base.enabled; } + set + { + base.enabled = value; + if (pastPath != null) { pastPath.enabled = value; } + if (currPath != null) { currPath.enabled = value; } + if (nextPath != null) { nextPath.enabled = value; } + } + } + + public void OnPreRender() + { + if (NewLineRenderer(ref pastPath)) { pastPath.startColor = pastPathColor; pastPath.endColor=pastPathColor; } + if (NewLineRenderer(ref currPath)) { currPath.startColor = currPathColor; currPath.endColor=currPathColor; } + if (NewLineRenderer(ref nextPath)) { nextPath.startColor = nextPathColor; nextPath.endColor=nextPathColor; } + if (NewLineRenderer(ref selWP)) { selWP.startColor = selWPColor; selWP.endColor=selWPColor; } + + //Debug.Log(ap.vessel.vesselName); + var window = ap.core.GetComputerModule(); + switch (ap.vessel.mainBody.bodyName) + { + case "Moho" : addHeight = window.MohoMapdist; break; + case "Eve" : addHeight = window.EveMapdist; break; + case "Gilly" : addHeight = window.GillyMapdist; break; + case "Kerbin" : addHeight = window.KerbinMapdist; break; + case "Mun" : addHeight = window.MunMapdist; break; + case "Minmus" : addHeight = window.MinmusMapdist; break; + case "Duna" : addHeight = window.DunaMapdist; break; + case "Ike" : addHeight = window.IkeMapdist; break; + case "Dres" : addHeight = window.DresMapdist; break; + case "Jool" : addHeight = window.JoolMapdist; break; + case "Laythe" : addHeight = window.LaytheMapdist; break; + case "Vall" : addHeight = window.VallMapdist; break; + case "Tylo" : addHeight = window.TyloMapdist; break; + case "Bop" : addHeight = window.BopMapdist; break; + case "Pol" : addHeight = window.PolMapdist; break; + case "Eeloo" : addHeight = window.EelooMapdist; break; + default: addHeight = window.KerbinMapdist; break; + } + + if (ap != null && ap.Waypoints.Count > 0 && ap.vessel.isActiveVessel && HighLogic.LoadedSceneIsFlight) + { + float targetHeight = (MapView.MapIsEnabled ? (float)addHeight : 3f); + float scale = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, ap.vessel.CoM) / 700f; + float width = (MapView.MapIsEnabled ? (float)(0.005 * PlanetariumCamera.fetch.Distance) : scale + 0.1f); + //float width = (MapView.MapIsEnabled ? (float)mainBody.Radius / 10000 : 1); + + pastPath.startWidth = width; + pastPath.endWidth = width; + currPath.startWidth = width; + currPath.endWidth = width; + nextPath.startWidth = width; + nextPath.endWidth = width; + selWP.gameObject.layer = pastPath.gameObject.layer = currPath.gameObject.layer = nextPath.gameObject.layer = (MapView.MapIsEnabled ? 9 : 0); + + int sel = ap.core.GetComputerModule().selIndex; + selWP.enabled = sel > -1 && !MapView.MapIsEnabled; + if (selWP.enabled) + { + float w = Vector3.Distance(FlightCamera.fetch.mainCamera.transform.position, ap.Waypoints[sel].Position) / 600f + 0.1f; + selWP.startWidth = 0; + selWP.endWidth = w * 10f; + selWP.SetPosition(0, RaisePositionOverTerrain(ap.Waypoints[sel].Position, targetHeight + 3f)); + selWP.SetPosition(1, RaisePositionOverTerrain(ap.Waypoints[sel].Position, targetHeight + 3f + w * 15f)); + } + + if (ap.WaypointIndex > 0) + { +// Debug.Log("drawing pastPath"); + pastPath.enabled = true; + pastPath.positionCount = ap.WaypointIndex + 1; + for (int i = 0; i < ap.WaypointIndex; i++) + { +// Debug.Log("vert " + i.ToString()); + pastPath.SetPosition(i, RaisePositionOverTerrain(ap.Waypoints[i].Position, targetHeight)); + } + pastPath.SetPosition(ap.WaypointIndex, RaisePositionOverTerrain(ap.vessel.CoM, targetHeight)); +// Debug.Log("pastPath drawn"); + } + else + { +// Debug.Log("no pastPath"); + pastPath.enabled = false; + } + + if (ap.WaypointIndex > -1) + { +// Debug.Log("drawing currPath"); + currPath.enabled = true; + currPath.SetPosition(0, RaisePositionOverTerrain(ap.vessel.CoM, targetHeight)); + currPath.SetPosition(1, RaisePositionOverTerrain(ap.Waypoints[ap.WaypointIndex].Position, targetHeight)); +// Debug.Log("currPath drawn"); + } + else + { +// Debug.Log("no currPath"); + currPath.enabled = false; + } + + var nextCount = ap.Waypoints.Count - ap.WaypointIndex; + if (nextCount > 1) + { +// Debug.Log("drawing nextPath of " + nextCount + " verts"); + nextPath.enabled = true; + nextPath.positionCount = nextCount; + nextPath.SetPosition(0, RaisePositionOverTerrain((ap.WaypointIndex == -1 ? ap.vessel.CoM : (Vector3)ap.Waypoints[ap.WaypointIndex].Position), targetHeight)); + for (int i = 0; i < nextCount - 1; i++) + { +// Debug.Log("vert " + i.ToString() + " (" + (ap.WaypointIndex + 1 + i).ToString() + ")"); + nextPath.SetPosition(i + 1, RaisePositionOverTerrain(ap.Waypoints[ap.WaypointIndex + 1 + i].Position, targetHeight)); + } +// Debug.Log("nextPath drawn"); + } + else + { +// Debug.Log("no nextPath"); + nextPath.enabled = false; + } + } + else + { + //Debug.Log("moo"); + selWP.enabled = pastPath.enabled = currPath.enabled = nextPath.enabled = false; + } + } + } +} diff --git a/MechJeb2/VesselState.cs b/MechJeb2/VesselState.cs index ac0ea7e14..a6cadeffd 100644 --- a/MechJeb2/VesselState.cs +++ b/MechJeb2/VesselState.cs @@ -1,1961 +1,1961 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Smooth.Pools; -using UnityEngine; -using System.Reflection; -using KSP.Localization; - -namespace MuMech -{ - public class VesselState - { - public static bool isLoadedProceduralFairing = false; - public static bool isLoadedRealFuels = false; - // RealFuels.ModuleEngineRF ullageSet field to call via reflection - private static FieldInfo RFullageSetField; - // RealFuels.ModuleEngineRF ignitions field to call via reflection - private static FieldInfo RFignitionsField; - // RealFuels.ModuleEngineRF ullage field to call via reflection - private static FieldInfo RFullageField; - // RealFuels.Ullage.UllageSet GetUllageStability method to call via reflection - private static MethodInfo RFGetUllageStabilityMethod; - // RealFuels.Ullage.UllageSimulator fields to determine ullage status - private static double RFveryStableValue; - private static double RFstableValue; - private static double RFriskyValue; - private static double RFveryRiskyValue; - private static double RFunstableValue; - - public enum UllageState { - VeryUnstable, - Unstable, - VeryRisky, - Risky, - Stable, - VeryStable // "Nominal" also winds up here - } - - // lowestUllage is always VeryStable without RealFuels installed - public UllageState lowestUllage { get { return this.einfo.lowestUllage; } } - - public static bool isLoadedFAR = false; - private delegate double FARVesselDelegate(Vessel v); - private static FARVesselDelegate FARVesselDragCoeff; - private static FARVesselDelegate FARVesselRefArea; - private static FARVesselDelegate FARVesselTermVelEst; - private static FARVesselDelegate FARVesselDynPres; - private delegate void FARCalculateVesselAeroForcesDelegate(Vessel vessel, out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude); - private static FARCalculateVesselAeroForcesDelegate FARCalculateVesselAeroForces; - - private Vessel vesselRef = null; - - private EngineInfo einfo = new EngineInfo(); - private IntakeInfo iinfo = new IntakeInfo(); - public readonly List enginesWrappers = new List(); - - [ValueInfoItem("#MechJeb_UniversalTime", InfoItem.Category.Recorder, format = ValueInfoItem.TIME)]//Universal Time - public double time; //planetarium time - public double deltaT; //TimeWarp.fixedDeltaTime - - public Vector3d CoM; - public Vector3d MoI; //Diagonal components of the inertia tensor (almost always the dominant components) - - public Vector3d up; - public Vector3d north; - public Vector3d east; - public Vector3d forward; //the direction the vessel is pointing - public Vector3d horizontalOrbit; //unit vector in the direction of horizontal component of orbit velocity - public Vector3d horizontalSurface; //unit vector in the direction of horizontal component of surface velocity - public Vector3d rootPartPos; - - public Quaternion rotationSurface; - public Quaternion rotationVesselSurface; - - public Vector3d velocityMainBodySurface; - - public Vector3d orbitalVelocity; - public Vector3d orbitalPosition; - public Vector3d surfaceVelocity; - - public Vector3d angularVelocity; - public Vector3d angularMomentum; - - public Vector3d radialPlus; //unit vector in the plane of up and velocityVesselOrbit and perpendicular to velocityVesselOrbit - public Vector3d radialPlusSurface; //unit vector in the plane of up and velocityVesselSurface and perpendicular to velocityVesselSurface - public Vector3d normalPlus; //unit vector perpendicular to up and velocityVesselOrbit - public Vector3d normalPlusSurface; //unit vector perpendicular to up and velocityVesselSurface - - public Vector3d gravityForce; - [ValueInfoItem("#MechJeb_LocalGravity", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "m/s²")]//Local gravity - public double localg; //magnitude of gravityForce - - //How about changing these so we store the instantaneous values and *also* - //the smoothed MovingAverages? Sometimes we need the instantaneous value. - [ValueInfoItem("#MechJeb_OrbitalSpeed", InfoItem.Category.Orbit, format = ValueInfoItem.SI, units = "m/s")]//Orbital speed - public MovingAverage speedOrbital = new MovingAverage(); - [ValueInfoItem("#MechJeb_SurfaceSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Surface speed - public MovingAverage speedSurface = new MovingAverage(); - [ValueInfoItem("#MechJeb_VerticalSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Vertical speed - public MovingAverage speedVertical = new MovingAverage(); - [ValueInfoItem("#MechJeb_SurfaceHorizontalSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Surface horizontal speed - public MovingAverage speedSurfaceHorizontal = new MovingAverage(); - [ValueInfoItem("#MechJeb_OrbitHorizontalSpeed", InfoItem.Category.Orbit, format = ValueInfoItem.SI, units = "m/s")]//Orbit horizontal speed - public double speedOrbitHorizontal; - [ValueInfoItem("#MechJeb_Heading", InfoItem.Category.Surface, format = "F1", units = "º")]//Heading - public MovingAverage vesselHeading = new MovingAverage(); - [ValueInfoItem("#MechJeb_Pitch", InfoItem.Category.Surface, format = "F1", units = "º")]//Pitch - public MovingAverage vesselPitch = new MovingAverage(); - [ValueInfoItem("#MechJeb_Roll", InfoItem.Category.Surface, format = "F1", units = "º")]//Roll - public MovingAverage vesselRoll = new MovingAverage(); - [ValueInfoItem("#MechJeb_Altitude_ASL", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = -1, units = "m")]//Altitude (ASL) - public MovingAverage altitudeASL = new MovingAverage(); - [ValueInfoItem("#MechJeb_Altitude_true", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = -1, units = "m")]//Altitude (true) - public MovingAverage altitudeTrue = new MovingAverage(); - [ValueInfoItem("#MechJeb_SurfaceAltitudeASL", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 4, siMaxPrecision = -1, units = "m")]//Surface altitude ASL - public double surfaceAltitudeASL; - - [ValueInfoItem("#MechJeb_Apoapsis", InfoItem.Category.Orbit, units = "m", format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, category = InfoItem.Category.Orbit)]//Apoapsis - public MovingAverage orbitApA = new MovingAverage(); - [ValueInfoItem("#MechJeb_Periapsis", InfoItem.Category.Orbit, units = "m", format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, category = InfoItem.Category.Orbit)]//Periapsis - public MovingAverage orbitPeA = new MovingAverage(); - [ValueInfoItem("#MechJeb_OrbitalPeriod", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 2, category = InfoItem.Category.Orbit)]//Orbital period - public MovingAverage orbitPeriod = new MovingAverage(); - [ValueInfoItem("#MechJeb_TimeToApoapsis", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 1)]//Time to apoapsis - public MovingAverage orbitTimeToAp = new MovingAverage(); - [ValueInfoItem("#MechJeb_TimeToPeriapsis", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 1)]//Time to periapsis - public MovingAverage orbitTimeToPe = new MovingAverage(); - [ValueInfoItem("#MechJeb_LAN", InfoItem.Category.Orbit, format = ValueInfoItem.ANGLE)]//LAN - public MovingAverage orbitLAN = new MovingAverage(); - [ValueInfoItem("#MechJeb_ArgumentOfPeriapsis", InfoItem.Category.Orbit, format = "F1", units = "º")]//Argument of periapsis - public MovingAverage orbitArgumentOfPeriapsis = new MovingAverage(); - [ValueInfoItem("#MechJeb_Inclination", InfoItem.Category.Orbit, format = "F3", units = "º")]//Inclination - public MovingAverage orbitInclination = new MovingAverage(); - [ValueInfoItem("#MechJeb_Eccentricity", InfoItem.Category.Orbit, format = "F3")]//Eccentricity - public MovingAverage orbitEccentricity = new MovingAverage(); - [ValueInfoItem("#MechJeb_SemiMajorAxis", InfoItem.Category.Orbit, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, units = "m")]//Semi-major axis - public MovingAverage orbitSemiMajorAxis = new MovingAverage(); - [ValueInfoItem("#MechJeb_Latitude", InfoItem.Category.Surface, format = ValueInfoItem.ANGLE_NS)]//Latitude - public MovingAverage latitude = new MovingAverage(); - [ValueInfoItem("#MechJeb_Longitude", InfoItem.Category.Surface, format = ValueInfoItem.ANGLE_EW)]//Longitude - public MovingAverage longitude = new MovingAverage(); - [ValueInfoItem("#MechJeb_AngleOfAttack", InfoItem.Category.Misc, format = "F2", units = "º")]//Angle of Attack - public MovingAverage AoA = new MovingAverage(); - [ValueInfoItem("#MechJeb_AngleOfSideslip", InfoItem.Category.Misc, format = "F2", units = "º")]//Angle of Sideslip - public MovingAverage AoS = new MovingAverage(); - [ValueInfoItem("#MechJeb_DisplacementAngle", InfoItem.Category.Misc, format = "F2", units = "º")]//Displacement Angle - public MovingAverage displacementAngle = new MovingAverage(); - - public MovingAverage3d angularVelocityAvg = new MovingAverage3d(5); - - public double radius; //distance from planet center - - public double mass; - - // Thrust is a vector. These are in the same frame of reference as forward and other vectors. - public Vector3d thrustVectorLastFrame = new Vector3d(); - public Vector3d thrustVectorMaxThrottle = new Vector3d(); - public Vector3d thrustVectorMinThrottle = new Vector3d(); - - // Thrust in the forward direction (for historical reasons). - public double thrustAvailable { get { return Vector3d.Dot(thrustVectorMaxThrottle, forward); } } - public double thrustMinimum { get { return Vector3d.Dot(thrustVectorMinThrottle, forward); } } - public double thrustCurrent { get { return Vector3d.Dot(thrustVectorLastFrame, forward); } } - - // Acceleration in the forward direction, for when dividing by mass is too complicated. - public double maxThrustAccel { get { return thrustAvailable / mass; } } - public double minThrustAccel { get { return thrustMinimum / mass; } } - public double currentThrustAccel { get { return thrustCurrent / mass; } } - - public double maxEngineResponseTime = 0; - - public bool rcsThrust = false; - /* the current throttle limit, this may include transient condition such as limiting to zero due to unstable propellants in RF */ - public float throttleLimit = 1; - /* the fixed throttle limit (i.e. user limited in the GUI), does not include transient conditions as limiting to zero due to unstable propellants in RF */ - public float throttleFixedLimit = 1; - public double limitedMaxThrustAccel { get { return maxThrustAccel * throttleFixedLimit + minThrustAccel * (1 - throttleFixedLimit); } } - - public Vector3d CoT; - public Vector3d DoT; - public double CoTScalar; - - - public Vector3d pureDragV; - [ValueInfoItem("#MechJeb_PureDrag", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s²")]//Pure Drag - public double pureDrag; - - public Vector3d pureLiftV; - [ValueInfoItem("#MechJeb_PureLift", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s²")]//Pure Lift - public double pureLift; - - // Drag is the force (pureDrag + PureLift) applied opposite of the surface vel - public double drag; - // Drag is the force (pureDrag + PureLift) applied in the "Up" direction - public double dragUp; - // Lift is the force (pureDrag + PureLift) applied in the "Lift" direction - public double lift; - // Lift is the force (pureDrag + PureLift) applied in the "Up" direction - public double liftUp; - - public Vector3d CoL; - public double CoLScalar; - - - [ValueInfoItem("#MechJeb_Mach", InfoItem.Category.Vessel, format = "F2")]//Mach - public double mach; - - [ValueInfoItem("#MechJeb_SpeedOfSound", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s")]//Speed of sound - public double speedOfSound; - - [ValueInfoItem("#MechJeb_DragCoefficient", InfoItem.Category.Vessel, format = "F2")]//Drag Coefficient - public double dragCoef; - - // Product of the drag surface area, drag coefficient and the physic multiplers - public double areaDrag; - - public double atmosphericDensity; - [ValueInfoItem("#MechJeb_AtmosphereDensity", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "g/m³")]//Atmosphere density - public double atmosphericDensityGrams; - [ValueInfoItem("#MechJeb_MaxDynamicPressure", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "Pa")]//Max dynamic pressure - public double maxDynamicPressure; - [ValueInfoItem("#MechJeb_DynamicPressure", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "Pa")]//Dynamic pressure - public double dynamicPressure; - [ValueInfoItem("#MechJeb_IntakeAir", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air - public double intakeAir; - [ValueInfoItem("#MechJeb_IntakeAirAllIntakes", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air (all intakes open) - public double intakeAirAllIntakes; - [ValueInfoItem("#MechJeb_IntakeAirNeeded", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air needed - public double intakeAirNeeded; - [ValueInfoItem("#MechJeb_intakeAirAtMax", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air needed (max) - public double intakeAirAtMax; - [ValueInfoItem("#MechJeb_AngleToPrograde", InfoItem.Category.Orbit, format = "F2", units = "º")]//Angle to prograde - public double angleToPrograde; - [ValueInfoItem("#MechJeb_AerothermalFlux", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "W/m²")]//Aerothermal flux - public double freeMolecularAerothermalFlux; - - public Vector6 rcsThrustAvailable = new Vector6(); // thrust available from RCS thrusters - - public Vector6 rcsTorqueAvailable = new Vector6(); // torque available from RCS thrusters - - - // Total torque - public Vector3d torqueAvailable; - - public Vector3d torqueReactionSpeed; - - // Torque from different components - public Vector6 torqueReactionWheel = new Vector6(); // torque available from Reaction wheels - //public Vector6 torqueRcs = new Vector6(); // torque available from RCS from stock code (not working properly ATM) - public Vector6 torqueControlSurface = new Vector6(); // torque available from Aerodynamic control surfaces - public Vector6 torqueGimbal = new Vector6(); // torque available from Gimbaled engines - public Vector6 torqueOthers = new Vector6(); // torque available from Mostly FAR - - // Variable part of torque related to differential throttle - public Vector3d torqueDiffThrottle; - - // List of parachutes - public List parachutes = new List(); - - public bool parachuteDeployed; - - // Resource information keyed by resource Id. - public Dictionary resources = new Dictionary(); - - public CelestialBody mainBody; - - // A convenient debug message to display in the UI - public static string message; - [GeneralInfoItem("#MechJeb_DebugString", InfoItem.Category.Misc, showInEditor = true)]//Debug String - public void DebugString() - { - GUILayout.BeginVertical(); - GUILayout.Label(message); - GUILayout.EndVertical(); - } - - // Callbacks for external module - public delegate void VesselStatePartExtension(Part p); - public delegate void VesselStatePartModuleExtension(PartModule pm); - - private Dictionary engines = new Dictionary(); - - public List vesselStatePartExtensions = new List(); - public List vesselStatePartModuleExtensions = new List(); - public delegate double DTerminalVelocity(); - - static VesselState() - { - FARVesselDragCoeff = null; - FARVesselRefArea = null; - FARVesselTermVelEst = null; - FARVesselDynPres = null; - isLoadedProceduralFairing = ReflectionUtils.isAssemblyLoaded("ProceduralFairings"); - isLoadedRealFuels = ReflectionUtils.isAssemblyLoaded("RealFuels"); - if (isLoadedRealFuels) - { - Debug.Log("MechJeb: RealFuels Assembly is loaded"); - RFullageSetField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ullageSet"); - if (RFullageSetField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ullageSet field, disabling RF"); - isLoadedRealFuels = false; - } - RFGetUllageStabilityMethod = ReflectionUtils.getMethodByReflection("RealFuels", "RealFuels.Ullage.UllageSet", "GetUllageStability", BindingFlags.Public|BindingFlags.Instance); - if (RFGetUllageStabilityMethod == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSet has no GetUllageStability method, disabling RF"); - isLoadedRealFuels = false; - } - RFignitionsField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ignitions"); - if (RFignitionsField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ignitions field, disabling RF"); - isLoadedRealFuels = false; - } - RFullageField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ullage"); - if (RFullageField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ullage field, disabling RF"); - isLoadedRealFuels = false; - } - FieldInfo RFveryStableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "veryStable", BindingFlags.NonPublic|BindingFlags.Static); - if (RFveryStableField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no veryStable field, disabling RF"); - isLoadedRealFuels = false; - } - try - { - RFveryStableValue = (double) RFveryStableField.GetValue(null); - } - catch (Exception e1) - { - Debug.Log("MechJeb BUG Exception thrown while getting veryStable value from RealFuels, ullage integration disabled: " + e1.Message); - isLoadedRealFuels = false; - return; - } - FieldInfo RFstableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "stable", BindingFlags.NonPublic|BindingFlags.Static); - if (RFstableField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no stable field, disabling RF"); - isLoadedRealFuels = false; - } - try - { - RFstableValue = (double) RFstableField.GetValue(null); - } - catch (Exception e2) - { - Debug.Log("MechJeb BUG Exception thrown while getting stable value from RealFuels, ullage integration disabled: " + e2.Message); - isLoadedRealFuels = false; - return; - } - FieldInfo RFriskyField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "risky", BindingFlags.NonPublic|BindingFlags.Static); - if (RFriskyField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no risky field, disabling RF"); - isLoadedRealFuels = false; - } - try - { - RFriskyValue = (double) RFriskyField.GetValue(null); - } - catch (Exception e3) - { - Debug.Log("MechJeb BUG Exception thrown while getting risky value from RealFuels, ullage integration disabled: " + e3.Message); - isLoadedRealFuels = false; - return; - } - FieldInfo RFveryRiskyField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "veryRisky", BindingFlags.NonPublic|BindingFlags.Static); - if (RFveryRiskyField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no veryRisky field, disabling RF"); - isLoadedRealFuels = false; - } - try - { - RFveryRiskyValue = (double) RFveryRiskyField.GetValue(null); - } - catch (Exception e4) - { - Debug.Log("MechJeb BUG Exception thrown while getting veryRisky value from RealFuels, ullage integration disabled: " + e4.Message); - isLoadedRealFuels = false; - return; - } - FieldInfo RFunstableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "unstable", BindingFlags.NonPublic|BindingFlags.Static); - if (RFunstableField == null) - { - Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no unstable field, disabling RF"); - isLoadedRealFuels = false; - } - try - { - RFunstableValue = (double) RFunstableField.GetValue(null); - } - catch (Exception e5) - { - Debug.Log("MechJeb BUG Exception thrown while getting unstable value from RealFuels, ullage integration disabled: " + e5.Message); - isLoadedRealFuels = false; - return; - } - if (isLoadedRealFuels) - { - Debug.Log("MechJeb: RealFuels Assembly is wired up properly"); - } - } - isLoadedFAR = ReflectionUtils.isAssemblyLoaded("FerramAerospaceResearch"); - if (isLoadedFAR) - { - List farNames = new List{ "VesselDragCoeff", "VesselRefArea", "VesselTermVelEst", "VesselDynPres" }; - foreach (var name in farNames) - { - var methodInfo = ReflectionUtils.getMethodByReflection( - "FerramAerospaceResearch", - "FerramAerospaceResearch.FARAPI", - name, - BindingFlags.Public | BindingFlags.Static, - new Type[] { typeof(Vessel) } - ); - if (methodInfo == null) - { - Debug.Log("MJ BUG: FAR loaded, but FerramAerospaceResearch.FARAPI has no " + name + " method. Disabling FAR"); - isLoadedFAR = false; - } - else - { - typeof(VesselState).GetField("FAR" + name, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, (FARVesselDelegate)Delegate.CreateDelegate(typeof(FARVesselDelegate), methodInfo)); - } - } - - var FARCalculateVesselAeroForcesMethodInfo = ReflectionUtils.getMethodByReflection( - "FerramAerospaceResearch", - "FerramAerospaceResearch.FARAPI", - "CalculateVesselAeroForces", - BindingFlags.Public | BindingFlags.Static, - new Type[] { typeof(Vessel), typeof(Vector3).MakeByRefType(), typeof(Vector3).MakeByRefType(), typeof(Vector3), typeof(double) } - ); - if (FARCalculateVesselAeroForcesMethodInfo == null){ - Debug.Log("MJ BUG: FAR loaded, but FerramAerospaceResearch.FARAPI has no CalculateVesselAeroForces method, disabling FAR"); - isLoadedFAR = false; - } - else - { - FARCalculateVesselAeroForces = (FARCalculateVesselAeroForcesDelegate)Delegate.CreateDelegate(typeof(FARCalculateVesselAeroForcesDelegate), FARCalculateVesselAeroForcesMethodInfo); - } - } - } - - public VesselState() - { - if (isLoadedFAR) - { - TerminalVelocityCall = TerminalVelocityFAR; - } - else - { - TerminalVelocityCall = TerminalVelocityStockKSP; - } - } - - private double last_update; - - //public static bool SupportsGimbalExtension() where T : PartModule - //{ - // return gimbalExtDict.ContainsKey(typeof(T)); - //} - // - //public static void AddGimbalExtension(GimbalExt gimbalExtension) where T : PartModule - //{ - // gimbalExtDict[typeof(T)] = gimbalExtension; - //} - public bool Update(Vessel vessel) - { - if (last_update == Planetarium.GetUniversalTime()) - return true; - - if (vessel.rootPart.rb == null) return false; //if we try to update before rigidbodies exist we spam the console with NullPointerExceptions. - - TestStuff(vessel); - - UpdateVelocityAndCoM(vessel); - - UpdateBasicInfo(vessel); - - UpdateRCSThrustAndTorque(vessel); - - enginesWrappers.Clear(); - - einfo.Update(CoM, vessel); - iinfo.Update(); - AnalyzeParts(vessel, einfo, iinfo); - - UpdateResourceRequirements(einfo, iinfo); - - ToggleRCSThrust(vessel); - - UpdateMoIAndAngularMom(vessel); - - last_update = Planetarium.GetUniversalTime();; - - return true; - } - - private void TestStuff(Vessel vessel) - { - //int partCount = vessel.parts.Count; - //for (int index = 0; index < partCount; ++index) - //{ - // if (!vessel.parts[index].DragCubes.None) - // vessel.parts[index].DragCubes.SetDragWeights(); - //} - //for (int index = 0; index < partCount; ++index) - //{ - // if (!vessel.parts[index].DragCubes.None) - // vessel.parts[index].DragCubes.SetPartOcclusion(); - //} - - //for (int index = 0; index < partCount; ++index) - //{ - // Part part = vessel.parts[index]; - // if (!part.DragCubes.None) - // part.DragCubes.SetDrag(part.dragVectorDirLocal, 0.1f); - //} - - //cube = new DragCubeList(); - //cube.ClearCubes(); - - //for (int index = 0; index < partCount; ++index) - //{ - // Part part = vessel.parts[index]; - // if (!part.DragCubes.None) - // { - // for (int face = 0; face < 6; face++) - // { - // //cube.WeightedArea[face] += part.DragCubes.WeightedArea[face]; - // cube.WeightedDrag[face] += part.DragCubes.WeightedDrag[face]; - // cube.AreaOccluded[face] += part.DragCubes.AreaOccluded[face]; - // } - // } - //} - // - //cube.SetDrag(vessel.srf_velocity, (float)vessel.mach); - // - //double dragScale = cube.AreaDrag * PhysicsGlobals.DragCubeMultiplier; - - - //SimulatedVessel simVessel = SimulatedVessel.New(vessel); - - //MechJebCore.print("KPA " + vessel.dynamicPressurekPa.ToString("F9")); - - //Vector3 localVel = vessel.GetTransform().InverseTransformDirection( vessel.srf_velocity ); - //Vector3 localVel = vessel.GetTransform().InverseTransformDirection( vessel.rigidbody.velocity + Krakensbane.GetFrameVelocity()); - - //MechJebCore.print(MuUtils.PrettyPrint(localVel)); - - //Vector3 simDrag = simVessel.Drag(localVel, - // (float)(0.0005 * vessel.atmDensity * vessel.srf_velocity.sqrMagnitude), - // (float)vessel.mach); - // - // - //Vector3 simLift = simVessel.Lift(vessel.rigidbody.velocity + Krakensbane.GetFrameVelocity(), - // (float)(0.0005 * vessel.atmDensity * vessel.srf_velocity.sqrMagnitude), - // (float)vessel.mach); - // - //dragScalar = simDrag.magnitude; - // - //liftScalar = simLift.magnitude; - - //double exposedArea = 0; - //double skinExposedArea = 0; - //double radiativeArea = 0; - //foreach (Part part in vessel.Parts) - //{ - // exposedArea += part.exposedArea; - // skinExposedArea += part.skinExposedArea; - // radiativeArea += part.radiativeArea; - // //MechJebCore.print(part.name + " " + part.exposedArea.ToString("F4") + " " + part.skinExposedArea.ToString("F4")); - //} - //MechJebCore.print(exposedArea.ToString("F2") + " " + skinExposedArea.ToString("F2") + " " + radiativeArea.ToString("F2")); - - //message = "\nPools :\n" + - // SimulatedVessel.PoolSize + " SimulatedVessel\n" + - // SimulatedPart.PoolSize + " SimulatedPart\n" + - // SimulatedParachute.PoolSize + " SimulatedParachute\n" + - // ListPool.Instance.Size + " AbsoluteVector\n" + - // ReentrySimulation.PoolSize + " ReentrySimulation\n" + - // ReentrySimulation.Result.PoolSize + " Result\n" + - // SimulatedPart.DragCubePool.Instance.Size + " DragCubePool\n" + - // FuelNode.PoolSize + " FuelNode\n"; - - //ListPool.Instance. - } - - - // TODO memo for later. egg found out that vessel.pos is actually 1 frame in the future while vessel.obt_vel is not. - // This should have changed in 1.1 - // This most likely has some impact on the code. - - - void UpdateVelocityAndCoM(Vessel vessel) - { - mass = vessel.totalMass; - CoM = vessel.CoMD; - orbitalVelocity = vessel.obt_velocity; - orbitalPosition = CoM - vessel.mainBody.position; - } - - // Calculate a bunch of simple quantities each frame. - void UpdateBasicInfo(Vessel vessel) - { - time = Planetarium.GetUniversalTime(); - deltaT = TimeWarp.fixedDeltaTime; - - //CoM = °; - up = orbitalPosition.normalized; - - Rigidbody rigidBody = vessel.rootPart.rb; - if (rigidBody != null) rootPartPos = rigidBody.position; - - north = vessel.north; - east = vessel.east; - forward = vessel.GetTransform().up; - rotationSurface = Quaternion.LookRotation(north, up); - rotationVesselSurface = Quaternion.Inverse(Quaternion.Euler(90, 0, 0) * Quaternion.Inverse(vessel.GetTransform().rotation) * rotationSurface); - - surfaceVelocity = orbitalVelocity - vessel.mainBody.getRFrmVel(CoM); - - velocityMainBodySurface = rotationSurface * surfaceVelocity; - - horizontalOrbit = Vector3d.Exclude(up, orbitalVelocity).normalized; - horizontalSurface = Vector3d.Exclude(up, surfaceVelocity).normalized; - - angularVelocity = Quaternion.Inverse(vessel.GetTransform().rotation) * vessel.rootPart.rb.angularVelocity; - - radialPlusSurface = Vector3d.Exclude(surfaceVelocity, up).normalized; - radialPlus = Vector3d.Exclude(orbitalVelocity, up).normalized; - normalPlusSurface = -Vector3d.Cross(radialPlusSurface, surfaceVelocity.normalized); - normalPlus = -Vector3d.Cross(radialPlus, orbitalVelocity.normalized); - - mach = vessel.mach; - - gravityForce = FlightGlobals.getGeeForceAtPosition(CoM); // TODO vessel.gravityForPos or vessel.gravityTrue - localg = gravityForce.magnitude; - - speedOrbital.value = orbitalVelocity.magnitude; - speedSurface.value = surfaceVelocity.magnitude; - speedVertical.value = Vector3d.Dot(surfaceVelocity, up); - speedSurfaceHorizontal.value = Vector3d.Exclude(up, surfaceVelocity).magnitude; //(velocityVesselSurface - (speedVertical * up)).magnitude; - speedOrbitHorizontal = (orbitalVelocity - (speedVertical * up)).magnitude; - - // Angle of Attack, angle between surface velocity and the ship-nose vector (KSP "up" vector) in the plane that has no ship-right/left in it - Vector3 srfProj = Vector3.ProjectOnPlane(surfaceVelocity.normalized, vessel.ReferenceTransform.right); - double tmpAoA = UtilMath.Rad2Deg * Math.Atan2(Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.forward), Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.up) ); - AoA.value = double.IsNaN(tmpAoA) || speedSurface.value < 0.01 ? 0 : tmpAoA; - - // Angle of Sideslip, angle between surface velocity and the ship-nose vector (KSP "up" vector) in the plane that has no ship-top/bottom in it (KSP "forward"/"back") - srfProj = Vector3.ProjectOnPlane(surfaceVelocity.normalized, vessel.ReferenceTransform.forward); - double tmpAoS = UtilMath.Rad2Deg * Math.Atan2(Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.right), Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.up) ); - AoS.value = double.IsNaN(tmpAoS) || speedSurface.value < 0.01 ? 0 : tmpAoS; - - // Displacement Angle, angle between surface velocity and the ship-nose vector (KSP "up" vector) -- ignores roll of the craft (0 to 180 degrees) - double tempAoD = UtilMath.Rad2Deg * Math.Acos(MuUtils.Clamp(Vector3.Dot(vessel.ReferenceTransform.up, surfaceVelocity.normalized), -1, 1)); - displacementAngle.value = double.IsNaN(tempAoD) || speedSurface.value < 0.01 ? 0 : tempAoD; - - vesselHeading.value = rotationVesselSurface.eulerAngles.y; - vesselPitch.value = (rotationVesselSurface.eulerAngles.x > 180) ? (360.0 - rotationVesselSurface.eulerAngles.x) : -rotationVesselSurface.eulerAngles.x; - vesselRoll.value = (rotationVesselSurface.eulerAngles.z > 180) ? (rotationVesselSurface.eulerAngles.z - 360.0) : rotationVesselSurface.eulerAngles.z; - - altitudeASL.value = vessel.mainBody.GetAltitude(CoM); - - surfaceAltitudeASL = vessel.mainBody.pqsController != null ? vessel.pqsAltitude : 0d; - if (vessel.mainBody.ocean && surfaceAltitudeASL < 0) surfaceAltitudeASL = 0; - altitudeTrue.value = altitudeASL - surfaceAltitudeASL; - - // altitudeBottom will be recomputed if someone requests it. - altitudeBottomIsCurrent = false; - - double atmosphericPressure = FlightGlobals.getStaticPressure(altitudeASL, vessel.mainBody); - //if (atmosphericPressure < vessel.mainBody.atmosphereMultiplier * 1e-6) atmosphericPressure = 0; - double temperature = FlightGlobals.getExternalTemperature(altitudeASL); - atmosphericDensity = FlightGlobals.getAtmDensity(atmosphericPressure, temperature); - atmosphericDensityGrams = atmosphericDensity * 1000; - if (isLoadedFAR) - { - dynamicPressure = FARVesselDynPres(vessel) * 1000; - } - else - { - dynamicPressure = vessel.dynamicPressurekPa * 1000; - } - if (dynamicPressure > maxDynamicPressure) - maxDynamicPressure = dynamicPressure; - freeMolecularAerothermalFlux = 0.5 * atmosphericDensity * speedSurface * speedSurface * speedSurface; - - - speedOfSound = vessel.speedOfSound; - - orbitApA.value = vessel.orbit.ApA; - orbitPeA.value = vessel.orbit.PeA; - orbitPeriod.value = vessel.orbit.period; - orbitTimeToAp.value = vessel.orbit.timeToAp; - orbitTimeToPe.value = vessel.orbit.timeToPe; - - if (!vessel.LandedOrSplashed) - { - orbitLAN.value = vessel.orbit.LAN; - } - else - { - orbitLAN.value = -(vessel.transform.position - vessel.mainBody.transform.position).AngleInPlane(Planetarium.Zup.Z, Planetarium.Zup.X); - orbitTimeToAp.value = 0; - } - - orbitArgumentOfPeriapsis.value = vessel.orbit.argumentOfPeriapsis; - orbitInclination.value = vessel.orbit.inclination; - orbitEccentricity.value = vessel.orbit.eccentricity; - orbitSemiMajorAxis.value = vessel.orbit.semiMajorAxis; - latitude.value = vessel.mainBody.GetLatitude(CoM); - longitude.value = MuUtils.ClampDegrees180(vessel.mainBody.GetLongitude(CoM)); - - if (vessel.mainBody != Planetarium.fetch.Sun) - { - Vector3d delta = vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() + 1) - vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() - 1); - Vector3d plUp = Vector3d.Cross(vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime()) - vessel.mainBody.referenceBody.getPositionAtUT(Planetarium.GetUniversalTime()), vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() + vessel.mainBody.orbit.period / 4) - vessel.mainBody.referenceBody.getPositionAtUT(Planetarium.GetUniversalTime() + vessel.mainBody.orbit.period / 4)).normalized; - angleToPrograde = MuUtils.ClampDegrees360((((vessel.orbit.inclination > 90) || (vessel.orbit.inclination < -90)) ? 1 : -1) * ((Vector3)up).AngleInPlane(plUp, delta)); - } - else - { - angleToPrograde = 0; - } - - mainBody = vessel.mainBody; - - radius = orbitalPosition.magnitude; - - vesselRef = vessel; - } - - void UpdateRCSThrustAndTorque(Vessel vessel) - { - rcsThrustAvailable.Reset(); - rcsTorqueAvailable.Reset(); - - //torqueRcs.Reset(); - - if (!vessel.ActionGroups[KSPActionGroup.RCS]) - return; - - var rcsbal = vessel.GetMasterMechJeb().rcsbal; - if (rcsbal.enabled) - { - Vector3d rot = Vector3d.zero; - for (int i = 0; i < Vector6.Values.Length; i++) - { - Vector6.Direction dir6 = Vector6.Values[i]; - Vector3d dir = Vector6.directions[(int) dir6]; - double[] throttles; - List thrusters; - rcsbal.GetThrottles(dir, out throttles, out thrusters); - if (throttles != null) - { - for (int j = 0; j < throttles.Length; j++) - { - if (throttles[j] > 0) - { - Vector3d force = thrusters[j].GetThrust(dir, rot); - rcsThrustAvailable.Add( - vessel.GetTransform().InverseTransformDirection(dir * Vector3d.Dot(force * throttles[j], dir))); - // Are we missing an rcsTorqueAvailable calculation here? - } - } - } - } - } - - Vector3d movingCoM = vessel.CurrentCoM; - - for (int i = 0; i < vessel.parts.Count; i++) - { - Part p = vessel.parts[i]; - for (int m = 0; m < p.Modules.Count; m++) - { - ModuleRCS rcs = p.Modules[m] as ModuleRCS; - - if (rcs == null) - continue; - - //Vector3 pos; - //Vector3 neg; - //rcs.GetPotentialTorque(out pos, out neg); - - //torqueRcs.Add(pos); - //torqueRcs.Add(neg); - - //if (rcsbal.enabled) - // continue; - - if (!p.ShieldedFromAirstream && rcs.rcsEnabled && rcs.isEnabled && !rcs.isJustForShow) - { - Vector3 attitudeControl = new Vector3(rcs.enablePitch ? 1 : 0, rcs.enableRoll ? 1 : 0, rcs.enableYaw ? 1 : 0); - - Vector3 translationControl = new Vector3(rcs.enableX ? 1 : 0f, rcs.enableZ ? 1 : 0, rcs.enableY ? 1 : 0); - for (int j = 0; j < rcs.thrusterTransforms.Count; j++) - { - Transform t = rcs.thrusterTransforms[j]; - Vector3d thrusterPosition = t.position - movingCoM; - - Vector3d thrustDirection = rcs.useZaxis ? -t.forward : -t.up; - - float power = rcs.thrusterPower; - - if (FlightInputHandler.fetch.precisionMode) - { - if (rcs.useLever) - { - float lever = rcs.GetLeverDistance(t, thrustDirection, movingCoM); - if (lever > 1) - { - power = power / lever; - } - } - else - { - power *= rcs.precisionFactor; - } - } - - Vector3d thrusterThrust = thrustDirection * power; - - // This is a cheap hack to get rcsTorque with the RCS balancer active. - if (!rcsbal.enabled) - { - rcsThrustAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterThrust), translationControl)); - } - Vector3d thrusterTorque = Vector3.Cross(thrusterPosition, thrusterThrust); - - // Convert in vessel local coordinate - rcsTorqueAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterTorque), attitudeControl)); - //rcsThrustAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterThrust), translationControl)); - } - } - } - } - } - - - [GeneralInfoItem("#MechJeb_RCSTranslation", InfoItem.Category.Vessel, showInEditor = true)]//RCS Translation - public void RCSTranslation() - { - GUILayout.BeginVertical(); - GUILayout.Label(Localizer.Format("#MechJeb_RCSTranslation"));//"RCS Translation" - GUILayout.BeginHorizontal(); - GUILayout.Label("Pos", GUILayout.ExpandWidth(true));// - GUILayout.Label(MuUtils.PrettyPrint(rcsThrustAvailable.positive), GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - GUILayout.Label("Neg", GUILayout.ExpandWidth(true));// - GUILayout.Label(MuUtils.PrettyPrint(rcsThrustAvailable.negative), GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - GUILayout.EndVertical(); - } - - [GeneralInfoItem("#MechJeb_RCSTorque", InfoItem.Category.Vessel, showInEditor = true)]//RCS Torque - public void RCSTorque() - { - GUILayout.BeginVertical(); - GUILayout.Label(Localizer.Format("#MechJeb_RCSTorque"));//"RCS Torque" - GUILayout.BeginHorizontal(); - GUILayout.Label("Pos", GUILayout.ExpandWidth(true)); - GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueAvailable.positive), GUILayout.ExpandWidth(false)); - //GUILayout.Label(MuUtils.PrettyPrint(torqueRcs.positive), GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(); - GUILayout.Label("Neg", GUILayout.ExpandWidth(true)); - GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueAvailable.negative), GUILayout.ExpandWidth(false)); - //GUILayout.Label(MuUtils.PrettyPrint(torqueRcs.negative), GUILayout.ExpandWidth(false)); - GUILayout.EndHorizontal(); - GUILayout.EndVertical(); - } - - // Loop over all the parts in the vessel and calculate some things. - void AnalyzeParts(Vessel vessel, EngineInfo einfo, IntakeInfo iinfo) - { - parachutes.Clear(); - parachuteDeployed = false; - - torqueAvailable = Vector3d.zero; - - Vector6 torqueReactionSpeed6 = new Vector6(); - - torqueReactionWheel.Reset(); - torqueControlSurface.Reset(); - torqueGimbal.Reset(); - torqueOthers.Reset(); - - pureDragV = Vector3d.zero; - pureLiftV = Vector3d.zero; - - if (isLoadedFAR) - { - dragCoef = FARVesselDragCoeff(vessel); - areaDrag = FARVesselRefArea(vessel) * dragCoef * PhysicsGlobals.DragMultiplier; - } - else - { - dragCoef = 0; - areaDrag = 0; - } - - CoL = Vector3d.zero; - CoLScalar = 0; - - CoT = Vector3d.zero; - DoT = Vector3d.zero; - CoTScalar = 0; - - for (int i = 0; i < vessel.parts.Count; i++) - { - Part p = vessel.parts[i]; - - Vector3d partPureLift = Vector3.zero; - Vector3d partPureDrag = -p.dragVectorDir * p.dragScalar; - - if (!p.hasLiftModule) - { - Vector3 bodyLift = p.transform.rotation * (p.bodyLiftScalar * p.DragCubes.LiftForce); - partPureLift = Vector3.ProjectOnPlane(bodyLift, -p.dragVectorDir); - - //double liftScale = bodyLift.magnitude; - } - - //#warning while this works for real time it does not help for simulations. Need to get a coef even while in vacum - //if (p.dynamicPressurekPa > 0 && PhysicsGlobals.DragMultiplier > 0) - // dragCoef += p.simDragScalar / (p.dynamicPressurekPa * PhysicsGlobals.DragMultiplier); - - if (!isLoadedFAR) - { - dragCoef += p.DragCubes.DragCoeff; - areaDrag += p.DragCubes.AreaDrag * PhysicsGlobals.DragCubeMultiplier * PhysicsGlobals.DragMultiplier; - } - - for (int index = 0; index < vesselStatePartExtensions.Count; index++) - { - VesselStatePartExtension vspe = vesselStatePartExtensions[index]; - vspe(p); - } - - engines.Clear(); - - for (int m = 0; m < p.Modules.Count; m++) - { - PartModule pm = p.Modules[m]; - if (!pm.isEnabled) - { - continue; - } - - ModuleLiftingSurface ls = pm as ModuleLiftingSurface; - if (ls != null) - { - partPureLift += ls.liftForce; - partPureDrag += ls.dragForce; - } - - ModuleReactionWheel rw = pm as ModuleReactionWheel; - if (rw != null) - { - Vector3 pos; - Vector3 neg; - rw.GetPotentialTorque(out pos, out neg); - - // GetPotentialTorque reports the same value for pos & neg on ModuleReactionWheel - torqueReactionWheel.Add(pos); - torqueReactionWheel.Add(-neg); - } - else if (pm is ModuleEngines) - { - var moduleEngines = pm as ModuleEngines; - - if (!engines.ContainsKey(moduleEngines)) - engines.Add(moduleEngines, null); - } - else if (pm is ModuleResourceIntake) - { - iinfo.addIntake(pm as ModuleResourceIntake); - } - else if (pm is ModuleParachute) - { - ModuleParachute parachute = pm as ModuleParachute; - - parachutes.Add(parachute); - if (parachute.deploymentState == ModuleParachute.deploymentStates.DEPLOYED || - parachute.deploymentState == ModuleParachute.deploymentStates.SEMIDEPLOYED) - { - parachuteDeployed = true; - } - } - else if (pm is ModuleControlSurface) // also does ModuleAeroSurface - { - ModuleControlSurface cs = (pm as ModuleControlSurface); - - //if (p.ShieldedFromAirstream || cs.deploy) - // continue; - - Vector3 ctrlTorquePos; - Vector3 ctrlTorqueNeg; - - cs.GetPotentialTorque(out ctrlTorquePos, out ctrlTorqueNeg); - - torqueControlSurface.Add(ctrlTorquePos); - torqueControlSurface.Add(ctrlTorqueNeg); - - torqueReactionSpeed6.Add(Mathf.Abs(cs.ctrlSurfaceRange) / cs.actuatorSpeed * Vector3d.Max(ctrlTorquePos.Abs(), ctrlTorqueNeg.Abs())); - } - else if (pm is ModuleGimbal) - { - ModuleGimbal g = (pm as ModuleGimbal); - - if (g.engineMultsList == null) - g.CreateEngineList(); - - for (int j = 0; j < g.engineMultsList.Count; j++) - { - var engs = g.engineMultsList[j]; - for (int k = 0; k < engs.Count; k++) - { - engines[engs[k].Key] = g; - } - } - - Vector3 pos; - Vector3 neg; - g.GetPotentialTorque(out pos, out neg); - - // GetPotentialTorque reports the same value for pos & neg on ModuleGimbal - - torqueGimbal.Add(pos); - torqueGimbal.Add(-neg); - - if (g.useGimbalResponseSpeed) - torqueReactionSpeed6.Add((Mathf.Abs(g.gimbalRange) / g.gimbalResponseSpeed) * Vector3d.Max(pos.Abs(), neg.Abs())); - } - else if (pm is ModuleRCS) - { - // Already handled earlier. Prevent the generic ITorqueProvider to catch it - } - else if (pm is ITorqueProvider) // All mod that supports it. Including FAR - { - ITorqueProvider tp = pm as ITorqueProvider; - - Vector3 pos; - Vector3 neg; - tp.GetPotentialTorque(out pos, out neg); - - torqueOthers.Add(pos); - torqueOthers.Add(neg); - } - - for (int index = 0; index < vesselStatePartModuleExtensions.Count; index++) - { - VesselStatePartModuleExtension vspme = vesselStatePartModuleExtensions[index]; - vspme(pm); - } - } - - foreach (KeyValuePair engine in engines) - { - einfo.AddNewEngine(engine.Key, engine.Value, enginesWrappers, ref CoT, ref DoT, ref CoTScalar); - if (isLoadedRealFuels && RFullageSetField != null && RFignitionsField != null && RFullageField != null) - { - einfo.CheckUllageStatus(engine.Key); - } - } - - pureDragV += partPureDrag; - pureLiftV += partPureLift; - - Vector3d partAeroForce = partPureDrag + partPureLift; - - Vector3d partDrag = Vector3d.Project(partAeroForce, -surfaceVelocity); - Vector3d partLift = partAeroForce - partDrag; - - double partLiftScalar = partLift.magnitude; - - if (p.rb != null && partLiftScalar > 0.01) - { - CoLScalar += partLiftScalar; - CoL += ((Vector3d)p.rb.worldCenterOfMass + (Vector3d)(p.partTransform.rotation * p.CoLOffset)) * partLiftScalar; - } - } - - torqueAvailable += Vector3d.Max(torqueReactionWheel.positive, torqueReactionWheel.negative); - - //torqueAvailable += Vector3d.Max(torqueRcs.positive, torqueRcs.negative); - - torqueAvailable += Vector3d.Max(rcsTorqueAvailable.positive, rcsTorqueAvailable.negative); - - torqueAvailable += Vector3d.Max(torqueControlSurface.positive, torqueControlSurface.negative); - - torqueAvailable += Vector3d.Max(torqueGimbal.positive, torqueGimbal.negative); - - torqueAvailable += Vector3d.Max(torqueOthers.positive, torqueOthers.negative); // Mostly FAR - - torqueDiffThrottle = Vector3d.Max(einfo.torqueDiffThrottle.positive, einfo.torqueDiffThrottle.negative); - torqueDiffThrottle.y = 0; - - if (torqueAvailable.sqrMagnitude > 0) - { - torqueReactionSpeed = Vector3d.Max(torqueReactionSpeed6.positive, torqueReactionSpeed6.negative); - torqueReactionSpeed.Scale(torqueAvailable.InvertNoNaN()); - } - else - { - torqueReactionSpeed = Vector3d.zero; - } - - thrustVectorMaxThrottle = einfo.thrustMax; - thrustVectorMinThrottle = einfo.thrustMin; - thrustVectorLastFrame = einfo.thrustCurrent; - - if (CoTScalar > 0) - CoT = CoT / CoTScalar; - DoT = DoT.normalized; - - if (CoLScalar > 0) - CoL = CoL / CoLScalar; - - Vector3d liftDir = -Vector3d.Cross(vessel.transform.right, -surfaceVelocity.normalized); - - if (isLoadedFAR && !vessel.packed && surfaceVelocity != Vector3d.zero) - { - Vector3 farForce = Vector3.zero; - Vector3 farTorque = Vector3.zero; - FARCalculateVesselAeroForces(vessel, out farForce, out farTorque, surfaceVelocity, altitudeASL); - - Vector3d farDragVector = Vector3d.Dot(farForce, -surfaceVelocity.normalized) * -surfaceVelocity.normalized; - drag = farDragVector.magnitude / mass; - dragUp = Vector3d.Dot(farDragVector, up) / mass; - pureDragV = farDragVector; - pureDrag = drag; - - Vector3d farLiftVector = Vector3d.Dot(farForce, liftDir) * liftDir; - lift = farLiftVector.magnitude / mass; - liftUp = Vector3d.Dot(farForce, up) / mass; // Use farForce instead of farLiftVector to match code for stock aero - pureLiftV = farLiftVector; - pureLift = lift; - } - else - { - pureDragV = pureDragV / mass; - pureLiftV = pureLiftV / mass; - - pureDrag = pureDragV.magnitude; - - pureLift = pureLiftV.magnitude; - - Vector3d force = pureDragV + pureLiftV; - // Drag is the part (pureDrag + PureLift) applied opposite of the surface vel - drag = Vector3d.Dot(force, -surfaceVelocity.normalized); - // DragUp is the part (pureDrag + PureLift) applied in the "Up" direction - dragUp = Vector3d.Dot(pureDragV, up); - // Lift is the part (pureDrag + PureLift) applied in the "Lift" direction - lift = Vector3d.Dot(force, liftDir); - // LiftUp is the part (pureDrag + PureLift) applied in the "Up" direction - liftUp = Vector3d.Dot(force, up); - } - - maxEngineResponseTime = einfo.maxResponseTime; - } - - [GeneralInfoItem("#MechJeb_Torque", InfoItem.Category.Vessel, showInEditor = true)]//Torque - public void TorqueCompare() - { - var reactionTorque = Vector3d.Max(torqueReactionWheel.positive, torqueReactionWheel.negative); - //var rcsTorque = Vector3d.Max(torqueRcs.positive, torqueRcs.negative); - - var rcsTorqueMJ = Vector3d.Max(rcsTorqueAvailable.positive, rcsTorqueAvailable.negative); - - var controlTorque = Vector3d.Max(torqueControlSurface.positive, torqueControlSurface.negative); - var gimbalTorque = Vector3d.Max(torqueGimbal.positive, torqueGimbal.negative); - var diffTorque = Vector3d.Max(einfo.torqueDiffThrottle.positive, einfo.torqueDiffThrottle.negative); - diffTorque.y = 0; - var othersTorque = Vector3d.Max(torqueOthers.positive, torqueOthers.negative); - - GUILayout.Label("Torque sources", GuiUtils.LabelNoWrap); - GUILayout.BeginHorizontal(); - - GUILayout.BeginVertical(); - GUILayout.Label("ReactionWheel", GuiUtils.LabelNoWrap); - - GUILayout.Label("RCS", GuiUtils.LabelNoWrap); - //GUILayout.Label("RCS MJ", GuiUtils.LabelNoWrap); - - GUILayout.Label("ControlSurface", GuiUtils.LabelNoWrap); - GUILayout.Label("Gimbal", GuiUtils.LabelNoWrap); - GUILayout.Label("Diff Throttle", GuiUtils.LabelNoWrap); - GUILayout.Label("Others (FAR)", GuiUtils.LabelNoWrap); - GUILayout.EndVertical(); - - GUILayout.BeginVertical(); - GUILayout.Label(MuUtils.PrettyPrint(reactionTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - //GUILayout.Label(MuUtils.PrettyPrint(rcsTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - - GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueMJ), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - - GUILayout.Label(MuUtils.PrettyPrint(controlTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - GUILayout.Label(MuUtils.PrettyPrint(gimbalTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - GUILayout.Label(MuUtils.PrettyPrint(diffTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - GUILayout.Label(MuUtils.PrettyPrint(othersTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - } - - void UpdateResourceRequirements(EngineInfo einfo, IntakeInfo iinfo) - { - // Convert the resource information from the einfo and iinfo format - // to the more useful ResourceInfo format. - ResourceInfo.Release(resources.Values); - resources.Clear(); - foreach (var info in einfo.resourceRequired) - { - int id = info.Key; - var req = info.Value; - resources[id] = ResourceInfo.Borrow( - PartResourceLibrary.Instance.GetDefinition(id), - req.requiredLastFrame, - req.requiredAtMaxThrottle, - iinfo.getIntakes(id), - vesselRef); - } - - int intakeAirId = PartResourceLibrary.Instance.GetDefinition("IntakeAir").id; - intakeAir = 0; - intakeAirNeeded = 0; - intakeAirAtMax = 0; - intakeAirAllIntakes = 0; - if (resources.ContainsKey(intakeAirId)) - { - intakeAir = resources[intakeAirId].intakeProvided; - intakeAirAllIntakes = resources[intakeAirId].intakeAvailable; - intakeAirNeeded = resources[intakeAirId].required; - intakeAirAtMax = resources[intakeAirId].requiredAtMaxThrottle; - } - } - - // Decide whether to control the RCS thrusters from the main throttle - void ToggleRCSThrust(Vessel vessel) - { - if (thrustVectorMaxThrottle.magnitude == 0 && vessel.ActionGroups[KSPActionGroup.RCS]) - { - rcsThrust = true; - thrustVectorMaxThrottle += (Vector3d)(vessel.transform.up) * rcsThrustAvailable.down; - } - else - { - rcsThrust = false; - } - } - - void UpdateMoIAndAngularMom(Vessel vessel) - { - // stock code + fix - Matrix4x4 tensor = Matrix4x4.zero; - Matrix4x4 partTensor = Matrix4x4.identity; - Matrix4x4 inertiaMatrix = Matrix4x4.identity; - Matrix4x4 productMatrix = Matrix4x4.identity; - - QuaternionD invQuat = QuaternionD.Inverse(vessel.ReferenceTransform.rotation); - Transform vesselReferenceTransform = vessel.ReferenceTransform; - int count = vessel.parts.Count; - for (int i = 0; i < count; ++i) - { - Part part = vessel.parts[i]; - if (part.rb != null) - { - KSPUtil.ToDiagonalMatrix2(part.rb.inertiaTensor, ref partTensor); - - Quaternion rot = (Quaternion)invQuat * part.transform.rotation * part.rb.inertiaTensorRotation; - Quaternion inv = Quaternion.Inverse(rot); - - Matrix4x4 rotMatrix = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one); - Matrix4x4 invMatrix = Matrix4x4.TRS(Vector3.zero, inv, Vector3.one); - - KSPUtil.Add(ref tensor, rotMatrix * partTensor * invMatrix); - Vector3 position = vesselReferenceTransform.InverseTransformDirection(part.rb.position - vessel.CoMD); - - KSPUtil.ToDiagonalMatrix2(part.rb.mass * position.sqrMagnitude, ref inertiaMatrix); - KSPUtil.Add(ref tensor, inertiaMatrix); - - KSPUtil.OuterProduct2(position, -part.rb.mass * position, ref productMatrix); - KSPUtil.Add(ref tensor, productMatrix); - } - } - //MoI = vessel.MOI = KSPUtil.Diag(tensor); - MoI = KSPUtil.Diag(tensor); - angularMomentum = Vector3d.zero; - angularMomentum.x = (float)(MoI.x * vessel.angularVelocity.x); - angularMomentum.y = (float)(MoI.y * vessel.angularVelocity.y); - angularMomentum.z = (float)(MoI.z * vessel.angularVelocity.z); - - angularVelocityAvg.value = angularVelocity; - } - - [ValueInfoItem("#MechJeb_TerminalVelocity", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s")]//Terminal velocity - public double TerminalVelocity() - { - return TerminalVelocityCall(); - } - - public DTerminalVelocity TerminalVelocityCall; - - public double TerminalVelocityStockKSP() - { - if (mainBody == null || altitudeASL > mainBody.RealMaxAtmosphereAltitude()) return double.PositiveInfinity; - - return Math.Sqrt((2000 * mass * localg) / (areaDrag * vesselRef.atmDensity)); - } - - public double TerminalVelocityFAR() - { - return FARVesselTermVelEst(vesselRef); - } - - public double ThrustAccel(double throttle) - { - return (1.0 - throttle) * minThrustAccel + throttle * maxThrustAccel; - } - - public double HeadingFromDirection(Vector3d dir) - { - return MuUtils.ClampDegrees360(UtilMath.Rad2Deg * Math.Atan2(Vector3d.Dot(dir, east), Vector3d.Dot(dir, north))); - } - - // Altitude of bottom of craft, only calculated when requested because it is a bit expensive - private bool altitudeBottomIsCurrent = false; - private double _altitudeBottom; - [ValueInfoItem("#MechJeb_Altitude_bottom", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, units = "m")]//Altitude (bottom) - public double altitudeBottom - { - get - { - if (!altitudeBottomIsCurrent) - { - _altitudeBottom = ComputeVesselBottomAltitude(vesselRef); - altitudeBottomIsCurrent = true; - } - return _altitudeBottom; - } - } - - double ComputeVesselBottomAltitude(Vessel vessel) - { - if (vessel == null || vessel.rootPart.rb == null) return 0; - double ret = altitudeTrue; - for (int i = 0; i < vessel.parts.Count; i++) - { - Part p = vessel.parts[i]; - if (p.collider != null) - { - /*Vector3d bottomPoint = p.collider.ClosestPointOnBounds(vesselmainBody.position); - double partBottomAlt = vesselmainBody.GetAltitude(bottomPoint) - surfaceAltitudeASL; - _altitudeBottom = Math.Max(0, Math.Min(_altitudeBottom, partBottomAlt));*/ - Bounds bounds = p.collider.bounds; - Vector3 extents = bounds.extents; - float partRadius = Mathf.Max(extents[0], Mathf.Max(extents[1], extents[2])); - double partAltitudeBottom = vessel.mainBody.GetAltitude(bounds.center) - partRadius - surfaceAltitudeASL; - partAltitudeBottom = Math.Max(0, partAltitudeBottom); - if (partAltitudeBottom < ret) - { - ret = partAltitudeBottom; - } - } - } - return ret; - } - - // Used during the vesselState constructor; distilled to other - // variables later. - public class EngineInfo - { - public Vector3d thrustCurrent = new Vector3d(); // thrust at throttle achieved last frame - public Vector3d thrustMax = new Vector3d(); // thrust at full throttle - public Vector3d thrustMin = new Vector3d(); // thrust at zero throttle - public double maxResponseTime = 0; - public Vector6 torqueDiffThrottle = new Vector6(); - // lowestUllage is always VeryStable without RealFuels installed - public UllageState lowestUllage = UllageState.VeryStable; - - public struct FuelRequirement - { - public double requiredLastFrame; - public double requiredAtMaxThrottle; - } - public Dictionary resourceRequired = new Dictionary(); - - private Vector3d CoM; - private float atmP0; // pressure now - private float atmP1; // pressure after one timestep - private Queue rotSave = new Queue(); - - public void Update(Vector3d c, Vessel vessel) - { - thrustCurrent = Vector3d.zero; - thrustMax = Vector3d.zero; - thrustMin = Vector3d.zero; - maxResponseTime = 0; - - torqueDiffThrottle.Reset(); - - resourceRequired.Clear(); - - lowestUllage = UllageState.VeryStable; - - CoM = c; - - atmP0 = (float)(vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres); - float alt1 = (float)(vessel.altitude + TimeWarp.fixedDeltaTime * vessel.verticalSpeed); - atmP1 = (float)(FlightGlobals.getStaticPressure(alt1) * PhysicsGlobals.KpaToAtmospheres); - } - - public void CheckUllageStatus(ModuleEngines e) - { - // we report stable ullage for an unstable engine which is throttled up, so we let RF kill it - // instead of having MJ throttle it down. - if ((e.getFlameoutState) || (!e.EngineIgnited) || (!e.isEnabled) || (e.requestedThrottle > 0.0F)) - { - return; - } - - bool? ullage; - try - { - ullage = RFullageField.GetValue(e) as bool?; - } - catch (ArgumentException e1) - { - Debug.Log("MechJeb BUG ArgumentError thrown while getting ullage from RealFuels, ullage integration disabled: " + e1.Message); - RFullageField = null; - return; - } - - if (ullage == null) - { - Debug.Log("MechJeb BUG: getting ullage from RealFuels casted to null, ullage status likely broken"); - return; - } - - if (ullage == false) - { - return; - } - - /* ullage is 'stable' if the engine has no ignitions left */ - int? ignitions; - try - { - ignitions = RFignitionsField.GetValue(e) as int?; - } - catch (ArgumentException e2) - { - Debug.Log("MechJeb BUG ArgumentError thrown while getting ignitions from RealFuels, ullage integration disabled: " + e2.Message); - RFignitionsField = null; - return; - } - - if (ignitions == null) - { - Debug.Log("MechJeb BUG: getting ignitions from RealFuels casted to null, ullage status likely broken"); - return; - } - - /* -1 => infinite ignitions; 0 => no ignitions left; 1+ => ignitions remaining */ - if (ignitions == 0) - { - return; - } - - // finally we have an ignitable engine (that isn't already ignited), so check its propellant status - - // need to call RFullageSet to get the UllageSet then call GetUllageStability on that. - // then need to get all the constants off of UllageSimulator - double propellantStability; - - try - { - var ullageSet = RFullageSetField.GetValue(e); - if (ullageSet == null) - { - Debug.Log("MechJeb BUG: getting propellantStatus from RealFuels casted to null, ullage status likely broken"); - return; - } - try - { - propellantStability = (double) RFGetUllageStabilityMethod.Invoke(ullageSet, new object[0]); - } - catch (Exception e4) - { - Debug.Log("MechJeb BUG Exception thrown while calling GetUllageStability from RealFuels, ullage integration disabled: " + e4.Message); - RFullageSetField = null; - return; - } - } - catch (Exception e3) - { - Debug.Log("MechJeb BUG Exception thrown while getting ullageSet from RealFuels, ullage integration disabled: " + e3.Message); - RFullageSetField = null; - return; - } - - UllageState propellantState; - - if (propellantStability >= RFveryStableValue) - propellantState = UllageState.VeryStable; - else if (propellantStability >= RFstableValue) - propellantState = UllageState.Stable; - else if (propellantStability >= RFriskyValue) - propellantState = UllageState.Risky; - else if (propellantStability >= RFveryRiskyValue) - propellantState = UllageState.VeryRisky; - else - propellantState = UllageState.Unstable; - - if (propellantState < lowestUllage) - { - lowestUllage = propellantState; - } - } - - public void AddNewEngine(ModuleEngines e, ModuleGimbal gimbal, List enginesWrappers, ref Vector3d CoT, ref Vector3d DoT, ref double CoTScalar) - { - if ((!e.EngineIgnited) || (!e.isEnabled)) - { - return; - } - - // Compute the resource requirement at full thrust. - // mdot = maxthrust / (Isp * g0) in tonnes per second - // udot = mdot / mixdensity in units per second of propellant per ratio unit - // udot * ratio_i : units per second of propellant i - // Choose the worse Isp between now and after one timestep. - float Isp0 = e.atmosphereCurve.Evaluate(atmP0); - float Isp1 = e.atmosphereCurve.Evaluate(atmP1); - float Isp = Mathf.Min(Isp0, Isp1); - - for (int i = 0; i < e.propellants.Count; i++) - { - Propellant propellant = e.propellants[i]; - double maxreq = e.maxFuelFlow * propellant.ratio; - addResource(propellant.id, propellant.currentRequirement, maxreq); - } - - if (e.isOperational) - { - float thrustLimiter = e.thrustPercentage / 100f; - - double maxThrust = e.maxFuelFlow * e.flowMultiplier * Isp * e.g; - double minThrust = e.minFuelFlow * e.flowMultiplier * Isp * e.g; - - // RealFuels engines reports as operational even when they are shutdown - // REMOVED: this definitively screws up the 1kN thruster in RO/RF and sets minThrust/maxThrust - // to zero when the engine is just throttled down -- which screws up suicide burn calcs, etc. - // if (e.finalThrust == 0f && minThrust > 0f) - // minThrust = maxThrust = 0; - - //MechJebCore.print(maxThrust.ToString("F2") + " " + minThrust.ToString("F2") + " " + e.minFuelFlow.ToString("F2") + " " + e.maxFuelFlow.ToString("F2") + " " + e.flowMultiplier.ToString("F2") + " " + Isp.ToString("F2") + " " + thrustLimiter.ToString("F3")); - - double eMaxThrust = minThrust + (maxThrust - minThrust) * thrustLimiter; - double eMinThrust = e.throttleLocked ? eMaxThrust : minThrust; - double eCurrentThrust = e.finalThrust; - - rotSave.Clear(); - - // Used for Diff Throttle - Vector3d constantForce = Vector3d.zero; - Vector3d maxVariableForce = Vector3d.zero; - Vector3d constantTorque = Vector3d.zero; - Vector3d maxVariableTorque = Vector3d.zero; - double currentMaxThrust = maxThrust; - double currentMinThrust = minThrust; - - if (e.throttleLocked) - { - currentMaxThrust *= thrustLimiter; - currentMinThrust = currentMaxThrust; - } - - // Reset gimbals to default rotation - if (gimbal != null && !gimbal.gimbalLock) - { - rotSave.Clear(); - for (int i = 0; i < gimbal.gimbalTransforms.Count; i++) - { - Transform gimbalTransform = gimbal.gimbalTransforms[i]; - rotSave.Enqueue(gimbalTransform.localRotation); - gimbalTransform.localRotation = gimbal.initRots[i]; - } - } - - for (int i = 0; i < e.thrustTransforms.Count; i++) - { - var transform = e.thrustTransforms[i]; - // The rotation makes a +z vector point in the direction that molecules are ejected - // from the engine. The resulting thrust force is in the opposite direction. - Vector3d thrustDirectionVector = -transform.forward; - - double cosineLosses = Vector3d.Dot(thrustDirectionVector, e.part.vessel.GetTransform().up); - var thrustTransformMultiplier = e.thrustTransformMultipliers[i]; - var tCurrentThrust = eCurrentThrust * thrustTransformMultiplier; - - thrustCurrent += tCurrentThrust * cosineLosses * thrustDirectionVector; - thrustMax += eMaxThrust * cosineLosses * thrustDirectionVector * thrustTransformMultiplier; - thrustMin += eMinThrust * cosineLosses * thrustDirectionVector * thrustTransformMultiplier; - - CoT += tCurrentThrust * (Vector3d)transform.position; - DoT -= tCurrentThrust * thrustDirectionVector; - CoTScalar += tCurrentThrust; - - Quaternion inverseVesselRot = e.part.vessel.ReferenceTransform.rotation.Inverse(); - Vector3d thrust_dir = inverseVesselRot * thrustDirectionVector; - Vector3d pos = inverseVesselRot * (transform.position - CoM); - - maxVariableForce += (currentMaxThrust - currentMinThrust) * thrust_dir * thrustTransformMultiplier; - constantForce += currentMinThrust * thrust_dir * thrustTransformMultiplier; - maxVariableTorque += (currentMaxThrust - currentMinThrust) * thrustTransformMultiplier * Vector3d.Cross(pos, thrust_dir); - constantTorque += currentMinThrust * thrustTransformMultiplier * Vector3d.Cross(pos, thrust_dir); - - if (!e.throttleLocked) - { - torqueDiffThrottle.Add(Vector3d.Cross(pos, thrust_dir) * (float)(maxThrust - minThrust) * thrustTransformMultiplier); - } - } - - enginesWrappers.Add(new EngineWrapper(e, constantForce, maxVariableForce, constantTorque, maxVariableTorque)); - - // Restore gimbals rotation - if (gimbal != null && !gimbal.gimbalLock) - { - for (int i = 0; i < gimbal.gimbalTransforms.Count; i++) - { - gimbal.gimbalTransforms[i].localRotation = rotSave.Dequeue(); - } - } - - if (e.useEngineResponseTime) - { - double responseTime = 1.0 / Math.Min(e.engineAccelerationSpeed, e.engineDecelerationSpeed); - if (responseTime > maxResponseTime) maxResponseTime = responseTime; - } - } - } - - private void addResource(int id, double current /* u/sec */, double max /* u/sec */) - { - FuelRequirement req; - if (resourceRequired.ContainsKey(id)) - { - req = resourceRequired[id]; - } - else - { - req = new FuelRequirement(); - resourceRequired[id] = req; - } - - req.requiredLastFrame += current; - req.requiredAtMaxThrottle += max; - } - } - - // Used during the vesselState constructor; distilled to other variables later. - class IntakeInfo - { - public readonly Dictionary> allIntakes = new Dictionary>(); - - public void Update() - { - foreach (List intakes in allIntakes.Values) - { - ListPool.Instance.Release(intakes); - } - allIntakes.Clear(); - } - - public void addIntake(ModuleResourceIntake intake) - { - // TODO: figure out how much airflow we have, how much we could have, - // drag, etc etc. - List thelist; - int id = PartResourceLibrary.Instance.GetDefinition(intake.resourceName).id; - if (allIntakes.ContainsKey(id)) - { - thelist = allIntakes[id]; - } - else - { - thelist = ListPool.Instance.Borrow(); - allIntakes[id] = thelist; - } - thelist.Add(intake); - } - - private static readonly List empty = new List(); - public List getIntakes(int id) - { - if (allIntakes.ContainsKey(id)) - { - return allIntakes[id]; - } - else - { - return empty; - } - } - } - - // Stored. - public class ResourceInfo - { - public PartResourceDefinition definition; - - // We use kg/s rather than the more common T/s because these numbers tend to be small. - // One debate I've had is whether to use mass/s or unit/s. Dunno... - - public double required = 0; // kg/s - public double requiredAtMaxThrottle = 0; // kg/s - public double intakeAvailable = 0; // kg/s - public double intakeProvided - { // kg/s for currently-open intakes - get - { - double sum = 0; - for (int i = 0; i < intakes.Count; i++) - { - var intakeData = intakes[i]; - if (intakeData.intake.intakeEnabled) - { - sum += intakeData.predictedMassFlow; - } - } - return sum; - } - } - public List intakes = new List(); - - public struct IntakeData - { - public IntakeData(ModuleResourceIntake intake, double predictedMassFlow) - { - this.intake = intake; - this.predictedMassFlow = predictedMassFlow; - } - public ModuleResourceIntake intake; - public double predictedMassFlow; // min kg/s this timestep or next - } - - private static readonly Pool pool = new Pool(Create, Reset); - - public static int PoolSize - { - get { return pool.Size; } - } - - private static ResourceInfo Create() - { - return new ResourceInfo(); - } - - public virtual void Release() - { - pool.Release(this); - } - - public static void Release(Dictionary.ValueCollection objList) - { - foreach (ResourceInfo resourceInfo in objList) - { - resourceInfo.Release(); - } - } - - private static void Reset(ResourceInfo obj) - { - obj.required = 0; - obj.requiredAtMaxThrottle = 0; - obj.intakeAvailable = 0; - obj.intakes.Clear(); - } - - private ResourceInfo() - { - } - - public static ResourceInfo Borrow(PartResourceDefinition r, double req /* u per deltaT */, double atMax /* u per s */, List modules, Vessel vessel) - { - ResourceInfo resourceInfo = pool.Borrow(); - resourceInfo.Init(r, req /* u per deltaT */, atMax /* u per s */, modules, vessel); - return resourceInfo; - } - - private void Init(PartResourceDefinition r, double req /* u per deltaT */, double atMax /* u per s */, List modules, Vessel vessel) - { - definition = r; - double density = definition.density * 1000; // kg per unit (density is in T per unit) - float dT = TimeWarp.fixedDeltaTime; - required = req * density / dT; - requiredAtMaxThrottle = atMax * density; - - // For each intake, we want to know the min of what will (or can) be provided either now or at the end of the timestep. - // 0 means now, 1 means next timestep - Vector3d v0 = vessel.srf_velocity; - Vector3d v1 = v0 + dT * vessel.acceleration; - Vector3d v0norm = v0.normalized; - Vector3d v1norm = v1.normalized; - double v0mag = v0.magnitude; - double v1mag = v1.magnitude; - - float alt1 = (float)(vessel.altitude + dT * vessel.verticalSpeed); - - double staticPressure1 = vessel.staticPressurekPa; - double staticPressure2 = FlightGlobals.getStaticPressure(alt1); - - // As with thrust, here too we should get the static pressure at the intake, not at the center of mass. - double atmDensity0 = FlightGlobals.getAtmDensity(staticPressure1, vessel.externalTemperature); - double atmDensity1 = FlightGlobals.getAtmDensity(staticPressure2, FlightGlobals.getExternalTemperature(alt1)); - - double v0speedOfSound = vessel.mainBody.GetSpeedOfSound(staticPressure1, atmDensity0); - double v1speedOfSound = vessel.mainBody.GetSpeedOfSound(staticPressure2, atmDensity1); - - float v0mach = v0speedOfSound > 0 ? (float)(v0.magnitude / v0speedOfSound) : 0; - float v1mach = v1speedOfSound > 0 ? (float)(v1.magnitude / v1speedOfSound) : 0; - - intakes.Clear(); - int idx = 0; - for (int index = 0; index < modules.Count; index++) - { - var intake = modules[index]; - var intakeTransform = intake.intakeTransform; - if (intakeTransform == null) - continue; - Vector3d intakeFwd0 = intakeTransform.forward; // TODO : replace with the new public field - Vector3d intakeFwd1; - { - // Rotate the intake by the angular velocity for one timestep, in case the ship is spinning. - // Going through the Unity vector classes is about as many lines as hammering it out by hand. - Vector3 rot = dT * vessel.angularVelocity; - intakeFwd1 = Quaternion.AngleAxis(Mathf.Rad2Deg * rot.magnitude, rot) * intakeFwd0; - /*Vector3d cos; - Vector3d sin; - for(int i = 0; i < 3; ++i) { - cos[i] = Math.Cos (rot[i]); - sin[i] = Math.Sin (rot[i]); - } - intakeFwd1[0] - = intakeFwd0[0] * cos[1] * cos[2] - + intakeFwd0[1] * (sin[0]*sin[1]*cos[2] - cos[0]*sin[2]) - + intakeFwd0[2] * (sin[0]*sin[2] + cos[0]*sin[1]); - - intakeFwd1[1] - = intakeFwd0[0] * cos[1] * sin[2] - + intakeFwd0[1] * (cos[0]*cos[1] + sin[0]*sin[1]*sin[2]) - + intakeFwd0[2] * (cos[0]*sin[1]*sin[2] - sin[0]*cos[2]); - - intakeFwd1[2] - = intakeFwd0[0] * (-sin[1]) - + intakeFwd0[1] * sin[0] * cos[1] - + intakeFwd0[2] * cos[0] * cos[1];*/ - } - - double mass0 = massProvided(v0mag, v0norm, atmDensity0, staticPressure1, v0mach, intake, intakeFwd0); - double mass1 = massProvided(v1mag, v1norm, atmDensity1, staticPressure2, v1mach, intake, intakeFwd1); - double mass = Math.Min(mass0, mass1); - - // Also, we can't have more airflow than what fits in the resource tank of the intake part. - double capacity = 0; - for (int i = 0; i < intake.part.Resources.Count; i++) - { - PartResource tank = intake.part.Resources[i]; - if (tank.info.id == definition.id) - { - capacity += tank.maxAmount; // units per timestep - } - } - capacity = capacity * density / dT; // convert to kg/s - mass = Math.Min(mass, capacity); - - intakes.Add(new IntakeData(intake, mass)); - - idx++; - } - } - - // Return the number of kg of resource provided per second under certain conditions. - // We use kg since the numbers are typically small. - private double massProvided(double vesselSpeed, Vector3d normVesselSpeed, double atmDensity, double staticPressure, float mach, - ModuleResourceIntake intake, Vector3d intakeFwd) - { - if ((intake.checkForOxygen && !FlightGlobals.currentMainBody.atmosphereContainsOxygen) || staticPressure < intake.kPaThreshold) // TODO : add the new test (the bool and maybe the attach node ?) - { - return 0; - } - - // This is adapted from code shared by Amram at: - // http://forum.kerbalspaceprogram.com/showthread.php?34288-Maching-Bird-Challeng?p=440505 - // Seems to be accurate for 0.18.2 anyway. - double intakeSpeed = intake.intakeSpeed; // airspeed when the intake isn't moving - - double aoa = Vector3d.Dot(normVesselSpeed, intakeFwd); - if (aoa < 0) { aoa = 0; } - else if (aoa > 1) { aoa = 1; } - - double finalSpeed = intakeSpeed + aoa * vesselSpeed; - - double airVolume = finalSpeed * intake.area * intake.unitScalar * intake.machCurve.Evaluate(mach); - double airmass = atmDensity * airVolume; // tonnes per second - - // TODO: limit by the amount the intake can store - return airmass * 1000; - } - } - - - public class EngineWrapper - { - public readonly ModuleEngines engine; - - public float thrustRatio - { - get - { - return engine.thrustPercentage / 100; - } - set - { - engine.thrustPercentage = value * 100; - } - } - - private Vector3d _constantForce; - private Vector3d _maxVariableForce; - private Vector3d _constantTorque; - private Vector3d _maxVariableTorque; - - public Vector3d constantForce { get { return _constantForce; } } - public Vector3d maxVariableForce { get { return _maxVariableForce; } } - public Vector3d constantTorque { get { return _constantTorque; } } - public Vector3d maxVariableTorque { get { return _maxVariableTorque; } } - - public EngineWrapper(ModuleEngines module, Vector3d constantForce, Vector3d maxVariableForce, Vector3d constantTorque, Vector3d maxVariableTorque) - { - engine = module; - _constantForce = constantForce; - _maxVariableForce = maxVariableForce; - _constantTorque = constantTorque; - _maxVariableTorque = maxVariableTorque; - } - } - - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Smooth.Pools; +using UnityEngine; +using System.Reflection; +using KSP.Localization; + +namespace MuMech +{ + public class VesselState + { + public static bool isLoadedProceduralFairing = false; + public static bool isLoadedRealFuels = false; + // RealFuels.ModuleEngineRF ullageSet field to call via reflection + private static FieldInfo RFullageSetField; + // RealFuels.ModuleEngineRF ignitions field to call via reflection + private static FieldInfo RFignitionsField; + // RealFuels.ModuleEngineRF ullage field to call via reflection + private static FieldInfo RFullageField; + // RealFuels.Ullage.UllageSet GetUllageStability method to call via reflection + private static MethodInfo RFGetUllageStabilityMethod; + // RealFuels.Ullage.UllageSimulator fields to determine ullage status + private static double RFveryStableValue; + private static double RFstableValue; + private static double RFriskyValue; + private static double RFveryRiskyValue; + private static double RFunstableValue; + + public enum UllageState { + VeryUnstable, + Unstable, + VeryRisky, + Risky, + Stable, + VeryStable // "Nominal" also winds up here + } + + // lowestUllage is always VeryStable without RealFuels installed + public UllageState lowestUllage { get { return this.einfo.lowestUllage; } } + + public static bool isLoadedFAR = false; + private delegate double FARVesselDelegate(Vessel v); + private static FARVesselDelegate FARVesselDragCoeff; + private static FARVesselDelegate FARVesselRefArea; + private static FARVesselDelegate FARVesselTermVelEst; + private static FARVesselDelegate FARVesselDynPres; + private delegate void FARCalculateVesselAeroForcesDelegate(Vessel vessel, out Vector3 aeroForce, out Vector3 aeroTorque, Vector3 velocityWorldVector, double altitude); + private static FARCalculateVesselAeroForcesDelegate FARCalculateVesselAeroForces; + + private Vessel vesselRef = null; + + private EngineInfo einfo = new EngineInfo(); + private IntakeInfo iinfo = new IntakeInfo(); + public readonly List enginesWrappers = new List(); + + [ValueInfoItem("#MechJeb_UniversalTime", InfoItem.Category.Recorder, format = ValueInfoItem.TIME)]//Universal Time + public double time; //planetarium time + public double deltaT; //TimeWarp.fixedDeltaTime + + public Vector3d CoM; + public Vector3d MoI; //Diagonal components of the inertia tensor (almost always the dominant components) + + public Vector3d up; + public Vector3d north; + public Vector3d east; + public Vector3d forward; //the direction the vessel is pointing + public Vector3d horizontalOrbit; //unit vector in the direction of horizontal component of orbit velocity + public Vector3d horizontalSurface; //unit vector in the direction of horizontal component of surface velocity + public Vector3d rootPartPos; + + public Quaternion rotationSurface; + public Quaternion rotationVesselSurface; + + public Vector3d velocityMainBodySurface; + + public Vector3d orbitalVelocity; + public Vector3d orbitalPosition; + public Vector3d surfaceVelocity; + + public Vector3d angularVelocity; + public Vector3d angularMomentum; + + public Vector3d radialPlus; //unit vector in the plane of up and velocityVesselOrbit and perpendicular to velocityVesselOrbit + public Vector3d radialPlusSurface; //unit vector in the plane of up and velocityVesselSurface and perpendicular to velocityVesselSurface + public Vector3d normalPlus; //unit vector perpendicular to up and velocityVesselOrbit + public Vector3d normalPlusSurface; //unit vector perpendicular to up and velocityVesselSurface + + public Vector3d gravityForce; + [ValueInfoItem("#MechJeb_LocalGravity", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "m/s²")]//Local gravity + public double localg; //magnitude of gravityForce + + //How about changing these so we store the instantaneous values and *also* + //the smoothed MovingAverages? Sometimes we need the instantaneous value. + [ValueInfoItem("#MechJeb_OrbitalSpeed", InfoItem.Category.Orbit, format = ValueInfoItem.SI, units = "m/s")]//Orbital speed + public MovingAverage speedOrbital = new MovingAverage(); + [ValueInfoItem("#MechJeb_SurfaceSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Surface speed + public MovingAverage speedSurface = new MovingAverage(); + [ValueInfoItem("#MechJeb_VerticalSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Vertical speed + public MovingAverage speedVertical = new MovingAverage(); + [ValueInfoItem("#MechJeb_SurfaceHorizontalSpeed", InfoItem.Category.Surface, format = ValueInfoItem.SI, units = "m/s")]//Surface horizontal speed + public MovingAverage speedSurfaceHorizontal = new MovingAverage(); + [ValueInfoItem("#MechJeb_OrbitHorizontalSpeed", InfoItem.Category.Orbit, format = ValueInfoItem.SI, units = "m/s")]//Orbit horizontal speed + public double speedOrbitHorizontal; + [ValueInfoItem("#MechJeb_Heading", InfoItem.Category.Surface, format = "F1", units = "º")]//Heading + public MovingAverage vesselHeading = new MovingAverage(); + [ValueInfoItem("#MechJeb_Pitch", InfoItem.Category.Surface, format = "F1", units = "º")]//Pitch + public MovingAverage vesselPitch = new MovingAverage(); + [ValueInfoItem("#MechJeb_Roll", InfoItem.Category.Surface, format = "F1", units = "º")]//Roll + public MovingAverage vesselRoll = new MovingAverage(); + [ValueInfoItem("#MechJeb_Altitude_ASL", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = -1, units = "m")]//Altitude (ASL) + public MovingAverage altitudeASL = new MovingAverage(); + [ValueInfoItem("#MechJeb_Altitude_true", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = -1, units = "m")]//Altitude (true) + public MovingAverage altitudeTrue = new MovingAverage(); + [ValueInfoItem("#MechJeb_SurfaceAltitudeASL", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 4, siMaxPrecision = -1, units = "m")]//Surface altitude ASL + public double surfaceAltitudeASL; + + [ValueInfoItem("#MechJeb_Apoapsis", InfoItem.Category.Orbit, units = "m", format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, category = InfoItem.Category.Orbit)]//Apoapsis + public MovingAverage orbitApA = new MovingAverage(); + [ValueInfoItem("#MechJeb_Periapsis", InfoItem.Category.Orbit, units = "m", format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, category = InfoItem.Category.Orbit)]//Periapsis + public MovingAverage orbitPeA = new MovingAverage(); + [ValueInfoItem("#MechJeb_OrbitalPeriod", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 2, category = InfoItem.Category.Orbit)]//Orbital period + public MovingAverage orbitPeriod = new MovingAverage(); + [ValueInfoItem("#MechJeb_TimeToApoapsis", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 1)]//Time to apoapsis + public MovingAverage orbitTimeToAp = new MovingAverage(); + [ValueInfoItem("#MechJeb_TimeToPeriapsis", InfoItem.Category.Orbit, format = ValueInfoItem.TIME, timeDecimalPlaces = 1)]//Time to periapsis + public MovingAverage orbitTimeToPe = new MovingAverage(); + [ValueInfoItem("#MechJeb_LAN", InfoItem.Category.Orbit, format = ValueInfoItem.ANGLE)]//LAN + public MovingAverage orbitLAN = new MovingAverage(); + [ValueInfoItem("#MechJeb_ArgumentOfPeriapsis", InfoItem.Category.Orbit, format = "F1", units = "º")]//Argument of periapsis + public MovingAverage orbitArgumentOfPeriapsis = new MovingAverage(); + [ValueInfoItem("#MechJeb_Inclination", InfoItem.Category.Orbit, format = "F3", units = "º")]//Inclination + public MovingAverage orbitInclination = new MovingAverage(); + [ValueInfoItem("#MechJeb_Eccentricity", InfoItem.Category.Orbit, format = "F3")]//Eccentricity + public MovingAverage orbitEccentricity = new MovingAverage(); + [ValueInfoItem("#MechJeb_SemiMajorAxis", InfoItem.Category.Orbit, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, units = "m")]//Semi-major axis + public MovingAverage orbitSemiMajorAxis = new MovingAverage(); + [ValueInfoItem("#MechJeb_Latitude", InfoItem.Category.Surface, format = ValueInfoItem.ANGLE_NS)]//Latitude + public MovingAverage latitude = new MovingAverage(); + [ValueInfoItem("#MechJeb_Longitude", InfoItem.Category.Surface, format = ValueInfoItem.ANGLE_EW)]//Longitude + public MovingAverage longitude = new MovingAverage(); + [ValueInfoItem("#MechJeb_AngleOfAttack", InfoItem.Category.Misc, format = "F2", units = "º")]//Angle of Attack + public MovingAverage AoA = new MovingAverage(); + [ValueInfoItem("#MechJeb_AngleOfSideslip", InfoItem.Category.Misc, format = "F2", units = "º")]//Angle of Sideslip + public MovingAverage AoS = new MovingAverage(); + [ValueInfoItem("#MechJeb_DisplacementAngle", InfoItem.Category.Misc, format = "F2", units = "º")]//Displacement Angle + public MovingAverage displacementAngle = new MovingAverage(); + + public MovingAverage3d angularVelocityAvg = new MovingAverage3d(5); + + public double radius; //distance from planet center + + public double mass; + + // Thrust is a vector. These are in the same frame of reference as forward and other vectors. + public Vector3d thrustVectorLastFrame = new Vector3d(); + public Vector3d thrustVectorMaxThrottle = new Vector3d(); + public Vector3d thrustVectorMinThrottle = new Vector3d(); + + // Thrust in the forward direction (for historical reasons). + public double thrustAvailable { get { return Vector3d.Dot(thrustVectorMaxThrottle, forward); } } + public double thrustMinimum { get { return Vector3d.Dot(thrustVectorMinThrottle, forward); } } + public double thrustCurrent { get { return Vector3d.Dot(thrustVectorLastFrame, forward); } } + + // Acceleration in the forward direction, for when dividing by mass is too complicated. + public double maxThrustAccel { get { return thrustAvailable / mass; } } + public double minThrustAccel { get { return thrustMinimum / mass; } } + public double currentThrustAccel { get { return thrustCurrent / mass; } } + + public double maxEngineResponseTime = 0; + + public bool rcsThrust = false; + /* the current throttle limit, this may include transient condition such as limiting to zero due to unstable propellants in RF */ + public float throttleLimit = 1; + /* the fixed throttle limit (i.e. user limited in the GUI), does not include transient conditions as limiting to zero due to unstable propellants in RF */ + public float throttleFixedLimit = 1; + public double limitedMaxThrustAccel { get { return maxThrustAccel * throttleFixedLimit + minThrustAccel * (1 - throttleFixedLimit); } } + + public Vector3d CoT; + public Vector3d DoT; + public double CoTScalar; + + + public Vector3d pureDragV; + [ValueInfoItem("#MechJeb_PureDrag", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s²")]//Pure Drag + public double pureDrag; + + public Vector3d pureLiftV; + [ValueInfoItem("#MechJeb_PureLift", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s²")]//Pure Lift + public double pureLift; + + // Drag is the force (pureDrag + PureLift) applied opposite of the surface vel + public double drag; + // Drag is the force (pureDrag + PureLift) applied in the "Up" direction + public double dragUp; + // Lift is the force (pureDrag + PureLift) applied in the "Lift" direction + public double lift; + // Lift is the force (pureDrag + PureLift) applied in the "Up" direction + public double liftUp; + + public Vector3d CoL; + public double CoLScalar; + + + [ValueInfoItem("#MechJeb_Mach", InfoItem.Category.Vessel, format = "F2")]//Mach + public double mach; + + [ValueInfoItem("#MechJeb_SpeedOfSound", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s")]//Speed of sound + public double speedOfSound; + + [ValueInfoItem("#MechJeb_DragCoefficient", InfoItem.Category.Vessel, format = "F2")]//Drag Coefficient + public double dragCoef; + + // Product of the drag surface area, drag coefficient and the physic multiplers + public double areaDrag; + + public double atmosphericDensity; + [ValueInfoItem("#MechJeb_AtmosphereDensity", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "g/m³")]//Atmosphere density + public double atmosphericDensityGrams; + [ValueInfoItem("#MechJeb_MaxDynamicPressure", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "Pa")]//Max dynamic pressure + public double maxDynamicPressure; + [ValueInfoItem("#MechJeb_DynamicPressure", InfoItem.Category.Misc, format = ValueInfoItem.SI, units = "Pa")]//Dynamic pressure + public double dynamicPressure; + [ValueInfoItem("#MechJeb_IntakeAir", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air + public double intakeAir; + [ValueInfoItem("#MechJeb_IntakeAirAllIntakes", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air (all intakes open) + public double intakeAirAllIntakes; + [ValueInfoItem("#MechJeb_IntakeAirNeeded", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air needed + public double intakeAirNeeded; + [ValueInfoItem("#MechJeb_intakeAirAtMax", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "kg/s")]//Intake air needed (max) + public double intakeAirAtMax; + [ValueInfoItem("#MechJeb_AngleToPrograde", InfoItem.Category.Orbit, format = "F2", units = "º")]//Angle to prograde + public double angleToPrograde; + [ValueInfoItem("#MechJeb_AerothermalFlux", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "W/m²")]//Aerothermal flux + public double freeMolecularAerothermalFlux; + + public Vector6 rcsThrustAvailable = new Vector6(); // thrust available from RCS thrusters + + public Vector6 rcsTorqueAvailable = new Vector6(); // torque available from RCS thrusters + + + // Total torque + public Vector3d torqueAvailable; + + public Vector3d torqueReactionSpeed; + + // Torque from different components + public Vector6 torqueReactionWheel = new Vector6(); // torque available from Reaction wheels + //public Vector6 torqueRcs = new Vector6(); // torque available from RCS from stock code (not working properly ATM) + public Vector6 torqueControlSurface = new Vector6(); // torque available from Aerodynamic control surfaces + public Vector6 torqueGimbal = new Vector6(); // torque available from Gimbaled engines + public Vector6 torqueOthers = new Vector6(); // torque available from Mostly FAR + + // Variable part of torque related to differential throttle + public Vector3d torqueDiffThrottle; + + // List of parachutes + public List parachutes = new List(); + + public bool parachuteDeployed; + + // Resource information keyed by resource Id. + public Dictionary resources = new Dictionary(); + + public CelestialBody mainBody; + + // A convenient debug message to display in the UI + public static string message; + [GeneralInfoItem("#MechJeb_DebugString", InfoItem.Category.Misc, showInEditor = true)]//Debug String + public void DebugString() + { + GUILayout.BeginVertical(); + GUILayout.Label(message); + GUILayout.EndVertical(); + } + + // Callbacks for external module + public delegate void VesselStatePartExtension(Part p); + public delegate void VesselStatePartModuleExtension(PartModule pm); + + private Dictionary engines = new Dictionary(); + + public List vesselStatePartExtensions = new List(); + public List vesselStatePartModuleExtensions = new List(); + public delegate double DTerminalVelocity(); + + static VesselState() + { + FARVesselDragCoeff = null; + FARVesselRefArea = null; + FARVesselTermVelEst = null; + FARVesselDynPres = null; + isLoadedProceduralFairing = ReflectionUtils.isAssemblyLoaded("ProceduralFairings"); + isLoadedRealFuels = ReflectionUtils.isAssemblyLoaded("RealFuels"); + if (isLoadedRealFuels) + { + Debug.Log("MechJeb: RealFuels Assembly is loaded"); + RFullageSetField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ullageSet"); + if (RFullageSetField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ullageSet field, disabling RF"); + isLoadedRealFuels = false; + } + RFGetUllageStabilityMethod = ReflectionUtils.getMethodByReflection("RealFuels", "RealFuels.Ullage.UllageSet", "GetUllageStability", BindingFlags.Public|BindingFlags.Instance); + if (RFGetUllageStabilityMethod == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSet has no GetUllageStability method, disabling RF"); + isLoadedRealFuels = false; + } + RFignitionsField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ignitions"); + if (RFignitionsField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ignitions field, disabling RF"); + isLoadedRealFuels = false; + } + RFullageField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.ModuleEnginesRF", "ullage"); + if (RFullageField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.ModuleEnginesRF has no ullage field, disabling RF"); + isLoadedRealFuels = false; + } + FieldInfo RFveryStableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "veryStable", BindingFlags.NonPublic|BindingFlags.Static); + if (RFveryStableField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no veryStable field, disabling RF"); + isLoadedRealFuels = false; + } + try + { + RFveryStableValue = (double) RFveryStableField.GetValue(null); + } + catch (Exception e1) + { + Debug.Log("MechJeb BUG Exception thrown while getting veryStable value from RealFuels, ullage integration disabled: " + e1.Message); + isLoadedRealFuels = false; + return; + } + FieldInfo RFstableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "stable", BindingFlags.NonPublic|BindingFlags.Static); + if (RFstableField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no stable field, disabling RF"); + isLoadedRealFuels = false; + } + try + { + RFstableValue = (double) RFstableField.GetValue(null); + } + catch (Exception e2) + { + Debug.Log("MechJeb BUG Exception thrown while getting stable value from RealFuels, ullage integration disabled: " + e2.Message); + isLoadedRealFuels = false; + return; + } + FieldInfo RFriskyField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "risky", BindingFlags.NonPublic|BindingFlags.Static); + if (RFriskyField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no risky field, disabling RF"); + isLoadedRealFuels = false; + } + try + { + RFriskyValue = (double) RFriskyField.GetValue(null); + } + catch (Exception e3) + { + Debug.Log("MechJeb BUG Exception thrown while getting risky value from RealFuels, ullage integration disabled: " + e3.Message); + isLoadedRealFuels = false; + return; + } + FieldInfo RFveryRiskyField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "veryRisky", BindingFlags.NonPublic|BindingFlags.Static); + if (RFveryRiskyField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no veryRisky field, disabling RF"); + isLoadedRealFuels = false; + } + try + { + RFveryRiskyValue = (double) RFveryRiskyField.GetValue(null); + } + catch (Exception e4) + { + Debug.Log("MechJeb BUG Exception thrown while getting veryRisky value from RealFuels, ullage integration disabled: " + e4.Message); + isLoadedRealFuels = false; + return; + } + FieldInfo RFunstableField = ReflectionUtils.getFieldByReflection("RealFuels", "RealFuels.Ullage.UllageSimulator", "unstable", BindingFlags.NonPublic|BindingFlags.Static); + if (RFunstableField == null) + { + Debug.Log("MechJeb BUG: RealFuels loaded, but RealFuels.Ullage.UllageSimulator has no unstable field, disabling RF"); + isLoadedRealFuels = false; + } + try + { + RFunstableValue = (double) RFunstableField.GetValue(null); + } + catch (Exception e5) + { + Debug.Log("MechJeb BUG Exception thrown while getting unstable value from RealFuels, ullage integration disabled: " + e5.Message); + isLoadedRealFuels = false; + return; + } + if (isLoadedRealFuels) + { + Debug.Log("MechJeb: RealFuels Assembly is wired up properly"); + } + } + isLoadedFAR = ReflectionUtils.isAssemblyLoaded("FerramAerospaceResearch"); + if (isLoadedFAR) + { + List farNames = new List{ "VesselDragCoeff", "VesselRefArea", "VesselTermVelEst", "VesselDynPres" }; + foreach (var name in farNames) + { + var methodInfo = ReflectionUtils.getMethodByReflection( + "FerramAerospaceResearch", + "FerramAerospaceResearch.FARAPI", + name, + BindingFlags.Public | BindingFlags.Static, + new Type[] { typeof(Vessel) } + ); + if (methodInfo == null) + { + Debug.Log("MJ BUG: FAR loaded, but FerramAerospaceResearch.FARAPI has no " + name + " method. Disabling FAR"); + isLoadedFAR = false; + } + else + { + typeof(VesselState).GetField("FAR" + name, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, (FARVesselDelegate)Delegate.CreateDelegate(typeof(FARVesselDelegate), methodInfo)); + } + } + + var FARCalculateVesselAeroForcesMethodInfo = ReflectionUtils.getMethodByReflection( + "FerramAerospaceResearch", + "FerramAerospaceResearch.FARAPI", + "CalculateVesselAeroForces", + BindingFlags.Public | BindingFlags.Static, + new Type[] { typeof(Vessel), typeof(Vector3).MakeByRefType(), typeof(Vector3).MakeByRefType(), typeof(Vector3), typeof(double) } + ); + if (FARCalculateVesselAeroForcesMethodInfo == null){ + Debug.Log("MJ BUG: FAR loaded, but FerramAerospaceResearch.FARAPI has no CalculateVesselAeroForces method, disabling FAR"); + isLoadedFAR = false; + } + else + { + FARCalculateVesselAeroForces = (FARCalculateVesselAeroForcesDelegate)Delegate.CreateDelegate(typeof(FARCalculateVesselAeroForcesDelegate), FARCalculateVesselAeroForcesMethodInfo); + } + } + } + + public VesselState() + { + if (isLoadedFAR) + { + TerminalVelocityCall = TerminalVelocityFAR; + } + else + { + TerminalVelocityCall = TerminalVelocityStockKSP; + } + } + + private double last_update; + + //public static bool SupportsGimbalExtension() where T : PartModule + //{ + // return gimbalExtDict.ContainsKey(typeof(T)); + //} + // + //public static void AddGimbalExtension(GimbalExt gimbalExtension) where T : PartModule + //{ + // gimbalExtDict[typeof(T)] = gimbalExtension; + //} + public bool Update(Vessel vessel) + { + if (last_update == Planetarium.GetUniversalTime()) + return true; + + if (vessel.rootPart.rb == null) return false; //if we try to update before rigidbodies exist we spam the console with NullPointerExceptions. + + TestStuff(vessel); + + UpdateVelocityAndCoM(vessel); + + UpdateBasicInfo(vessel); + + UpdateRCSThrustAndTorque(vessel); + + enginesWrappers.Clear(); + + einfo.Update(CoM, vessel); + iinfo.Update(); + AnalyzeParts(vessel, einfo, iinfo); + + UpdateResourceRequirements(einfo, iinfo); + + ToggleRCSThrust(vessel); + + UpdateMoIAndAngularMom(vessel); + + last_update = Planetarium.GetUniversalTime();; + + return true; + } + + private void TestStuff(Vessel vessel) + { + //int partCount = vessel.parts.Count; + //for (int index = 0; index < partCount; ++index) + //{ + // if (!vessel.parts[index].DragCubes.None) + // vessel.parts[index].DragCubes.SetDragWeights(); + //} + //for (int index = 0; index < partCount; ++index) + //{ + // if (!vessel.parts[index].DragCubes.None) + // vessel.parts[index].DragCubes.SetPartOcclusion(); + //} + + //for (int index = 0; index < partCount; ++index) + //{ + // Part part = vessel.parts[index]; + // if (!part.DragCubes.None) + // part.DragCubes.SetDrag(part.dragVectorDirLocal, 0.1f); + //} + + //cube = new DragCubeList(); + //cube.ClearCubes(); + + //for (int index = 0; index < partCount; ++index) + //{ + // Part part = vessel.parts[index]; + // if (!part.DragCubes.None) + // { + // for (int face = 0; face < 6; face++) + // { + // //cube.WeightedArea[face] += part.DragCubes.WeightedArea[face]; + // cube.WeightedDrag[face] += part.DragCubes.WeightedDrag[face]; + // cube.AreaOccluded[face] += part.DragCubes.AreaOccluded[face]; + // } + // } + //} + // + //cube.SetDrag(vessel.srf_velocity, (float)vessel.mach); + // + //double dragScale = cube.AreaDrag * PhysicsGlobals.DragCubeMultiplier; + + + //SimulatedVessel simVessel = SimulatedVessel.New(vessel); + + //MechJebCore.print("KPA " + vessel.dynamicPressurekPa.ToString("F9")); + + //Vector3 localVel = vessel.GetTransform().InverseTransformDirection( vessel.srf_velocity ); + //Vector3 localVel = vessel.GetTransform().InverseTransformDirection( vessel.rigidbody.velocity + Krakensbane.GetFrameVelocity()); + + //MechJebCore.print(MuUtils.PrettyPrint(localVel)); + + //Vector3 simDrag = simVessel.Drag(localVel, + // (float)(0.0005 * vessel.atmDensity * vessel.srf_velocity.sqrMagnitude), + // (float)vessel.mach); + // + // + //Vector3 simLift = simVessel.Lift(vessel.rigidbody.velocity + Krakensbane.GetFrameVelocity(), + // (float)(0.0005 * vessel.atmDensity * vessel.srf_velocity.sqrMagnitude), + // (float)vessel.mach); + // + //dragScalar = simDrag.magnitude; + // + //liftScalar = simLift.magnitude; + + //double exposedArea = 0; + //double skinExposedArea = 0; + //double radiativeArea = 0; + //foreach (Part part in vessel.Parts) + //{ + // exposedArea += part.exposedArea; + // skinExposedArea += part.skinExposedArea; + // radiativeArea += part.radiativeArea; + // //MechJebCore.print(part.name + " " + part.exposedArea.ToString("F4") + " " + part.skinExposedArea.ToString("F4")); + //} + //MechJebCore.print(exposedArea.ToString("F2") + " " + skinExposedArea.ToString("F2") + " " + radiativeArea.ToString("F2")); + + //message = "\nPools :\n" + + // SimulatedVessel.PoolSize + " SimulatedVessel\n" + + // SimulatedPart.PoolSize + " SimulatedPart\n" + + // SimulatedParachute.PoolSize + " SimulatedParachute\n" + + // ListPool.Instance.Size + " AbsoluteVector\n" + + // ReentrySimulation.PoolSize + " ReentrySimulation\n" + + // ReentrySimulation.Result.PoolSize + " Result\n" + + // SimulatedPart.DragCubePool.Instance.Size + " DragCubePool\n" + + // FuelNode.PoolSize + " FuelNode\n"; + + //ListPool.Instance. + } + + + // TODO memo for later. egg found out that vessel.pos is actually 1 frame in the future while vessel.obt_vel is not. + // This should have changed in 1.1 + // This most likely has some impact on the code. + + + void UpdateVelocityAndCoM(Vessel vessel) + { + mass = vessel.totalMass; + CoM = vessel.CoMD; + orbitalVelocity = vessel.obt_velocity; + orbitalPosition = CoM - vessel.mainBody.position; + } + + // Calculate a bunch of simple quantities each frame. + void UpdateBasicInfo(Vessel vessel) + { + time = Planetarium.GetUniversalTime(); + deltaT = TimeWarp.fixedDeltaTime; + + //CoM = °; + up = orbitalPosition.normalized; + + Rigidbody rigidBody = vessel.rootPart.rb; + if (rigidBody != null) rootPartPos = rigidBody.position; + + north = vessel.north; + east = vessel.east; + forward = vessel.GetTransform().up; + rotationSurface = Quaternion.LookRotation(north, up); + rotationVesselSurface = Quaternion.Inverse(Quaternion.Euler(90, 0, 0) * Quaternion.Inverse(vessel.GetTransform().rotation) * rotationSurface); + + surfaceVelocity = orbitalVelocity - vessel.mainBody.getRFrmVel(CoM); + + velocityMainBodySurface = rotationSurface * surfaceVelocity; + + horizontalOrbit = Vector3d.Exclude(up, orbitalVelocity).normalized; + horizontalSurface = Vector3d.Exclude(up, surfaceVelocity).normalized; + + angularVelocity = Quaternion.Inverse(vessel.GetTransform().rotation) * vessel.rootPart.rb.angularVelocity; + + radialPlusSurface = Vector3d.Exclude(surfaceVelocity, up).normalized; + radialPlus = Vector3d.Exclude(orbitalVelocity, up).normalized; + normalPlusSurface = -Vector3d.Cross(radialPlusSurface, surfaceVelocity.normalized); + normalPlus = -Vector3d.Cross(radialPlus, orbitalVelocity.normalized); + + mach = vessel.mach; + + gravityForce = FlightGlobals.getGeeForceAtPosition(CoM); // TODO vessel.gravityForPos or vessel.gravityTrue + localg = gravityForce.magnitude; + + speedOrbital.value = orbitalVelocity.magnitude; + speedSurface.value = surfaceVelocity.magnitude; + speedVertical.value = Vector3d.Dot(surfaceVelocity, up); + speedSurfaceHorizontal.value = Vector3d.Exclude(up, surfaceVelocity).magnitude; //(velocityVesselSurface - (speedVertical * up)).magnitude; + speedOrbitHorizontal = (orbitalVelocity - (speedVertical * up)).magnitude; + + // Angle of Attack, angle between surface velocity and the ship-nose vector (KSP "up" vector) in the plane that has no ship-right/left in it + Vector3 srfProj = Vector3.ProjectOnPlane(surfaceVelocity.normalized, vessel.ReferenceTransform.right); + double tmpAoA = UtilMath.Rad2Deg * Math.Atan2(Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.forward), Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.up) ); + AoA.value = double.IsNaN(tmpAoA) || speedSurface.value < 0.01 ? 0 : tmpAoA; + + // Angle of Sideslip, angle between surface velocity and the ship-nose vector (KSP "up" vector) in the plane that has no ship-top/bottom in it (KSP "forward"/"back") + srfProj = Vector3.ProjectOnPlane(surfaceVelocity.normalized, vessel.ReferenceTransform.forward); + double tmpAoS = UtilMath.Rad2Deg * Math.Atan2(Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.right), Vector3.Dot(srfProj.normalized, vessel.ReferenceTransform.up) ); + AoS.value = double.IsNaN(tmpAoS) || speedSurface.value < 0.01 ? 0 : tmpAoS; + + // Displacement Angle, angle between surface velocity and the ship-nose vector (KSP "up" vector) -- ignores roll of the craft (0 to 180 degrees) + double tempAoD = UtilMath.Rad2Deg * Math.Acos(MuUtils.Clamp(Vector3.Dot(vessel.ReferenceTransform.up, surfaceVelocity.normalized), -1, 1)); + displacementAngle.value = double.IsNaN(tempAoD) || speedSurface.value < 0.01 ? 0 : tempAoD; + + vesselHeading.value = rotationVesselSurface.eulerAngles.y; + vesselPitch.value = (rotationVesselSurface.eulerAngles.x > 180) ? (360.0 - rotationVesselSurface.eulerAngles.x) : -rotationVesselSurface.eulerAngles.x; + vesselRoll.value = (rotationVesselSurface.eulerAngles.z > 180) ? (rotationVesselSurface.eulerAngles.z - 360.0) : rotationVesselSurface.eulerAngles.z; + + altitudeASL.value = vessel.mainBody.GetAltitude(CoM); + + surfaceAltitudeASL = vessel.mainBody.pqsController != null ? vessel.pqsAltitude : 0d; + if (vessel.mainBody.ocean && surfaceAltitudeASL < 0) surfaceAltitudeASL = 0; + altitudeTrue.value = altitudeASL - surfaceAltitudeASL; + + // altitudeBottom will be recomputed if someone requests it. + altitudeBottomIsCurrent = false; + + double atmosphericPressure = FlightGlobals.getStaticPressure(altitudeASL, vessel.mainBody); + //if (atmosphericPressure < vessel.mainBody.atmosphereMultiplier * 1e-6) atmosphericPressure = 0; + double temperature = FlightGlobals.getExternalTemperature(altitudeASL); + atmosphericDensity = FlightGlobals.getAtmDensity(atmosphericPressure, temperature); + atmosphericDensityGrams = atmosphericDensity * 1000; + if (isLoadedFAR) + { + dynamicPressure = FARVesselDynPres(vessel) * 1000; + } + else + { + dynamicPressure = vessel.dynamicPressurekPa * 1000; + } + if (dynamicPressure > maxDynamicPressure) + maxDynamicPressure = dynamicPressure; + freeMolecularAerothermalFlux = 0.5 * atmosphericDensity * speedSurface * speedSurface * speedSurface; + + + speedOfSound = vessel.speedOfSound; + + orbitApA.value = vessel.orbit.ApA; + orbitPeA.value = vessel.orbit.PeA; + orbitPeriod.value = vessel.orbit.period; + orbitTimeToAp.value = vessel.orbit.timeToAp; + orbitTimeToPe.value = vessel.orbit.timeToPe; + + if (!vessel.LandedOrSplashed) + { + orbitLAN.value = vessel.orbit.LAN; + } + else + { + orbitLAN.value = -(vessel.transform.position - vessel.mainBody.transform.position).AngleInPlane(Planetarium.Zup.Z, Planetarium.Zup.X); + orbitTimeToAp.value = 0; + } + + orbitArgumentOfPeriapsis.value = vessel.orbit.argumentOfPeriapsis; + orbitInclination.value = vessel.orbit.inclination; + orbitEccentricity.value = vessel.orbit.eccentricity; + orbitSemiMajorAxis.value = vessel.orbit.semiMajorAxis; + latitude.value = vessel.mainBody.GetLatitude(CoM); + longitude.value = MuUtils.ClampDegrees180(vessel.mainBody.GetLongitude(CoM)); + + if (vessel.mainBody != Planetarium.fetch.Sun) + { + Vector3d delta = vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() + 1) - vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() - 1); + Vector3d plUp = Vector3d.Cross(vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime()) - vessel.mainBody.referenceBody.getPositionAtUT(Planetarium.GetUniversalTime()), vessel.mainBody.getPositionAtUT(Planetarium.GetUniversalTime() + vessel.mainBody.orbit.period / 4) - vessel.mainBody.referenceBody.getPositionAtUT(Planetarium.GetUniversalTime() + vessel.mainBody.orbit.period / 4)).normalized; + angleToPrograde = MuUtils.ClampDegrees360((((vessel.orbit.inclination > 90) || (vessel.orbit.inclination < -90)) ? 1 : -1) * ((Vector3)up).AngleInPlane(plUp, delta)); + } + else + { + angleToPrograde = 0; + } + + mainBody = vessel.mainBody; + + radius = orbitalPosition.magnitude; + + vesselRef = vessel; + } + + void UpdateRCSThrustAndTorque(Vessel vessel) + { + rcsThrustAvailable.Reset(); + rcsTorqueAvailable.Reset(); + + //torqueRcs.Reset(); + + if (!vessel.ActionGroups[KSPActionGroup.RCS]) + return; + + var rcsbal = vessel.GetMasterMechJeb().rcsbal; + if (rcsbal.enabled) + { + Vector3d rot = Vector3d.zero; + for (int i = 0; i < Vector6.Values.Length; i++) + { + Vector6.Direction dir6 = Vector6.Values[i]; + Vector3d dir = Vector6.directions[(int) dir6]; + double[] throttles; + List thrusters; + rcsbal.GetThrottles(dir, out throttles, out thrusters); + if (throttles != null) + { + for (int j = 0; j < throttles.Length; j++) + { + if (throttles[j] > 0) + { + Vector3d force = thrusters[j].GetThrust(dir, rot); + rcsThrustAvailable.Add( + vessel.GetTransform().InverseTransformDirection(dir * Vector3d.Dot(force * throttles[j], dir))); + // Are we missing an rcsTorqueAvailable calculation here? + } + } + } + } + } + + Vector3d movingCoM = vessel.CurrentCoM; + + for (int i = 0; i < vessel.parts.Count; i++) + { + Part p = vessel.parts[i]; + for (int m = 0; m < p.Modules.Count; m++) + { + ModuleRCS rcs = p.Modules[m] as ModuleRCS; + + if (rcs == null) + continue; + + //Vector3 pos; + //Vector3 neg; + //rcs.GetPotentialTorque(out pos, out neg); + + //torqueRcs.Add(pos); + //torqueRcs.Add(neg); + + //if (rcsbal.enabled) + // continue; + + if (!p.ShieldedFromAirstream && rcs.rcsEnabled && rcs.isEnabled && !rcs.isJustForShow) + { + Vector3 attitudeControl = new Vector3(rcs.enablePitch ? 1 : 0, rcs.enableRoll ? 1 : 0, rcs.enableYaw ? 1 : 0); + + Vector3 translationControl = new Vector3(rcs.enableX ? 1 : 0f, rcs.enableZ ? 1 : 0, rcs.enableY ? 1 : 0); + for (int j = 0; j < rcs.thrusterTransforms.Count; j++) + { + Transform t = rcs.thrusterTransforms[j]; + Vector3d thrusterPosition = t.position - movingCoM; + + Vector3d thrustDirection = rcs.useZaxis ? -t.forward : -t.up; + + float power = rcs.thrusterPower; + + if (FlightInputHandler.fetch.precisionMode) + { + if (rcs.useLever) + { + float lever = rcs.GetLeverDistance(t, thrustDirection, movingCoM); + if (lever > 1) + { + power = power / lever; + } + } + else + { + power *= rcs.precisionFactor; + } + } + + Vector3d thrusterThrust = thrustDirection * power; + + // This is a cheap hack to get rcsTorque with the RCS balancer active. + if (!rcsbal.enabled) + { + rcsThrustAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterThrust), translationControl)); + } + Vector3d thrusterTorque = Vector3.Cross(thrusterPosition, thrusterThrust); + + // Convert in vessel local coordinate + rcsTorqueAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterTorque), attitudeControl)); + //rcsThrustAvailable.Add(Vector3.Scale(vessel.GetTransform().InverseTransformDirection(thrusterThrust), translationControl)); + } + } + } + } + } + + + [GeneralInfoItem("#MechJeb_RCSTranslation", InfoItem.Category.Vessel, showInEditor = true)]//RCS Translation + public void RCSTranslation() + { + GUILayout.BeginVertical(); + GUILayout.Label(Localizer.Format("#MechJeb_RCSTranslation"));//"RCS Translation" + GUILayout.BeginHorizontal(); + GUILayout.Label("Pos", GUILayout.ExpandWidth(true));// + GUILayout.Label(MuUtils.PrettyPrint(rcsThrustAvailable.positive), GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("Neg", GUILayout.ExpandWidth(true));// + GUILayout.Label(MuUtils.PrettyPrint(rcsThrustAvailable.negative), GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + } + + [GeneralInfoItem("#MechJeb_RCSTorque", InfoItem.Category.Vessel, showInEditor = true)]//RCS Torque + public void RCSTorque() + { + GUILayout.BeginVertical(); + GUILayout.Label(Localizer.Format("#MechJeb_RCSTorque"));//"RCS Torque" + GUILayout.BeginHorizontal(); + GUILayout.Label("Pos", GUILayout.ExpandWidth(true)); + GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueAvailable.positive), GUILayout.ExpandWidth(false)); + //GUILayout.Label(MuUtils.PrettyPrint(torqueRcs.positive), GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("Neg", GUILayout.ExpandWidth(true)); + GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueAvailable.negative), GUILayout.ExpandWidth(false)); + //GUILayout.Label(MuUtils.PrettyPrint(torqueRcs.negative), GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + } + + // Loop over all the parts in the vessel and calculate some things. + void AnalyzeParts(Vessel vessel, EngineInfo einfo, IntakeInfo iinfo) + { + parachutes.Clear(); + parachuteDeployed = false; + + torqueAvailable = Vector3d.zero; + + Vector6 torqueReactionSpeed6 = new Vector6(); + + torqueReactionWheel.Reset(); + torqueControlSurface.Reset(); + torqueGimbal.Reset(); + torqueOthers.Reset(); + + pureDragV = Vector3d.zero; + pureLiftV = Vector3d.zero; + + if (isLoadedFAR) + { + dragCoef = FARVesselDragCoeff(vessel); + areaDrag = FARVesselRefArea(vessel) * dragCoef * PhysicsGlobals.DragMultiplier; + } + else + { + dragCoef = 0; + areaDrag = 0; + } + + CoL = Vector3d.zero; + CoLScalar = 0; + + CoT = Vector3d.zero; + DoT = Vector3d.zero; + CoTScalar = 0; + + for (int i = 0; i < vessel.parts.Count; i++) + { + Part p = vessel.parts[i]; + + Vector3d partPureLift = Vector3.zero; + Vector3d partPureDrag = -p.dragVectorDir * p.dragScalar; + + if (!p.hasLiftModule) + { + Vector3 bodyLift = p.transform.rotation * (p.bodyLiftScalar * p.DragCubes.LiftForce); + partPureLift = Vector3.ProjectOnPlane(bodyLift, -p.dragVectorDir); + + //double liftScale = bodyLift.magnitude; + } + + //#warning while this works for real time it does not help for simulations. Need to get a coef even while in vacum + //if (p.dynamicPressurekPa > 0 && PhysicsGlobals.DragMultiplier > 0) + // dragCoef += p.simDragScalar / (p.dynamicPressurekPa * PhysicsGlobals.DragMultiplier); + + if (!isLoadedFAR) + { + dragCoef += p.DragCubes.DragCoeff; + areaDrag += p.DragCubes.AreaDrag * PhysicsGlobals.DragCubeMultiplier * PhysicsGlobals.DragMultiplier; + } + + for (int index = 0; index < vesselStatePartExtensions.Count; index++) + { + VesselStatePartExtension vspe = vesselStatePartExtensions[index]; + vspe(p); + } + + engines.Clear(); + + for (int m = 0; m < p.Modules.Count; m++) + { + PartModule pm = p.Modules[m]; + if (!pm.isEnabled) + { + continue; + } + + ModuleLiftingSurface ls = pm as ModuleLiftingSurface; + if (ls != null) + { + partPureLift += ls.liftForce; + partPureDrag += ls.dragForce; + } + + ModuleReactionWheel rw = pm as ModuleReactionWheel; + if (rw != null) + { + Vector3 pos; + Vector3 neg; + rw.GetPotentialTorque(out pos, out neg); + + // GetPotentialTorque reports the same value for pos & neg on ModuleReactionWheel + torqueReactionWheel.Add(pos); + torqueReactionWheel.Add(-neg); + } + else if (pm is ModuleEngines) + { + var moduleEngines = pm as ModuleEngines; + + if (!engines.ContainsKey(moduleEngines)) + engines.Add(moduleEngines, null); + } + else if (pm is ModuleResourceIntake) + { + iinfo.addIntake(pm as ModuleResourceIntake); + } + else if (pm is ModuleParachute) + { + ModuleParachute parachute = pm as ModuleParachute; + + parachutes.Add(parachute); + if (parachute.deploymentState == ModuleParachute.deploymentStates.DEPLOYED || + parachute.deploymentState == ModuleParachute.deploymentStates.SEMIDEPLOYED) + { + parachuteDeployed = true; + } + } + else if (pm is ModuleControlSurface) // also does ModuleAeroSurface + { + ModuleControlSurface cs = (pm as ModuleControlSurface); + + //if (p.ShieldedFromAirstream || cs.deploy) + // continue; + + Vector3 ctrlTorquePos; + Vector3 ctrlTorqueNeg; + + cs.GetPotentialTorque(out ctrlTorquePos, out ctrlTorqueNeg); + + torqueControlSurface.Add(ctrlTorquePos); + torqueControlSurface.Add(ctrlTorqueNeg); + + torqueReactionSpeed6.Add(Mathf.Abs(cs.ctrlSurfaceRange) / cs.actuatorSpeed * Vector3d.Max(ctrlTorquePos.Abs(), ctrlTorqueNeg.Abs())); + } + else if (pm is ModuleGimbal) + { + ModuleGimbal g = (pm as ModuleGimbal); + + if (g.engineMultsList == null) + g.CreateEngineList(); + + for (int j = 0; j < g.engineMultsList.Count; j++) + { + var engs = g.engineMultsList[j]; + for (int k = 0; k < engs.Count; k++) + { + engines[engs[k].Key] = g; + } + } + + Vector3 pos; + Vector3 neg; + g.GetPotentialTorque(out pos, out neg); + + // GetPotentialTorque reports the same value for pos & neg on ModuleGimbal + + torqueGimbal.Add(pos); + torqueGimbal.Add(-neg); + + if (g.useGimbalResponseSpeed) + torqueReactionSpeed6.Add((Mathf.Abs(g.gimbalRange) / g.gimbalResponseSpeed) * Vector3d.Max(pos.Abs(), neg.Abs())); + } + else if (pm is ModuleRCS) + { + // Already handled earlier. Prevent the generic ITorqueProvider to catch it + } + else if (pm is ITorqueProvider) // All mod that supports it. Including FAR + { + ITorqueProvider tp = pm as ITorqueProvider; + + Vector3 pos; + Vector3 neg; + tp.GetPotentialTorque(out pos, out neg); + + torqueOthers.Add(pos); + torqueOthers.Add(neg); + } + + for (int index = 0; index < vesselStatePartModuleExtensions.Count; index++) + { + VesselStatePartModuleExtension vspme = vesselStatePartModuleExtensions[index]; + vspme(pm); + } + } + + foreach (KeyValuePair engine in engines) + { + einfo.AddNewEngine(engine.Key, engine.Value, enginesWrappers, ref CoT, ref DoT, ref CoTScalar); + if (isLoadedRealFuels && RFullageSetField != null && RFignitionsField != null && RFullageField != null) + { + einfo.CheckUllageStatus(engine.Key); + } + } + + pureDragV += partPureDrag; + pureLiftV += partPureLift; + + Vector3d partAeroForce = partPureDrag + partPureLift; + + Vector3d partDrag = Vector3d.Project(partAeroForce, -surfaceVelocity); + Vector3d partLift = partAeroForce - partDrag; + + double partLiftScalar = partLift.magnitude; + + if (p.rb != null && partLiftScalar > 0.01) + { + CoLScalar += partLiftScalar; + CoL += ((Vector3d)p.rb.worldCenterOfMass + (Vector3d)(p.partTransform.rotation * p.CoLOffset)) * partLiftScalar; + } + } + + torqueAvailable += Vector3d.Max(torqueReactionWheel.positive, torqueReactionWheel.negative); + + //torqueAvailable += Vector3d.Max(torqueRcs.positive, torqueRcs.negative); + + torqueAvailable += Vector3d.Max(rcsTorqueAvailable.positive, rcsTorqueAvailable.negative); + + torqueAvailable += Vector3d.Max(torqueControlSurface.positive, torqueControlSurface.negative); + + torqueAvailable += Vector3d.Max(torqueGimbal.positive, torqueGimbal.negative); + + torqueAvailable += Vector3d.Max(torqueOthers.positive, torqueOthers.negative); // Mostly FAR + + torqueDiffThrottle = Vector3d.Max(einfo.torqueDiffThrottle.positive, einfo.torqueDiffThrottle.negative); + torqueDiffThrottle.y = 0; + + if (torqueAvailable.sqrMagnitude > 0) + { + torqueReactionSpeed = Vector3d.Max(torqueReactionSpeed6.positive, torqueReactionSpeed6.negative); + torqueReactionSpeed.Scale(torqueAvailable.InvertNoNaN()); + } + else + { + torqueReactionSpeed = Vector3d.zero; + } + + thrustVectorMaxThrottle = einfo.thrustMax; + thrustVectorMinThrottle = einfo.thrustMin; + thrustVectorLastFrame = einfo.thrustCurrent; + + if (CoTScalar > 0) + CoT = CoT / CoTScalar; + DoT = DoT.normalized; + + if (CoLScalar > 0) + CoL = CoL / CoLScalar; + + Vector3d liftDir = -Vector3d.Cross(vessel.transform.right, -surfaceVelocity.normalized); + + if (isLoadedFAR && !vessel.packed && surfaceVelocity != Vector3d.zero) + { + Vector3 farForce = Vector3.zero; + Vector3 farTorque = Vector3.zero; + FARCalculateVesselAeroForces(vessel, out farForce, out farTorque, surfaceVelocity, altitudeASL); + + Vector3d farDragVector = Vector3d.Dot(farForce, -surfaceVelocity.normalized) * -surfaceVelocity.normalized; + drag = farDragVector.magnitude / mass; + dragUp = Vector3d.Dot(farDragVector, up) / mass; + pureDragV = farDragVector; + pureDrag = drag; + + Vector3d farLiftVector = Vector3d.Dot(farForce, liftDir) * liftDir; + lift = farLiftVector.magnitude / mass; + liftUp = Vector3d.Dot(farForce, up) / mass; // Use farForce instead of farLiftVector to match code for stock aero + pureLiftV = farLiftVector; + pureLift = lift; + } + else + { + pureDragV = pureDragV / mass; + pureLiftV = pureLiftV / mass; + + pureDrag = pureDragV.magnitude; + + pureLift = pureLiftV.magnitude; + + Vector3d force = pureDragV + pureLiftV; + // Drag is the part (pureDrag + PureLift) applied opposite of the surface vel + drag = Vector3d.Dot(force, -surfaceVelocity.normalized); + // DragUp is the part (pureDrag + PureLift) applied in the "Up" direction + dragUp = Vector3d.Dot(pureDragV, up); + // Lift is the part (pureDrag + PureLift) applied in the "Lift" direction + lift = Vector3d.Dot(force, liftDir); + // LiftUp is the part (pureDrag + PureLift) applied in the "Up" direction + liftUp = Vector3d.Dot(force, up); + } + + maxEngineResponseTime = einfo.maxResponseTime; + } + + [GeneralInfoItem("#MechJeb_Torque", InfoItem.Category.Vessel, showInEditor = true)]//Torque + public void TorqueCompare() + { + var reactionTorque = Vector3d.Max(torqueReactionWheel.positive, torqueReactionWheel.negative); + //var rcsTorque = Vector3d.Max(torqueRcs.positive, torqueRcs.negative); + + var rcsTorqueMJ = Vector3d.Max(rcsTorqueAvailable.positive, rcsTorqueAvailable.negative); + + var controlTorque = Vector3d.Max(torqueControlSurface.positive, torqueControlSurface.negative); + var gimbalTorque = Vector3d.Max(torqueGimbal.positive, torqueGimbal.negative); + var diffTorque = Vector3d.Max(einfo.torqueDiffThrottle.positive, einfo.torqueDiffThrottle.negative); + diffTorque.y = 0; + var othersTorque = Vector3d.Max(torqueOthers.positive, torqueOthers.negative); + + GUILayout.Label("Torque sources", GuiUtils.LabelNoWrap); + GUILayout.BeginHorizontal(); + + GUILayout.BeginVertical(); + GUILayout.Label("ReactionWheel", GuiUtils.LabelNoWrap); + + GUILayout.Label("RCS", GuiUtils.LabelNoWrap); + //GUILayout.Label("RCS MJ", GuiUtils.LabelNoWrap); + + GUILayout.Label("ControlSurface", GuiUtils.LabelNoWrap); + GUILayout.Label("Gimbal", GuiUtils.LabelNoWrap); + GUILayout.Label("Diff Throttle", GuiUtils.LabelNoWrap); + GUILayout.Label("Others (FAR)", GuiUtils.LabelNoWrap); + GUILayout.EndVertical(); + + GUILayout.BeginVertical(); + GUILayout.Label(MuUtils.PrettyPrint(reactionTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + //GUILayout.Label(MuUtils.PrettyPrint(rcsTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + + GUILayout.Label(MuUtils.PrettyPrint(rcsTorqueMJ), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + + GUILayout.Label(MuUtils.PrettyPrint(controlTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + GUILayout.Label(MuUtils.PrettyPrint(gimbalTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + GUILayout.Label(MuUtils.PrettyPrint(diffTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + GUILayout.Label(MuUtils.PrettyPrint(othersTorque), GuiUtils.LabelNoWrap, GUILayout.ExpandWidth(false)); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + } + + void UpdateResourceRequirements(EngineInfo einfo, IntakeInfo iinfo) + { + // Convert the resource information from the einfo and iinfo format + // to the more useful ResourceInfo format. + ResourceInfo.Release(resources.Values); + resources.Clear(); + foreach (var info in einfo.resourceRequired) + { + int id = info.Key; + var req = info.Value; + resources[id] = ResourceInfo.Borrow( + PartResourceLibrary.Instance.GetDefinition(id), + req.requiredLastFrame, + req.requiredAtMaxThrottle, + iinfo.getIntakes(id), + vesselRef); + } + + int intakeAirId = PartResourceLibrary.Instance.GetDefinition("IntakeAir").id; + intakeAir = 0; + intakeAirNeeded = 0; + intakeAirAtMax = 0; + intakeAirAllIntakes = 0; + if (resources.ContainsKey(intakeAirId)) + { + intakeAir = resources[intakeAirId].intakeProvided; + intakeAirAllIntakes = resources[intakeAirId].intakeAvailable; + intakeAirNeeded = resources[intakeAirId].required; + intakeAirAtMax = resources[intakeAirId].requiredAtMaxThrottle; + } + } + + // Decide whether to control the RCS thrusters from the main throttle + void ToggleRCSThrust(Vessel vessel) + { + if (thrustVectorMaxThrottle.magnitude == 0 && vessel.ActionGroups[KSPActionGroup.RCS]) + { + rcsThrust = true; + thrustVectorMaxThrottle += (Vector3d)(vessel.transform.up) * rcsThrustAvailable.down; + } + else + { + rcsThrust = false; + } + } + + void UpdateMoIAndAngularMom(Vessel vessel) + { + // stock code + fix + Matrix4x4 tensor = Matrix4x4.zero; + Matrix4x4 partTensor = Matrix4x4.identity; + Matrix4x4 inertiaMatrix = Matrix4x4.identity; + Matrix4x4 productMatrix = Matrix4x4.identity; + + QuaternionD invQuat = QuaternionD.Inverse(vessel.ReferenceTransform.rotation); + Transform vesselReferenceTransform = vessel.ReferenceTransform; + int count = vessel.parts.Count; + for (int i = 0; i < count; ++i) + { + Part part = vessel.parts[i]; + if (part.rb != null) + { + KSPUtil.ToDiagonalMatrix2(part.rb.inertiaTensor, ref partTensor); + + Quaternion rot = (Quaternion)invQuat * part.transform.rotation * part.rb.inertiaTensorRotation; + Quaternion inv = Quaternion.Inverse(rot); + + Matrix4x4 rotMatrix = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one); + Matrix4x4 invMatrix = Matrix4x4.TRS(Vector3.zero, inv, Vector3.one); + + KSPUtil.Add(ref tensor, rotMatrix * partTensor * invMatrix); + Vector3 position = vesselReferenceTransform.InverseTransformDirection(part.rb.position - vessel.CoMD); + + KSPUtil.ToDiagonalMatrix2(part.rb.mass * position.sqrMagnitude, ref inertiaMatrix); + KSPUtil.Add(ref tensor, inertiaMatrix); + + KSPUtil.OuterProduct2(position, -part.rb.mass * position, ref productMatrix); + KSPUtil.Add(ref tensor, productMatrix); + } + } + //MoI = vessel.MOI = KSPUtil.Diag(tensor); + MoI = KSPUtil.Diag(tensor); + angularMomentum = Vector3d.zero; + angularMomentum.x = (float)(MoI.x * vessel.angularVelocity.x); + angularMomentum.y = (float)(MoI.y * vessel.angularVelocity.y); + angularMomentum.z = (float)(MoI.z * vessel.angularVelocity.z); + + angularVelocityAvg.value = angularVelocity; + } + + [ValueInfoItem("#MechJeb_TerminalVelocity", InfoItem.Category.Vessel, format = ValueInfoItem.SI, units = "m/s")]//Terminal velocity + public double TerminalVelocity() + { + return TerminalVelocityCall(); + } + + public DTerminalVelocity TerminalVelocityCall; + + public double TerminalVelocityStockKSP() + { + if (mainBody == null || altitudeASL > mainBody.RealMaxAtmosphereAltitude()) return double.PositiveInfinity; + + return Math.Sqrt((2000 * mass * localg) / (areaDrag * vesselRef.atmDensity)); + } + + public double TerminalVelocityFAR() + { + return FARVesselTermVelEst(vesselRef); + } + + public double ThrustAccel(double throttle) + { + return (1.0 - throttle) * minThrustAccel + throttle * maxThrustAccel; + } + + public double HeadingFromDirection(Vector3d dir) + { + return MuUtils.ClampDegrees360(UtilMath.Rad2Deg * Math.Atan2(Vector3d.Dot(dir, east), Vector3d.Dot(dir, north))); + } + + // Altitude of bottom of craft, only calculated when requested because it is a bit expensive + private bool altitudeBottomIsCurrent = false; + private double _altitudeBottom; + [ValueInfoItem("#MechJeb_Altitude_bottom", InfoItem.Category.Surface, format = ValueInfoItem.SI, siSigFigs = 6, siMaxPrecision = 0, units = "m")]//Altitude (bottom) + public double altitudeBottom + { + get + { + if (!altitudeBottomIsCurrent) + { + _altitudeBottom = ComputeVesselBottomAltitude(vesselRef); + altitudeBottomIsCurrent = true; + } + return _altitudeBottom; + } + } + + double ComputeVesselBottomAltitude(Vessel vessel) + { + if (vessel == null || vessel.rootPart.rb == null) return 0; + double ret = altitudeTrue; + for (int i = 0; i < vessel.parts.Count; i++) + { + Part p = vessel.parts[i]; + if (p.collider != null) + { + /*Vector3d bottomPoint = p.collider.ClosestPointOnBounds(vesselmainBody.position); + double partBottomAlt = vesselmainBody.GetAltitude(bottomPoint) - surfaceAltitudeASL; + _altitudeBottom = Math.Max(0, Math.Min(_altitudeBottom, partBottomAlt));*/ + Bounds bounds = p.collider.bounds; + Vector3 extents = bounds.extents; + float partRadius = Mathf.Max(extents[0], Mathf.Max(extents[1], extents[2])); + double partAltitudeBottom = vessel.mainBody.GetAltitude(bounds.center) - partRadius - surfaceAltitudeASL; + partAltitudeBottom = Math.Max(0, partAltitudeBottom); + if (partAltitudeBottom < ret) + { + ret = partAltitudeBottom; + } + } + } + return ret; + } + + // Used during the vesselState constructor; distilled to other + // variables later. + public class EngineInfo + { + public Vector3d thrustCurrent = new Vector3d(); // thrust at throttle achieved last frame + public Vector3d thrustMax = new Vector3d(); // thrust at full throttle + public Vector3d thrustMin = new Vector3d(); // thrust at zero throttle + public double maxResponseTime = 0; + public Vector6 torqueDiffThrottle = new Vector6(); + // lowestUllage is always VeryStable without RealFuels installed + public UllageState lowestUllage = UllageState.VeryStable; + + public struct FuelRequirement + { + public double requiredLastFrame; + public double requiredAtMaxThrottle; + } + public Dictionary resourceRequired = new Dictionary(); + + private Vector3d CoM; + private float atmP0; // pressure now + private float atmP1; // pressure after one timestep + private Queue rotSave = new Queue(); + + public void Update(Vector3d c, Vessel vessel) + { + thrustCurrent = Vector3d.zero; + thrustMax = Vector3d.zero; + thrustMin = Vector3d.zero; + maxResponseTime = 0; + + torqueDiffThrottle.Reset(); + + resourceRequired.Clear(); + + lowestUllage = UllageState.VeryStable; + + CoM = c; + + atmP0 = (float)(vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres); + float alt1 = (float)(vessel.altitude + TimeWarp.fixedDeltaTime * vessel.verticalSpeed); + atmP1 = (float)(FlightGlobals.getStaticPressure(alt1) * PhysicsGlobals.KpaToAtmospheres); + } + + public void CheckUllageStatus(ModuleEngines e) + { + // we report stable ullage for an unstable engine which is throttled up, so we let RF kill it + // instead of having MJ throttle it down. + if ((e.getFlameoutState) || (!e.EngineIgnited) || (!e.isEnabled) || (e.requestedThrottle > 0.0F)) + { + return; + } + + bool? ullage; + try + { + ullage = RFullageField.GetValue(e) as bool?; + } + catch (ArgumentException e1) + { + Debug.Log("MechJeb BUG ArgumentError thrown while getting ullage from RealFuels, ullage integration disabled: " + e1.Message); + RFullageField = null; + return; + } + + if (ullage == null) + { + Debug.Log("MechJeb BUG: getting ullage from RealFuels casted to null, ullage status likely broken"); + return; + } + + if (ullage == false) + { + return; + } + + /* ullage is 'stable' if the engine has no ignitions left */ + int? ignitions; + try + { + ignitions = RFignitionsField.GetValue(e) as int?; + } + catch (ArgumentException e2) + { + Debug.Log("MechJeb BUG ArgumentError thrown while getting ignitions from RealFuels, ullage integration disabled: " + e2.Message); + RFignitionsField = null; + return; + } + + if (ignitions == null) + { + Debug.Log("MechJeb BUG: getting ignitions from RealFuels casted to null, ullage status likely broken"); + return; + } + + /* -1 => infinite ignitions; 0 => no ignitions left; 1+ => ignitions remaining */ + if (ignitions == 0) + { + return; + } + + // finally we have an ignitable engine (that isn't already ignited), so check its propellant status + + // need to call RFullageSet to get the UllageSet then call GetUllageStability on that. + // then need to get all the constants off of UllageSimulator + double propellantStability; + + try + { + var ullageSet = RFullageSetField.GetValue(e); + if (ullageSet == null) + { + Debug.Log("MechJeb BUG: getting propellantStatus from RealFuels casted to null, ullage status likely broken"); + return; + } + try + { + propellantStability = (double) RFGetUllageStabilityMethod.Invoke(ullageSet, new object[0]); + } + catch (Exception e4) + { + Debug.Log("MechJeb BUG Exception thrown while calling GetUllageStability from RealFuels, ullage integration disabled: " + e4.Message); + RFullageSetField = null; + return; + } + } + catch (Exception e3) + { + Debug.Log("MechJeb BUG Exception thrown while getting ullageSet from RealFuels, ullage integration disabled: " + e3.Message); + RFullageSetField = null; + return; + } + + UllageState propellantState; + + if (propellantStability >= RFveryStableValue) + propellantState = UllageState.VeryStable; + else if (propellantStability >= RFstableValue) + propellantState = UllageState.Stable; + else if (propellantStability >= RFriskyValue) + propellantState = UllageState.Risky; + else if (propellantStability >= RFveryRiskyValue) + propellantState = UllageState.VeryRisky; + else + propellantState = UllageState.Unstable; + + if (propellantState < lowestUllage) + { + lowestUllage = propellantState; + } + } + + public void AddNewEngine(ModuleEngines e, ModuleGimbal gimbal, List enginesWrappers, ref Vector3d CoT, ref Vector3d DoT, ref double CoTScalar) + { + if ((!e.EngineIgnited) || (!e.isEnabled)) + { + return; + } + + // Compute the resource requirement at full thrust. + // mdot = maxthrust / (Isp * g0) in tonnes per second + // udot = mdot / mixdensity in units per second of propellant per ratio unit + // udot * ratio_i : units per second of propellant i + // Choose the worse Isp between now and after one timestep. + float Isp0 = e.atmosphereCurve.Evaluate(atmP0); + float Isp1 = e.atmosphereCurve.Evaluate(atmP1); + float Isp = Mathf.Min(Isp0, Isp1); + + for (int i = 0; i < e.propellants.Count; i++) + { + Propellant propellant = e.propellants[i]; + double maxreq = e.maxFuelFlow * propellant.ratio; + addResource(propellant.id, propellant.currentRequirement, maxreq); + } + + if (e.isOperational) + { + float thrustLimiter = e.thrustPercentage / 100f; + + double maxThrust = e.maxFuelFlow * e.flowMultiplier * Isp * e.g; + double minThrust = e.minFuelFlow * e.flowMultiplier * Isp * e.g; + + // RealFuels engines reports as operational even when they are shutdown + // REMOVED: this definitively screws up the 1kN thruster in RO/RF and sets minThrust/maxThrust + // to zero when the engine is just throttled down -- which screws up suicide burn calcs, etc. + // if (e.finalThrust == 0f && minThrust > 0f) + // minThrust = maxThrust = 0; + + //MechJebCore.print(maxThrust.ToString("F2") + " " + minThrust.ToString("F2") + " " + e.minFuelFlow.ToString("F2") + " " + e.maxFuelFlow.ToString("F2") + " " + e.flowMultiplier.ToString("F2") + " " + Isp.ToString("F2") + " " + thrustLimiter.ToString("F3")); + + double eMaxThrust = minThrust + (maxThrust - minThrust) * thrustLimiter; + double eMinThrust = e.throttleLocked ? eMaxThrust : minThrust; + double eCurrentThrust = e.finalThrust; + + rotSave.Clear(); + + // Used for Diff Throttle + Vector3d constantForce = Vector3d.zero; + Vector3d maxVariableForce = Vector3d.zero; + Vector3d constantTorque = Vector3d.zero; + Vector3d maxVariableTorque = Vector3d.zero; + double currentMaxThrust = maxThrust; + double currentMinThrust = minThrust; + + if (e.throttleLocked) + { + currentMaxThrust *= thrustLimiter; + currentMinThrust = currentMaxThrust; + } + + // Reset gimbals to default rotation + if (gimbal != null && !gimbal.gimbalLock) + { + rotSave.Clear(); + for (int i = 0; i < gimbal.gimbalTransforms.Count; i++) + { + Transform gimbalTransform = gimbal.gimbalTransforms[i]; + rotSave.Enqueue(gimbalTransform.localRotation); + gimbalTransform.localRotation = gimbal.initRots[i]; + } + } + + for (int i = 0; i < e.thrustTransforms.Count; i++) + { + var transform = e.thrustTransforms[i]; + // The rotation makes a +z vector point in the direction that molecules are ejected + // from the engine. The resulting thrust force is in the opposite direction. + Vector3d thrustDirectionVector = -transform.forward; + + double cosineLosses = Vector3d.Dot(thrustDirectionVector, e.part.vessel.GetTransform().up); + var thrustTransformMultiplier = e.thrustTransformMultipliers[i]; + var tCurrentThrust = eCurrentThrust * thrustTransformMultiplier; + + thrustCurrent += tCurrentThrust * cosineLosses * thrustDirectionVector; + thrustMax += eMaxThrust * cosineLosses * thrustDirectionVector * thrustTransformMultiplier; + thrustMin += eMinThrust * cosineLosses * thrustDirectionVector * thrustTransformMultiplier; + + CoT += tCurrentThrust * (Vector3d)transform.position; + DoT -= tCurrentThrust * thrustDirectionVector; + CoTScalar += tCurrentThrust; + + Quaternion inverseVesselRot = e.part.vessel.ReferenceTransform.rotation.Inverse(); + Vector3d thrust_dir = inverseVesselRot * thrustDirectionVector; + Vector3d pos = inverseVesselRot * (transform.position - CoM); + + maxVariableForce += (currentMaxThrust - currentMinThrust) * thrust_dir * thrustTransformMultiplier; + constantForce += currentMinThrust * thrust_dir * thrustTransformMultiplier; + maxVariableTorque += (currentMaxThrust - currentMinThrust) * thrustTransformMultiplier * Vector3d.Cross(pos, thrust_dir); + constantTorque += currentMinThrust * thrustTransformMultiplier * Vector3d.Cross(pos, thrust_dir); + + if (!e.throttleLocked) + { + torqueDiffThrottle.Add(Vector3d.Cross(pos, thrust_dir) * (float)(maxThrust - minThrust) * thrustTransformMultiplier); + } + } + + enginesWrappers.Add(new EngineWrapper(e, constantForce, maxVariableForce, constantTorque, maxVariableTorque)); + + // Restore gimbals rotation + if (gimbal != null && !gimbal.gimbalLock) + { + for (int i = 0; i < gimbal.gimbalTransforms.Count; i++) + { + gimbal.gimbalTransforms[i].localRotation = rotSave.Dequeue(); + } + } + + if (e.useEngineResponseTime) + { + double responseTime = 1.0 / Math.Min(e.engineAccelerationSpeed, e.engineDecelerationSpeed); + if (responseTime > maxResponseTime) maxResponseTime = responseTime; + } + } + } + + private void addResource(int id, double current /* u/sec */, double max /* u/sec */) + { + FuelRequirement req; + if (resourceRequired.ContainsKey(id)) + { + req = resourceRequired[id]; + } + else + { + req = new FuelRequirement(); + resourceRequired[id] = req; + } + + req.requiredLastFrame += current; + req.requiredAtMaxThrottle += max; + } + } + + // Used during the vesselState constructor; distilled to other variables later. + class IntakeInfo + { + public readonly Dictionary> allIntakes = new Dictionary>(); + + public void Update() + { + foreach (List intakes in allIntakes.Values) + { + ListPool.Instance.Release(intakes); + } + allIntakes.Clear(); + } + + public void addIntake(ModuleResourceIntake intake) + { + // TODO: figure out how much airflow we have, how much we could have, + // drag, etc etc. + List thelist; + int id = PartResourceLibrary.Instance.GetDefinition(intake.resourceName).id; + if (allIntakes.ContainsKey(id)) + { + thelist = allIntakes[id]; + } + else + { + thelist = ListPool.Instance.Borrow(); + allIntakes[id] = thelist; + } + thelist.Add(intake); + } + + private static readonly List empty = new List(); + public List getIntakes(int id) + { + if (allIntakes.ContainsKey(id)) + { + return allIntakes[id]; + } + else + { + return empty; + } + } + } + + // Stored. + public class ResourceInfo + { + public PartResourceDefinition definition; + + // We use kg/s rather than the more common T/s because these numbers tend to be small. + // One debate I've had is whether to use mass/s or unit/s. Dunno... + + public double required = 0; // kg/s + public double requiredAtMaxThrottle = 0; // kg/s + public double intakeAvailable = 0; // kg/s + public double intakeProvided + { // kg/s for currently-open intakes + get + { + double sum = 0; + for (int i = 0; i < intakes.Count; i++) + { + var intakeData = intakes[i]; + if (intakeData.intake.intakeEnabled) + { + sum += intakeData.predictedMassFlow; + } + } + return sum; + } + } + public List intakes = new List(); + + public struct IntakeData + { + public IntakeData(ModuleResourceIntake intake, double predictedMassFlow) + { + this.intake = intake; + this.predictedMassFlow = predictedMassFlow; + } + public ModuleResourceIntake intake; + public double predictedMassFlow; // min kg/s this timestep or next + } + + private static readonly Pool pool = new Pool(Create, Reset); + + public static int PoolSize + { + get { return pool.Size; } + } + + private static ResourceInfo Create() + { + return new ResourceInfo(); + } + + public virtual void Release() + { + pool.Release(this); + } + + public static void Release(Dictionary.ValueCollection objList) + { + foreach (ResourceInfo resourceInfo in objList) + { + resourceInfo.Release(); + } + } + + private static void Reset(ResourceInfo obj) + { + obj.required = 0; + obj.requiredAtMaxThrottle = 0; + obj.intakeAvailable = 0; + obj.intakes.Clear(); + } + + private ResourceInfo() + { + } + + public static ResourceInfo Borrow(PartResourceDefinition r, double req /* u per deltaT */, double atMax /* u per s */, List modules, Vessel vessel) + { + ResourceInfo resourceInfo = pool.Borrow(); + resourceInfo.Init(r, req /* u per deltaT */, atMax /* u per s */, modules, vessel); + return resourceInfo; + } + + private void Init(PartResourceDefinition r, double req /* u per deltaT */, double atMax /* u per s */, List modules, Vessel vessel) + { + definition = r; + double density = definition.density * 1000; // kg per unit (density is in T per unit) + float dT = TimeWarp.fixedDeltaTime; + required = req * density / dT; + requiredAtMaxThrottle = atMax * density; + + // For each intake, we want to know the min of what will (or can) be provided either now or at the end of the timestep. + // 0 means now, 1 means next timestep + Vector3d v0 = vessel.srf_velocity; + Vector3d v1 = v0 + dT * vessel.acceleration; + Vector3d v0norm = v0.normalized; + Vector3d v1norm = v1.normalized; + double v0mag = v0.magnitude; + double v1mag = v1.magnitude; + + float alt1 = (float)(vessel.altitude + dT * vessel.verticalSpeed); + + double staticPressure1 = vessel.staticPressurekPa; + double staticPressure2 = FlightGlobals.getStaticPressure(alt1); + + // As with thrust, here too we should get the static pressure at the intake, not at the center of mass. + double atmDensity0 = FlightGlobals.getAtmDensity(staticPressure1, vessel.externalTemperature); + double atmDensity1 = FlightGlobals.getAtmDensity(staticPressure2, FlightGlobals.getExternalTemperature(alt1)); + + double v0speedOfSound = vessel.mainBody.GetSpeedOfSound(staticPressure1, atmDensity0); + double v1speedOfSound = vessel.mainBody.GetSpeedOfSound(staticPressure2, atmDensity1); + + float v0mach = v0speedOfSound > 0 ? (float)(v0.magnitude / v0speedOfSound) : 0; + float v1mach = v1speedOfSound > 0 ? (float)(v1.magnitude / v1speedOfSound) : 0; + + intakes.Clear(); + int idx = 0; + for (int index = 0; index < modules.Count; index++) + { + var intake = modules[index]; + var intakeTransform = intake.intakeTransform; + if (intakeTransform == null) + continue; + Vector3d intakeFwd0 = intakeTransform.forward; // TODO : replace with the new public field + Vector3d intakeFwd1; + { + // Rotate the intake by the angular velocity for one timestep, in case the ship is spinning. + // Going through the Unity vector classes is about as many lines as hammering it out by hand. + Vector3 rot = dT * vessel.angularVelocity; + intakeFwd1 = Quaternion.AngleAxis(Mathf.Rad2Deg * rot.magnitude, rot) * intakeFwd0; + /*Vector3d cos; + Vector3d sin; + for(int i = 0; i < 3; ++i) { + cos[i] = Math.Cos (rot[i]); + sin[i] = Math.Sin (rot[i]); + } + intakeFwd1[0] + = intakeFwd0[0] * cos[1] * cos[2] + + intakeFwd0[1] * (sin[0]*sin[1]*cos[2] - cos[0]*sin[2]) + + intakeFwd0[2] * (sin[0]*sin[2] + cos[0]*sin[1]); + + intakeFwd1[1] + = intakeFwd0[0] * cos[1] * sin[2] + + intakeFwd0[1] * (cos[0]*cos[1] + sin[0]*sin[1]*sin[2]) + + intakeFwd0[2] * (cos[0]*sin[1]*sin[2] - sin[0]*cos[2]); + + intakeFwd1[2] + = intakeFwd0[0] * (-sin[1]) + + intakeFwd0[1] * sin[0] * cos[1] + + intakeFwd0[2] * cos[0] * cos[1];*/ + } + + double mass0 = massProvided(v0mag, v0norm, atmDensity0, staticPressure1, v0mach, intake, intakeFwd0); + double mass1 = massProvided(v1mag, v1norm, atmDensity1, staticPressure2, v1mach, intake, intakeFwd1); + double mass = Math.Min(mass0, mass1); + + // Also, we can't have more airflow than what fits in the resource tank of the intake part. + double capacity = 0; + for (int i = 0; i < intake.part.Resources.Count; i++) + { + PartResource tank = intake.part.Resources[i]; + if (tank.info.id == definition.id) + { + capacity += tank.maxAmount; // units per timestep + } + } + capacity = capacity * density / dT; // convert to kg/s + mass = Math.Min(mass, capacity); + + intakes.Add(new IntakeData(intake, mass)); + + idx++; + } + } + + // Return the number of kg of resource provided per second under certain conditions. + // We use kg since the numbers are typically small. + private double massProvided(double vesselSpeed, Vector3d normVesselSpeed, double atmDensity, double staticPressure, float mach, + ModuleResourceIntake intake, Vector3d intakeFwd) + { + if ((intake.checkForOxygen && !FlightGlobals.currentMainBody.atmosphereContainsOxygen) || staticPressure < intake.kPaThreshold) // TODO : add the new test (the bool and maybe the attach node ?) + { + return 0; + } + + // This is adapted from code shared by Amram at: + // http://forum.kerbalspaceprogram.com/showthread.php?34288-Maching-Bird-Challeng?p=440505 + // Seems to be accurate for 0.18.2 anyway. + double intakeSpeed = intake.intakeSpeed; // airspeed when the intake isn't moving + + double aoa = Vector3d.Dot(normVesselSpeed, intakeFwd); + if (aoa < 0) { aoa = 0; } + else if (aoa > 1) { aoa = 1; } + + double finalSpeed = intakeSpeed + aoa * vesselSpeed; + + double airVolume = finalSpeed * intake.area * intake.unitScalar * intake.machCurve.Evaluate(mach); + double airmass = atmDensity * airVolume; // tonnes per second + + // TODO: limit by the amount the intake can store + return airmass * 1000; + } + } + + + public class EngineWrapper + { + public readonly ModuleEngines engine; + + public float thrustRatio + { + get + { + return engine.thrustPercentage / 100; + } + set + { + engine.thrustPercentage = value * 100; + } + } + + private Vector3d _constantForce; + private Vector3d _maxVariableForce; + private Vector3d _constantTorque; + private Vector3d _maxVariableTorque; + + public Vector3d constantForce { get { return _constantForce; } } + public Vector3d maxVariableForce { get { return _maxVariableForce; } } + public Vector3d constantTorque { get { return _constantTorque; } } + public Vector3d maxVariableTorque { get { return _maxVariableTorque; } } + + public EngineWrapper(ModuleEngines module, Vector3d constantForce, Vector3d maxVariableForce, Vector3d constantTorque, Vector3d maxVariableTorque) + { + engine = module; + _constantForce = constantForce; + _maxVariableForce = maxVariableForce; + _constantTorque = constantTorque; + _maxVariableTorque = maxVariableTorque; + } + } + + + } +}