Miscellaneous and tips for custom modules

From KSP 2 Modding Wiki


Localization

For module properties that support a LocalizationField parameter it's best to use a localization file.

[LocalizedField("Path/To/Your/Localization/String")]
public ModuleProperty<bool> SomeToggleProperty = new(false);

Localizations will be automatically loaded by SpaceWarp if placed in ../BepInEx/plugins/YourModFolder/localizations/ inside a .csv file.

Structure your .csv file like so:

Key,Type,Desc,English,German,French,Italian,Spanish,Japanese,Korean,Polish,Russian,Chinese (Simplified),Portuguese (Brazil),Chinese (Traditional)
Path/To/Your/Localization/String,Text,,English translation goes here,German translation goes here

If you don't provide a translation string for a language, it will default to English for that particular string.

If you need to place a comma (",") in your translation string, wrap it inside double quotations marks, like so:

Path/To/Your/Localization/String,Text,,"I can write commas here, no problem, as much commas I'd like."

PAM module headers and sorting

If you want your module to have a header before its properties or if it needs to be sorted relative to other modules in the same part, define this in your Patch Manager .patch file:

:parts {
    +Module_OrbitalSurvey {
        +Data_OrbitalSurvey {
            ..
        }
    }
    PAMModuleVisualsOverride +: [
        {
            PartComponentModuleName: PartComponentModule_MyCustomModule,
            ModuleDisplayName: "Path/To/Your/Localization/String/Where/Your/Module/Name/Is/Defined",
            ShowHeader: true,
            ShowFooter: true // setting ShowFooter doesn't appear to have any effect? Update this guide if you have new info
        }
    ];
    PAMModuleSortOverride +: [
        {
            PartComponentModuleName: PartComponentModule_MyCustomModule,
            sortIndex: 40 // try setting a different value if you don't get what you need
        }
    ];
}

Register your module for background resource processing

Resource consumption while the vessel is unloaded is disabled by default, as an effort to enhance performance of the stock game. If your module needs to consume resources while it's not loaded (for example Life support consumption), Space Warp supports registration of your module to consume resources.

Do this somewhere where you initialize stuff for your mod (example here: in your plugin class):

public class YourModPlugin : BaseSpaceWarpPlugin
{
    public override void OnInitialized()
    {
        SpaceWarp.API.Parts.PartComponentModuleOverride
            .RegisterModuleForBackgroundResourceProcessing<PartComponentModule_YourCustomModule>();
    }
}

Tip: add reference to your Part Component module in your Data module class

If you need a reference to your Part Component class from inside your Data class, you can do it like so:

public class Data_MyCustomModule : ModuleData
{
    // be sure to put the JsonIgnore attribute, as otherwise there will be a circular reference and bad things will happen
    [JsonIgnore]
    public PartComponentModule_MyCustomModule PartComponentModule;
}

public class PartComponentModule_MyCustomModule : PartComponentModule
{
    private Data_MyCustomModule _dataMyCustomModule;

    public override void OnStart(double universalTime)
    {
        if (!DataModules.TryGetByType<Data_MyCustomModule>(out _dataMyCustomModule))
        {
            return;
        }

        _dataMyCustomModule.PartComponentModule = this;
    }
}

Tip: fetch VesselComponent, PartOwner, Body or other values of your vessel or part

public class PartComponentModule_MyCustomModule : PartComponentModule
{
    public override void OnStart(double universalTime)
    {
        var partOwner = base.Part.PartOwner;
        var vessel = partOwner.SimulationObject.Vessel;
        var body = vessel.mainBody.Name;
        var altitude = vessel.AltitudeFromRadius;
    }
}

Tip: fetch other modules attached to the part

Sometimes you might need data from other modules.

public class PartComponentModule_MyCustomModule : PartComponentModule
{
    private Data_Deployable _dataDeployable;
    private PartComponentModule_ScienceExperiment _moduleScienceExperiment;

    public override void OnStart(double universalTime)
    {
        // get the ScienceExperiment module
        Part.TryGetModule(typeof(PartComponentModule_ScienceExperiment), out var m);
        _moduleScienceExperiment = m as PartComponentModule_ScienceExperiment;

        // try to get Data_Deployable if the part has a Deployable module
        Part.TryGetModule(typeof(PartComponentModule_Deployable), out var m2);
        if (m2 != null)
        {
            var moduleDeployable = m2 as PartComponentModule_Deployable;
            foreach (var dataDeployable in moduleDeployable.DataModules?.ValuesList)
            {
                if (dataDeployable is Data_Deployable)
                {
                    _dataDeployable = dataDeployable as Data_Deployable;

                    // register for the deployment event
                    _dataDeployable.toggleExtend.OnChangedValue += (_) => YourMethodThatWillReactToDeploying();
                    break;
                }
            }
        }
    }
}