Class descriptions for custom modules: Difference between revisions

From KSP 2 Modding Wiki
(Add page contents)
m (Formatting)
Line 5: Line 5:
While you're mostly free to define how your classes would be set up, there are some guidelines that need to be followed, restrictions to be aware of and inner workings to understand.
While you're mostly free to define how your classes would be set up, there are some guidelines that need to be followed, restrictions to be aware of and inner workings to understand.


== # Data class ==
== Data class ==


=== ## Defining your class ===
=== Defining your class ===
  [Serializable]
  [Serializable]
  public class Data_MyCustomModule : ModuleData
  public class Data_MyCustomModule : ModuleData
  { .. }
  { .. }


=== ## Set your ParthBehaviourModule type reference ===
=== Set your ParthBehaviourModule type reference ===
  public override Type ModuleType => typeof(Module_MyCustomModule);
  public override Type ModuleType => typeof(Module_MyCustomModule);


=== ## Defining module properties ===
=== Defining module properties ===


* these are entries shown in the PAM
* these are entries shown in the PAM
Line 43: Line 43:
  public ModuleProperty<float> SomeEditableProperty = new (1f, false, val => $"{val:N0}°"); // initial value, isReadOnly, ToStringDelegate
  public ModuleProperty<float> SomeEditableProperty = new (1f, false, val => $"{val:N0}°"); // initial value, isReadOnly, ToStringDelegate


=== ## Defining a dropdown list property ===
=== Defining a dropdown list property ===


* dropdown list properties are string properties for which you define dropdown values in OnPartBehaviourModuleInit()
* dropdown list properties are string properties for which you define dropdown values in OnPartBehaviourModuleInit()
Line 59: Line 59:
  }
  }


=== ## OnPartBehaviourModuleInit() ===
=== OnPartBehaviourModuleInit() ===


* runs when this module is initialized when entering Flight/OAB state
* runs when this module is initialized when entering Flight/OAB state
Line 66: Line 66:
  { /* use this to initialize some values for your module, if needed */ }
  { /* use this to initialize some values for your module, if needed */ }


=== ## OAB module description ===
=== OAB module description ===


* set the description of your module for all parts it’s being attached to
* set the description of your module for all parts it’s being attached to
Line 114: Line 114:
  }
  }


=== ## Setting up resource consumptions ===
=== Setting up resource consumptions ===


* Note: trigger this from OnStart() in the Part Component class
* Note: trigger this from OnStart() in the Part Component class
Line 151: Line 151:
   
   


== # Part Behaviour class ==
== Part Behaviour class ==


=== ## Defining your class ===
=== Defining your class ===
  [DisallowMultipleComponent]
  [DisallowMultipleComponent]
  public class Module_OrbitalSurvey : PartBehaviourModule
  public class Module_OrbitalSurvey : PartBehaviourModule
  { .. }
  { .. }


=== ## Set your PartComponentModule type reference ===
=== Set your PartComponentModule type reference ===
  public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);
  public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);


=== ## Create Data module instance ===
=== Create Data module instance ===
  [SerializeField]
  [SerializeField]
  protected Data_MyCustomModule _dataMyCustomModule;
  protected Data_MyCustomModule _dataMyCustomModule;
Line 172: Line 172:
  }
  }


=== ## Initialize the module behaviour ===
=== Initialize the module behaviour ===
  private ModuleAction _myCustomAction;
  private ModuleAction _myCustomAction;
   
   
Line 201: Line 201:
  { /* do stuff here */}
  { /* do stuff here */}


=== ## FixedUpdate loop - Flight ===
=== FixedUpdate loop - Flight ===


* define stuff that needs to be executed continuously on every FixedUpdate loop. Be careful not to do expensive stuff here
* define stuff that needs to be executed continuously on every FixedUpdate loop. Be careful not to do expensive stuff here
Line 220: Line 220:
  }
  }


=== ## Update loop ===
=== Update loop ===


* similar to FixedUpdate, but this is a regular Update loop independent of game time
* similar to FixedUpdate, but this is a regular Update loop independent of game time
Line 228: Line 228:
  { .. }
  { .. }


=== ## FixedUpdate loop - OAB ===
=== FixedUpdate loop - OAB ===


* same as OnModuleFixedUpdate but it triggers only in OAB
* same as OnModuleFixedUpdate but it triggers only in OAB
Line 235: Line 235:
  { .. }
  { .. }


=== ## Define behaviour when the behaviour module instance will be destroyed ===
=== Define behaviour when the behaviour module instance will be destroyed ===


* cases: exiting Flight view, part has been destroyed, exiting the game
* cases: exiting Flight view, part has been destroyed, exiting the game
Line 245: Line 245:
  }
  }


=== ## Setting visibility for PAM properties ===
=== Setting visibility for PAM properties ===
  private void UpdateFlightPAMVisibility(bool state)
  private void UpdateFlightPAMVisibility(bool state)
  {
  {
Line 253: Line 253:
  }
  }


=== ## OnEnable ===
=== OnEnable ===


* triggers when Flight view is loaded and in OAB when part is added to the assembly
* triggers when Flight view is loaded and in OAB when part is added to the assembly
Line 260: Line 260:
  { .. }
  { .. }


== # Part Component class ==
== Part Component class ==


=== ## Defining your class ===
=== Defining your class ===
  public class PartComponentModule_MyCustomModule : PartBehaviourModule
  public class PartComponentModule_MyCustomModule : PartBehaviourModule
  { .. }
  { .. }


=== ## Set your PartBehaviourModule type reference ===
=== Set your PartBehaviourModule type reference ===
  public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);
  public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);


=== ## OnStart(double universalTime) ===
=== OnStart(double universalTime) ===


* for new vessels this will run when the Flight view is loaded
* for new vessels this will run when the Flight view is loaded
Line 290: Line 290:
  }
  }


=== ## OnUpdate ===
=== OnUpdate ===


* this starts triggering when the vessel is first placed in Flight. Doesn't trigger in the OAB before that
* this starts triggering when the vessel is first placed in Flight. Doesn't trigger in the OAB before that
Line 302: Line 302:
  }
  }


=== ## Resource consumption ===
=== Resource consumption ===
  private void ResourceConsumptionUpdate(double deltaTime)
  private void ResourceConsumptionUpdate(double deltaTime)
  {
  {
Line 352: Line 352:
  }
  }


=== ## OnShutdown ===
=== OnShutdown ===


* define behaviour when the Part Component instance will be destroyed
* define behaviour when the Part Component instance will be destroyed

Revision as of 18:36, 11 January 2024



While you're mostly free to define how your classes would be set up, there are some guidelines that need to be followed, restrictions to be aware of and inner workings to understand.

Data class

Defining your class

[Serializable]
public class Data_MyCustomModule : ModuleData
{ .. }

Set your ParthBehaviourModule type reference

public override Type ModuleType => typeof(Module_MyCustomModule);

Defining module properties

  • these are entries shown in the PAM
// Toggle (true/false) property
[KSPState] // KSPState attribute tells the game to save the state of this property in the save game file
[LocalizedField("Path/To/Your/Localization/String1")] // localization string for this attribute (see 'localization' paragraph)
[PAMDisplayControl(SortIndex = 2)] // sets the sorting index for this property. Lower values are placed first
public ModuleProperty<bool> SomeToggleProperty = new(false); // value in the parentheses defines the initial value

// String property
[LocalizedField("Path/To/Your/Localization/String2")]
[PAMDisplayControl(SortIndex = 4)]
[KSPDefinition] // KSPDefinition tells this property that its value is set from the part definition json (set by Patch Manager)
public ModuleProperty<string> SomeStringProperty = new ("");

// Float property - readonly
[LocalizedField("Path/To/Your/Localization/String3")]
[PAMDisplayControl(SortIndex = 7)]
[JsonIgnore] // either [KSPState] or [JsonIgnore] is needed if you want this property to be readonly
public ModuleProperty<float> SomeReadOnlyFloat = new (0, true, val => $"{val:N0} m");

// Float property – editable by players, will be built as a slider
[LocalizedField("Path/To/Your/Localization/String4")]
[PAMDisplayControl(SortIndex = 1)]
[SteppedRange(1f, 45f, 1f)] // minimum, maximum and step values
public ModuleProperty<float> SomeEditableProperty = new (1f, false, val => $"{val:N0}°"); // initial value, isReadOnly, ToStringDelegate

Defining a dropdown list property

  • dropdown list properties are string properties for which you define dropdown values in OnPartBehaviourModuleInit()
// Dropdown property
[LocalizedField("Path/To/Your/Localization/String5")]
public ModuleProperty<string> DropdownProperty = new ModuleProperty<string>("Some value");

public override void OnPartBehaviourModuleInit()
{
    var dropdownList = new DropdownItemList();
    dropdownList.Add("some key", new DropdownItem() { key = "some key", text = "Some value" });
    dropdownList.Add("another key", new DropdownItem() { key = "another key", text = "Another value" });
    SetDropdownData(DropdownProperty, dropdownList);
}

OnPartBehaviourModuleInit()

  • runs when this module is initialized when entering Flight/OAB state
public override void OnPartBehaviourModuleInit()
{ /* use this to initialize some values for your module, if needed */ }

OAB module description

  • set the description of your module for all parts it’s being attached to
  • description is shown in OAB and R&D while hovering over the part after pressing SHIFT
public override List<OABPartData.PartInfoModuleEntry> GetPartInfoEntries(Type partBehaviourModuleType, List<OABPartData.PartInfoModuleEntry> delegateList)
{
    if (partBehaviourModuleType == ModuleType)
    {
        // add module description
        delegateList.Add(new OABPartData.PartInfoModuleEntry("", (_) => „Path/To/Your/Localization/String5“));

        // entry header
        var entry = new OABPartData.PartInfoModuleEntry(„Path/To/Your/Localization/String6“,
            _ =>
            {
                // subentries
                var subEntries = new List<OABPartData.PartInfoModuleSubEntry>();
       
                // first subentry
                subEntries.Add(new OABPartData.PartInfoModuleSubEntry(
                    "Path/To/Your/Localization/String7", // subentry NAME
                    "subentry value"
                ));

                // second subentry
                subEntries.Add(new OABPartData.PartInfoModuleSubEntry(
                    "Path/To/Your/Localization/String8", // subentry NAME
                    "subentry value"
                ));

                // if your module is using resources, you can add them to the description
                // this doesn't set the value, it's just used to display it to the player
                if (UseResources)
                {
                    subEntries.Add(new OABPartData.PartInfoModuleSubEntry(
                        "Path/To/Your/Localization/String/ResourceName",
                        $"{RequiredResource.Rate.ToString("N3")} /s"
                    ));
                }

                return subEntries;
            });
        delegateList.Add(entry);
    }
    return delegateList;
}

Setting up resource consumptions

  • Note: trigger this from OnStart() in the Part Component class
public override void SetupResourceRequest(ResourceFlowRequestBroker resourceFlowRequestBroker)
{
    if (UseResources)
    {
        ResourceDefinitionID resourceIDFromName =
            GameManager.Instance.Game.ResourceDefinitionDatabase.GetResourceIDFromName(this.RequiredResource.ResourceName);
        if (resourceIDFromName == ResourceDefinitionID.InvalidID)
        {
            _LOGGER.LogError($"There are no resources with name {this.RequiredResource.ResourceName}");
            return;
        }
        RequestConfig = new ResourceFlowRequestCommandConfig();
        RequestConfig.FlowResource = resourceIDFromName;
        RequestConfig.FlowDirection = FlowDirection.FLOW_OUTBOUND;
        RequestConfig.FlowUnits = 0.0;
        RequestHandle = resourceFlowRequestBroker.AllocateOrGetRequest("MyCustomModule", default(ResourceFlowRequestHandle));
        resourceFlowRequestBroker.SetCommands(this.RequestHandle, 1.0, new ResourceFlowRequestCommandConfig[] { this.RequestConfig });
    }
}

[KSPDefinition]
[Tooltip("Whether the module consumes resources")]
public bool UseResources = true;

public bool HasResourcesToOperate = true;

[KSPDefinition]
[Tooltip("Resource required to operate this module if it consumes resources")]
public PartModuleResourceSetting RequiredResource;

public ResourceFlowRequestCommandConfig RequestConfig;

   

Part Behaviour class

Defining your class

[DisallowMultipleComponent]
public class Module_OrbitalSurvey : PartBehaviourModule
{ .. }

Set your PartComponentModule type reference

public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);

Create Data module instance

[SerializeField]
protected Data_MyCustomModule _dataMyCustomModule;

public override void AddDataModules()
{
    base.AddDataModules();
    _dataMyCustomModule ??= new Data_MyCustomModule();
    DataModules.TryAddUnique(_dataMyCustomModule, out _dataMyCustomModule);
}

Initialize the module behaviour

private ModuleAction _myCustomAction;

public override void OnInitialize()
{
    base.OnInitialize();

    // module actions are triggered when players press a button on the PAM property
    _myCustomAction = new ModuleAction(MethodThatWillHandleTheAction);
    _dataMyCustomModule.AddAction("Path/To/Your/Localization/String/X", _myCustomAction, 1);

    if (PartBackingMode == PartBackingModes.Flight)
    {
        /* do stuff that's only needed in Flight view */

        // example1: hide or show PAM properties depending on the Flight/OAB view
        UpdateFlightPAMVisibility(); 

        // example2: subscribe to the enabled toggle
        _dataMyCustomModule.EnabledToggleProperty.OnChangedValue += MethodThatWillHandleThis;
    } 

    if (PartBackingMode == PartBackingModes.OAB)
    { /* do stuff that's only needed in the OAB*/ }
}

private void MethodThatWillHandleTheAction()
{ /* do stuff here */}

FixedUpdate loop - Flight

  • define stuff that needs to be executed continuously on every FixedUpdate loop. Be careful not to do expensive stuff here
  • this triggers in Flight view only
// This triggers in flight
public override void OnModuleFixedUpdate(float fixedDeltaTime)
{   
    // example1: do stuff only if the module is enabled
    if (_dataMyCustomModule.EnabledToggleProperty.GetValue())
    { .. }

    // example2: update PAM items
    if (someConditionMet)
    {
        UpdateFlightPAMVisibility();
    }
}

Update loop

  • similar to FixedUpdate, but this is a regular Update loop independent of game time
  • this triggers in Flight and in OAB
public override void OnUpdate(float deltaTime)
{ .. }

FixedUpdate loop - OAB

  • same as OnModuleFixedUpdate but it triggers only in OAB
public override void OnModuleOABFixedUpdate(float deltaTime)
{ .. }

Define behaviour when the behaviour module instance will be destroyed

  • cases: exiting Flight view, part has been destroyed, exiting the game
public override void OnShutdown()
{
    // example: unsubscribe from events
    _dataMyCustomModule.EnabledToggleProperty.OnChangedValue -= OnToggleChangedValue;
}

Setting visibility for PAM properties

private void UpdateFlightPAMVisibility(bool state)
{
    _dataMyCustomModule.SetVisible(_dataMyCustomModule.SomeProperty, state);
    _dataMyCustomModule.SetVisible(_dataMyCustomModule.SomeOtherProperty, true);
    _dataMyCustomModule.SetVisible(_dataMyCustomModule.YetAnotherProperty, false);
}

OnEnable

  • triggers when Flight view is loaded and in OAB when part is added to the assembly
protected void OnEnable()
{ .. }

Part Component class

Defining your class

public class PartComponentModule_MyCustomModule : PartBehaviourModule
{ .. }

Set your PartBehaviourModule type reference

public override Type PartComponentModuleType => typeof(PartComponentModule_MyCustomModule);

OnStart(double universalTime)

  • for new vessels this will run when the Flight view is loaded
  • also runs on load for every vessel currently in Flight (don't need to be loaded).
  • best used for any kind of initialization of backend tasks this vessel/module needs to go through
private Data_MyCustomModule _dataMyCustomModule;

public override void OnStart(double universalTime)
{
    // set a reference to the Data class
    if (!DataModules.TryGetByType<Data_MyCustomModule>(out _dataMyCustomModule))
    {
        _LOGGER.LogError("Unable to find a Data_MyCustomModule in the PartComponentModule for " + base.Part.PartName);
        return;
    }

    // initialize resource requests
    _dataMyCustomModule.SetupResourceRequest(base.resourceFlowRequestBroker);
}

OnUpdate

  • this starts triggering when the vessel is first placed in Flight. Doesn't trigger in the OAB before that
  • once the vessel is in Flight, it will always trigger, in any view, until the part is destroyed/recovered
  • use this for tasks that need to continually run, even when vessel is unloaded. Be careful not to put expensive tasks here.
public override void OnUpdate(double universalTime, double deltaUniversalTime)
{
    ResourceConsumptionUpdate(deltaUniversalTime); // example1: trigger resources consumption
    UpdateStatusAndState(); // example2: do general status updates if needed
}

Resource consumption

private void ResourceConsumptionUpdate(double deltaTime)
{
    if (_dataMyCustomModule.UseResources)
    {
        if (GameManager.Instance.Game.SessionManager.IsDifficultyOptionEnabled("InfinitePower"))
        {
            _dataMyCustomModule.HasResourcesToOperate = true;
            if (base.resourceFlowRequestBroker.IsRequestActive(_dataMyCustomModule.RequestHandle))
            {
                base.resourceFlowRequestBroker.SetRequestInactive(_dataMyCustomModule.RequestHandle);
                return;
            }
        }
        else
        {
            if (this._hasOutstandingRequest)
            {
                this._returnedRequestResolutionState =
                    base.resourceFlowRequestBroker.GetRequestState(_dataMyCustomModule.RequestHandle);
                _dataMyCustomModule.HasResourcesToOperate = this._returnedRequestResolutionState.WasLastTickDeliveryAccepted;
            }
            this._hasOutstandingRequest = false;
            if (!_dataMyCustomModule.EnabledToggleProperty.GetValue() &&
                base.resourceFlowRequestBroker.IsRequestActive(_dataMyCustomModule.RequestHandle))
            {
                base.resourceFlowRequestBroker.SetRequestInactive(_dataMyCustomModule.RequestHandle);
                _dataMyCustomModule.HasResourcesToOperate = false;
            }
            else if (_dataMyCustomModule.EnabledToggle.GetValue() &&
                base.resourceFlowRequestBroker.IsRequestInactive(_dataMyCustomModule.RequestHandle))
            {
                base.resourceFlowRequestBroker.SetRequestActive(_dataMyCustomModule.RequestHandle);
            }
            if (_dataMyCustomModule.EnabledToggleProperty.GetValue())
            {
                _dataMyCustomModule.RequestConfig.FlowUnits = (double)_dataMyCustomModule.RequiredResource.Rate;
                base.resourceFlowRequestBroker.SetCommands(_dataMyCustomModule.RequestHandle, 1.0,
                    new ResourceFlowRequestCommandConfig[] { _dataMyCustomModule.RequestConfig });
                this._hasOutstandingRequest = true;
                return;
            }
        }
    }
    else
    {
        _dataMyCustomModule.HasResourcesToOperate = true;
    }
}

OnShutdown

  • define behaviour when the Part Component instance will be destroyed
  • cases: part has been destroyed, exiting the game
public override void OnShutdown()
{ .. }