diff --git a/Directory.Build.props b/Directory.Build.props
index 0264a58a7..c6a584568 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -37,8 +37,11 @@
$(KspDir)/KSP_Data
-
+
$(KspDir)/GameData/kOS/Plugins/kOS.dll
+
+ $(KspDir)/GameData/kOS/Plugins/kOS.Safe.dll
+
diff --git a/MechJebKos/Addon.cs b/MechJebKos/Addon.cs
new file mode 100644
index 000000000..7b8bf738f
--- /dev/null
+++ b/MechJebKos/Addon.cs
@@ -0,0 +1,43 @@
+/*
+ * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
+ * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
+ */
+
+using JetBrains.Annotations;
+using kOS;
+using kOS.AddOns;
+using kOS.Safe.Encapsulation;
+using kOS.Safe.Encapsulation.Suffixes;
+using kOS.Safe.Utilities;
+
+namespace MuMech.MechJebKos
+{
+ // The kOS addon entry point, reached from scripts as ADDONS:MECHJEB.
+ //
+ // kOS instantiates one Addon per processor (per SharedObjects), so this is inherently
+ // local to a single vessel: everything resolves through shared.Vessel and we never reach
+ // for FlightGlobals.activeVessel or a global singleton. Cross-vessel coordination is left
+ // to kOS itself.
+ [kOSAddon("MechJeb")]
+ [KOSNomenclature("MechJebAddon")]
+ [UsedImplicitly]
+ public class Addon : kOS.Suffixed.Addon
+ {
+ public Addon(SharedObjects shared) : base(shared) => RegisterInitializer(InitializeSuffixes);
+
+ // must never be cached, it can be updated dyanmically
+ private MechJebCore? _core => shared.Vessel.GetMasterMechJeb();
+
+ public override BooleanValue Available() => !(_core is null);
+
+ private void InitializeSuffixes()
+ {
+ var nodeExecutor = new NodeExecutorBinding(() => _core);
+
+ AddSuffix("RUNNING", new NoArgsSuffix(() => _core?.running ?? false,
+ "True if MechJeb is present and running on this vessel."));
+ AddSuffix(new[] { "NODE", "NODEEXECUTOR" }, new NoArgsSuffix(() => nodeExecutor,
+ "The maneuver node executor."));
+ }
+ }
+}
diff --git a/MechJebKos/ComputerModuleBinding.cs b/MechJebKos/ComputerModuleBinding.cs
new file mode 100644
index 000000000..887aef0bc
--- /dev/null
+++ b/MechJebKos/ComputerModuleBinding.cs
@@ -0,0 +1,52 @@
+/*
+ * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
+ * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
+ */
+
+using System;
+using kOS.Safe.Encapsulation;
+using kOS.Safe.Encapsulation.Suffixes;
+using kOS.Safe.Exceptions;
+using kOS.Safe.Utilities;
+
+namespace MuMech.MechJebKos
+{
+ [KOSNomenclature("MechJebComputerModule")]
+ public abstract class ComputerModuleBinding : Structure where T : ComputerModule
+ {
+ private readonly Func _core;
+
+ protected ComputerModuleBinding(Func core)
+ {
+ _core = core;
+ RegisterInitializer(InitSuffixes);
+ }
+
+ // the Module deliberately re-evaluates on every call since the core can update dynamically
+ protected T Module
+ {
+ get
+ {
+ MechJebCore core = _core() ?? throw new KOSException("MechJeb is not available on this vessel.");
+ return core.GetComputerModule();
+ }
+ }
+
+ private void InitSuffixes()
+ {
+ AddSuffix("ENABLED", new SetSuffix(() => Module.Enabled, value => SetEnabled(value),
+ "Whether the module is enabled."));
+ InitializeSuffixes();
+ }
+
+ protected abstract void InitializeSuffixes();
+
+ protected virtual void SetEnabled(bool enabled)
+ {
+ if (enabled)
+ Module.Enable();
+ else
+ Module.Disable();
+ }
+ }
+}
diff --git a/MechJebKos/MechJebKos.csproj b/MechJebKos/MechJebKos.csproj
index c30537798..e8421f631 100644
--- a/MechJebKos/MechJebKos.csproj
+++ b/MechJebKos/MechJebKos.csproj
@@ -7,6 +7,7 @@
false
$(DefineConstants);UNITY_2017_1
false
+ enable
@@ -24,11 +25,21 @@
False
False
+
+ $(KosSafeDll)
+ False
+ False
+
$(KspData)/Managed/Assembly-CSharp.dll
False
False
+
+ $(KspData)/Managed/Assembly-CSharp-firstpass.dll
+ False
+ False
+
$(KspData)/Managed/UnityEngine.dll
False
diff --git a/MechJebKos/MechJebKosAddon.cs b/MechJebKos/MechJebKosAddon.cs
deleted file mode 100644
index 620b1041a..000000000
--- a/MechJebKos/MechJebKosAddon.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
- * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
- */
-
-namespace MuMech
-{
- internal static class MechJebKos
- {
- }
-}
diff --git a/MechJebKos/NodeExecutorBinding.cs b/MechJebKos/NodeExecutorBinding.cs
new file mode 100644
index 000000000..247e69d5a
--- /dev/null
+++ b/MechJebKos/NodeExecutorBinding.cs
@@ -0,0 +1,37 @@
+/*
+ * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
+ * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
+ */
+
+using System;
+using kOS.Safe.Encapsulation;
+using kOS.Safe.Encapsulation.Suffixes;
+using kOS.Safe.Utilities;
+
+namespace MuMech.MechJebKos
+{
+ // ADDONS:MECHJEB:NODE - drives MechJebModuleNodeExecutor.
+ [KOSNomenclature("MechJebNodeExecutor")]
+ public class NodeExecutorBinding : ComputerModuleBinding
+ {
+ public NodeExecutorBinding(Func core) : base(core) { }
+
+ protected override void InitializeSuffixes()
+ {
+ AddSuffix("STATE", new Suffix(() => Module.State.ToString(),
+ "Executor state: WARPALIGN, LEAD, BURN, or IDLE."));
+ AddSuffix(new[] { "AUTOWARP", "WARP" },
+ new SetSuffix(() => Module.Autowarp, value => Module.Autowarp = value,
+ "Automatically time-warp to the node."));
+ }
+
+ // The node executor engages via ExecuteOneNode / Abort rather than the plain Enabled toggle.
+ protected override void SetEnabled(bool enabled)
+ {
+ if (enabled)
+ Module.ExecuteOneNode(this);
+ else
+ Module.Abort();
+ }
+ }
+}
diff --git a/MechJebKos/Properties/AssemblyInfo.cs b/MechJebKos/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..eaf258fc3
--- /dev/null
+++ b/MechJebKos/Properties/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+/*
+ * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
+ * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
+ */
+
+// KSP load-order dependencies. The (major, minor) pair is a *minimum* version requirement;
+// KSP refuses to load this assembly unless a matching-or-newer KSPAssembly is present.
+[assembly: KSPAssemblyDependency("kOS", 1, 6)]
+[assembly: KSPAssemblyDependency("MechJeb2", 2, 16)]