-
Notifications
You must be signed in to change notification settings - Fork 5
Reference
The Patch Manager Language for KSP 2 is a patching format based off of a modified form of SCSS to define patches to multiple things in the game such as parts/celestial bodies/general json files. The following things motivated this formats creation
- The old MM (Module Manager) syntax was very obscure and not easy to learn and based off of an obscure CFG format that KSP1 used to define parts/modules/what have you
- KSP 2 does not do part "configuration" in the same way as KSP 1 in any case so MM cannot be ported into KSP 2
- There are similarities between the old MM syntax and CSS, and the usage of "selectors" to find parts lends itself to a language based off of (S)CSS
- Case studies showing the patching syntax to an old KSP 1 MM part patcher gave positive reactions to the syntax
The following patch, stored in a double_container.patch
file will double the value of any parts resource container
:parts {
* > ResourceContainers > * {
capacityUnits: *5;
initialUnits: *5;
}
}
Let's go through the patch in sections to describe how everything works
:parts {
This defines a part
patch, which means the inside defines a specialized set of patch rules for parts
* > ResourceContainers > * {
This one is a bit more complex, it uses a >
operator, which means that the right side is a child of the left side, and the *
operator which "selects" everything. So essentially read right to left, this selects every resource container in every parts list of resource containers.
capacityUnits: *5;
initialUnits: *5;
These 2 do essentially the same thing, they multiply the capacityUnits/initialUnits of the resource container by 5 respectively, the prefixed *
does multiplication, though it is syntactic sugar for $value*
, so the following 2 lines would be identical to above in terms of function
capacityUnits: $value*5;
initialUnits: $value*5;
And finally the last 2 lines
}
}
These 2 are pretty explanatory, they just end the patch
There are a few primitive values in the Patch Manager Language, they correspond to JSON values mostly
Type | Example Value | Description |
---|---|---|
none | null | A completely empty value |
bool | true/false | A boolean value, pretty self explanatory |
int | 123 | Any integer number |
real | 123.45 | Any non-integer number |
string | "Hello" | Any sequence of text |
list | [1,2] | A sequence of values |
object | {x:"y"} | A sequence of values where each index has a name |
delete | @delete | A value that deletes whatever it is set to |
Patch Manager uses a system of selectors to match what it is trying to transform, be it parts, bodys, etc...
Selector | Example | Description |
---|---|---|
:ruleset | :parts | Indicates a certain ruleset for matching or creation |
* | * | Selects all elements |
#name | #booster* | Depending on ruleset selects something by name |
element | ResourceContainers | Depends on the current ruleset but usually selects a field of some sort |
.element | .methalox | Depends on ruleset, same as above but usually selects stuff that has said field |
selector,selector | .methalox,.hydrogen | Matches either selector |
(selector) | (part,*) | Wraps a selector phrase to combine w/ other selectors |
selector selector | part #xyz | Matches both selectors |
selector>selector | * > * | Matches the left selector w/ things that are children of the right selection |
+element | +hydrogen | Means that this adds the element on the righthand side to the parent |
~.element | ~.Gimbal | Select all w/o the element |
~#name | ~#booster* | Select all that do not have a name like such |
%element | %hydrogen | Select all of said element if it exists, otherwise create one and select it |
Some differences from CSS to note
- Classes are able to have
.
in them,.
is not used to split on anything -
.x.y
does not select both x and y, it insteads selects x.y -
.x .y
will select both x and y, not all elements w/ y that are descendents of an element w/ x use.x > .y
for that -
>
can be used w/ more than just "elements" -
#...
can include.
in the name -
#...
also has support for wildcards like*
and?
Patch Manager currently has the following rulesets defined
:resources
:parts
:json
Each of these has specific things that they do
Name | Description |
---|---|
.recipe | Means that this resource is a recipe rather than a regular resource |
.resource | Means that this resource is just a resource |
The resource @new(...)
constructor attribute takes 1-2 arguments, the first argument being the resource name, and the second being an optional boolean that defines wether or not this is a recipe
Resources have the following fields, and has nothing that can be "added" to them with the +element
selector
Name | Type | Example | Description |
---|---|---|---|
name | string | "ElectricCharge" | The name of the resource |
displayNameKey | string | "Resource/DisplayName/Electric Charge" | The localization key for the display name |
abbreviationKey | string | "Resource/Abbreviation/EC" | The localization key for the abbreviation |
isTweakable | bool | true | TBD |
isVisible | bool | true | TBD |
massPerUnit | real | 0.0 | The mass per unit of resource |
volumePerUnit | real | 0.0 | The volume per unit of resource |
specificHeatCapacityPerUnit | real | 0.0 | The specific heat per unit of resource |
flowMode | int | 3 | The flow behaviour of this resource |
transferMode | int | 1 | The transfer behaviour of this resource |
costPerUnit | real | 0.0 | Unused: How much one unit costs |
NonStageable | bool | true | TBD |
resourceIconAssetAddress | string | "Assets/UI/Sprites/Whitebox/WB-ICO-Battery.png" | The addressables address for the resources icon |
This is the most generic
Statements are what come in the braces after selectors
:parts #engine_1v_methalox_swivel {
// Statements are in here
}
Statement Syntax
// Fields
cost: [Expression];
// Indexed field
ResourceContainers[0]: [Expression];
ResourceContainers[.methalox]: [Expression];
// setValue (Just sets this entire field to the value determined by the expression)
@set [Expression];
// Merge (Merges the values in the expression w/ this value, not really a need for this but)
@merge [Expression]
// Deletion, deletes this whatever from its parent
@delete;
// Local Variables (Always scoped to the current block)
$x: [Expression]
There are a limited set of "Expressions" in the Patch Manager Language, as they are mostly the prefix sugar ones before "Sub Expressions"
// Literally any value defined in the values section
[SubExpression]
+[SubExpression] // Add to the original value w/ the result of expression
-[SubExpression] // Same as above but subtraction
// To define a negative value or use unary minus
// you need to wrap it in parentheses like (-1)
*[SubExpression] // Multiplication
/[SubExpression] // Division
[Any Value] // Any value defined in the values table
5.0 // Example, the number 5
$VARIABLE_NAME // Refer to a variable, there are a list of already defined variables as well
$value // Example, the current value of the field
$current // The current object being patched
$parent // The parent of the current object being patched
$$FIELD_NAME // Refers to a field on the current object
$$crewCapacity // Example, refers to the crew capacity of the current part
([SubExpression]) // Just for precedence
($value*$value) // Example, a multiplication operation wrapped in parentheses
-[SubExpression] // Negate a subexpression
// (if this is at the expression level just wrap it in parentheses)
-$value // Example, the additive inverse of the current value
+[SubExpression] // Does nothing really
+3 // Example, positive 3
![SubExpression] // Not expression, turns a boolean from true to false and vice versa
[SubExpression]+[SubExpression] // Add 2 sub expressions
5+$y // Example adding 5 to a variable named y
[SubExpression]-[SubExpression] // Subtract 2 sub expressions
1-$x // Example subtracting x from 1
[SubExpression]*[SubExpression] // Multiplication
(5+$y)*6 // Example
[SubExpression]/[SubExpression] // Division
1/$value // Example, get the inverse of the current value
[SubExpression]%[SubExpression] // Remainder
$x % 2 // The remainder of x/2
[SubExpression]>[SubExpression] // Comparison
$x > 0 // true if x is greater than 0 else false
[SubExpression]<[SubExpression] // Comparison
$x < 0 // true if x is less than 0 else false
[SubExpression]==[SubExpression] // Comparison
$x = 0 // true if x equal to 0 else false
[SubExpression]!=[SubExpression] // Comparison (glyph is "!" followed by "=")
$x != 0 // true if x is less than 0 else false
[SubExpression]&[SubExpression] // a and b
($x > 0) & $y // x and $y
[SubExpression]|[SubExpression] // a or b
($x > 0) | $y // x or $y
[SubExpression][[SubExpression]] // index a value
$current["x"] // Example, get the field "x" out of the current "object"
[SubExpression].[name] // Same as above
$current.x // Example, does the exact same as above
// More advanced stuff
[SubExpression]([SubExpression]...) // Function call
max(1,2,3,4) // Get the max value between 1,2,3 and 4
[SubExpression]:[name]([SubExpression]) // Member Function call
[1,2,3]:append(4) // Results in [1,2,3,4]
[SubExpression] if [SubExpression] else [SubExpression] // Ternary
5 if ($x % 2) == 0 else 3 // results in 5 if x is even, otherwise 3
$value - The current value of the field being patched
$current - The current object being patched
$parent - The parent of the current object being patched
Patch Manager has attributes that can be attached to patch selectors beforehand
All attributes apply to all nested selectors as well, and you can't have conflicting attributes
@require 'mod-guid' // This selector will only match if there is a mod w/ mod-guid installed
@require-not 'mod-guid' // The opposite of the above attribute
@stage 'stage-name' // This selector runs during the given stage
@new(...) // This selector generates a new piece of data based off its ruleset, the arguments are ruleset dependent
Patch Manager also has a few top level declarations to be used
@use 'library'; // Imports a patch file called "_library.patch" into this patch
// (used if you want to define a set of things to be common across your patches)
// Any patch file that starts w/ an underscore
// is meant for use as a library and is not ran
$x: 5; // Define a top level variable, these are global for the patch file,
// and can only be redefined at the top level
@define-stage 'pre',5; // Defines a stage for this patch file to use,
// stages are per mod so you can use them across patch files
// and you only have to define them in one file, you can call this one stages.patch (make sure it is not a library!)
// The value after is the "priority" which is any number,
// the priority system runs from lowest to highest, within this mod
// Note for using other mods stages,
// when using the @stage attribute,
// just prepend the guid to the stage name
// Like "com.github.x606.spacewarp:pre"
// Other patches using this stage outside of this mod will be run at the same time as the stage within the mod
@define-global-stage 'something', 10; // Works like above, except the priority is global, and not within the mod
In a patch block or at the top level you can do the following
@if condition
// patch that gets ran if condition is true
@else // Optional
// patch that gets ran if it is false
@end
You can template patches using mixins
@mixin scale-resource($amount: 1) {
capacityUnits: *$amount;
initialUnits: *$amount;
}
Which are included using @include
inside a patch body
:part > ResourceContainers > * {
@include scale-resources($amount: 5);
}
You can define functions in the Patch Manager that can be run.
@function double($value) {
@return $value * 2;
}
Which can then be used in a patch like follows
:parts > ResourceContainers > * {
capacityUnits: double($value); // Normal call syntax
initialUnits: $value:double(); // "Member call" syntax
}
Functions can also have default arguments
@function $double($value: 5) {
@return $value * 2;
}
You can also define closures, anonymous scope capturing functions, inline with values
@use 'builtin:functional';
$double: @function($value) { @return $value*2; };
$two: $double:invoke($value);
Patch Manager also has a builtin library of functions to be used
(TODO)
The following patch multiplies the amount of resources all parts can store by 5
:parts {
* > resourceContainers > * {
capacityUnits: *5;
initialUnits: *5;
}
}
The following patch increases the crew capacity of all parts that can store crew by 10
:parts {
@if $$crewCapacity {
crewCapacity: +10;
}
}
The following patch defines a new resource called stormlight
@new("Stormlight")
:resources {
displayNameKey: "Resource/DisplayName/Stormlight";
abbreviationKey: "Resource/Abbreviation/SL";
isTweakable: true;
isVisible: true;
massPerUnit: 0;
volumePerUnit: 0;
specificHeatCapacityPerUnit: 2010;
flowMode: 4;
transferMode: 1;
costPerUnit: 0.8;
NonStageable: false;
resourceIconAssetAddress: "";
vfxFuelType: "Pressurized";
}
:parts {
$name: $$partName;
+Module_Test {
+Data_Test {
TestData: $name;
}
}
}
/* Example of a top level variable */
$minCommRange: 1000000000;
:parts {
/* Select parts with a DataTransmitter module. */
/* Then, take the value from DataTransmitter field 'CommunicationRange' and return it as a variable called 'commRange' */
.Module_DataTransmitter:[$commRange: $$CommunicationRange;] {
/* Select only parts that have a higher comm range value than the defined minimum */
@if $commRange > $minCommRange {
/* Add your own MyCustomModule to selected parts */
+Module_MyCustomModule {
/* Add ModuleData to MyCustomModule */
+Data_MyCustomModule {
/* Add your custom made class that will hold some data */
+MyComplexClassProperty {
MyString: "initial value of the property";
MyInt: 1;
MyBool: true;
}
/* Change the value of other properties */
MyStateStringProperty: "initial value of the property";
MyDefinitionStringProperty: "initial value of the property";
}
}
/* Adds your module section to Parts Manager */
PAMModuleVisualsOverride: +[
{
PartComponentModuleName: "PartComponentModule_MyCustomModule",
ModuleDisplayName: "PartModules/MyCustomModule/Name",
ShowHeader: true,
ShowFooter: false
}
];
/* Define sorting for your module */
PAMModuleSortOverride: +[
{
PartComponentModuleName: "PartComponentModule_MyCustomModule",
sortIndex: 100
}
];
}
}
}