Skip to content

Tutorials

cheese3660 edited this page Dec 12, 2023 · 4 revisions

Simple Patching Tutorials

Settings Recommendations

If you already have Patch Manager installed, it is recommended if you are doing anything with parts modding or writing patches to always have the Always Invalidate Cache option selected.

Setting up a mod folder

The first thing you need to do for any kind of patching is have a mod folder, if your mod has code, or is a part mod made in unity, this folder will already exist for you depending on if you are using KSP2UnityTools or SpaceWarp.Template, if you aren't and you are adding parts/code, you should go follow those tutorials to set up an environment first, and then the mod folder will be the same for both for deployment, the plugin_template/ folder under the main folders for them. Otherwise creating a simple mod folder for deployment is easy. And if you want to test this as easily as possible you will want to put this directly in the plugins folder in your KSP2 installation.

The file structure of this mod folder is quite simple:

FolderName/
    swinfo.json

The contents of the aforementioned swinfo.json are used to describe your mod, and also tell Space Warp how to load it. Here is a basic one for Patch Manager based mods. The fields you will need to set yourself are as follows

  • "mod_id" should be set to the ID of your mod, this is usually your mods name in CamelCase with an optional prefix of your Username and a .
  • "author" should be your (user)name
  • "description" should be a short description of what your mod does
  • "source" should point to the repository you are storing your mods source code at, otherwise it should be removed
  • "version" should be set to your mods version, and changed whenever you update your mod
{
    "spec": "2.0",
    "mod_id": "YourModId", 
    "author": "You",
    "name": "Your Mod",
    "description": "Does what your mods",
    "source": "your/source/link",
    "version": "1.0.0",
    "ksp2_version": {
        "min": "0.1.5",
        "max": "*"
    },
    "dependencies": [
        {
            "id": "com.github.x606.spacewarp",
            "version": {
                "min": "1.5.0",
                "max": "*"
            }
        },
        {
            "id": "PatchManager",
            "version": {
                "min": "0.4.0",
                "max": "*"
            }
        }
    ]
}

Changing the mass of a single part in the game

This will be one of the simplest things we can do with a patch, changing the mass of a single part, first though we need to know what part we want to change the mass of, this being the name of the parts json definition, for now lets go with the wheel_0v_rover, which is the smallest rover wheel. First we want to create a rover.patch file under our mods folder and put the following in it.

:parts #wheel_0v_rover {
    mass: *5; // Multiply the mass *5 of the rover wheel
}

This patch will then multiply the aforementioned rover wheels mass by 5, but to see how, let's break down the patch symbol by symbol

:parts

The :parts symbol tells Patch Manager that you are patching parts, it doesn't tell it which parts you want to patch, so patch manager is first going to assume you want to patch every part

#wheel_0v_rover

The #wheel_0v_rover is a name selector, it will select any object that it is being used on that has a name matching wheel_0v_rover, these name selectors can also use wildcards such as ? for a single character and * for multiple characters. When combined with the :parts symbol and nothing else between them, this will be used to filter down all the parts only to the ones that are named wheel_0v_rover which is the part we are targeting.

{

The { starts a modification/action block for us, more patch selections can follow after this, but we can also modify fields and whatnot as well.

mass:

The mass: starts a modification on the field in the parts definition with the name "mass", which holds, you guessed it, the mass of the part.

*

The * with nothing on its lefthandside is an implicit multiplication of the current value of the field by whatever is on its righthandside.

5

The 5 is the righthandside of the *, and when taken together with the mass field and the * operator, means that the result of this is going to make the mass be 5 times its current mass when this patch is run

;

This just ends the modification of the field, you need this at the end of every line where you are modifying a field

}

And this terminates the modification/selection action block for us, you always need matching { and }

Adding a type of resource to a part that already has a resource container

[TBD]

Adding a simple module to a part

[TBD]

Changing the crew capacity of a part

Creating a new resource

[TBD]

Creating a new recipe

[TBD]

Intermediate Patching Tutorials

Organizing the patches for your mods, including with using libraries

Once you start making multiple patches for your mod, some of which may be using values across patches, you should start thinking about organizing your patches, and deduplicating the values that used in multiple places.

File structure organization of patches

This is a suggestion, that is used across quite a few mods, on how you should organize your patch files under your mod

mod_root/
    swinfo.json
    patches/
        *.patch
    libraries/
        _*.patch

This being, you should have 2 folders under your mod, one for patches, where the patches are grouped into separate files based on whatever criteria you decide (e.g. function, whether or not they are creating things, whether or not they are patches to add compatibility to other mods, etc...), and the other for patch libraries, which contain constant values/mixins/functions for use across your patches, note how all the files in this folder start with an _, this is intentional and required.

Usage of libraries

Patch libraries are patch files with a filename that starts with an _, any patch blocks within patch libraries will not be run, and they are generally recommended to be used to store constant values/mixins/functions/etc... that you may need to use in multiple places. For this example we will be making a library that contains a constant value for scaling the mass of a part by a value, and then we will be using that in our main patch.

For the library, we can name it something simple like _constants.patch and put it under the libraries/ folder

$MASS_SCALE: 5; // The $...: defines a variable/constant which can be referred to by name elsewhere

Now in our main patch, this can be named whatever under the patches/ folder

@use "constants"; // This imports the _constants.patch library we just made allowing us to use the values from it
:parts #antenna_* { // This patch will apply to every antenna due to the wildcard
    mass: *$MASS_SCALE; // Multiply the mass by the mass scale value we created in our library
}
:parts #airbrake_1v {
    mass: *$MASS_SCALE; // And do the same with the airbrake part as well
}

## Increasing the crew capacities of a part
\[TBD]
## Replacing a resource in a part
\[TBD]
## Adding a module to all parts that have a certain module
\[TBD]
## Increasing the crew capacity of all crewed parts
\[TBD]
## Adding PAM overrides for modules added to parts
So, you've added a module to your part, but you want it to show up in the PAM (Part Action Manager), as this does not happen by default.
For this you want to add the following after the part where you add your modules
```scss
:parts ... {
    // Add the modules here
    PAMModuleVisualsOverride: +[
        {
            PartComponentModuleName: "...", // This is the module (usually of the form PartComponentModule_etc...) you want to add to the PAM of (e.g. PartComponentModule_ResourceConverter)
            ModuleDisplayName: "Your/Localized/Name", // This is the localization key of your modules PAM name
            ShowHeader: true, // Whether or not you want to show the header in the PAM
            ShowFooter: false // Whether or not you want to show the footer in the PAM
        }
        // You can add more modules by adding more comma separated blocks like above
    ];
    // If you wish to change how your module is sorted as well
    PAMModuleSortOverride: +[
        {
            PartComponentModuleName: "...", // This is the module (usually of the form PartComponentModule_etc...) you want to change the sorting of (e.g. PartComponentModule_ResourceConverter)
            sortIndex: 40 // This is the sort index in the PAM for your part, you have to fine tune this yourself, 40 is a good starting point
        }
        // You can add more overrides by adding more comma separated blocks like above
    ];
}

Advanced Patching Tutorials

Creating a separate library mod and using that for constants in your mods patches

[TBD]

Capturing values from modules of part(s) that have a specific module and using that value

There may be times where you want to add a module whose values depend on those in another module, or only if it has another module and a value in that module is above a specific number. For this you are going to want to use capture selectors, a variant of the .class selector meant for you to run code in the selector to add variables to the current scope and the like. The syntax for this selector is .class:[...] where ... is a semicolon separated (and terminated) list of statements running inside the "scope" of the module. To put this into use in an example for the purposes of the tutorial, we are going to capture the communication range of parts that have an antennae, and add a custom module with this communication range.

// This is going to be a part based selection
:parts {
    // Now let's do our "class capture", the .Module_DataTransmitter is the module for an antenna
    .Module_DataTransmitter:[
        $commRange: $$CommunicationRange; // And this is our capture, where we are capturing the object local value $$CommunicationRange from the Antenna module, and storing it in a scope local variable of $commRange
    ] {
        // In this scope $commRange is accessable, but $$CommunicationRange is not
        // Add the module like usual
        +Module_Test {
            // And the data
            +Data_Test {
                // Setting the value of Test inside the data to our captured value
                Test: $commRange;
            }
        }
    }
}

Patching JSON files that aren't of the default loaded rulesets

[TBD]