Jump to content

The Great Unofficial Module Manager Help Thread!


Recommended Posts

IMPORTANT: PAGE UNDER CONSTRUCTION, NOT FINAL

Before I begin:

1. This is my own idea, it is in no way official, nor affiliated with any developer of ModuleManager.

2. I thought it would be helpful.

3. No one told me not to.

4. This is for you, @Ooglak Kerman. And all the others who wished to manage their modules, but could not.

 

So, I figured that many KSP players who use a lot of mods may not exactly have the mods just the way the want. Or maybe a budding modder wants to learn some stuff about CFG files. 

Part 1: What is Module Manager?

ModuleManager is a mod created by @ialdabaoth and currently maintained by @sarbian

A large portion of KSP's data are stored in text files with the .cfg extension. This includes resource definitions, part functionality, science flavor text and gains, and much, much, more.

Sometimes, a mod may need to change this existing data as well as adding it's own. That is where Module Manager comes in to help. With ModuleManager, the text in CFG files can change what is actually loaded into the game from what is in the other files.

For example, Snacks, a life-support mods, adds some life support supplies to all crewed pods using ModuleManager.

THIS GUIDE WILL NOT TELL YOU HOW TO INSTALL MODULE MANAGER.

Part 2: The Node Structure

The CFG files are set up into a hierarchy of nodes. These nodes contain fields, where data is, and other nodes.

Let's take a look at the definition of the Liquid Fuel resource.

Spoiler

RESOURCE_DEFINITION
{
  name = LiquidFuel
  displayName = #autoLOC_500999 //#autoLOC_500999 = Liquid Fuel
  abbreviation = #autoLOC_6002095  //#autoLOC_6002095 = LF
  density = 0.005
  unitCost = 0.8
  hsp = 2010
  flowMode = STACK_PRIORITY_SEARCH
  transfer = PUMP
  isTweakable = true
  volume = 5
  RESOURCE_DRAIN_DEFINITION
  {
    isDrainable = true
    showDrainFX = true
    drainFXPriority = 7
    drainForceISP = 5
    drainFXDefinition = gasDraining
  }
}

At the top, in caps, we see "RESOURCE_DEFINITION". This is the type of node. As it does not exist within another node, we call it a top-level node.

Within it, are some other nodes and fields.

First, is the "name" field. This is not what is displayed, it is an internal name. It is very important, however, for selecting that node for later editing. It also has some other functions for things like functionality.

You may notice, that in the displayName and abbreviation fields, there is some #autoLOC_<numbers>. This uses the localization system, displaying a different value based on what language is selected for the game. This will be later discussed further.

So, we have those fields, but we also have the "RESOURCE_DRAIN_DEFINITION" node, with its own fields inside. There could be other nodes inside that, and you can go on forever.

Part 3: Selecting a Node for Editing

At the top of the previous example, we saw the line "RESOURCE_DEFINITION". In front of that, we could have put a few thing, and behind it, we could have put a lot more things.

If, at the front, we had an "@" character, we could select a RESOURCE_DEFINITION for editing. if we had a "+" character, it would make a copy and let us edit that. the "!" and "-" characters delete the selected node, but you still have to specify a little something. More detail on this later. Finally, the "%" character edits something if it already exists, and if not, creates it.

At the back, we could have put some square brackets. Inside those brackets, we could select a name of the specific node to edit, as there are lots of nodes of the same type. The name is the same as the "name" field specified in the initial creation of the node. You can put the "*" character inside the name, and it will represent any number of any characters. So, "abc*" would select "abc1", "abc2", "abcde", but not "ab3".  Additionally, the "?" character can be any one random character. If you want to select every part, you can put just the "*" character into the square brackets.

After the square brackets, we can put a comma and then a number, or some other selectors. The numbers indicate which node to edit, if the node they are in have multiple with the same name. The selectors are: HAS, NEEDS, FOR, BEFORE, AFTER, FIRST, and FINAL. They will be discussed in more detail later.

After each of these words, there is some stuff in square brackets, and then a colon between everything. 

Here's an example, from the Blueshift thread:

Spoiler

@PART[wbiS3WarpCore,wbiS3WarpEngine,wbiMk2WarpCore,wbiS2WarpCore]
{
    @MODULE[WBIWarpEngine]
    {
        @warpSpeedSkillMultiplier = 0.6
    }
}

Let's look at each line.

First, it edits the parts with a specific set of nodes. Curly braces indicate that we are going a level in. Then, we edit a module node with the name "WBIWarpEngine". Again, curly braces. Finally, we edit the warpSpeedSkillMultiplier field to be 0.6.

Remember to make sure that each opening curly brace is paired with a closed curly brace. This is one of the most common mistakes!

Part 4: Selecting Fields

Fields also need to have the selection character at the front, and can have the other selectors or numbers applied to them. Oh, and the number can be the star symbol to select all of them. If there is no number, the first one is used. These go before the new value, like this:

@WhatEverThisIs, 3:NEEDS[xyz] = abc

Part 5: Other Selectors

There are a few things you can put after each selected node or field:

1. HAS

HAS will only make the patch apply to parts with some condition.

After the word goes a set of square brackets. Inside, you can put: <character><node/fieldType>[<name>]. 

The character can be @, # or !.

If the character is @, then the patch will only run on parts with that node. If the character is !, then the patch will only run on parts without that node. The # character will be covered later, when we get to variables.

The node type should be self-explanatory. is it a PART? MODULE? RESOURCE? 

The name is just the same as the name field when selecting a part.

 

2. FOR

This one's simple, a mod name goes in the box. This lets Module Manager know that the patch is for that mod.

 

3. NEEDS

For this selector, inside the box goes the name of a mod. The patch will only run if that mod is installed. The ! character can be used to make the patch only run if the mod is not installed, and the | character acts like a logical OR gate, so either of two mods can be installed and the patch will run.

To determine whether a mod exists:

A. if there is a patch, that runs at all, with the FOR selector carrying a name that is the same as what is in the NEEDS box

B. Or, if there is a folder in GameData with the name you are looking for.

 

4-5. BEFORE and AFTER

Mod names go in the box.

These make the patch run before or after patches with FOR selectors of the mod name. Useful to avoid pouring the drink before getting out a cup, so to speak.

 

6-7. FIRST and FINAL

Simply put, these just make the patches run before or after everything else. No square brackets needed.

COMMUNITY UPDATING

As it seems others have begun to add things to this, I will quote them here in the OP. Thanks, everyone!

On 6/5/2022 at 7:06 AM, Aelfhe1m said:

Contributions for this thread:

Selecting Nodes - or how to use :HAS

Consider the following nodes partially extracted from the stock files:

  Reveal hidden contents
PART
{
	name = cupola
	CrewCapacity = 1
	TechRequired = commandModules
	// snip extra fields

	MODULE
	{
		name = ModuleCommand
		minimumCrew = 1
	}
	RESOURCE
	{
		name = ElectricCharge
		amount = 200
		maxAmount = 200
	}
	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = crewReport
		interactionRange = 2
		// snip
	}
	// snip
}

EXPERIMENT_DEFINITION
{
	id = crewReport
	title = #autoLOC_501009 //#autoLOC_501009 = Crew Report
	baseValue = 5
	scienceCap = 5
	dataScale = 1
	
	requireAtmosphere = False
	situationMask = 63
	biomeMask = 7

	RESULTS
	{
		default = #autoLOC_501258 //#autoLOC_501258 = You record the crew's assessment of the situation.

		KerbinSrfLandedLaunchpad = #autoLOC_501259 //#autoLOC_501259 = We don't seem to be moving very fast right now.
		// many more results 
	}
}

 

How would we select the cupola part to change it?

The most common way you will see this done in MM patches is

@PART[cupola]

This is actually a special shortcut for

@PART:HAS[#name[cupola]]

Because it is a shortcut for selecting nodes based on their name fields it won't work for nodes that don't have a name field like the EXPERIMENT_DEFINITION node in the above example. To select those you need to identify a field with a useful value and then explicitly refer to it in the HAS clause:

@EXPERIMENT_DEFINITION:HAS[#experiment_id[crewReport]]

You can also select nodes based on whether or not they contain a certain subnode. For example the cupola PART node above has several MODULE subnodes (as do almost all PART nodes), so the following patch would affect it:

@PART:HAS[@MODULE[ModuleCommand]]

This selector will affect every PART node that has at least one MODULE subnode that has a name field with the vale "ModuleCommand" (remember [X] is a shortcut for :HAS[#name[X]])

We can also multiple conditions. e.g.

@PART[*]:HAS[@MODULE[ModuleCommand]],RESOURCE[ElectricCharge])

This breaks down as - select all PART nodes that have a name field with any value (* is a wildcard), and a MODULE subnode with a name field containing the value "ModuleCommand" and a RESOURCE subnode with a name field with the value "ElectricCharge".

In this example the "," represents AND. MM also allows you to use "&" for AND so this could also be written as

@PART[*]:HAS[@MODULE[ModuleCommand]]&RESOURCE[ElectricCharge])

Which you choose to use is a matter of personal preference as they will behave identically.

Unfortunately MM only recognises OR in a limited number of places. You can use it inside the name shortcut or in a :NEEDS clause but it won't work inside of a :HAS clause. So the following will work (MM uses "|" to mean OR)

@PART[cupola|Mark1Cockpit]
@PART:NEEDS[modA|modB]

but this WON'T work:

@PART:HAS[@RESOURCE[ElectricCharge|MonoPropellant]]

You would need to write separate patches for each resource type

@PART:HAS[@RESOURCE[ElectricCharge]] {}
@PART:HAS[@RESOURCE[MonoPropellant]] {}

b. Choosing which subnodes to edit

:HAS is also used within the contents of a patch filter which subnodes are affected by a given change.

Suppose for example we wanted to changed the minimum number of crew required to operate a cupola in the above PART example. First you would use a selector to choose the cupola PART node, then inside the contents of the patch you would use another selector to choose the ModuleCommand MODULE subnode:

@PART[cupola]
{
	@MODULE[ModuleCommand]
	{
		@minimumCrew = 0
	}
}

Because both PART and MODULE have name fields we can use the shorthand [X] form here. We could also have written out the patch in the full form:

@PART:HAS[#name[cupola]]
{
	@MODULE:HAS[#name[ModuleCommand]]
	{
		@minimumCrew = 0
	}
}

but that takes more typing.

If a subnode does not have a name field or there are multiple subnodes with the same value in the name field then you will need to use a HAS to identify the specific subnode based on some other unique value.

For example suppose we have a part with two experiments - a temperatureScan and a barometerScan. This would look like:

  Reveal hidden contents
PART
{
	name = foo
	...

	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = temperatureScan
		experimentActionName = Log Temperature
	}

	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = barometerScan
		experimentActionName = Log Pressure Data
	}
}

To change the temperatureScan's action name we would use:

@PART[foo]
{
	@MODULE[ModuleScieneExperiment]:HAS[#experimentID[temperatureScan]]
	{
		@experimentActionName = Take the temperature
	}
}

Notice how we first use :HAS[@MODULE[ModuleScienceExperiment] to identify that we want to affect ModuleScienceExperiment subnodes then we use an extra :HAS[#experimentID[temperatureScan]] sub-clause to narrow this down to just the MODULES with an experimentID field with the value "temperatureScan".

NOTE we use @ when selecting based on subnodes and # to select based on fields

If instead of only modifying the foo PART, we wanted to modify all PARTs that had a temperatureScan experiment MODULE then we would repeat the selector on the main node as well:

@PART:HAS[@MODULE[ModuleScienceExperiment]:HAS[#experimentID[temperatureScan]]]
{
	@MODULE[ModuleScieneExperiment]:HAS[#experimentID[temperatureScan]]
	{
		@experimentActionName = Take the temperature
	}
}

c. Selecting nodes that do not match a specific criterium

Suppose we want to select all PART nodes that do not have a "bulkheadProfile" field:

@PART:HAS[~bulkheadProfile[]]

or all PART nodes that do not contain a MODULE subnode with name "ModuleCommand"

@PART:HAS[!MODULE[ModuleCommand]]

Notice that we use ~ when comparing fields and ! when comparing subnodes.

d. Lists of fields

Consider the following node

RESULTS
{
	message = One
	message = Two
	message = Three
	message = four
}

Suppose we wanted to change "Two" to "2". How would we do that? MM provides indexers to distinguish between identical options

@RESULTS
{
	@message,1 = 2
}

Here the ",1" means the second message field (indexes count up from 0). You can also count from the end using negative indexes (",-1" = last value, ",-2" the second last and so on). The wildcard index ",*" means select ALL.

We can also modify specific values within fields that have a list of values. For example:

PART
{
	name = foo
	node_stack_top = 0.0, .9, 0.0, 0.0, 1.0, 0.0, 2
}

Suppose we want to change the .9 in the second value to 1.0

@PART[foo]
{
	@node_stack_top[1] = 1.0
}

The use of the index here will work for any field where the values are separated by commas. But what if the mod author had used a different separator?

PART
{
	name = foo
	otherfield = one|two|three|four
}

then we specify both the index and the separator

@PART[foo]
{
	@otherfield[2,|] = 3
}

// result
PART
{
	name = foo
	otherfield = one|two|3|four
}

 

 

On 6/7/2022 at 9:02 AM, Aelfhe1m said:

Making a patch only apply if a given mod is installed

MM uses :NEEDS to mark that a patch should only be processed if the mods it needs are present. Each patch may only have a single NEEDS statement, but more than one mod can be specified within the needs.

@PART:NEEDS[Tweakscale,ModularFuelTanks]

The above example is a patch that would only be applied if BOTH Tweakscale and ModularFuelTanks were found in your GameData folder.

Patches can also specify that they need a given mod NOT to be installed.

@PART:NEEDS[!Tweakscale,!ModularFuelTanks]

In this case the "!" character stands for NOT and is specifying that this patch should only be applied if Tweakscale and ModularFuelTanks are NOT present.

You can of course mix and match both required and not required

@PART:NEEDS[ModularFuelTanks,!Tweakscale]

NEEDS can also recognise the "|" character to mean OR

@PART:NEEDS[Tweakscale|ModularFuelTanks]

This specifies that the patch should be applied if EITHER Tweakscale or ModularFuelTanks are installed (or both it's not exclusive)

MM "knows" which mods are installed based on several tests:

  • there is a DLL with the specified name somewhere inside the game's folder
  • there is a folder with that name inside GameData
  • there is another patch that has a :FOR[] statement containing the mod name
  • there is special code inside the mod's DLL (assembly) to tell MM to add a name to its list of recognised mods

So for example MM would believe that Tweakscale was installed if:

  • it found a file called Tweakscale.dll
  • it found a folder called GameData/Tweakscale
  • it found another patch with :FOR[Tweakscale]
  • if another mod had code inside its DLL that told MM Tweakscale existed

MM includes a full list of all the mods it has found near the start of its logfile (KSP/Logs/ModuleManager/ModuleManager.log)

NEEDS can also be used to check for subfolders within GameData

@PART:NEEDS[TriggerTech/KerbalAlarmClock]

Because of the way Module Manager detects mods it is VERY important to make sure that you completely delete a mods folder and all its related files when removing a mod. An empty folder will still cause MM to treat a mod as being present and it will apply any patches related to that mod which can lead to errors or unexpected behaviours.

Also all patch authors need to be careful to use FOR correctly, since Module Manager uses this to detect the presence of a mod. ONLY the original mod should use FOR[Modname], ALL other mods or personal patches should use BEFORE, AFTER or NEEDS when referring to the name of another mod.

Controlling the order in which patches are applied - FIRST, BEFORE, FOR, AFTER, LAST, FINAL

When doing patching Module Manager starts by scanning through the GameData folder to find all the contained patches. Then:

  • Nodes with no operator are loaded by the KSP GameDatabase first.
  • Patches for modname values in NEEDS, BEFORE, AFTER that don't exist are removed.
  • All patches with :FIRST are applied.
  • All patches without an ordering directive (:FIRST, :BEFORE, :FOR, :AFTER, :LAST, :FINAL) are applied.
  • For each recognised modname:
    •  All patches with :BEFORE are applied
    • All patches with :FOR are applied
    • All patches with :AFTER are applied
  • All patches with :LAST are applied in order
  • All patches with :FINAL are applied

If two patches have the same priority in the above list then they will be sorted based on the filename of the files containing the patch or if they are in the same file then the order with which they appear in that file.

Consider the following (very contrived) example:

wk4jfRQ.png

Module Manager will produce the following patch log for this KSP install:

  Reveal hidden contents
[LOG 16:42:11.513] Log started at 2022-06-07 16:42:11.513
[LOG 16:42:12.729] Checking Cache
[LOG 16:42:12.971] SHA generated in 0.236s
[LOG 16:42:12.971]       SHA = 31-D6-78-79-56-5F-51-6E-3C-70-41-04-16-B4-39-AC-47-A0-3B-5D-38-B9-8E-2F-56-CC-07-52-46-6D-76-49
[LOG 16:42:12.972] Pre patch init
[LOG 16:42:13.173] compiling list of loaded mods...
Mod DLLs found:
  Name                                    Assembly Version         Assembly File Version    KSPAssembly Version      SHA256

  Assembly-CSharp                         0.0.0.0                  0.0.0.0                  1.12                     9be4953fe5b14018d2f62ce040c188da90f05d1e1fbaf1a2262775a63cd5607e
  ModuleManager                           4.2.1.0                  4.2.1.0                  2.5                      2eb4963f73a5255163953a270026f50a8f1a7dd27f4bb0dd69dd4699d0f2268b
  KSPSteamCtrlr                           0.0.1.35                 0.0.1.35                                          1675fa4fcb61d014eb1babe7eed703e7f76a1008537ee57ca7cec65cd9ff94ac
Non-DLL mods added (:FOR[xxx]):
  ModA
  Handwavium
Mods by directory (sub directories of GameData):
  ModB
  Squad
  SquadExpansion
Mods added by assemblies:

[LOG 16:42:13.174] Loading Physics.cfg
[LOG 16:42:13.176] Extracting patches
[LOG 16:42:13.198] Deleting root node in file ModB/MMPatches node: !RESOURCE_DEFINITION[newResource]:NEEDS[WarpDrive] as it can't satisfy its NEEDS
[LOG 16:42:13.293] Applying patches
[LOG 16:42:13.295] :INSERT (initial) pass
[LOG 16:42:13.367] :FIRST pass
[LOG 16:42:13.370] Applying update ModA/morepatches/@PART[HandwaviumTank]:FIRST to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.377] :LEGACY (default) pass
[LOG 16:42:13.380] Applying copy ModA/morepatches/+PART[HandwaviumTank]:NEEDS[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.383] :BEFORE[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :FOR[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :AFTER[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :BEFORE[HANDWAVIUM] pass
[LOG 16:42:13.383] Applying copy ModA/patches/+PART[HandwaviumTank]:BEFORE[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.384] :FOR[HANDWAVIUM] pass
[LOG 16:42:13.384] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to ModB/Resrources/res.cfg/RESOURCE_DEFINITION[newResource]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[LiquidFuel]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Oxidizer]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[SolidFuel]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[MonoPropellant]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[XenonGas]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[ElectricCharge]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[IntakeAir]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[EVA Propellant]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Ore]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Ablator]
[LOG 16:42:13.385] :AFTER[HANDWAVIUM] pass
[LOG 16:42:13.385] :BEFORE[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :FOR[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :AFTER[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :BEFORE[MODA] pass
[LOG 16:42:13.385] :FOR[MODA] pass
[LOG 16:42:13.386] Applying update ModA/morepatches/@PART[fuelTankSmallFlat]:FOR[ModA] to Squad/Parts/FuelTank/Size1_Tanks/fuelTankT100.cfg/PART[fuelTankSmallFlat]
[LOG 16:42:13.387] :AFTER[MODA] pass
[LOG 16:42:13.387] :BEFORE[MODB] pass
[LOG 16:42:13.387] :FOR[MODB] pass
[LOG 16:42:13.387] :AFTER[MODB] pass
[LOG 16:42:13.387] Applying copy ModA/patches/+PART[HandwaviumTank]:AFTER[ModB] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.388] :BEFORE[MODULEMANAGER] pass
[LOG 16:42:13.388] :FOR[MODULEMANAGER] pass
[LOG 16:42:13.388] :AFTER[MODULEMANAGER] pass
[LOG 16:42:13.388] :BEFORE[SQUAD] pass
[LOG 16:42:13.388] :FOR[SQUAD] pass
[LOG 16:42:13.388] :AFTER[SQUAD] pass
[LOG 16:42:13.388] Applying update ModB/MMPatches/@RESOURCE_DEFINITION[newResource]:AFTER[Squad] to ModB/Resrources/res.cfg/RESOURCE_DEFINITION[newResource]
[LOG 16:42:13.388] :BEFORE[SQUADEXPANSION] pass
[LOG 16:42:13.388] :FOR[SQUADEXPANSION] pass
[LOG 16:42:13.388] :AFTER[SQUADEXPANSION] pass
[LOG 16:42:13.389] :LAST[HANDWAVIUM] pass
[LOG 16:42:13.389] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[LargeHandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[LargeHandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[SmallHandwaviumTank]
[LOG 16:42:13.396] :FINAL pass
[LOG 16:42:13.396] Done patching
[LOG 16:42:13.397] Saving Cache
[LOG 16:42:13.552] Saving cache
[LOG 16:42:13.874] ModuleManager: 21 patches applied

[LOG 16:42:13.874] Ran in 1.145s
[LOG 16:42:13.907] Done!

Notice for example that BEFORE[Handwavium],FOR[Handwavium] and AFTER[Handwavium] all occurred before FOR{ModA] but that LAST[Handwavium] occurred after all the BEFORE/FOR/AFTER passes (and before FINAL)
 

Edited by SkyFall2489
Link to comment
Share on other sites

Here's one of the changes that I have been previously been doing by hand for each release of Blueshift.  The file in question is Blueshift/Patches/StockParts.cfg

Change from:

@PART[ISRU]:NEEDS[!WildBlueTools,!FarFutureTechnologies]

To:

@PART[ISRU]:NEEDS[!FarFutureTechnologies]

To effect this, do I need to include the entire module definition?  Or can I modify just the NEEDS selector.

Edited by Ooglak Kerman
Link to comment
Share on other sites

2 hours ago, Ooglak Kerman said:

To effect this, do I need to include the entire module definition?

Yes.

2 hours ago, Ooglak Kerman said:

Or can I modify just the NEEDS selector.

You can't use MM to change the NEEDS of another patch.

Link to comment
Share on other sites

2 hours ago, Aelfhe1m said:

Yes.

You can't use MM to change the NEEDS of another patch.

Thank you for the response on this.  I'll have to do some trials and testing of what I've got in mind.

Next question:  Changing of templates.  Specifically WildBlueIndustires/000WildBlueTools/Templates/ClassicStock/Production/OmniConverters.cfg

If you go down to the OMNICONVERTER --> ConverterName = FusionPellets Processor

INPUT_RESOURCE
	{
		ResourceName = Ore
		Ratio = 15
		FlowMode = ALL_VESSEL
	}

If you do the math, the amount of ore required for a single fusion pellet is quite high.  I am looking to use MM to modify this.  Can MM do this?

Link to comment
Share on other sites

29 minutes ago, Ooglak Kerman said:

If you do the math, the amount of ore required for a single fusion pellet is quite high.  I am looking to use MM to modify this.  Can MM do this?

In principle to patch that template would be something like:

// There is no name field on the OMNICONVERTER templates so use ConverterName field which looks unique
// and replace space in value with wildcard
@OMNICONVERTER:HAS[#ConverterName[FusionPellets?Processor]]
{
	// again no name field so use ResourceName field instead
	@INPUT_RESOURCE:HAS[#ResourceName[Ore]]
	{
		@Ratio = 15 // new number here
	}
}

Although how  WBT uses the templates may affect the end result.

Link to comment
Share on other sites

@Aelfhe1m Thank you for this.  This gives me a starting point.  The ease of making a complete clone of the game makes this sort of testing rather trivial - if not tedious.  If it works, I'll be posting the results and the patch. 

I hope this topic will benefit others as well as myself.  As I start to gain more insight into how the game works and can be modified, it has given new depth and enjoyment to it.

Link to comment
Share on other sites

Contributions for this thread:

Selecting Nodes - or how to use :HAS

Consider the following nodes partially extracted from the stock files:

Spoiler
PART
{
	name = cupola
	CrewCapacity = 1
	TechRequired = commandModules
	// snip extra fields

	MODULE
	{
		name = ModuleCommand
		minimumCrew = 1
	}
	RESOURCE
	{
		name = ElectricCharge
		amount = 200
		maxAmount = 200
	}
	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = crewReport
		interactionRange = 2
		// snip
	}
	// snip
}

EXPERIMENT_DEFINITION
{
	id = crewReport
	title = #autoLOC_501009 //#autoLOC_501009 = Crew Report
	baseValue = 5
	scienceCap = 5
	dataScale = 1
	
	requireAtmosphere = False
	situationMask = 63
	biomeMask = 7

	RESULTS
	{
		default = #autoLOC_501258 //#autoLOC_501258 = You record the crew's assessment of the situation.

		KerbinSrfLandedLaunchpad = #autoLOC_501259 //#autoLOC_501259 = We don't seem to be moving very fast right now.
		// many more results 
	}
}

 

How would we select the cupola part to change it?

The most common way you will see this done in MM patches is

@PART[cupola]

This is actually a special shortcut for

@PART:HAS[#name[cupola]]

Because it is a shortcut for selecting nodes based on their name fields it won't work for nodes that don't have a name field like the EXPERIMENT_DEFINITION node in the above example. To select those you need to identify a field with a useful value and then explicitly refer to it in the HAS clause:

@EXPERIMENT_DEFINITION:HAS[#experiment_id[crewReport]]

You can also select nodes based on whether or not they contain a certain subnode. For example the cupola PART node above has several MODULE subnodes (as do almost all PART nodes), so the following patch would affect it:

@PART:HAS[@MODULE[ModuleCommand]]

This selector will affect every PART node that has at least one MODULE subnode that has a name field with the vale "ModuleCommand" (remember [X] is a shortcut for :HAS[#name[X]])

We can also multiple conditions. e.g.

@PART[*]:HAS[@MODULE[ModuleCommand]],RESOURCE[ElectricCharge])

This breaks down as - select all PART nodes that have a name field with any value (* is a wildcard), and a MODULE subnode with a name field containing the value "ModuleCommand" and a RESOURCE subnode with a name field with the value "ElectricCharge".

In this example the "," represents AND. MM also allows you to use "&" for AND so this could also be written as

@PART[*]:HAS[@MODULE[ModuleCommand]]&RESOURCE[ElectricCharge])

Which you choose to use is a matter of personal preference as they will behave identically.

Unfortunately MM only recognises OR in a limited number of places. You can use it inside the name shortcut or in a :NEEDS clause but it won't work inside of a :HAS clause. So the following will work (MM uses "|" to mean OR)

@PART[cupola|Mark1Cockpit]
@PART:NEEDS[modA|modB]

but this WON'T work:

@PART:HAS[@RESOURCE[ElectricCharge|MonoPropellant]]

You would need to write separate patches for each resource type

@PART:HAS[@RESOURCE[ElectricCharge]] {}
@PART:HAS[@RESOURCE[MonoPropellant]] {}

b. Choosing which subnodes to edit

:HAS is also used within the contents of a patch filter which subnodes are affected by a given change.

Suppose for example we wanted to changed the minimum number of crew required to operate a cupola in the above PART example. First you would use a selector to choose the cupola PART node, then inside the contents of the patch you would use another selector to choose the ModuleCommand MODULE subnode:

@PART[cupola]
{
	@MODULE[ModuleCommand]
	{
		@minimumCrew = 0
	}
}

Because both PART and MODULE have name fields we can use the shorthand [X] form here. We could also have written out the patch in the full form:

@PART:HAS[#name[cupola]]
{
	@MODULE:HAS[#name[ModuleCommand]]
	{
		@minimumCrew = 0
	}
}

but that takes more typing.

If a subnode does not have a name field or there are multiple subnodes with the same value in the name field then you will need to use a HAS to identify the specific subnode based on some other unique value.

For example suppose we have a part with two experiments - a temperatureScan and a barometerScan. This would look like:

Spoiler
PART
{
	name = foo
	...

	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = temperatureScan
		experimentActionName = Log Temperature
	}

	MODULE
	{
		name = ModuleScienceExperiment
		experimentID = barometerScan
		experimentActionName = Log Pressure Data
	}
}

To change the temperatureScan's action name we would use:

@PART[foo]
{
	@MODULE[ModuleScieneExperiment]:HAS[#experimentID[temperatureScan]]
	{
		@experimentActionName = Take the temperature
	}
}

Notice how we first use :HAS[@MODULE[ModuleScienceExperiment] to identify that we want to affect ModuleScienceExperiment subnodes then we use an extra :HAS[#experimentID[temperatureScan]] sub-clause to narrow this down to just the MODULES with an experimentID field with the value "temperatureScan".

NOTE we use @ when selecting based on subnodes and # to select based on fields

If instead of only modifying the foo PART, we wanted to modify all PARTs that had a temperatureScan experiment MODULE then we would repeat the selector on the main node as well:

@PART:HAS[@MODULE[ModuleScienceExperiment]:HAS[#experimentID[temperatureScan]]]
{
	@MODULE[ModuleScieneExperiment]:HAS[#experimentID[temperatureScan]]
	{
		@experimentActionName = Take the temperature
	}
}

c. Selecting nodes that do not match a specific criterium

Suppose we want to select all PART nodes that do not have a "bulkheadProfile" field:

@PART:HAS[~bulkheadProfile[]]

or all PART nodes that do not contain a MODULE subnode with name "ModuleCommand"

@PART:HAS[!MODULE[ModuleCommand]]

Notice that we use ~ when comparing fields and ! when comparing subnodes.

d. Lists of fields

Consider the following node

RESULTS
{
	message = One
	message = Two
	message = Three
	message = four
}

Suppose we wanted to change "Two" to "2". How would we do that? MM provides indexers to distinguish between identical options

@RESULTS
{
	@message,1 = 2
}

Here the ",1" means the second message field (indexes count up from 0). You can also count from the end using negative indexes (",-1" = last value, ",-2" the second last and so on). The wildcard index ",*" means select ALL.

We can also modify specific values within fields that have a list of values. For example:

PART
{
	name = foo
	node_stack_top = 0.0, .9, 0.0, 0.0, 1.0, 0.0, 2
}

Suppose we want to change the .9 in the second value to 1.0

@PART[foo]
{
	@node_stack_top[1] = 1.0
}

The use of the index here will work for any field where the values are separated by commas. But what if the mod author had used a different separator?

PART
{
	name = foo
	otherfield = one|two|three|four
}

then we specify both the index and the separator

@PART[foo]
{
	@otherfield[2,|] = 3
}

// result
PART
{
	name = foo
	otherfield = one|two|3|four
}

 

Edited by Aelfhe1m
Fixed example for | separator
Link to comment
Share on other sites

Making a patch only apply if a given mod is installed

MM uses :NEEDS to mark that a patch should only be processed if the mods it needs are present. Each patch may only have a single NEEDS statement, but more than one mod can be specified within the needs.

@PART:NEEDS[Tweakscale,ModularFuelTanks]

The above example is a patch that would only be applied if BOTH Tweakscale and ModularFuelTanks were found in your GameData folder.

Patches can also specify that they need a given mod NOT to be installed.

@PART:NEEDS[!Tweakscale,!ModularFuelTanks]

In this case the "!" character stands for NOT and is specifying that this patch should only be applied if Tweakscale and ModularFuelTanks are NOT present.

You can of course mix and match both required and not required

@PART:NEEDS[ModularFuelTanks,!Tweakscale]

NEEDS can also recognise the "|" character to mean OR

@PART:NEEDS[Tweakscale|ModularFuelTanks]

This specifies that the patch should be applied if EITHER Tweakscale or ModularFuelTanks are installed (or both it's not exclusive)

MM "knows" which mods are installed based on several tests:

  • there is a DLL with the specified name somewhere inside the game's folder
  • there is a folder with that name inside GameData
  • there is another patch that has a :FOR[] statement containing the mod name
  • there is special code inside the mod's DLL (assembly) to tell MM to add a name to its list of recognised mods

So for example MM would believe that Tweakscale was installed if:

  • it found a file called Tweakscale.dll
  • it found a folder called GameData/Tweakscale
  • it found another patch with :FOR[Tweakscale]
  • if another mod had code inside its DLL that told MM Tweakscale existed

MM includes a full list of all the mods it has found near the start of its logfile (KSP/Logs/ModuleManager/ModuleManager.log)

NEEDS can also be used to check for subfolders within GameData

@PART:NEEDS[TriggerTech/KerbalAlarmClock]

Because of the way Module Manager detects mods it is VERY important to make sure that you completely delete a mods folder and all its related files when removing a mod. An empty folder will still cause MM to treat a mod as being present and it will apply any patches related to that mod which can lead to errors or unexpected behaviours.

Also all patch authors need to be careful to use FOR correctly, since Module Manager uses this to detect the presence of a mod. ONLY the original mod should use FOR[Modname], ALL other mods or personal patches should use BEFORE, AFTER or NEEDS when referring to the name of another mod.

Controlling the order in which patches are applied - FIRST, BEFORE, FOR, AFTER, LAST, FINAL

When doing patching Module Manager starts by scanning through the GameData folder to find all the contained patches. Then:

  • Nodes with no operator are loaded by the KSP GameDatabase first.
  • Patches for modname values in NEEDS, BEFORE, AFTER that don't exist are removed.
  • All patches with :FIRST are applied.
  • All patches without an ordering directive (:FIRST, :BEFORE, :FOR, :AFTER, :LAST, :FINAL) are applied.
  • For each recognised modname:
    •  All patches with :BEFORE are applied
    • All patches with :FOR are applied
    • All patches with :AFTER are applied
  • All patches with :LAST are applied in order
  • All patches with :FINAL are applied

If two patches have the same priority in the above list then they will be sorted based on the filename of the files containing the patch or if they are in the same file then the order with which they appear in that file.

Consider the following (very contrived) example:

wk4jfRQ.png

Module Manager will produce the following patch log for this KSP install:

Spoiler
[LOG 16:42:11.513] Log started at 2022-06-07 16:42:11.513
[LOG 16:42:12.729] Checking Cache
[LOG 16:42:12.971] SHA generated in 0.236s
[LOG 16:42:12.971]       SHA = 31-D6-78-79-56-5F-51-6E-3C-70-41-04-16-B4-39-AC-47-A0-3B-5D-38-B9-8E-2F-56-CC-07-52-46-6D-76-49
[LOG 16:42:12.972] Pre patch init
[LOG 16:42:13.173] compiling list of loaded mods...
Mod DLLs found:
  Name                                    Assembly Version         Assembly File Version    KSPAssembly Version      SHA256

  Assembly-CSharp                         0.0.0.0                  0.0.0.0                  1.12                     9be4953fe5b14018d2f62ce040c188da90f05d1e1fbaf1a2262775a63cd5607e
  ModuleManager                           4.2.1.0                  4.2.1.0                  2.5                      2eb4963f73a5255163953a270026f50a8f1a7dd27f4bb0dd69dd4699d0f2268b
  KSPSteamCtrlr                           0.0.1.35                 0.0.1.35                                          1675fa4fcb61d014eb1babe7eed703e7f76a1008537ee57ca7cec65cd9ff94ac
Non-DLL mods added (:FOR[xxx]):
  ModA
  Handwavium
Mods by directory (sub directories of GameData):
  ModB
  Squad
  SquadExpansion
Mods added by assemblies:

[LOG 16:42:13.174] Loading Physics.cfg
[LOG 16:42:13.176] Extracting patches
[LOG 16:42:13.198] Deleting root node in file ModB/MMPatches node: !RESOURCE_DEFINITION[newResource]:NEEDS[WarpDrive] as it can't satisfy its NEEDS
[LOG 16:42:13.293] Applying patches
[LOG 16:42:13.295] :INSERT (initial) pass
[LOG 16:42:13.367] :FIRST pass
[LOG 16:42:13.370] Applying update ModA/morepatches/@PART[HandwaviumTank]:FIRST to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.377] :LEGACY (default) pass
[LOG 16:42:13.380] Applying copy ModA/morepatches/+PART[HandwaviumTank]:NEEDS[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.383] :BEFORE[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :FOR[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :AFTER[ASSEMBLY-CSHARP] pass
[LOG 16:42:13.383] :BEFORE[HANDWAVIUM] pass
[LOG 16:42:13.383] Applying copy ModA/patches/+PART[HandwaviumTank]:BEFORE[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.384] :FOR[HANDWAVIUM] pass
[LOG 16:42:13.384] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to ModB/Resrources/res.cfg/RESOURCE_DEFINITION[newResource]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[LiquidFuel]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Oxidizer]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[SolidFuel]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[MonoPropellant]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[XenonGas]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[ElectricCharge]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[IntakeAir]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[EVA Propellant]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Ore]
[LOG 16:42:13.385] Applying update ModB/Resrources/res/@RESOURCE_DEFINITION:FOR[Handwavium] to Squad/Resources/ResourcesGeneric.cfg/RESOURCE_DEFINITION[Ablator]
[LOG 16:42:13.385] :AFTER[HANDWAVIUM] pass
[LOG 16:42:13.385] :BEFORE[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :FOR[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :AFTER[KSPSTEAMCTRLR] pass
[LOG 16:42:13.385] :BEFORE[MODA] pass
[LOG 16:42:13.385] :FOR[MODA] pass
[LOG 16:42:13.386] Applying update ModA/morepatches/@PART[fuelTankSmallFlat]:FOR[ModA] to Squad/Parts/FuelTank/Size1_Tanks/fuelTankT100.cfg/PART[fuelTankSmallFlat]
[LOG 16:42:13.387] :AFTER[MODA] pass
[LOG 16:42:13.387] :BEFORE[MODB] pass
[LOG 16:42:13.387] :FOR[MODB] pass
[LOG 16:42:13.387] :AFTER[MODB] pass
[LOG 16:42:13.387] Applying copy ModA/patches/+PART[HandwaviumTank]:AFTER[ModB] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.388] :BEFORE[MODULEMANAGER] pass
[LOG 16:42:13.388] :FOR[MODULEMANAGER] pass
[LOG 16:42:13.388] :AFTER[MODULEMANAGER] pass
[LOG 16:42:13.388] :BEFORE[SQUAD] pass
[LOG 16:42:13.388] :FOR[SQUAD] pass
[LOG 16:42:13.388] :AFTER[SQUAD] pass
[LOG 16:42:13.388] Applying update ModB/MMPatches/@RESOURCE_DEFINITION[newResource]:AFTER[Squad] to ModB/Resrources/res.cfg/RESOURCE_DEFINITION[newResource]
[LOG 16:42:13.388] :BEFORE[SQUADEXPANSION] pass
[LOG 16:42:13.388] :FOR[SQUADEXPANSION] pass
[LOG 16:42:13.388] :AFTER[SQUADEXPANSION] pass
[LOG 16:42:13.389] :LAST[HANDWAVIUM] pass
[LOG 16:42:13.389] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[HandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[LargeHandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[LargeHandwaviumTank]
[LOG 16:42:13.392] Applying update ModA/morepatches/@PART:HAS[@RESOURCE[newResource]]:LAST[Handwavium] to ModB/Resrources/res.cfg/PART[SmallHandwaviumTank]
[LOG 16:42:13.396] :FINAL pass
[LOG 16:42:13.396] Done patching
[LOG 16:42:13.397] Saving Cache
[LOG 16:42:13.552] Saving cache
[LOG 16:42:13.874] ModuleManager: 21 patches applied

[LOG 16:42:13.874] Ran in 1.145s
[LOG 16:42:13.907] Done!

Notice for example that BEFORE[Handwavium],FOR[Handwavium] and AFTER[Handwavium] all occurred before FOR{ModA] but that LAST[Handwavium] occurred after all the BEFORE/FOR/AFTER passes (and before FINAL)
 

Link to comment
Share on other sites

  • 2 weeks later...
  • 1 month later...

Hope someone can help with this.

One of my mods has the following code:

@PART[*]:HAS[!MODULE[ModuleCargoPart],@MODULE[ModuleInventoryPart],!MODULE[KerbalEVA]]:FINAL
{

	MODULE
	{
		name = ModuleCargoPart
		packedVolume = -1
	}
	MODULE
	{
		name = ModuleInventoryPart		
		InventorySlots = #$/MODULE[ModuleInventoryPart]/InventorySlots$
		packedVolumeLimit = #$/MODULE[ModuleInventoryPart]/packedVolumeLimit$
	}
	MODULE
	{
		name = KSPPartVolumeModule
	}
	!MODULE[ModuleInventoryPart] {}
}

 

Turns out that at least one (and probably more) parts mods don't bother to put in the "packedVolumeLimit" value, so MM borks on that line since there is nothing to reference.

Not being a MM guru, and busy with other stuff, I was hoping that someone could provide some code which would work if the packedVolumeLimit is missing

 

Link to comment
Share on other sites

@linuxgurugamer You could pre-patch with a default value where an existing value is missing:

@PART[*]:HAS[!MODULE[ModuleCargoPart],@MODULE[ModuleInventoryPart],!MODULE[KerbalEVA]]:FINAL
{
	@MODULE[ModuleInventoryPart]
	{
		&packedVolumeLimit = 1 // & means add value only if not already present - you can use a different default if 1 isn't appropriate
	}
}

// rest of patch as before
@PART[*]:HAS[!MODULE[ModuleCargoPart],@MODULE[ModuleInventoryPart],!MODULE[KerbalEVA]]:FINAL
{

	MODULE
	{
		name = ModuleCargoPart
		packedVolume = -1
	}
	MODULE
	{
		name = ModuleInventoryPart		
		InventorySlots = #$/MODULE[ModuleInventoryPart]/InventorySlots$
		packedVolumeLimit = #$/MODULE[ModuleInventoryPart]/packedVolumeLimit$
	}
	MODULE
	{
		name = KSPPartVolumeModule
	}
	!MODULE[ModuleInventoryPart] {}
}

You could also use @MODULE[ModuleInventoryPart]:HAS[~packedVolumeLimit[]] on the first @PART clause to filter only for ModuleInventoryPart modules without packedVolumeLimit values but using the & makes that mostly redundant (might save a little processing time and reduce total patch count)

Link to comment
Share on other sites

39 minutes ago, Aelfhe1m said:

@linuxgurugamer You could pre-patch with a default value where an existing value is missing:

@PART[*]:HAS[!MODULE[ModuleCargoPart],@MODULE[ModuleInventoryPart],!MODULE[KerbalEVA]]:FINAL
{
	@MODULE[ModuleInventoryPart]
	{
		&packedVolumeLimit = 1 // & means add value only if not already present - you can use a different default if 1 isn't appropriate
	}
}

// rest of patch as before
@PART[*]:HAS[!MODULE[ModuleCargoPart],@MODULE[ModuleInventoryPart],!MODULE[KerbalEVA]]:FINAL
{

	MODULE
	{
		name = ModuleCargoPart
		packedVolume = -1
	}
	MODULE
	{
		name = ModuleInventoryPart		
		InventorySlots = #$/MODULE[ModuleInventoryPart]/InventorySlots$
		packedVolumeLimit = #$/MODULE[ModuleInventoryPart]/packedVolumeLimit$
	}
	MODULE
	{
		name = KSPPartVolumeModule
	}
	!MODULE[ModuleInventoryPart] {}
}
 

You could also use @MODULE[ModuleInventoryPart]:HAS[~packedVolumeLimit[]] on the first @PART clause to filter only for ModuleInventoryPart modules without packedVolumeLimit values but using the & makes that mostly redundant (might save a little processing time and reduce total patch count)

Thanks.  The prepatch is what I need, just didn' t know how to do it.  

 

Edited by linuxgurugamer
Link to comment
Share on other sites

This guide is incredible! Especially useful to me is the directions on how to target nodes which don't have a name field.

@Scatterer_planetsList:FOR[Spectra]
{
	@scattererCelestialBodies
	{
		!Item:HAS[#celestialBodyName[Duna]]
		{
		}
	}
}

^ This is the code I set up to delete the node with CelestialBodyName = Duna

Edited by HafCoJoe
Link to comment
Share on other sites

  • 3 weeks later...

Hey guys I'm trying to make a mod to make the cost of everything more real. 

 

So falcon 9 should cost 100mil to fly. Satellite contracts should pay 25mil not 25k. 

 

Extracting ore and landing should reward with millions in dollars. 

 

Here is my code - but the contracts don't seem to reflect the changes at all.... Can anyone help me out? 

```

 
@CONTRACT_TYPE[*]:HAS[@rewardFunds]:FINAL
{
   cash= $rewardFunds
   @rewardFunds = 200*cash
}
  @Contracts:HAS[@Funds]:FINAL
    {
 
   cash= $rewardFunds
   @rewardFunds = 70*cash
     
        @Funds
        {
        @BaseReward *= 70
        @BaseAdvance *= 70
        }
         @BaseReward *= 70
        @BaseAdvance *= 70
    }
 
@Contracts:HAS[*,@Funds]:FINAL
    {
       
   cash= $rewardFunds
   @rewardFunds = 200*cash
        @Funds
        {
        @BaseReward *= 70
        @BaseAdvance *= 70
        }
         @BaseReward *= 70
        @BaseAdvance *= 70
    }
 


 
@PART[*]
{
    @cost *= 150.0
}
 
@RESOURCE_DEFINITION[*]
{
    @unitCost *= 150.0
}
Link to comment
Share on other sites

  • 1 month later...

Hey all, I'm having a hell of a time with a specific part using an MM patch I've used for awhile that changes part attach rules so that all parts can be attached in any way (mostly to make parts have surface attach added to them). I have an MM patch that works on pretty much every part I can find except for the Davon Supply Mod refill part. I physically changed the part.cfg itself, as well as tried a plethora of different versions of the attach rules in using MM patches, but it still will not set the stack attach to "1". I have a workaround with NodeHelper, but since that's only temporary I I'm trying to find out why it's getting reverted back to have stack attach turned off (though it does have surface attach).

Spoiler
@PART[DavonStationLogisticsHub]:NEEDS[DavonSupplyMod]:AFTER[DavonSupplyMod]
{
	@attachRules = 1,1,1,1,0
}

 

I've tried using ":FINAL" and ":AFTER[zzzDavonPatches]" (my MM patch is in its' own folder), and it just keeps reverting the stack attach node to "0". I've also tried quite a few combos not listed here as well (I create a part off this mod that doubles the size but even there it brings over the attach rules regardless if I am over-writing them with the part patch or not). Anyone have an idea of why could be the cause? I'm about to clear my MM cache and run it again and see if that fixes it, otherwise I can't think of one mod that would over-ride the attach rules, especially when the patches are being loaded as ":FINAL"...

 

*Edit* I think I figured it out, the patch I was using was using some regex expression that was supposed to double the 6th node attachment value (rescale factor was doubled for this part), but it seemed to leave an extra "1" at the end, so instead of x,y,z,angx,angy,angz,scale it added an extra integer after the scale which may have messed with it.

 

*Edit 2* Turns out I was wrong. I was able to get rid of that extra number, but now I have edited it so much I can't even get the nodes to scale correctly anymore. I have a working rescaled patch for some hub connectors that I have no issue with, but it looks like setting the rescale factor seems to also scale the other nodes on this part (after changing around some MM patch variables it started doubling the doubled attachment node parts), but it still just will not allow stacking on the bottom (for whatever reason it will stack on the top node no problem, but I have to use NodeHelper to enable stacking in the VAB to get it to stack on the bottom node). I just created another testbench folder to bypass the 15 minute load so I'll be testing it there but if anyone has any ideas I'd appreciate it...

Edited by shoe7ess
Link to comment
Share on other sites

I'm looking to scale the min and max dimensions of all Procedural Parts by some factor MIN_SCALE and MAX_SCALE (for a 10x rescale system I'm playing). I want this to work on all parts' starting size limits, as well as all part upgrades.

Here is what I have so far. Does this look correct (enough)?

@PART[*]:FINAL // all parts...
{
	@MODULE[ProceduralPart] // with the procedural parts module...
	{
		// set constants
		%MIN_SCALE = 2
		%MAX_SCALE = 5
		// change the mins and maxes
		@diameterMin /= #$MIN_SCALE$
		@diameterMax *= #$MAX_SCALE$
		@lengthMin /= #$MIN_SCALE$
		@lengthMax *= #$MAX_SCALE$
		@volumeMin /= #$MIN_SCALE$
		@volumeMax *= #$MAX_SCALE$
		
		@UPGRADES // pick the upgrades...
		{
			@UPGRADE:HAS[#diameterMax] // that has this field
			{
				// modify the value
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#diameterMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
			@UPGRADE:HAS[#lengthMax]
			{
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#lengthMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
			@UPGRADE:HAS[#volumeMax]
			{
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#volumeMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
		}
	}
}

 

Link to comment
Share on other sites

46 minutes ago, bigyihsuan said:

I'm looking to scale the min and max dimensions of all Procedural Parts by some factor MIN_SCALE and MAX_SCALE (for a 10x rescale system I'm playing). I want this to work on all parts' starting size limits, as well as all part upgrades.

Here is what I have so far. Does this look correct (enough)?

@PART[*]:FINAL // all parts...
{
	@MODULE[ProceduralPart] // with the procedural parts module...
	{
		// set constants
		%MIN_SCALE = 2
		%MAX_SCALE = 5
		// change the mins and maxes
		@diameterMin /= #$MIN_SCALE$
		@diameterMax *= #$MAX_SCALE$
		@lengthMin /= #$MIN_SCALE$
		@lengthMax *= #$MAX_SCALE$
		@volumeMin /= #$MIN_SCALE$
		@volumeMax *= #$MAX_SCALE$
		
		@UPGRADES // pick the upgrades...
		{
			@UPGRADE:HAS[#diameterMax] // that has this field
			{
				// modify the value
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#diameterMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
			@UPGRADE:HAS[#lengthMax]
			{
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#lengthMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
			@UPGRADE:HAS[#volumeMax]
			{
				@diameterMax *= #$../MAX_SCALE$
			}
			@UPGRADE:HAS[#volumeMin]
			{
				@diameterMin /= #$../MIN_SCALE$
			}
		}
	}
}

 

There might be multiple upgrades with the fields you are looking for. This will only select the first of those.

Just put a comma, then a space, then an asterisk after the HAS node, like this:

@UPRGADE:HAS[#diameterMax], *

On 8/15/2022 at 1:04 AM, spaded1 said:

Hey guys I'm trying to make a mod to make the cost of everything more real. 

 

So falcon 9 should cost 100mil to fly. Satellite contracts should pay 25mil not 25k. 

 

Extracting ore and landing should reward with millions in dollars. 

 

Here is my code - but the contracts don't seem to reflect the changes at all.... Can anyone help me out? 

```

 
@CONTRACT_TYPE[*]:HAS[@rewardFunds]:FINAL
{
   cash= $rewardFunds
   @rewardFunds = 200*cash
}
  @Contracts:HAS[@Funds]:FINAL
    {
 
   cash= $rewardFunds
   @rewardFunds = 70*cash
     
        @Funds
        {
        @BaseReward *= 70
        @BaseAdvance *= 70
        }
         @BaseReward *= 70
        @BaseAdvance *= 70
    }
 
@Contracts:HAS[*,@Funds]:FINAL
    {
       
   cash= $rewardFunds
   @rewardFunds = 200*cash
        @Funds
        {
        @BaseReward *= 70
        @BaseAdvance *= 70
        }
         @BaseReward *= 70
        @BaseAdvance *= 70
    }
 


 
@PART[*]
{
    @cost *= 150.0
}
 
@RESOURCE_DEFINITION[*]
{
    @unitCost *= 150.0
}

The issue is how you are selecting the variables. The syntax is kinda overcomplicated, check out some examples.

Also, you can just say rewardFunds *= 200 on line 4

Link to comment
Share on other sites

  • 2 weeks later...
On 10/14/2022 at 2:25 PM, Forked Camphor said:

Question: Is there  a way to mass clone all parts (Or fueltanks, engines, etc) similar to using "+part(...)"?

Well, thing is, names.

It's totally doable if you can figure that out.

Every part needs an unique vale in its name field. (NAME, not TITLE, NAME is what the game uses, TITLE is displayed to the player)

So, you'd need to figure out that wierd regular expression thingy to modify every part name into a new part name that is not the old name, nor the name of any other part, such as by appending to the string. I'm not entirely sure how to do that, but I think it's possible. Go look at MoarKerbals, it appends a string to the description of several parts using a single patch.

Link to comment
Share on other sites

I'm trying to make a patch that adds a different amount of usi wolf system crew capacity to each deployable centrifuge part of a given deployed crew capacity,

Like, parts with 1 deployed crew capacity get 1 wolf crew capacity, but parts with 2 deployed crew capacity get 2 wolf crew capacity.

Basically, I'm trying to select parts that have a specific module that has a specific variable of a specific value.

Link to comment
Share on other sites

5 hours ago, Clancythecat said:

I'm trying to make a patch that adds a different amount of usi wolf system crew capacity to each deployable centrifuge part of a given deployed crew capacity,

Like, parts with 1 deployed crew capacity get 1 wolf crew capacity, but parts with 2 deployed crew capacity get 2 wolf crew capacity.

Basically, I'm trying to select parts that have a specific module that has a specific variable of a specific value.

Actually, you don't exactly need that.

Check out the Variable system. It will let you just set one field to another. What does each part module look like? Then, I can show you how it works.

If you do need to select keys of certain values within nodes, you can say @PART[*]:HAS[#MODULE[DeployableCentrifuge]:HAS[#deployedCapacity[xyz]]]

Link to comment
Share on other sites

1 hour ago, SkyFall2489 said:

Actually, you don't exactly need that.

Check out the Variable system. It will let you just set one field to another. What does each part module look like? Then, I can show you how it works.

If you do need to select keys of certain values within nodes, you can say @PART[*]:HAS[#MODULE[DeployableCentrifuge]:HAS[#deployedCapacity[xyz]]]

The module looks like this

    MODULE
    {
        name = WOLF_CrewCargoModule
        EconomyBerths = 2
        LuxuryBerths = 1
        ModuleName = #LOC_USI_WOLF_CrewCargoModuleName
        PartInfo = #LOC_USI_WOLF_CrewCargoCrate_PartInfo_Summary
    }

i'm trying to give them 1 EconomyBerths for every deployedCapacity, and half as many LuxuryBerths

Link to comment
Share on other sites

20 minutes ago, Clancythecat said:

The module looks like this

    MODULE
    {
        name = WOLF_CrewCargoModule
        EconomyBerths = 2
        LuxuryBerths = 1
        ModuleName = #LOC_USI_WOLF_CrewCargoModuleName
        PartInfo = #LOC_USI_WOLF_CrewCargoCrate_PartInfo_Summary
    }

i'm trying to give them 1 EconomyBerths for every deployedCapacity, and half as many LuxuryBerths

What about the deployable centrifuge module?

Link to comment
Share on other sites

2 hours ago, SkyFall2489 said:

What about the deployable centrifuge module?

it looks like this

    MODULE
    {
        name = ModuleDeployableCentrifuge

        DeployAnimationName = CentrifugeCollapse
    // Speed of the deploy animation
    AnimationSpeed = 0.025
    // Layer of the deploy animation
    AnimationLayer = 1
        // Crew needed to deploy
        CrewToDeploy = 3
        // Skill Required
        CrewSkillNeeded = #autoLOC_500103
        // Skill Display Name
        CrewSkillNeededName = #autoLOC_500103
    // Crew capacity when deployed
    DeployedCrewCapacity = 16

    Deployed = True

        // Radius, for display purposes only
        Radius = 15

   // Name of the deploy action
    DeployActionName = #LOC_SSPX_Inflatable_Action_Deploy_Start_Title
    // Name of the retract action
    RetractActionName = #LOC_SSPX_Inflatable_Action_Deploy_Stop_Title
    // Name of the toggle action
    ToggleActionName = #LOC_SSPX_Inflatable_Action_Deploy_Toggle_Title

        // Name of the start action
        StartSpinActionName = #LOC_SSPX_Deployable_Action_Spin_Start_Title
        // Name of the stop action
        StopSpinActionName = #LOC_SSPX_Deployable_Action_Spin_Stop_Title
        // Name of the toggle action
        ToggleSpinActionName = #LOC_SSPX_Deployable_Action_Spin_Toggle_Title

        // Speed of the centrifuge rotation in deg/s
        SpinRate = -35
        // Rate at which the SpinRate accelerates (deg/s/s)
        SpinAccelerationRate = 1.0

        // Rate at which the counterweight spins in deg/s, typically faster than SpinRate and reversed
        CounterweightSpinRate = 70.0
        // Rate at which the counterweight accelerates (deg/s/s)
        CounterweightSpinAccelerationRate = 2.0

        // Transform to rotate for the centrifuge
        SpinTransformName = B_SpinCore
        // Transform to rotate for the counterweight
        CounterweightTransformName = B_Counterweight

        InternalSpinMapping = 1
    }

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...