Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions doc/source/structures/vessels/partmodule.rst
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a draft version of the changes required for the docs(as I see it). I didn't built it, since they would probably still require some polishing from you because I'm not a native english speaker.

Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha
* - :attr:`ALLFIELDS`
- :struct:`List` of strings
- Accessible fields
* - :attr:`ALLHIDDENFIELDS`
- :struct:`List` of strings
- Fields not normally accessible via GUI
* - :attr:`ALLFIELDNAMES`
- :struct:`List` of strings
- Accessible fields (name only)
* - :attr:`ALLHIDDENFIELDNAMES`
- :struct:`List` of strings
- Fields not normally accessible via GUI (name only)
* - :attr:`ALLEVENTS`
- :struct:`List` of strings
- Triggerable events
Expand All @@ -47,6 +53,9 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha
* - :meth:`GETFIELD(name)`
-
- Get value of a field by name
* - :meth:`GETHIDDENFIELD(name)`
-
- Get value of a hidden field by name
* - :meth:`SETFIELD(name,value)`
-
- Set value of a field by name
Expand All @@ -59,6 +68,9 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha
* - :meth:`HASFIELD(name)`
- :struct:`Boolean`
- Check if field exists
* - :meth:`HASHIDDENFIELD(name)`
- :struct:`Boolean`
- Check if a hidden field exists
* - :meth:`HASEVENT(name)`
- :struct:`Boolean`
- Check if event exists
Expand Down Expand Up @@ -90,12 +102,26 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha

Get a list of all the names of KSPFields on this PartModule that the kos script is CURRENTLY allowed to get or set with :GETFIELD or :SETFIELD. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

.. attribute:: PartModule:ALLHIDDENFIELDS

:access: Get only
:test: :struct:`List` of strings

Get a list of all the names of KSPFields on this PartModule that are not normally displayed in the GUI. The returned field names should be used with :GETHIDDENFIELD and cannot be used with :SETFIELD. Note the Security access comments below. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

.. attribute:: PartModule:ALLFIELDNAMES

:access: Get only
:test: :struct:`List` of strings

Similar to :ALLFIELDS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

.. attribute:: PartModule:ALLHIDDENFIELDNAMES

:access: Get only
:test: :struct:`List` of strings

Similar to :ALLHIDDENFIELDS except that it returns the string without the formatting to make it easier to use in a script. This list can become obsolete as the game continues running depending on what the PartModule chooses to do.

.. attribute:: PartModule:ALLEVENTS

Expand Down Expand Up @@ -132,6 +158,13 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha

Get the value of one of the fields that this PartModule has placed onto the right-click menu for the part. Note the Security comments below.

.. method:: PartModule:GETHIDDENFIELD(name)

:parameter name: (:struct:`String`) Name of the field
:return: varies

Get the value of one of the hidden(internal) PartModule fields that aren't normally displayed in the right-click menu for the part. Note the Security comments below.

.. method:: PartModule:SETFIELD(name,value)

:parameter name: (:struct:`String`) Name of the field
Expand Down Expand Up @@ -177,6 +210,13 @@ Once you have a :struct:`PartModule`, you can use it to invoke the behaviors tha

Return true if the given field name is currently available for use with :GETFIELD or :SETFIELD on this PartModule, false otherwise.

.. method:: PartModule:HASHIDDENFIELD(name)

:parameter name: (:struct:`String`) Name of the field
:return: :struct:`Boolean`

Return true if the given hidden(internal) field is present in the PartModule, false otherwise.

.. method:: PartModule:HASEVENT(name)

:parameter name: (:struct:`String`) Name of the event
Expand All @@ -197,19 +237,20 @@ Notes
-----

In all the above cases where there is a name being passed in to :GETFIELD, :SETFIELD, :DOEVENT, or :DOACTION, the name is meant to be the name that is seen by you, the user, in the GUI screen, and NOT necessarily the actual name of the variable that the programmer of that PartModule chose to call the value behind the scenes. This is so that you can view the GUI right-click menu to see what to call things in your script.
When it comes to the hidden fields, they can be inspected using the :ALLHIDDENFIELDS method, or by looking into the source code. Generally, manipulating the hidden fields is recommended for experienced users only.

.. note::

**Security and Respecting other Mod Authors**

There are often a lot more fields and events and actions that a partmodule can do than are usable via kOS. In designing kOS, the kOS developers have deliberately chosen NOT to expose any "hidden" fields of a partmodule that are not normally shown to the user, without the express permission of a mod's author to do so.
It was decided that providing the kOS users with a read-only access to the part module's hidden fields would allow to create even better scripts and wouldn't do any harm. Obviously, manipulating the module's internal state would result in all kind of troubles, so this is forbidden.

The access rules that kOS uses are as follows:

KSPFields
~~~~~~~~~

Is this a value that the user can normally see on the right-click context menu for a part? If so, then let kOS scripts GET the value. Is this a value that the user can normally manipulate via "tweakable" adjustments on the right-click context menu for a part, AND, is that tweakable a CURRENTLY enabled one? If so, then let KOS scripts SET the value, BUT they must set it to one of the values that the GUI would normally allow, according to the following rules.
Is this a value that the user can normally see on the right-click context menu for a part? If so, then let kOS scripts GET the value using the :GETFIELD method. Is this a value that represents the part's internal state and not normally displayed in the GUI? Then the :GETHIDDENFIELD method should be used. Is this a value that the user can normally manipulate via "tweakable" adjustments on the right-click context menu for a part, AND, is that tweakable a CURRENTLY enabled one? If so, then let KOS scripts SET the value, BUT they must set it to one of the values that the GUI would normally allow, according to the following rules.

- If the KSPField is boolean:
- The value must be true, false, or 0 or 1.
Expand Down
2 changes: 1 addition & 1 deletion src/kOS/AddOns/RemoteTech/RemoteTechAntennaModuleFields.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using kOS.Safe.Encapsulation;
using kOS.Safe.Encapsulation;
using kOS.Safe.Encapsulation.Suffixes;
using kOS.Safe.Exceptions;
using kOS.Suffixed;
Expand Down
67 changes: 58 additions & 9 deletions src/kOS/Suffixed/PartModuleField/PartModuleFields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ protected virtual ListValue AllFields(string formatter)
{
var returnValue = new ListValue();

IEnumerable<BaseField> visibleFields = partModule.Fields.Cast<BaseField>().Where(FieldIsVisible);
IEnumerable<BaseField> visibleFields = partModule.Fields.Cast<BaseField>().Where(field => FieldIsVisible(field));

foreach (BaseField field in visibleFields)
{
Expand All @@ -199,16 +199,42 @@ protected virtual ListValue AllFields(string formatter)
return returnValue;
}

/// <summary>
/// Return a list of all the strings of all KSPfields registered to this PartModule
/// which are currently NOT showing on the part's RMB menu.
/// </summary>
/// <returns>List of all the strings field names.</returns>
protected ListValue AllHiddenFields(string formatter)
{
var returnValue = new ListValue();

IEnumerable<BaseField> hiddenFields = partModule.Fields.Cast<BaseField>().Where(field => FieldIsVisible(field, false));

foreach (BaseField field in hiddenFields)
{
returnValue.Add(new StringValue(string.Format(formatter,
"get-only",
GetFieldName(field).ToLower(),
Utilities.Utils.KOSType(field.FieldInfo.FieldType))));
}
return returnValue;
}

/// <summary>
/// Return a list of all the strings of all KSPfields registered to this PartModule
/// which are currently showing on the part's RMB menu, without formating.
/// </summary>
/// <returns>List of all the strings field names.</returns>
protected virtual ListValue AllFieldNames()
{
return AllFieldNames(field => FieldIsVisible(field));
}

private ListValue AllFieldNames(Func<BaseField, bool> visibilityFilterPredicate)
{
var returnValue = new ListValue();

IEnumerable<BaseField> visibleFields = partModule.Fields.Cast<BaseField>().Where(FieldIsVisible);
IEnumerable<BaseField> visibleFields = partModule.Fields.Cast<BaseField>().Where(visibilityFilterPredicate);

foreach (BaseField field in visibleFields)
{
Expand All @@ -228,12 +254,28 @@ public virtual BooleanValue HasField(StringValue fieldName)
return FieldIsVisible(GetField(fieldName));
}

/// <summary>
/// Determine if the Partmodule has this KSPField on it, which is currently
/// NOT showing on the part's RMB menu:
/// </summary>
/// <param name="fieldName">The field to search for</param>
/// <returns>true if it is on the PartModule, false if it is not</returns>
public BooleanValue HasHiddenField(StringValue fieldName)
{
return GetField(fieldName, field => FieldIsVisible(field, false)) != null;
}

/// <summary>
/// Return the field itself that goes with the name (the BaseField, not the value).
/// </summary>
/// <param name="cookedGuiName">The case-insensitive guiName (or name if guiname is empty) of the field.</param>
/// <returns>a BaseField - a KSP type that can be used to get the value, or its GUI name or its reflection info.</returns>
protected BaseField GetField(string cookedGuiName)
{
return GetField(cookedGuiName, field => FieldIsVisible(field));
}

private BaseField GetField(string cookedGuiName, Func<BaseField, bool> visibilityFilterPredicate)
{
// Conceptually this should be a single hit using FirstOrDefault(), because there should only
// be one Field with the given GUI name. But Issue #2666 forced kOS to change it to an array of hits
Expand All @@ -251,7 +293,7 @@ protected BaseField GetField(string cookedGuiName)
// Issue #2666 is handled here. kOS should not return the invisible field when there's
// a visible one of the same name it could have picked instead. Only return an invisible
// field if there's no visible one to pick.
BaseField preferredMatch = allMatches.FirstOrDefault(field => FieldIsVisible(field));
BaseField preferredMatch = allMatches.FirstOrDefault(visibilityFilterPredicate);
return preferredMatch ?? allMatches.First();
}

Expand Down Expand Up @@ -417,14 +459,18 @@ private void InitializeSuffixesAfterConstruction()
AddSuffix("SETFIELD", new TwoArgsSuffix<StringValue, Structure>(SetKSPFieldValue));
AddSuffix("DOEVENT", new OneArgsSuffix<StringValue>(CallKSPEvent));
AddSuffix("DOACTION", new TwoArgsSuffix<StringValue, BooleanValue>(CallKSPAction));
AddSuffix("ALLHIDDENFIELDS", new Suffix<ListValue>(() => AllHiddenFields("({0}) {1}, is {2}")));
AddSuffix("ALLHIDDENFIELDNAMES", new Suffix<ListValue>(() => AllFieldNames(field => FieldIsVisible(field, false))));
AddSuffix("HASHIDDENFIELD", new OneArgsSuffix<BooleanValue, StringValue>(HasHiddenField));
AddSuffix("GETHIDDENFIELD", new OneArgsSuffix<Structure, StringValue>(argument => GetKSPFieldValue(argument, field => FieldIsVisible(field, false))));
}

private static bool FieldIsVisible(BaseField field)
private bool FieldIsVisible(BaseField field, bool isVisible = true)
{
return (field != null) && (HighLogic.LoadedSceneIsEditor ? field.guiActiveEditor : field.guiActive);
return (field != null) && (HighLogic.LoadedSceneIsEditor ? field.guiActiveEditor == isVisible : field.guiActive == isVisible);
}

private static bool EventIsVisible(BaseEvent evt)
private bool EventIsVisible(BaseEvent evt)
{
return (evt != null) && (
(HighLogic.LoadedSceneIsEditor ? evt.guiActiveEditor : evt.guiActive) &&
Expand All @@ -440,11 +486,14 @@ private static bool EventIsVisible(BaseEvent evt)
/// <returns></returns>
protected Structure GetKSPFieldValue(StringValue suffixName)
{
BaseField field = GetField(suffixName);
return GetKSPFieldValue(suffixName, field => FieldIsVisible(field));
}

private Structure GetKSPFieldValue(StringValue suffixName, Func<BaseField, bool> visibilityFilterPredicate)
{
BaseField field = GetField(suffixName, visibilityFilterPredicate);
if (field == null)
throw new KOSLookupFailException("FIELD", suffixName, this);
if (!FieldIsVisible(field))
throw new KOSLookupFailException("FIELD", suffixName, this, true);
Structure obj = FromPrimitiveWithAssert(field.GetValue(partModule));
return obj;
}
Expand Down
Loading