From c685b8fc1ecd801b0a52b812754aee1a88913251 Mon Sep 17 00:00:00 2001 From: sug44 Date: Sun, 17 May 2026 15:03:54 +0300 Subject: [PATCH 1/5] New part module control system draft --- src/kOS/Suffixed/Part/PartValue.cs | 6 +- .../PartModuleField/ActionsLexicon.cs | 79 +++++++++++++++ .../Suffixed/PartModuleField/EventsLexicon.cs | 86 ++++++++++++++++ .../Suffixed/PartModuleField/FieldsLexicon.cs | 97 ++++++++++++++++++ .../Suffixed/PartModuleField/ModuleLexicon.cs | 98 +++++++++++++++++++ .../PartModuleField/PartModuleFields.cs | 42 ++++++-- 6 files changed, 399 insertions(+), 9 deletions(-) create mode 100644 src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs create mode 100644 src/kOS/Suffixed/PartModuleField/EventsLexicon.cs create mode 100644 src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs create mode 100644 src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs diff --git a/src/kOS/Suffixed/Part/PartValue.cs b/src/kOS/Suffixed/Part/PartValue.cs index 7d3b6a78b..e2f6609a9 100644 --- a/src/kOS/Suffixed/Part/PartValue.cs +++ b/src/kOS/Suffixed/Part/PartValue.cs @@ -26,6 +26,7 @@ public class PartValue : Structure, IKOSTargetable public Structure ParentValue { get { return (Structure)Parent ?? StringValue.None; } } public Structure DecouplerValue { get { return (Structure)Decoupler ?? StringValue.None; } } public int DecoupledIn { get { return (Decoupler != null) ? Decoupler.Part.inverseStage : -1; } } + private ModuleLexicon modulesLexicon; /// /// Do not call! VesselTarget.ConstructPart uses this, would use `friend VesselTarget` if this was C++! @@ -38,6 +39,8 @@ internal PartValue(SharedObjects shared, global::Part part, PartValue parent, De Decoupler = decoupler; RegisterInitializer(PartInitializeSuffixes); Children = new ListValue(); + + modulesLexicon = new ModuleLexicon(this, shared); } private void PartInitializeSuffixes() @@ -61,7 +64,8 @@ private void PartInitializeSuffixes() AddSuffix("HASMODULE", new OneArgsSuffix(HasModule)); AddSuffix("GETMODULE", new OneArgsSuffix(GetModule)); AddSuffix("GETMODULEBYINDEX", new OneArgsSuffix(GetModuleIndex)); - AddSuffix(new[] { "MODULES", "ALLMODULES" }, new Suffix(GetAllModules, "A List of all the modules' names on this part")); + AddSuffix("ALLMODULES", new Suffix(GetAllModules, "A List of all the modules' names on this part")); + AddSuffix("MODULES", new Suffix(() => modulesLexicon)); AddSuffix("PARENT", new Suffix(() => ParentValue, "The parent part of this part")); AddSuffix(new[] { "DECOUPLER", "SEPARATOR" }, new Suffix(() => DecouplerValue, "The part that will decouple/separate this part when activated")); AddSuffix(new[] { "DECOUPLEDIN", "SEPARATEDIN" }, new Suffix(() => DecoupledIn)); diff --git a/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs b/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs new file mode 100644 index 000000000..4f5723970 --- /dev/null +++ b/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs @@ -0,0 +1,79 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; + +namespace kOS.Suffixed.PartModuleField +{ + [kOS.Safe.Utilities.KOSNomenclature("ActionsLexicon", KOSToCSharp = false)] + public class ActionsLexicon : Structure + { + private readonly PartModuleFields partModuleFields; + private readonly PartModule partModule; + + public ActionsLexicon(PartModuleFields partModuleFields, PartModule partModule) + { + this.partModuleFields = partModuleFields; + this.partModule = partModule; + + AddSuffix("MODULE", new NoArgsSuffix(() => partModuleFields)); + AddSuffix("KEYS", new Suffix(Keys)); + AddSuffix("HASKEY", new OneArgsSuffix(HasKey)); + AddSuffix("LENGTH", new NoArgsSuffix(() => Keys().Count)); + } + + private ListValue Keys() + { + var list = new ListValue(); + foreach (var action in partModule.Actions) + { + list.Add(new StringValue(action.name)); + } + return list; + } + + private BooleanValue HasKey(StringValue key) + { + foreach (var action in partModule.Actions) + { + if (string.Equals(action.name, key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + } + + public override string ToString() + { + string str = "ActionsLexicon of " + partModule.moduleName + ", containing visible keys:"; + foreach (var actions in partModule.Actions) + { + str += "\n" + actions.name + " (" + actions.guiName.ToLower() + ")"; + } + return str; + } + + public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + { + foreach (var action in partModule.Actions) + { + if (string.Equals(action.name, suffixName, StringComparison.CurrentCultureIgnoreCase)) + { + return new OneArgsSuffix(value => partModuleFields.CallKSPActionProper(action, value)).Get(); + } + } + + var baseResult = base.GetSuffix(suffixName, true); + if (baseResult != null) return baseResult; + + if (failOkay) return null; + throw new Exception("No key or suffix \""+suffixName+"\" found on "+ToString()); + } + + public override bool SetSuffix(string suffixName, object value, bool failOkay = false) + { + if (failOkay) return false; + throw new Exception("Cannot set suffixes on ActionsLexicon."); + } + } +} diff --git a/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs b/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs new file mode 100644 index 000000000..4b8748028 --- /dev/null +++ b/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs @@ -0,0 +1,86 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; + +namespace kOS.Suffixed.PartModuleField +{ + [kOS.Safe.Utilities.KOSNomenclature("EventsLexicon", KOSToCSharp = false)] + public class EventsLexicon : Structure + { + private readonly PartModuleFields partModuleFields; + private readonly PartModule partModule; + + public EventsLexicon(PartModuleFields partModuleFields, PartModule partModule) + { + this.partModuleFields = partModuleFields; + this.partModule = partModule; + + AddSuffix("MODULE", new NoArgsSuffix(() => partModuleFields)); + AddSuffix("KEYS", new Suffix(Keys)); + AddSuffix("HASKEY", new OneArgsSuffix(HasKey)); + AddSuffix("LENGTH", new NoArgsSuffix(() => Keys().Count)); + } + + private ListValue Keys() + { + var list = new ListValue(); + foreach (var ev in partModule.Events) + { + if (PartModuleFields.EventIsVisible(ev)) + { + list.Add(new StringValue(ev.name)); + } + } + return list; + } + + private BooleanValue HasKey(StringValue key) + { + foreach (var ev in partModule.Events) + { + if (PartModuleFields.EventIsVisible(ev) && string.Equals(ev.name, key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + } + + public override string ToString() + { + string str = "EventsLexicon of " + partModule.moduleName + ", containing visible keys:"; + foreach (var ev in partModule.Events) + { + if (PartModuleFields.EventIsVisible(ev)) + { + str += "\n" + ev.name + " (" + ev.guiName.ToLower() + ")"; + } + } + return str; + } + + public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + { + foreach (var ev in partModule.Events) + { + if (PartModuleFields.EventIsVisible(ev) && + string.Equals(ev.name, suffixName, StringComparison.CurrentCultureIgnoreCase)) + { + return new NoArgsVoidSuffix(() => partModuleFields.CallKSPEventProper(ev, suffixName)).Get(); + } + } + + var baseResult = base.GetSuffix(suffixName, true); + if (baseResult != null) return baseResult; + + if (failOkay) return null; + throw new Exception("No key or suffix \""+suffixName+"\" found on "+ToString()); + } + + public override bool SetSuffix(string suffixName, object value, bool failOkay = false) + { + if (failOkay) return false; + throw new Exception("Cannot set suffixes on EventsLexicon."); + } + } +} \ No newline at end of file diff --git a/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs b/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs new file mode 100644 index 000000000..45f536913 --- /dev/null +++ b/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs @@ -0,0 +1,97 @@ +using System; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; + +namespace kOS.Suffixed.PartModuleField +{ + [kOS.Safe.Utilities.KOSNomenclature("FieldsLexicon", KOSToCSharp = false)] + public class FieldsLexicon : Structure + { + private readonly PartModule partModule; + + public FieldsLexicon(PartModuleFields partModuleFields, PartModule partModule) + { + this.partModule = partModule; + + AddSuffix("MODULE", new NoArgsSuffix(() => partModuleFields)); + AddSuffix("KEYS", new Suffix(Keys)); + AddSuffix("HASKEY", new OneArgsSuffix(HasKey)); + AddSuffix("LENGTH", new NoArgsSuffix(() => Keys().Count)); + } + + private ListValue Keys() + { + var list = new ListValue(); + foreach (var field in partModule.Fields) + { + if (PartModuleFields.FieldIsVisible(field)) + { + list.Add(new StringValue(field.name)); + } + } + + return list; + } + + private BooleanValue HasKey(StringValue key) + { + foreach (var field in partModule.Fields) + { + if (PartModuleFields.FieldIsVisible(field) && string.Equals(field.name, key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + } + + public override string ToString() + { + string str = "FieldsLexicon of " + partModule.moduleName + ", containing visible keys:"; + foreach (var field in partModule.Fields) + { + if (PartModuleFields.FieldIsVisible(field)) + { + partModule.Fields.TryGetFieldUIControl(field.name, out UI_Control control); + str += "\n" + (control.controlEnabled && !(control is UI_Label) ? "(settable) " : "(get-only) ") + + field.name + " (" + field.guiName.ToLower() + "), is " + + Utilities.Utils.KOSType(field.FieldInfo.FieldType); + } + } + return str; + } + + public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + { + // Allows getting "hidden" fields + foreach (var field in partModule.Fields) + { + if (string.Equals(field.name, suffixName, StringComparison.CurrentCultureIgnoreCase)) + { + return new SuffixResult(FromPrimitiveWithAssert(field.GetValue(partModule))); + } + } + + var baseResult = base.GetSuffix(suffixName, true); + if (baseResult != null) return baseResult; + + if (failOkay) return null; + throw new Exception("No key or suffix \""+suffixName+"\" found on "+ToString()); + } + + public override bool SetSuffix(string suffixName, object value, bool failOkay = false) + { + foreach (var field in partModule.Fields) + { + if (string.Equals(field.name, suffixName, StringComparison.CurrentCultureIgnoreCase)) + { + PartModuleFields.SetFieldProper(partModule, field, FromPrimitiveWithAssert(value)); + return true; + } + } + + if (failOkay) return false; + throw new Exception("No key \""+suffixName+"\" found on "+ToString()); + } + } +} diff --git a/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs new file mode 100644 index 000000000..6fe9485d3 --- /dev/null +++ b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs @@ -0,0 +1,98 @@ +using System; +using System.Text.RegularExpressions; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Suffixed.Part; + +namespace kOS.Suffixed.PartModuleField +{ + [kOS.Safe.Utilities.KOSNomenclature("ModuleLexicon")] + public class ModuleLexicon : Structure + { + private readonly PartValue part; + private readonly SharedObjects shared; + + public ModuleLexicon(PartValue part, SharedObjects shared) + { + this.part = part; + this.shared = shared; + + AddSuffix("PART", new NoArgsSuffix(() => part)); + AddSuffix("KEYS", new Suffix(GetKeys)); + AddSuffix("HASKEY", new OneArgsSuffix(HasKey)); + AddSuffix("LENGTH", new Suffix(() => part.Part.Modules.Count)); + } + + private static string TrimModuleName(string moduleName) => Regex.Replace(moduleName, "^Module", ""); + + private ListValue GetKeys() + { + var list = new ListValue(); + foreach (PartModule module in part.Part.Modules) + { + string trimmedName = TrimModuleName(module.moduleName); + list.Add(new StringValue(module.moduleName)); + if (trimmedName != module.moduleName) + list.Add(new StringValue(trimmedName)); + } + return list; + } + + private BooleanValue HasKey(StringValue key) + { + foreach (PartModule module in part.Part.Modules) + { + if (string.Equals(module.moduleName, key, StringComparison.OrdinalIgnoreCase) || + string.Equals(TrimModuleName(module.moduleName), key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + } + + public override string ToString() + { + string str = "ModuleLexicon, containing keys:"; + foreach (PartModule module in part.Part.Modules) + { + str += "\n" + Regex.Replace(module.moduleName, "^Module", "[Module]"); + } + return str; + } + + public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + { + // For convenience its possible to access a module without "Module" at the start of its name + // Here we need to make sure module named "Anything" cant be hidden by module "ModuleAnything" + PartModule matchedModule = null; + foreach (PartModule module in part.Part.Modules) + { + if (string.Equals(module.moduleName, suffixName, StringComparison.OrdinalIgnoreCase)) + { + matchedModule = module; + break; + } + if (string.Equals(TrimModuleName(module.moduleName), suffixName, StringComparison.OrdinalIgnoreCase)) + { + matchedModule = module; + } + } + + if (matchedModule != null) + return new SuffixResult(PartModuleFieldsFactory.Construct(matchedModule, shared)); + + var baseResult = base.GetSuffix(suffixName, true); + if (baseResult != null) return baseResult; + + if (failOkay) return null; + throw new Exception("No key or suffix \""+suffixName+"\" found on "+ToString()); + } + + public override bool SetSuffix(string suffixName, object value, bool failOkay = false) + { + if (failOkay) return false; + throw new Exception("Cannot set suffixes on ModuleLexicon."); + } + } +} diff --git a/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs b/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs index f2dc78c69..1c1da9b65 100644 --- a/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs +++ b/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs @@ -23,6 +23,9 @@ public class PartModuleFields : Structure { protected readonly PartModule partModule; protected readonly SharedObjects shared; + private readonly FieldsLexicon fieldsLexicon; + private readonly EventsLexicon eventsLexicon; + private readonly ActionsLexicon actionsLexicon; /// /// Create a kOS-user variable wrapper around a KSP PartModule attached to a part. @@ -33,6 +36,9 @@ public PartModuleFields(PartModule partModule, SharedObjects shared) { this.partModule = partModule; this.shared = shared; + fieldsLexicon = new FieldsLexicon(this, partModule); + eventsLexicon = new EventsLexicon(this, partModule); + actionsLexicon = new ActionsLexicon(this, partModule); // Overriding Structure.InitializeSuffixes() doesn't work because the base constructor calls it // prior to calling this constructor, and so partModule isn't set yet: @@ -80,7 +86,7 @@ public override string ToString() /// alter the value to an acceptable replacement which is why it passes by ref /// An exception you can choose to throw if you want, or null if the value is legal. /// Is it legal? - private bool IsLegalValue(BaseField field, ref Structure newVal, out KOSException except) + private static bool IsLegalValue(PartModule partModule, BaseField field, ref Structure newVal, out KOSException except) { except = null; bool isLegal = true; @@ -191,7 +197,7 @@ protected virtual ListValue AllFields(string formatter) if ( partModule.Fields.TryGetFieldUIControl(field.name, out control)) { returnValue.Add(new StringValue(string.Format(formatter, - control.controlEnabled ? "settable" : "get-only", + control.controlEnabled && !(control is UI_Label) ? "settable" : "get-only", GetFieldName(field).ToLower(), Utilities.Utils.KOSType(field.FieldInfo.FieldType)))); } @@ -463,14 +469,18 @@ private void InitializeSuffixesAfterConstruction() AddSuffix("ALLHIDDENFIELDNAMES", new Suffix(() => AllFieldNames(field => FieldIsVisible(field, false)))); AddSuffix("HASHIDDENFIELD", new OneArgsSuffix(HasHiddenField)); AddSuffix("GETHIDDENFIELD", new OneArgsSuffix(argument => GetKSPFieldValue(argument, field => FieldIsVisible(field, false)))); + + AddSuffix("FIELDS", new Suffix(() => fieldsLexicon)); + AddSuffix("EVENTS", new Suffix(() => eventsLexicon)); + AddSuffix("ACTIONS", new Suffix(() => actionsLexicon)); } - private bool FieldIsVisible(BaseField field, bool isVisible = true) + public static bool FieldIsVisible(BaseField field, bool isVisible = true) { return (field != null) && (HighLogic.LoadedSceneIsEditor ? field.guiActiveEditor == isVisible : field.guiActive == isVisible); } - private bool EventIsVisible(BaseEvent evt) + public static bool EventIsVisible(BaseEvent evt) { return (evt != null) && ( (HighLogic.LoadedSceneIsEditor ? evt.guiActiveEditor : evt.guiActive) && @@ -512,8 +522,13 @@ protected virtual void SetKSPFieldValue(StringValue suffixName, Structure newVal if (!FieldIsVisible(field)) throw new KOSLookupFailException("FIELD", suffixName, this, true); + SetFieldProper(partModule, field, newValue); + } + + public static void SetFieldProper(PartModule partModule, BaseField field, Structure newValue) + { KOSException except; - if (IsLegalValue(field, ref newValue, out except)) + if (IsLegalValue(partModule, field, ref newValue, out except)) { object convertedValue = Convert.ChangeType(newValue, field.FieldInfo.FieldType); field.SetValue(convertedValue, partModule); @@ -537,12 +552,18 @@ protected virtual void SetKSPFieldValue(StringValue suffixName, Structure newVal /// private void CallKSPEvent(StringValue suffixName) { - ThrowIfNotCPUVessel(); BaseEvent evt = GetEvent(suffixName); if (evt == null) throw new KOSLookupFailException("EVENT", suffixName, this); + + CallKSPEventProper(evt, suffixName); + } + + public void CallKSPEventProper(BaseEvent evt, string eventName) + { + ThrowIfNotCPUVessel(); if (!EventIsVisible(evt)) - throw new KOSLookupFailException("EVENT", suffixName, this, true); + throw new KOSLookupFailException("EVENT", eventName, this, true); if (RemoteTechHook.IsAvailable()) { @@ -565,10 +586,15 @@ private void CallKSPEvent(StringValue suffixName) /// true = activate, false = de-activate private void CallKSPAction(StringValue suffixName, BooleanValue param) { - ThrowIfNotCPUVessel(); BaseAction act = GetAction(suffixName); if (act == null) throw new KOSLookupFailException("ACTION", suffixName, this); + CallKSPActionProper(act, param); + } + + public void CallKSPActionProper(BaseAction act, BooleanValue param) + { + ThrowIfNotCPUVessel(); string careerReason; if (!Career.CanDoActions(out careerReason)) throw new KOSLowTechException("use :DOACTION", careerReason); From 26136d9b178e9350e026ffd963680c591e014f0a Mon Sep 17 00:00:00 2001 From: sug44 Date: Tue, 19 May 2026 18:29:24 +0300 Subject: [PATCH 2/5] Make string returned by PartModule.ToString() more readable and less confusing --- src/kOS/Suffixed/PartModuleField/PartModuleFields.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs b/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs index 1c1da9b65..b9c65178e 100644 --- a/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs +++ b/src/kOS/Suffixed/PartModuleField/PartModuleFields.cs @@ -64,7 +64,10 @@ public override string ToString() { var returnValue = new StringBuilder(); returnValue.AppendLine(GetModuleName() + ", containing:"); - returnValue.AppendLine(AllThings().ToString()); + foreach (Structure thingString in AllThings()) + { + returnValue.AppendLine(thingString.ToString()); + } return returnValue.ToString(); } From 29ccff0a8516caeef1bb290388236bd871410a31 Mon Sep 17 00:00:00 2001 From: sug44 Date: Tue, 19 May 2026 18:48:33 +0300 Subject: [PATCH 3/5] Allow indexing ModuleLexicon structure as an alternative to `part:getmodulebyindex` --- .../PartModuleField/ActionsLexicon.cs | 2 +- .../Suffixed/PartModuleField/EventsLexicon.cs | 2 +- .../Suffixed/PartModuleField/FieldsLexicon.cs | 2 +- .../Suffixed/PartModuleField/ModuleLexicon.cs | 61 +++++++++++++++---- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs b/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs index 4f5723970..785179f2e 100644 --- a/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs +++ b/src/kOS/Suffixed/PartModuleField/ActionsLexicon.cs @@ -4,7 +4,7 @@ namespace kOS.Suffixed.PartModuleField { - [kOS.Safe.Utilities.KOSNomenclature("ActionsLexicon", KOSToCSharp = false)] + [kOS.Safe.Utilities.KOSNomenclature("ActionsLexicon")] public class ActionsLexicon : Structure { private readonly PartModuleFields partModuleFields; diff --git a/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs b/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs index 4b8748028..10a7f6c07 100644 --- a/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs +++ b/src/kOS/Suffixed/PartModuleField/EventsLexicon.cs @@ -4,7 +4,7 @@ namespace kOS.Suffixed.PartModuleField { - [kOS.Safe.Utilities.KOSNomenclature("EventsLexicon", KOSToCSharp = false)] + [kOS.Safe.Utilities.KOSNomenclature("EventsLexicon")] public class EventsLexicon : Structure { private readonly PartModuleFields partModuleFields; diff --git a/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs b/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs index 45f536913..fea00056e 100644 --- a/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs +++ b/src/kOS/Suffixed/PartModuleField/FieldsLexicon.cs @@ -4,7 +4,7 @@ namespace kOS.Suffixed.PartModuleField { - [kOS.Safe.Utilities.KOSNomenclature("FieldsLexicon", KOSToCSharp = false)] + [kOS.Safe.Utilities.KOSNomenclature("FieldsLexicon")] public class FieldsLexicon : Structure { private readonly PartModule partModule; diff --git a/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs index 6fe9485d3..923101aee 100644 --- a/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs +++ b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs @@ -7,7 +7,7 @@ namespace kOS.Suffixed.PartModuleField { [kOS.Safe.Utilities.KOSNomenclature("ModuleLexicon")] - public class ModuleLexicon : Structure + public class ModuleLexicon : Structure, IIndexable { private readonly PartValue part; private readonly SharedObjects shared; @@ -30,10 +30,7 @@ private ListValue GetKeys() var list = new ListValue(); foreach (PartModule module in part.Part.Modules) { - string trimmedName = TrimModuleName(module.moduleName); list.Add(new StringValue(module.moduleName)); - if (trimmedName != module.moduleName) - list.Add(new StringValue(trimmedName)); } return list; } @@ -54,33 +51,46 @@ private BooleanValue HasKey(StringValue key) public override string ToString() { string str = "ModuleLexicon, containing keys:"; + int i = 0; foreach (PartModule module in part.Part.Modules) { - str += "\n" + Regex.Replace(module.moduleName, "^Module", "[Module]"); + str += "\n[" + i++ + "] " + module.moduleName; + string trimmedName = TrimModuleName(module.moduleName); + if (trimmedName != module.moduleName) + str += " / " + trimmedName; } return str; - } + } - public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + private PartModuleFields GetModule(string moduleName) { // For convenience its possible to access a module without "Module" at the start of its name // Here we need to make sure module named "Anything" cant be hidden by module "ModuleAnything" PartModule matchedModule = null; foreach (PartModule module in part.Part.Modules) { - if (string.Equals(module.moduleName, suffixName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(module.moduleName, moduleName, StringComparison.OrdinalIgnoreCase)) { matchedModule = module; break; } - if (string.Equals(TrimModuleName(module.moduleName), suffixName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(TrimModuleName(module.moduleName), moduleName, StringComparison.OrdinalIgnoreCase)) { matchedModule = module; } } - if (matchedModule != null) - return new SuffixResult(PartModuleFieldsFactory.Construct(matchedModule, shared)); + if (matchedModule == null) + return null; + return PartModuleFieldsFactory.Construct(matchedModule, shared); + } + + public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false) + { + PartModuleFields module = GetModule(suffixName); + + if (module != null) + return new SuffixResult(module); var baseResult = base.GetSuffix(suffixName, true); if (baseResult != null) return baseResult; @@ -94,5 +104,34 @@ public override bool SetSuffix(string suffixName, object value, bool failOkay = if (failOkay) return false; throw new Exception("Cannot set suffixes on ModuleLexicon."); } + + public Structure GetIndex(Structure index) + { + if (index is StringValue str) + { + PartModuleFields module = GetModule(str); + if (module != null) + return module; + } + else if (index is ScalarValue scalar) + { + int intIndex = (int)scalar; + if (intIndex >= 0 && intIndex < part.Part.Modules.Count) + { + return PartModuleFieldsFactory.Construct(part.Part.Modules[intIndex], shared); + } + throw new Exception("Index out of range for " + ToString()); + } + throw new Exception("No key \"" + index + "\" found on " + ToString()); + } + + public void SetIndex(Structure index, Structure value) + { + throw new Exception("Cannot set indexes on ModuleLexicon."); + } + + public Structure GetIndex(int index) => GetIndex((Structure)new ScalarIntValue(index)); + + public void SetIndex(int index, Structure value) => SetIndex(null, null); } } From c1b993eab40ce2ead3f93d27a220376a505f6103 Mon Sep 17 00:00:00 2001 From: sug44 Date: Tue, 19 May 2026 19:27:51 +0300 Subject: [PATCH 4/5] Plan deprecation of hasmodule, getmodule, getmodulebyindex, allmodules suffixes on Part structure Up to 1.6.3.0 version warnings will be printed on the terminal when these suffixes are used. The warnings can be disabled by `config:deprecatedwarnings off.`. At version 1.6.3.0 these suffixes will throw KOSObsoletionException --- src/kOS.Safe.Test/Execution/Config.cs | 13 +++++++ src/kOS.Safe/Encapsulation/IConfig.cs | 1 + src/kOS.Safe/Encapsulation/VersionInfo.cs | 17 +++++++++ src/kOS.Safe/Utilities/DeprecationHandler.cs | 25 +++++++++++++ src/kOS/Core.cs | 8 +---- src/kOS/Suffixed/Config.cs | 7 +++- src/kOS/Suffixed/Part/PartValue.cs | 36 ++++++++++++++++--- .../Suffixed/PartModuleField/ModuleLexicon.cs | 2 +- 8 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 src/kOS.Safe/Utilities/DeprecationHandler.cs diff --git a/src/kOS.Safe.Test/Execution/Config.cs b/src/kOS.Safe.Test/Execution/Config.cs index 980b9adbe..0cee1706e 100644 --- a/src/kOS.Safe.Test/Execution/Config.cs +++ b/src/kOS.Safe.Test/Execution/Config.cs @@ -198,6 +198,19 @@ public bool SuppressAutopilot { } } + + public bool DeprecatedWarnings + { + get + { + return true; + } + + set + { + } + } + public DateTime TimeStamp { get diff --git a/src/kOS.Safe/Encapsulation/IConfig.cs b/src/kOS.Safe/Encapsulation/IConfig.cs index 22bca8061..958345507 100644 --- a/src/kOS.Safe/Encapsulation/IConfig.cs +++ b/src/kOS.Safe/Encapsulation/IConfig.cs @@ -25,6 +25,7 @@ public interface IConfig: ISuffixed int TerminalDefaultHeight { get; set; } bool AllowClobberBuiltIns { get; set; } bool SuppressAutopilot { get; set; } + bool DeprecatedWarnings { get; set; } /// diff --git a/src/kOS.Safe/Encapsulation/VersionInfo.cs b/src/kOS.Safe/Encapsulation/VersionInfo.cs index aecd7b0a3..67e9f2913 100644 --- a/src/kOS.Safe/Encapsulation/VersionInfo.cs +++ b/src/kOS.Safe/Encapsulation/VersionInfo.cs @@ -1,3 +1,4 @@ +using System; using kOS.Safe.Encapsulation.Suffixes; namespace kOS.Safe.Encapsulation @@ -9,7 +10,23 @@ public class VersionInfo : Structure private readonly int minor; private readonly int patch; private readonly int build; + public Version SystemVersion => new Version(major, minor, patch, build); + public VersionInfo(Version version) + { + // NOTICE: there is a clash of nomenclature here. C# calls the + // 3rd number "BUILD" and the 4th number "Revision" while the AVC mod + // (and presumably CKAN) calls the 3rd number "PATCH" and the 4th number "BUILD". + // We'll be using the AVC terminology in kerboscript, thus why this next line + // passes in "ver.Revision" where the VersionInfo's "BUILD" goes, and the + // "ver.Build" where VersionInfo's "PATCH" goes: + major = version.Major; + minor = version.Minor; + patch = version.Build; + build = version.Revision; + VersionInitializeSuffixes(); + } + public VersionInfo(int major, int minor, int patch, int build) { this.major = major; diff --git a/src/kOS.Safe/Utilities/DeprecationHandler.cs b/src/kOS.Safe/Utilities/DeprecationHandler.cs new file mode 100644 index 000000000..4d312351c --- /dev/null +++ b/src/kOS.Safe/Utilities/DeprecationHandler.cs @@ -0,0 +1,25 @@ +using kOS.Safe.Encapsulation; +using kOS.Safe.Exceptions; + +namespace kOS.Safe.Utilities +{ + public static class DeprecationHandler + { + private const string DeprecationMessage = + "WARNING: Usage of {0} is being deprecated.\nUse {1} instead.\n"+ + "At kOS version {2} this usage will be removed.\n"+ + "To disable these warnings do \"config:deprecatedwarnings off.\""; + + public static void DeprecatedUsage(SafeSharedObjects shared, string oldUsage, string newUsage, VersionInfo completeDeprecationVersion) + { + if (SafeHouse.Version.SystemVersion >= completeDeprecationVersion.SystemVersion) + { + throw new KOSObsoletionException(completeDeprecationVersion.ToString(), oldUsage, newUsage, null); + } + if (SafeHouse.Config.DeprecatedWarnings) + { + shared.Screen.Print(string.Format(DeprecationMessage, oldUsage, newUsage, completeDeprecationVersion), true); + } + } + } +} diff --git a/src/kOS/Core.cs b/src/kOS/Core.cs index 8ced629dd..d3d54adf7 100644 --- a/src/kOS/Core.cs +++ b/src/kOS/Core.cs @@ -20,13 +20,7 @@ public class Core : kOSProcessorFields static Core() { var ver = typeof(Core).Assembly.GetName().Version; - // NOTICE: there is a clash of nomenclature here. C# calls the - // 3rd number "BUILD" and the 4th number "Revision" while the AVC mod - // (and presumably CKAN) calls the 3rd number "PATCH" and the 4th number "BUILD". - // We'll be using the AVC terminology in kerboscript, thus why this next line - // passes in "ver.Revision" where the VersionInfo's "BUILD" goes, and the - // "ver.Build" where VersionInfo's "PATCH" goes: - VersionInfo = new VersionInfo(ver.Major, ver.Minor, ver.Build, ver.Revision); + VersionInfo = new VersionInfo(ver); } public Core(kOSProcessor processor, SharedObjects shared):base(processor, shared) diff --git a/src/kOS/Suffixed/Config.cs b/src/kOS/Suffixed/Config.cs index cae46522f..1773a1d37 100644 --- a/src/kOS/Suffixed/Config.cs +++ b/src/kOS/Suffixed/Config.cs @@ -35,6 +35,7 @@ public class Config : Structure, IConfig public bool UseBlizzyToolbarOnly { get { return kOSCustomParameters.Instance.useBlizzyToolbarOnly; } set { kOSCustomParameters.Instance.useBlizzyToolbarOnly = value; } } public bool DebugEachOpcode { get { return kOSCustomParameters.Instance.debugEachOpcode; } set { kOSCustomParameters.Instance.debugEachOpcode = value; } } public bool SuppressAutopilot { get { return GetPropValue(PropId.SuppressAutopilot); } set { SetPropValue(PropId.SuppressAutopilot, value); } } + public bool DeprecatedWarnings { get { return GetPropValue(PropId.DeprecatedWarnings); } set { SetPropValue(PropId.DeprecatedWarnings, value); } } // NOTE TO FUTURE MAINTAINERS: If it looks like overkill to use a double instead of a float for this next field, you're right. // But KSP seems to have a bug where single-precision floats don't get saved in the config XML file. Doubles seem to work, though. @@ -69,6 +70,7 @@ private void InitializeSuffixes() AddSuffix("DEFAULTWIDTH", new ClampSetSuffix(() => TerminalDefaultWidth, value => TerminalDefaultWidth = value, 15f, 255f, 1f)); AddSuffix("DEFAULTHEIGHT", new ClampSetSuffix(() => TerminalDefaultHeight, value => TerminalDefaultHeight = value, 3f, 160f, 1f)); AddSuffix("SUPPRESSAUTOPILOT", new SetSuffix(() => SuppressAutopilot, value => SuppressAutopilot = value)); + AddSuffix("DEPRECATEDWARNINGS", new SetSuffix(() => DeprecatedWarnings, value => DeprecatedWarnings = value)); } private void BuildValuesDictionary() @@ -92,6 +94,8 @@ private void BuildValuesDictionary() new ConfigKey("TerminalDefaultHeight", "DEFAULTHEIGHT", "Initial Terminal:HEIGHT when a terminal is first opened", 36, 3, 160, typeof(int))); AddConfigKey(PropId.SuppressAutopilot, new ConfigKey("SuppressAutopilot", "SUPPRESSAUTOPILOT", "Suppress all kOS autopiloting for emergency manual control", false, false, true, typeof(bool))); + AddConfigKey(PropId.DeprecatedWarnings, + new ConfigKey("DeprecatedWarnings", "DEPRECATEDWARNINGS", "Enables/disables deprecated warning messages", true, false, true, typeof(bool))); } private void AddConfigKey(PropId id, ConfigKey key) @@ -256,7 +260,8 @@ private enum PropId TerminalBrightness = 17, TerminalDefaultWidth = 18, TerminalDefaultHeight = 19, - SuppressAutopilot = 20 + SuppressAutopilot = 20, + DeprecatedWarnings = 21 } } } diff --git a/src/kOS/Suffixed/Part/PartValue.cs b/src/kOS/Suffixed/Part/PartValue.cs index e2f6609a9..36899daef 100644 --- a/src/kOS/Suffixed/Part/PartValue.cs +++ b/src/kOS/Suffixed/Part/PartValue.cs @@ -61,10 +61,38 @@ private void PartInitializeSuffixes() AddSuffix("RESOURCES", new Suffix(() => GatherResources(Part))); AddSuffix("TARGETABLE", new Suffix(() => Part.Modules.OfType().Any())); AddSuffix("SHIP", new Suffix(() => VesselTarget.CreateOrGetExisting(Part.vessel, Shared))); - AddSuffix("HASMODULE", new OneArgsSuffix(HasModule)); - AddSuffix("GETMODULE", new OneArgsSuffix(GetModule)); - AddSuffix("GETMODULEBYINDEX", new OneArgsSuffix(GetModuleIndex)); - AddSuffix("ALLMODULES", new Suffix(GetAllModules, "A List of all the modules' names on this part")); + + VersionInfo oldModuleSystemDeprecationVersion = new VersionInfo(1, 6, 3, 0); + AddSuffix("HASMODULE", new OneArgsSuffix(str => + { + DeprecationHandler.DeprecatedUsage(Shared, ":HASMODULE", ":MODULES:HASKEY", + oldModuleSystemDeprecationVersion); + return HasModule(str); + })); + AddSuffix("GETMODULE", new OneArgsSuffix(str => + { + string newUsage = ":MODULES:" + str; + string trimmedName = ModuleLexicon.TrimModuleName(str); + if (trimmedName != str) + newUsage += " or :MODULES:" + trimmedName; + + DeprecationHandler.DeprecatedUsage(Shared, ":GETMODULE(\"" + str + "\")", newUsage, + oldModuleSystemDeprecationVersion); + return GetModule(str); + })); + AddSuffix("GETMODULEBYINDEX", new OneArgsSuffix(scalar => + { + DeprecationHandler.DeprecatedUsage(Shared, ":GETMODULEBYINDEX(index)", ":MODULES[index]", + oldModuleSystemDeprecationVersion); + return GetModuleIndex(scalar); + })); + AddSuffix("ALLMODULES", new Suffix(() => + { + DeprecationHandler.DeprecatedUsage(Shared, ":ALLMODULES", ":MODULES:KEYS", + oldModuleSystemDeprecationVersion); + return GetAllModules(); + }, "A List of all the modules' names on this part")); + AddSuffix("MODULES", new Suffix(() => modulesLexicon)); AddSuffix("PARENT", new Suffix(() => ParentValue, "The parent part of this part")); AddSuffix(new[] { "DECOUPLER", "SEPARATOR" }, new Suffix(() => DecouplerValue, "The part that will decouple/separate this part when activated")); diff --git a/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs index 923101aee..14fd457d2 100644 --- a/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs +++ b/src/kOS/Suffixed/PartModuleField/ModuleLexicon.cs @@ -23,7 +23,7 @@ public ModuleLexicon(PartValue part, SharedObjects shared) AddSuffix("LENGTH", new Suffix(() => part.Part.Modules.Count)); } - private static string TrimModuleName(string moduleName) => Regex.Replace(moduleName, "^Module", ""); + public static string TrimModuleName(string moduleName) => Regex.Replace(moduleName, "^Module", ""); private ListValue GetKeys() { From e51d0057dbcff89c1c9cd11e17805d2a8af9f664 Mon Sep 17 00:00:00 2001 From: sug44 Date: Sat, 23 May 2026 19:56:47 +0300 Subject: [PATCH 5/5] Add docs for ModuleLexicon structure. Remove deprecating part suffixes --- doc/source/addons/OrbitalScience.rst | 6 +- doc/source/addons/RemoteTech.rst | 2 +- doc/source/general/parts_and_partmodules.rst | 6 +- doc/source/structures/misc/config.rst | 11 +++ doc/source/structures/vessels/part.rst | 86 ++++++++----------- .../structures/vessels/scienceexperiment.rst | 4 +- 6 files changed, 57 insertions(+), 58 deletions(-) diff --git a/doc/source/addons/OrbitalScience.rst b/doc/source/addons/OrbitalScience.rst index 6fd4f5630..dd84f6e75 100644 --- a/doc/source/addons/OrbitalScience.rst +++ b/doc/source/addons/OrbitalScience.rst @@ -13,7 +13,7 @@ Most of the time Orbital Science experiments should work exactly like stock ones they inherit all suffixes from :ref:`ScienceExperimentModule `:: SET P TO SHIP:PARTSTAGGED("")[0]. - SET M TO P:GETMODULE("dmmodulescienceanimate"). + SET M TO P:MODULES:dmmodulescienceanimate. PRINT M:RERUNNABLE. PRINT M:INOPERABLE. @@ -26,7 +26,7 @@ deactivates them:: SET P TO SHIP:PARTSTAGGED("collector")[0]. - SET M TO P:GETMODULE("dmsolarcollector"). + SET M TO P:MODULES:dmsolarcollector. M:TOGGLE. @@ -34,7 +34,7 @@ deactivates them:: lights on and off:: SET P TO SHIP:PARTSTAGGED("bathymetry")[0]. - SET M TO P:GETMODULE("dmbathymetry"). + SET M TO P:MODULES:dmbathymetry. M:LIGHTSON. WAIT 3. diff --git a/doc/source/addons/RemoteTech.rst b/doc/source/addons/RemoteTech.rst index 78d3ea48a..28c393ee0 100644 --- a/doc/source/addons/RemoteTech.rst +++ b/doc/source/addons/RemoteTech.rst @@ -72,7 +72,7 @@ Antennas It is possible to activate/deactivate RT antennas, as well as set their targets using kOS:: SET P TO SHIP:PARTSNAMED("mediumDishAntenna")[0]. - SET M to p:GETMODULE("ModuleRTAntenna"). + SET M to p:MODULES:ModuleRTAntenna. M:DOEVENT("activate"). M:SETFIELD("target", "Mission Control"). M:SETFIELD("target", mun). diff --git a/doc/source/general/parts_and_partmodules.rst b/doc/source/general/parts_and_partmodules.rst index ff6e8f001..b515f4f38 100644 --- a/doc/source/general/parts_and_partmodules.rst +++ b/doc/source/general/parts_and_partmodules.rst @@ -86,7 +86,7 @@ Examples:: // Change the altitude at which all the drogue chutes will deploy: FOR somechute IN somevessel:PARTSNAMED("parachuteDrogue") { - somechute:GETMODULE("ModuleParachute"):SETFIELD("DEPLOYALTITUDE", 1500). + somechute:MODULES:ModuleParachute:SETFIELD("DEPLOYALTITUDE", 1500). }. Pattern matching @@ -217,14 +217,14 @@ B: **Use the :MODULES suffix of Part:** If you have a handle on any part in kOS, LOG P:MODULES TO MODLIST. }. -Do that, and the file MODLIST should now contain a verbose dump of all the module names of all the parts on your ship. You can get any of the modules now by using Part:GETMODULE("module name"). +Do that, and the file MODLIST should now contain a verbose dump of all the module names of all the parts on your ship. You can get any of the modules now by using Part:MODULES:ModuleName. What are the names of the stuff that a PartModule can do? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These three suffixes tell you everything a part module can do:: - SET MOD TO P:GETMODULE("some name here"). + SET MOD TO P:MODULE:SomeModuleName. LOG ("These are all the things that I can currently USE GETFIELD AND SETFIELD ON IN " + MOD:NAME + ":") TO NAMELIST. LOG MOD:ALLFIELDS TO NAMELIST. LOG ("These are all the things that I can currently USE DOEVENT ON IN " + MOD:NAME + ":") TO NAMELIST. diff --git a/doc/source/structures/misc/config.rst b/doc/source/structures/misc/config.rst index 94a610e5b..efaeee7a1 100644 --- a/doc/source/structures/misc/config.rst +++ b/doc/source/structures/misc/config.rst @@ -88,6 +88,10 @@ Configuration of kOS - :struct:`Boolean` - False - If true, kOS's controls do nothing. + * - :attr:`DEPRECATEDWARNINGS` + - :struct:`Boolean` + - True + - If true, warnings will be printed when using deprecated globals, suffixes or functions. * - :attr:`TELNET` - :struct:`Boolean` - False @@ -273,6 +277,13 @@ Configuration of kOS While it does suppress steering, throttle, and translation, it cannot suppress action groups or staging. +.. attribute:: Config:DEPRECATEDWARNINGS + + :access: Get/Set + :type: :struct:`Boolean` + + If true, warnings will be printed when using deprecated globals, suffixes or functions. + .. attribute:: Config:TELNET :access: Get/Set diff --git a/doc/source/structures/vessels/part.rst b/doc/source/structures/vessels/part.rst index b2716a323..96de5a589 100644 --- a/doc/source/structures/vessels/part.rst +++ b/doc/source/structures/vessels/part.rst @@ -66,21 +66,9 @@ These are the generic properties every PART has. You can obtain a list of values * - :attr:`SHIP` - :struct:`Vessel` - the vessel that contains this part - * - :meth:`GETMODULE(name)` - - :struct:`PartModule` - - Get one of the :struct:`PartModules ` by name - * - :meth:`GETMODULEBYINDEX(index)` - - :struct:`PartModule` - - Get one of the :struct:`PartModules ` by index * - :attr:`MODULES` - - :struct:`List` - - Names (:struct:`String`) of all :struct:`PartModules ` - * - :attr:`ALLMODULES` - - :struct:`List` - - Same as :attr:`MODULES` - * - :meth:`HASMODULE(name)` - - :struct:`Boolean` - - True if the part has the named module in it, false if not. + - :struct:`ModuleLexicon` + - Special :struct:`ModuleLexicon` structure for getting modules. * - :attr:`PARENT` - :struct:`Part` - Adjacent :struct:`Part` on this :struct:`Vessel`. @@ -319,25 +307,47 @@ These are the generic properties every PART has. You can obtain a list of values the vessel that contains this part. -.. method:: Part:GETMODULE(name) +.. attribute:: Part:MODULES + + :access: Get only + :type: :struct:`ModuleLexicon` + + .. structure:: ModuleLexicon + + .. list-table:: Members + :header-rows: 1 + :widths: 1 1 4 - :parameter name: (:struct:`String`) Name of the part module - :returns: :struct:`PartModule` + * - Suffix + - Type + - Description - Get one of the :struct:`PartModules ` attached to this part, given the name of the module. (See :attr:`Part:MODULES` for a list of all the names available). -.. method:: Part:GETMODULEBYINDEX(index) + * - :attr:`PART` + - :struct:`Part` + - The part this ModuleLexicon gets modules from + * - :attr:`KEYS` + - :struct:`List` of strings + - List of module names + * - :meth:`HASKEY(key)` + - :struct:`Boolean` + - Does the part have a module with this name + * - :attr:`LENGTH` + - :struct:`Scalar` + - Number of modules on a part - :parameter index: (:struct:`Scalar`) Index number of the part module - :returns: :struct:`PartModule` + Structure for getting :struct:`PartModules ` from a part with a name or an index. To get a module by its name use the suffix syntax with the module name(the leading "Module" string in the name is optional). Example:: - Get one of the :struct:`PartModules ` attached to this part, - given the index number of the module. You can use :attr:`Part:MODULES` for a - list of names of all modules on the part. The indexes are not guaranteed to - always be in the same order. It is recommended to iterate over the indexes - with a loop and verify the module name:: + print part:modules. + ModuleLexicon, containing keys: + [0] ModuleCommand + [1] ModuleReactionWheel + set command to part:modules:command. + set reactionwheel to part:modules[1]. - local moduleNames is part:modules. + The indexes are not guaranteed to always be in the same order. It is recommended to iterate over the indexes with a loop and verify the module name:: + + local moduleNames is part:modules:keys. for idx in range(0, moduleNames:length) { if moduleNames[idx] = "test module" { local pm is part:getmodulebyindex(idx). @@ -345,28 +355,6 @@ These are the generic properties every PART has. You can obtain a list of values } } - -.. attribute:: Part:MODULES - - :access: Get only - :type: :struct:`List` of strings - - list of the names of :struct:`PartModules ` enabled for this part. - -.. attribute:: Part:ALLMODULES - - Same as :attr:`Part:MODULES` - -.. method:: Part:HASMODULE(name) - - :parameter name: (:struct:`String`) The name of the module to check for - :returns: :struct:`Boolean` - - Checks to see if this part contains the :struct:`PartModule` with the name - given. If it does, this returns true, else it returns false. (If - ``HASMODULE(name)`` returns false, then this means an attempt to use - ``GETMODULE(name)`` would fail with an error.) - .. attribute:: Part:PARENT :access: Get only diff --git a/doc/source/structures/vessels/scienceexperiment.rst b/doc/source/structures/vessels/scienceexperiment.rst index 3112b0362..6dcdbe3eb 100644 --- a/doc/source/structures/vessels/scienceexperiment.rst +++ b/doc/source/structures/vessels/scienceexperiment.rst @@ -9,7 +9,7 @@ Some of the science-related tasks are normally not available to kOS scripts. It example possible to deploy a science experiment:: SET P TO SHIP:PARTSNAMED("GooExperiment")[1]. - SET M TO P:GETMODULE("ModuleScienceExperiment"). + SET M TO P:MODULES:ModuleScienceExperiment. M:DOEVENT("observe mystery goo"). However, this results in a dialog being shown to the user. Only from that dialog it is possible @@ -18,7 +18,7 @@ to reset the experiment or transmit the experiment results back to Kerbin. to perform all science-related tasks without any manual intervention:: SET P TO SHIP:PARTSNAMED("GooExperiment")[0]. - SET M TO P:GETMODULE("ModuleScienceExperiment"). + SET M TO P:MODULES:ModuleScienceExperiment. M:DEPLOY. WAIT UNTIL M:HASDATA. M:TRANSMIT.