-
Notifications
You must be signed in to change notification settings - Fork 5
Tutorials
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.
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": "*"
}
}
]
}
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 }
[TBD]
[TBD]
[TBD]
[TBD]
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.
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.
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
];
}
[TBD]
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;
}
}
}
}
[TBD]