Jump to content

[1.8.x-1.12.x] Module Manager 4.2.3 (July 03th 2023) - Fireworks season


sarbian

Recommended Posts

I'd like to contribute some code to module manager, just a bit confused as to which repository to fork.

Ialdabaoth/ModuleManager seems to have the 2.0 code, but then sarbian/ModuleManager has been the repo with the most activity for some time.

Also the MM switch code seems not to have made it to the Ialdabaoth version.

What I'd like to contribute is that when editing, the order of definitions stays the same. This matters with tweakables, it affects the order the tweakers appear in the list. It's also sometimes important for initialization.

Link to comment
Share on other sites

I'd like to contribute some code to module manager, just a bit confused as to which repository to fork.

Ialdabaoth/ModuleManager seems to have the 2.0 code, but then sarbian/ModuleManager has been the repo with the most activity for some time.

Also the MM switch code seems not to have made it to the Ialdabaoth version.

What I'd like to contribute is that when editing, the order of definitions stays the same. This matters with tweakables, it affects the order the tweakers appear in the list. It's also sometimes important for initialization.

It's hard to tell what's going on with that right now. Just from looking at the network graph (fork map) it doesn't look like the two code paths have been rebased at all since Sarbian took over. On the other hand Ialdabaoth could have merged them by hand before commiting 2.0. Would be nice to know for sure what's happening...

Link to comment
Share on other sites

I just merged Ialdabaoth latest changes. I'll need to work on the first post documentation but for now :

https://ksp.sarbian.com/jenkins/job/ModuleManager/lastSuccessfulBuild/artifact/jenkins-ModuleManager-2/ModuleManager.2.0.3.dll

New features:

MATH!


@PART[*]:FOR[Realism] {
@mass *= 0.25
@maxTemp -= 500
@scale += 2
}

PROPER INDEXING!


@PART[*]:HAS[MODULE[MultiModeEngine]]:FOR[Pony] {
@MODULE[ModuleEngineFX],1 {
@PROPELLANT[Oxidizer]
{
@name = LiquidOxygen
}
}
}

PER-MOD PARSE PASSES!


@PART[fuelTank]:BEFORE[RealFuels]
{
@RESOURCE[LiquidFuel] {
@amount *= 5
@maxAmount *= 5
}
}


@PART[fuelTank]:AFTER[RealFuels]
{
!RESOURCE[LiquidFuel] {}
!RESOURCE[Oxidizer] {}
}

DEPENDENCY CHECKING!


@PART[fuelTank]:AFTER[RealFuels]:NEEDS[RealSolarSystem,!RealKerbalSystem]
{
@scale *= 4;
}

If it detects a loaded DLL, it assumes it's a mod and creates a :BEFORE, :FOR and :AFTER pass for it.

If you use underscores to specify your mod's version in the filename, ModuleManager will regrettably think that these are part of the filename, because I can't tell the difference between "MyMod_1_3_6" and "Mod_1337s_Cool_Stuff". On the other hand, if you use periods to specify your mod's version, then "MyMod.1.3.6" will correctly be identified as "MyMod".

This means that there will always be the following passes:

:FIRST

:BEFORE[Assembly-CSharp]

:FOR[Assembly-CSharp]

:AFTER[Assembly-CSharp]

:BEFORE[ModuleManager]

:FOR[ModuleManager]

:AFTER[ModuleManager]

:FINAL

Specifying ':FIRST' is optional; I just named the 'main' pass so that the log file is clearer.

If your mod includes a DLL put all your MM patch nodes in the :FOR[yourMod] pass.

If your mod does not include a DLL, then pick a name for your mod THAT DOES NOT CONFLICT WITH ANY OTHER MOD'S DLL, and then put all your MM patch nodes in the :FOR[yourMod] pass.

If you do this, other mods can use :BEFORE[yourMod] and :AFTER[yourMod] to politely modify things furthr at the correct sequence.

The following parameters are currently implemented:

:BEFORE[ModName] - execute this patch BEFORE ModName executes its patches.

:FOR[ModName] - I am ModName, and these are my patches.

:AFTER[ModName] - execute this patch AFTER ModName executes its patches.

:NEEDS[ModName1] - execute this patch only if ModName1 is installed.

:NEEDS[!ModName2] - do not execute this patch if ModName2 is installed.

You can combine NEEDS nodes like this:

:NEEDS[ModName1, !ModName2]

You can match subnodes in one of seven ways:

@NODE[name] // will wildcard match name (so you can do ModuleEnginesFX or ModuleEngines*), and apply itself to the first NODE it finds.

@NODE[name],index // will wildcard match name, and apply itself to the indexth NODE it finds.

@NODE[name],* // will wildcard match name, and apply itself to ALL matching NODEs.

@NODE[name]:HAS[criteria] // will wildcard match name and apply itself to all matching NODEs which also meet the :HAS criteria

@NODE:HAS[criteria] // will apply itself to all matching NODEs which also meet the :HAS criteria

@NODE,index // will apply itself to the indexth NODE it finds

@NODE,* // will apply itself to ALL NODEs

These apply to @, ! and % nodes. $ nodes are necessarily single-application, and thus will always apply to the first node they find.

Link to comment
Share on other sites

So, now that this is all official-like, let's talk about what this would look like in the Real World.

A really good example of this is RealFuels.

All RealFuels confignodes should look like this now :


@PART[liquidEngineMini]:FOR[RealFuels] //48-7S
{
@mass = 0.03125
@maxTemp = 2028

@MODULE[ModuleEngines]
{
@maxThrust = 30
@heatProduction = 191
@atmosphereCurve
{
@key,0 = 0 347
@key,1 = 1 285
}
!PROPELLANT[LiquidFuel] {}
!PROPELLANT[Oxidizer] {}
!PROPELLANT[MonoPropellant] {}
PROPELLANT
{
name = LiquidFuel
ratio = 37.694087
DrawGauge = True
}
PROPELLANT
{
name = CryOx
ratio = 62.305913
}
}

MODULE
{
name = ModuleEngineConfigs
techLevel = 7
origTechLevel = 7
engineType = L+
origMass = 0.03125
configuration = LiquidFuel+CryOx
modded = false
CONFIG
{
name = CryoFuel+CryOx
maxThrust = 22.5
heatProduction = 191
PROPELLANT
{
name = CryoFuel
ratio = 0.7630831
DrawGauge = True
}
PROPELLANT
{
name = CryOx
ratio = 0.2369169
}
IspSL = 1.261
IspV = 1.3
throttle = 0

ModuleEngineIgnitor
{
name = ModuleEngineIgnitor
ignitionsAvailable = 1
autoIgnitionTemperature = 800
ignitorType = Electric
useUllageSimulation = true
IGNITOR_RESOURCE
{
name = ElectricCharge
amount = 0.3
}
}
}
CONFIG
{
name = LiquidFuel+CryOx
maxThrust = 30
heatProduction = 191
PROPELLANT
{
name = LiquidFuel
ratio = 0.37694087
DrawGauge = True
}
PROPELLANT
{
name = CryOx
ratio = 0.62305913
}
IspSL = 1
IspV = 1
throttle = 0

ModuleEngineIgnitor
{
name = ModuleEngineIgnitor
ignitionsAvailable = 1
autoIgnitionTemperature = 800
ignitorType = Electric
useUllageSimulation = true
IGNITOR_RESOURCE
{
name = ElectricCharge
amount = 0.3
}
}
}
CONFIG
{
name = Hypergolic+Oxidizer
maxThrust = 30
heatProduction = 191
PROPELLANT
{
name = Hypergolic
ratio = 0.49620149
DrawGauge = True
}
PROPELLANT
{
name = Oxidizer
ratio = 0.50379851
}
IspSL = 0.953
IspV = 0.952
throttle = 0

ModuleEngineIgnitor
{
name = ModuleEngineIgnitor
ignitionsAvailable = 2
autoIgnitionTemperature = 800
ignitorType = Electric
useUllageSimulation = true
IGNITOR_RESOURCE
{
name = ElectricCharge
amount = 0.3
}
}
}

}
!MODULE[ModuleEngineIgnitor] {}
MODULE
{
name = ModuleEngineIgnitor
ignitionsAvailable = 1
autoIgnitionTemperature = 800
ignitorType = Electric
useUllageSimulation = true
IGNITOR_RESOURCE
{
name = ElectricCharge
amount = 0.3
}
}
}

Note that :FOR[RealFuels] has replaced the :Final block! This allows other mod authors to create @PART[] patches with :BEFORE[RealFuels] and :AFTER[RealFuels], to tweak things before or after RealFuels gets to them.

A mod author who releases their own parts - not mods of other people's parts - should just use the standard format. For example, from B9:


PART
{
// --- general parameters ---
name = B9_Utility_Leg_H50
module = HLandingLeg
author = bac9

// --- asset parameters ---
mesh = model.mu
rescaleFactor = 1.25
scale = 1

animationName = leg_h50_toggle2

PhysicsSignificance = 0

// --- node definitions ---
node_attach = 0.0, 0, 0, 0.0, 0.0, 0.0

// --- editor parameters ---
TechRequired = advLanding
entryCost = 6700
cost = 1250
category = Utility
subcategory = 0
title = H50-A Landing Leg
manufacturer = Tetragon Projects
description = Landing leg for excessively heavy landers. We aren't sure how you got things like these into space in the first place, but this thing will help to get them back onto the ground. Probably. This model extends vertically and should be attached to the side of your lander.

// attachment rules: stack, srfAttach, allowStack, allowSrfAttach, allowCollision
attachRules = 0,1,0,0,0

// --- standard part parameters ---
mass = 0.2
dragModelType = default
maximum_drag = 0.2
minimum_drag = 0.2
angularDrag = 2
crashTolerance = 120
maxTemp = 2900

CoMOffset = 0, 0, 0

breakingForce = 250
breakingTorque = 250

deploySpeed = 1
retractSpeed = -1
randomSpeedFactor = 0.05
}

This is a standard configNode, NOT a ModuleManager patch node. When you make your own part, just define it as normal.

So, let's say you have a mod with a plugin, that mixes original parts, adjustments to other parts, AND adjustments to other mod's adjustments. You will wind up with ALL THREE forms of configNodes.

Original Parts/Resources/etc.:


PART
{
name = myPart
...
}

Modifications to other parts, for my mod's DLL (which is called MyMod.dll)


@PART[otherPart]:FOR[MyMod]
{
...
}

Modifications to my parts OR other parts, for another mod's DLL (which is called OtherMod.dll)


@PART[otherPart]:BEFORE[OtherMod]
{
...
}
// or
@PART[otherPart]:AFTER[OtherMod]
{
...
}

NEVER EVER EVER use :FOR[OtherMod]. Only the mod author of a mod is allowed to use :FOR[mod]. if you are not the one providing SomeMod.DLL or a SomeMod.cfg "tweak pack", using :FOR[someMod] is like licking all the frosting off your co-worker's donut and then putting it back on his desk. No one's gonna STOP you, but you are a very naughty person and no one should be friends with you.

Finally, there's :NEEDS[OtherMod] - this is for when you want to do thing in your :FOR[] pass, or another mod's :BEFORE[] or :AFTER[] pass, but only if yet ANOTHER mod is present. So you can do things like:


@PART[mk1pod]:FOR[DeadlyReentry]:NEEDS[SolarSystemReplacer]
{
@MODULE[ModuleHeatShield]
{ // change the heat shield info to handle thicker atmosphere
@reflective = 0.08 // 5% of heat is ignored at correct angle
@dissipation
{ // dissipation is based on the part's current temperature
key = 300 0 // begin ablating at 300 degrees C
key = 1200 225 // maximum dissipation at 1200 degrees C
}
}
@RESOURCE[AblativeShielding]
{
@amount = 500
@maxAmount = 500
}
}

Edited by ialdabaoth
Link to comment
Share on other sites

I just merged Ialdabaoth latest changes. I'll need to work on the first post documentation but for now :

Awesome!

@NODE[name],* // will wildcard match name, and apply itself to ALL matching NODEs.

That's one of the things I was planning on fixing :)

The other one was an ordering thing.

Ideally if you had:



PART
{
name = myPart
// ... stuff here
MODULE
{
name=A
}
MODULE
{
name=B
}
}

and had a patch file:


@PART[myPart]
{
@MODULE[A]
{
// stuff here
}
}

Then the resulting part would still have the modules in the same order (A, then B). At the moment they end up B then A, unless you add an empty patch for B.

Why does this matter?

  • Modules are loaded from the save file in a set order (which is a bug with KSP IMHO) so if you apply a patch then the new version won't be able to load saves from the unpatched
  • The order of tweakables is based on the module order, and can end up looking daft
  • Some modules may be coded with a specific initiation order in mind (which probably isn't a good thing)


    Anyhow, I know how to fix it so if you want I can fork and fix and push.
Link to comment
Share on other sites

Yes, this is a massive issue. It's actually in some ways worse, because even if MM doesn't change the order of *existing* modules, saves/craft will be broken depending on when new modules are inserted (or if they are).

(This is why, for example, CrossFeedEnabler doesn't default to being applied to stretchies/PP: because it will break .craft / saves.)

Link to comment
Share on other sites

I'm not sure how ModuleManager could properly control that, though. Short of scanning the PART node, storing the order of each node, wiping all of them, and rebuilding them carefully, I have no idea what could be done - and even then, if two nodes have the same type and name, it won't be able to remember which one is which when it rebuilds everything.

This is really an issue with KSP more than MM.

Link to comment
Share on other sites

I recon I can do it :P

Just copy the child nodes, clear the container, add all the nodes before, then the altered node, then the nodes after.

The other thing I think would be good would be:


PART:NEEDS[RealFuels]
{

}

As in dependency checking for ordinary old parts.

And also:


!PART[name]:FOR[RealFuels]
{

}

eg: delete a part if real fuels is present.

I've figured out how to delete (and insert too) top level nodes so that should be possible.

Edit: Here's how: https://github.com/Swamp-Ig/KSPAPIExtensions/blob/master/Source/PartDependencyChecker.cs#L37

Edited by swamp_ig
Link to comment
Share on other sites

I think I may have found a bug... I'm trying to update the config that adds MechJeb to all capsules, and it's failing with an error that seems to indicate a parsing error. This is the config I'm trying to apply:

@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:NEEDS[MechJeb2]:FINAL
{
MODULE
{
name = MechJebCore
MechJebLocalSettings {
MechJebModuleCustomWindowEditor { unlockTechs = flightControl }
MechJebModuleSmartASS { unlockTechs = flightControl }
MechJebModuleManeuverPlanner { unlockTechs = advFlightControl }
MechJebModuleNodeEditor { unlockTechs = advFlightControl }
MechJebModuleTranslatron { unlockTechs = advFlightControl }
MechJebModuleWarpHelper { unlockTechs = advFlightControl }
MechJebModuleAttitudeAdjustment { unlockTechs = advFlightControl }
MechJebModuleThrustWindow { unlockTechs = advFlightControl }
MechJebModuleRCSBalancerWindow { unlockTechs = advFlightControl }
MechJebModuleRoverWindow { unlockTechs = fieldScience }
MechJebModuleAscentGuidance { unlockTechs = unmannedTech }
MechJebModuleLandingGuidance { unlockTechs = unmannedTech }
MechJebModuleSpaceplaneGuidance { unlockTechs = unmannedTech }
MechJebModuleDockingGuidance { unlockTechs = advUnmanned }
MechJebModuleRendezvousAutopilotWindow { unlockTechs = advUnmanned }
MechJebModuleRendezvousGuidance { unlockTechs = advUnmanned }
}
}
}

And this is the error I'm seeing in ksp.log:

[LOG 23:48:03.960] [ModuleManager] node Customizations/Use MechJeb everywhere/@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:NEEDS[MechJeb2]:FINAL - MechJeb2:FINAL not found!

It seems to be adding the word "FINAL" to the name of the mod instead of interpreting it as a keyword. I've tried spelling it both FINAL and Final and still get the same error. Any ideas?

EDIT: it appears that if I put FINAL before NEEDS then it works (ie "@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:FINAL:NEEDS[MechJeb2]" does the right thing). Didn't see that mentioned in the OP though, is this something that needs to be fixed or documented? :)

EDIT2: Aaaah, I just realized that I was doing it wrong. I need to use the FOR keyword, not FINAL, since this patch is FOR mechjeb. So what I needed actually was "@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:FOR[MechJeb2]".

Edited by OrbitalDebris
Link to comment
Share on other sites

FOR is really supposed to be used by the mod developer. I think AFTER is what you're looking for.

Might be worth changing the keyword to :WITHIN or something similar as you can see how it would get confusing.

Link to comment
Share on other sites

I guess that makes sense, but I thought that BEFORE and AFTER are supposed to be to make sure that patches are applied so they don't stomp on each other. That does leave the weird requirement that FINAL must be before NEEDS[...], I'd like to hear from Ialdabaoth as to whether that is the way it's supposed to be.

Link to comment
Share on other sites

Quick question:

Assume I don't like physicsless parts, and I want to make sure that none exist. Could I use one single ModuleManager config that removes the line "PhysicsSignificance = 1" from all parts that have it, stock and modded alike? As long as that line isn't explicitly written out, the value defaults to 0. So removing it from the few parts that have it sounds a simpler solution than adding/updating it to every last part with a setting of 0, which would be identical to the default anyway.

I'm just not sure if that line is consistently in the same spot in every part, and whether or not that makes any difference from ModuleManager's point of view.

Link to comment
Share on other sites

Just NEVER complain to any mod dev after you removed PhysicsSignificance from your parts. It's not here just to annoy you.

OrbitalDebris

One hint : five [ and six ]

I should add a test that count braces and spit error messages ^^

swamp_ig :

fell free do contribute. It's how mods evolve faster :)

Link to comment
Share on other sites

I guess that makes sense, but I thought that BEFORE and AFTER are supposed to be to make sure that patches are applied so they don't stomp on each other. That does leave the weird requirement that FINAL must be before NEEDS[...], I'd like to hear from Ialdabaoth as to whether that is the way it's supposed to be.

So, :NEEDS[...] gets applied to a :BEFORE or :AFTER in the same way that :HAS gets applied to the part name filter.

i.e., it's not just

@PART[...]:NEEDS[OtherMod]

it's actually

@PART[...]:BEFORE[OtherMod]:NEEDS[AnotherMod, !YetAnotherMod]

which says "Apply this node before the :FOR[OtherMod] pass, but ONLY if AnotherMod is also installed AND YetAnotherMod ISN'T installed."

Just doing @PART[...]:NEEDS[OtherMod] is identical to doing @PART[...]:FIRST:NEEDS[OtherMod] in the same way that @PART[...] is identical to @PART[...]:FIRST; if you want it to happen in the FINAL pass, do @PART[...]:FINAL:NEEDS[OtherMod]

So, in your example, rather than:

@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:NEEDS[MechJeb2]:FINAL

You need to do:

@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:FINAL:NEEDS[MechJeb2]

You can't do :NEEDS[MechJeb2]:FINAL or :NEEDS[MechJeb2]:BEFORE[blah] any more than you could do @HAS[@MODULE[ModuleCommand],!MODULE[MechJebCore]]]:PART[*]

The order MUST be:

*NODE:HAS[FilterOptions]:PASS:NEEDS[NeedOptions]

Where:

- '*' can be one of '@', '!', '%'

- NODE can be PART, RESOURCE_DEFINITION, or any other root-level node, and can optionally include [nodeNameFilter] and/or ,index

- HAS[FilterOptions] is optional

- PASS can be FIRST, BEFORE[modname], FOR[modname], AFTER[modname], or FINAL. BEFORE[modname] and AFTER[modname] only get called if the FOR[modname] pass will be called. If PASS is not specified, it defaults to FIRST.

- NEEDS[NeedOptions] is optional.

Does that make sense?

Edited by ialdabaoth
Link to comment
Share on other sites

Just NEVER complain to any mod dev after you removed PhysicsSignificance from your parts. It's not here just to annoy you.

That's a given :) If I encounter an issue that isn't there without my private tweaks, then I have nobody but myself to blame.

Link to comment
Share on other sites

What happens if ExampleMod doesn't have a dll or any FOR[ExampleMod] but it does have parts that you want to modify? I would expect NEEDS[ExampleMod] to cause the modifications to not be made which might be confusing.

Can AFTER be used with several mod names so that it loads after both have been loaded? eg AFTER[RemoteTech2,MechJeb2]

A little confused about % functionality. If I use %MODULE[someModule] do I then still need to put in a line name = SomeModule?

If I want to change some bits within a NODE but not others if the NODE exists, or define the NODE in total if it doesn't exist then % seems to be the way to go. But how do I then define which get changed if they exist and which don't? If I use no modifier ie "foo = value" or "MODULE={name = somename ...}" then wouldn't that just create an extra MODULE and not modify the existing one? If I used @ then it wouldn't create it if the NODE didn't exist previously. If I used % it would overwrite it if it already existed.

In my case what I am trying to do is replace


@PART[*]:HAS[@MODULE[ModuleCommand],@MODULE[ModuleRTAntennaPassive]]:FINAL
{
@MODULE[ModuleRTAntennaPassive]
{
@TechRequired = unmannedTech
@OmniRange = 1000
}
}

@PART[*]:HAS[@MODULE[ModuleCommand],!MODULE[ModuleRTAntennaPassive]]:FINAL
{
MODULE
{
name = ModuleRTAntennaPassive
TechRequired = unmannedTech
OmniRange = 1000

TRANSMITTER
{
PacketInterval = 0.3
PacketSize = 2
PacketResourceCost = 15.0
}
}
}

Perhaps what I am really asking for is an operator that adds something if it doesn't exist or otherwise does nothing. Unless ~ can be used outside of HAS[]. So


@PART[*]:Final
{
~TechRequired=advScienceTech
}

Edward

Link to comment
Share on other sites

My change does not seem to work ... both engines still use LF/OX.

Using HotRockets I want to cover for ModuleEnginesFX, my config folder begins with "zzz..." to run last.


@PART[nuclearEngine]
{

@MODULE[ModuleEngines*]
{
!PROPELLANT[LiquidFuel] {}

!PROPELLANT[Oxidizer] {}

PROPELLANT
{
name = Kethane
ratio = 5.00
DrawGauge = True
}
}
}

@PART[cl_large_nuclearEngine]
{

@MODULE[ModuleEngines*]
{
!PROPELLANT[LiquidFuel] {}

!PROPELLANT[Oxidizer] {}

PROPELLANT
{
name = Kethane
ratio = 5.00
DrawGauge = True
}
}
}

Narf ... forgot some {} ...

But now they are using LF/OX and Kethane ...

Forgot even more {} ... though the example in the second post does not have them! :P

Edited by KerbMav
Link to comment
Share on other sites

My change does not seem to work ... both engines still use LF/OX.

Using HotRockets I want to cover for ModuleEnginesFX, my config folder begins with "zzz..." to run last.


@PART[nuclearEngine]
{

@MODULE[ModuleEngines*]
{
!PROPELLANT[LiquidFuel] {}

!PROPELLANT[Oxidizer] {}

PROPELLANT
{
name = Kethane
ratio = 5.00
DrawGauge = True
}
}
}

@PART[cl_large_nuclearEngine]
{

@MODULE[ModuleEngines*]
{
!PROPELLANT[LiquidFuel] {}

!PROPELLANT[Oxidizer] {}

PROPELLANT
{
name = Kethane
ratio = 5.00
DrawGauge = True
}
}
}

Narf ... forgot some {} ...

But now they are using LF/OX and Kethane ...

Forgot even more {} ... though the example in the second post does not have them! :P

Get rid of those spaces

!PROPELLANT[LiquidFuel]{}

not

!PROPELLANT[LiquidFuel] {}

Link to comment
Share on other sites

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...