Class descriptions for custom modules: Difference between revisions

From KSP 2 Modding Wiki
m (Formatting)
(Additional clarification that PartBehaviour is only for loaded vessels)
 
Line 201: Line 201:
  { /* do stuff here */}
  { /* do stuff here */}


=== FixedUpdate loop - Flight ===
=== FixedUpdate loop - Flight/Map ===


* 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
* this triggers in Flight view only
* this triggers when the vessel is loaded, in Flight/Map view only


  // This triggers in flight
  // This triggers in flight
Line 223: Line 223:


* 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
* this triggers in Flight and in OAB
* this triggers in Flight/Map when the vessel is loaded and in OAB when the part is attached to the assembly


  public override void OnUpdate(float deltaTime)
  public override void OnUpdate(float deltaTime)
Line 255: Line 255:
=== OnEnable ===
=== OnEnable ===


* triggers when Flight view is loaded and in OAB when part is added to the assembly
* triggers when Flight view is loaded (only for the loaded vessel) and in OAB when part is added to the assembly


  protected void OnEnable()
  protected void OnEnable()

Latest revision as of 16:01, 26 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/Map

  • define stuff that needs to be executed continuously on every FixedUpdate loop. Be careful not to do expensive stuff here
  • this triggers when the vessel is loaded, in Flight/Map 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/Map when the vessel is loaded and in OAB when the part is attached to the assembly
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 (only for the loaded vessel) 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()
{ .. }