Jump to content

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


sarbian

Recommended Posts

Have you checked output_log.txt for errors when your cfg is applied? Or to see if something else is adding new engine modules in?

... I had two different versions of Module Manager installed.

I'll just go sit over here now.

Link to comment
Share on other sites

I'm trying to update a KAS config

[...]

and this is my MM config:

@PART[KAS_ContainerBay1]:Final{
!scale
!mesh
@rescaleFactor = 1
@node_stack_top = 0.0, -0.35, 0.0, 0.0, 1.0, 0.0, 0
@node_stack_bottom = 0.0, -0.4, 0.0, 0.0, 1.0, 0.0, 0
@node_attach = 0.0, -0.4, 0.0, 0.0, -1.0, 0.0
MODEL
{
model = KAS/Parts/containerBay1/containerBay1
scale = 0.5,0.5,0.5
}
MODULE
{
name = KASModuleGrab
evaPartPos = (0.0, 0.10, -0.15)
evaPartDir = (0,0,-1)
customGroundPos = true
dropPartPos = (0.0, 0.0, -0.55)
dropPartRot = (-10.0, 0.0, 0.0)
attachOnPart = True
attachOnEva = False
attachOnStatic = False
attachSendMsgOnly = False
}
}

It looks like you're trying to do what I just tried to do with the container storage rack, but I found that no matter what attach node I tried, it always sank halfway into whatever I tried to attach it to (part or terrain). Have you had any luck getting it to work?

Link to comment
Share on other sites

I'm trying to change some things to the ion engine. Yet for some reason not everything is working correctly:

@PART[ionEngine]
{
@category = Propulsion

@MODULE[ModuleEngines]
{
@maxThrust = 1.25

@PROPELLANT[ElectricCharge]
{
@ratio = 0.8
DrawGauge = true
}
}
}

The category change works, but the engine stuff doesn't. What am I doing wrong?

Link to comment
Share on other sites

It looks like you're trying to do what I just tried to do with the container storage rack, but I found that no matter what attach node I tried, it always sank halfway into whatever I tried to attach it to (part or terrain). Have you had any luck getting it to work?

Yes, I think so. I've played with it in place the last few days and haven't noticed any issues. I can PM you the cfg when I get home from work if you want.

Link to comment
Share on other sites

If I am already using :Final to add some TechRequired entries, how can I make sure that everything I missed gets edited by this here:

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

?

This is what I use.


@PART[*]:HAS[#module[Part],~TechRequired[]]:Final
{
TechRequired = advRocketry
entryCost = 1000
}

Really the same as what you have except that it only adds it to Parts. (probably some unnecessary weeding; 'Winglets' would get weeded out...)

entryCost is superfluous... NOW. In the future it'll probably have a use.

Link to comment
Share on other sites

If I am already using :Final to add some TechRequired entries, how can I make sure that everything I missed gets edited by this here:

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

?

Make sure that the config file with that in is alphabetically last, so stick it in a folder called "GameData/ZZZZ_Final_Mods".

All of the configs are parsed in find-them order, then all the :Final ones, in the same order.

Link to comment
Share on other sites

Sarbian: Node[Name, Tag] is useless, because almost no one uses 'tag', and if you don't have a tag, you CAN'T pick anything but the first node.

Originally, it was supposed to work identically to value keys - i.e., Node[Name, index]

So you should be able to say MODULE[ModuleEnginesFX, 1] {

}

Here's a partial rewrite that fixes it:


public static ConfigNode FindConfigNodeIn(ConfigNode src, string nodeType,
string nodeName = null, int index)
{
#if DEBUG
if (nodeTag == null)
print ("Searching node for " + nodeType + "[" + nodeName + "]");
else
print ("Searching node for " + nodeType + "[" + nodeName + "," + nodeTag + "]");
#endif
int found = 0;
foreach (ConfigNode n in src.GetNodes(nodeType)) {
if (nodeName == null && nodeTag == null)
return n;
if (n.HasValue("name") && WildcardMatch(n.GetValue("name"), nodeName)) {
if (found == index)
{
#if DEBUG
print ("found node " + found.ToString() + "!");
#endif
return n;
} else {
found++;
}
}
}
return null;
}


public static ConfigNode ModifyNode(ConfigNode original, ConfigNode mod)
{
if (!IsSane(original) || !IsSane(mod)) {
print("[ModuleManager] A node has an empty name. Skipping it. Original: " + original.name);
return original;
}

ConfigNode newNode = original.CreateCopy();

foreach (ConfigNode.Value val in mod.values) {
if (val.name[0] != '@' && val.name[0] != '!' && val.name[0] != '%')
newNode.AddValue(val.name, val.value);
else {
// Parsing: Format is @key = value or @key,index = value
string valName = val.name.Substring(1);
int index = 0;
if (valName.Contains(",")) {
int.TryParse(valName.Split(',')[1], out index);
valName = valName.Split(',')[0];
} // index is useless right now, but some day it might not be.
if (val.name[0] == '@')
newNode.SetValue(valName, val.value, index);
else if (val.name[0] == '!')
newNode.RemoveValues(valName);
else if (val.name[0] == '%') {
newNode.RemoveValues(valName);
newNode.AddValue(valName, val.value);
}
}
}

foreach (ConfigNode subMod in mod.nodes) {
subMod.name = RemoveWS(subMod.name);
if (subMod.name[0] != '@' && subMod.name[0] != '!' && subMod.name[0] != '%' && subMod.name[0] != '$')
newNode.AddNode(subMod);
else {
ConfigNode subNode;
//if (subMod.name[0] == '@' && subMod.name[0] != '%')
// subNode = null;

if (subMod.name.Contains("[")) {
// format @NODETYPE[Name] {...} or @NODETYPE[Name, index] {...} or ! instead of @
string nodeType = subMod.name.Substring(1).Split('[')[0];
string nodeName = subMod.name.Split('[')[1].Replace("]", "");
int index = 0;
if (nodeName.Contains(",")) {
// format @NODETYPE[Name, index] {...} or ! instead of @
int.TryParse(nodeType.Split(',')[1], out index);
nodeName = nodeName.Split(',')[0];
}
subNode = FindConfigNodeIn(newNode, nodeType, nodeName, index);
} else {
// format @NODETYPE {...} or ! instead of @
string nodeType = subMod.name.Substring(1);

// format @NODETYPE,N {...} or ! instead of @
// The problem with ! is that the index is messed up
// So the patch need to take that into account
// and lower the index for the next search
int index = 0;
if (nodeType.Contains(",")) {
int.TryParse(nodeType.Split(',')[1], out index);
nodeType = nodeType.Split(',')[0];
}
ConfigNode[] subNodes = newNode.GetNodes(nodeType);
if (subNodes.Length > index)
subNode = subNodes[index];
else
subNode = null;
}
if (subMod.name[0] == '@') {
// find the original subnode to modify, modify it and add the modified.
if (subNode != null) {
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
} else
print("[ModuleManager] Could not find node to modify: " + subMod.name);
}
if (subMod.name[0] == '%') {
// if the original node exist add it
if (subNode != null) {
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
}
else { // if not add the mod node without the % in its name
// This part is messy. ialdabaoth is right, i need to rewrite.
string type;
string name;
if (subMod.name.Contains("[")) {
type = subMod.name.Substring(1).Split('[')[0];
name = subMod.name.Split('[')[1].Replace("]", "");
}
else
{
type = subMod.name.Substring(1);
name = null;
}

ConfigNode copy = new ConfigNode(type);

if (name != null)
copy.AddValue("name", name);

ConfigNode newSubNode = ModifyNode(copy, subMod);
newNode.nodes.Add(newSubNode);
}
}
if (subMod.name[0] == '$')
{
// find the original subnode to copy, add the original, add the the modified copy.
if (subNode != null) {
newNode.nodes.Add(subNode);
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
}
else
print("[ModuleManager] Could not find node to copy: " + subMod.name);
}
if (subNode != null)
newNode.nodes.Remove(subNode);

}
}
return newNode;
}

Link to comment
Share on other sites

ALSO: I can't test it right now, but the following should allow you to apply patches to ALL subnodes that wildcard-match, rather than merely the first:


public static ConfigNode ModifyNode(ConfigNode original, ConfigNode mod)
{
if (!IsSane(original) || !IsSane(mod)) {
print("[ModuleManager] A node has an empty name. Skipping it. Original: " + original.name);
return original;
}

ConfigNode newNode = original.CreateCopy();

foreach (ConfigNode.Value val in mod.values) {
if (val.name[0] != '@' && val.name[0] != '!' && val.name[0] != '%')
newNode.AddValue(val.name, val.value);
else {
// Parsing: Format is @key = value or @key,index = value
string valName = val.name.Substring(1);
int index = 0;
if (valName.Contains(",")) {
int.TryParse(valName.Split(',')[1], out index);
valName = valName.Split(',')[0];
} // index is useless right now, but some day it might not be.
if (val.name[0] == '@')
newNode.SetValue(valName, val.value, index);
else if (val.name[0] == '!')
newNode.RemoveValues(valName);
else if (val.name[0] == '%') {
newNode.RemoveValues(valName);
newNode.AddValue(valName, val.value);
}
}
}

foreach (ConfigNode subMod in mod.nodes) {
subMod.name = RemoveWS(subMod.name);
if (subMod.name[0] != '@' && subMod.name[0] != '!' && subMod.name[0] != '%' && subMod.name[0] != '$')
newNode.AddNode(subMod);
else {
ConfigNode subNode;
//if (subMod.name[0] == '@' && subMod.name[0] != '%')
// subNode = null;
string nodeType;
string nodeName;
string indexes = "";
int index;

if (subMod.name.Contains("["))
{
nodeType = subMod.name.Substring(1).Split('[')[0];
nodeName = subMod.name.Split('[')[1].Replace("]", "");
indexes = "";
index = 0;

// format @NODETYPE[Name] {...} or @NODETYPE[Name], index {...} or ! instead of @
if (nodeName.Contains(","))
{
// format @NODETYPE[Name], index {...} or ! instead of @
nodeName = nodeName.Split(',')[0];
indexes = nodeType.Split(',')[1];
}
}
else
{
// format @NODETYPE {...} or ! instead of @
nodeType = subMod.name.Substring(1);
nodeName = null;
indexes = "";
index = 0;

// format @NODETYPE,N {...} or ! instead of @
// The problem with ! is that the index is messed up
// So the patch need to take that into account
// and lower the index for the next search

if (nodeType.Contains(","))
{
nodeType = nodeType.Split(',')[0];
indexes = nodeType.Split(',');
}
ConfigNode[] subNodes = newNode.GetNodes(nodeType);
if (subNodes.Length > index)
subNode = subNodes[index];
else
subNode = null;
}

// an index of '*' means apply to ALL subnodes that match
if (indexes == "*" || indexes == "")
index = 0;
else
int.TryParse(indexes, out index);


bool moreToDo = true;
do
{
subNode = FindConfigNodeIn(newNode, nodeType, nodeName, index);
if (subNode != null)
{
if (subMod.name[0] == '@')
{
// find the original subnode to modify, modify it and add the modified.
if (subNode != null)
{
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
}
else
print("[ModuleManager] Could not find node to modify: " + subMod.name);
}
if (subMod.name[0] == '%')
{
// if the original node exist add it
if (subNode != null)
{
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
}
else
{ // if not add the mod node without the % in its name
// This part is messy. ialdabaoth is right, i need to rewrite.
string type;
string name;
if (subMod.name.Contains("["))
{
type = subMod.name.Substring(1).Split('[')[0];
name = subMod.name.Split('[')[1].Replace("]", "");
}
else
{
type = subMod.name.Substring(1);
name = null;
}

ConfigNode copy = new ConfigNode(type);

if (name != null)
copy.AddValue("name", name);

ConfigNode newSubNode = ModifyNode(copy, subMod);
newNode.nodes.Add(newSubNode);
}
}
if (subMod.name[0] == '$')
{
// find the original subnode to copy, add the original, add the the modified copy.
if (subNode != null)
{
newNode.nodes.Add(subNode);
ConfigNode newSubNode = ModifyNode(subNode, subMod);
newNode.nodes.Add(newSubNode);
}
else
print("[ModuleManager] Could not find node to copy: " + subMod.name);
}

// an index of '*' means apply to ALL subnodes that match
if (indexes == "*")
index++;
else
moreToDo = false;
}
else
{
moreToDo = false;
}
} while (moreToDo);

if (subNode != null)
newNode.nodes.Remove(subNode);

}
}
return newNode;
}

Link to comment
Share on other sites

UPDATE! I just talked to sarbian; in his absence, I will be creating and releasing Module Manager 2.0.0

I will do EVERYTHING I CAN not to break backwards compatibility, but no guarantees - there's a few cases where the current system does not provide an unambiguous configNode application sequence.

Here is a preview of 2.0.0 syntax:

1. Mod Definitions


MOD {
name = MyMod // this is the name of your mod
version = 23 // this is the version of your mod. It must be a single number, that increases whenever you release a new version.
plugin = MyMod\mymod.dll
url = http://www.mymod.com/download

NEEDS { // a 'NEEDS' block defines a dependency
name = NeededMod // i.e., ModManager should throw an error if the correct version of NeededMod isn't installed.
min_version = 15 // If NeededMod is installed, but the installed Version is 14 or lower, ModManager should throw an error.
// if min_version is blank, defaults to -1
max_version = 9999 // If NeededMod is installed, but the installed Version is 10000 or higher, ModManager should throw an error.
// if max_version is blank, defaults to a ridiculously large number (2 billion or so)
error_message = MyMod version 23 requires NeededMod version 15 or higher. (Last tested version was 17). Please check $KSPFORUM:threads/75562-MyMod-Thread for details.
}

CONFLICTS { // a 'CONFLICTS' block is the opposite of a 'NEEDS' block.
name = BadMod // i.e., ModManager should throw an error if a conflicting version of BadMod is installed.
min_version = 11 // If BadMod is installed, but the installed Version is 10 or lower, ModManager should NOT throw an error.
// if min_version is blank, defaults to -1
max_version = 13 // If BadMod is installed, but the installed Version is 14 or higher, ModManager should NOT throw an error.
// if max_version is blank, defaults to a ridiculously large number (2 billion or so)
}
}

If you do not include a Mod Definition with your mod, ModuleManager 2.0.0 WILL STILL WORK, but you (and other modders!) will not have access to any of the new fancy sequencing rules, or dependency checking. You'll only have access to the first-pass run and the :FINAL run.

If you DO include a Mod Definition, ModuleManager 2.0.0 will do two awesome things for you:

2. Dependency and Conflict Checking

ModuleManager 2.0 will check dependencies, by which I mean it will look for any other MOD nodes that match your NEEDS {} and CONFLICTS {} nodes, and throw up a warning to the user that they need to resolve any dependencies or conflicts indicated.

3. Per-Mod Patch Sequencing

ModuleManager 2.0 will create three new passes for each MOD node it finds, that happen after the first-pass but before :FINAL. These passes look like this:

:BEFORE[MyMod]

The :BEFORE[MyMod] pass happens first, and happens once for each mod. You can specify it one of two ways:


@PART[some_part]:BEFORE[MyMod] // this config patch will ONLY be applied if any version of the MyMod mod is installed.
{
// do stuff
}

if you require a specific version, you can do:


@PART[some_part]:BEFORE[MyMod,23] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed.
{
// do stuff
}

And if you need versions of MULTIPLE mods, you can do:


@PART[some_part]:BEFORE[MyMod,23]:NEEDS[ModuleManager,200] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed, AND version 200 or greater of ModuleManager is installed.
{
// do stuff
}


@PART[some_part]:BEFORE[MyMod,23]:NEEDS[!Firespitter] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed, AND no version of Firespitter is installed.
{
// do stuff
}

:FOR[MyMod]

The :FOR[MyMod] pass happens next, and happens once for each mod. It uses the same syntax:


@PART[some_part]:FOR[MyMod] // this config patch will ONLY be applied any version of the MyMod mod is installed.
{
// do stuff
}


@PART[some_part]:FOR[MyMod,23] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed.
{
// do stuff
}


@PART[some_part]:FOR[MyMod,23]:NEEDS[ModuleManager,200]:NEEDS[!RealFuels,50] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed, AND version 200 or greater of ModuleManager is installed, AND version 50 or greater of RealFuels ISN'T installed.
{
// do stuff
}

:AFTER[MyMod]

The :AFTER[MyMod] pass happens next, and happens once for each mod. It uses the same syntax as above:


@PART[some_part]:AFTER[MyMod] // this config patch will ONLY be applied if any version of the MyMod mod is installed
{
// do stuff
}


@PART[some_part]:AFTER[MyMod,23]:NEEDS[!ModuleManager] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed, AND no version of ModuleManager is installed. Good luck with that.
{
// do stuff
}

I'll continue to post here with progress.

Link to comment
Share on other sites

While I like the additions of more passes to better handle multiple mods making changes to the same parts, I don't like the syntax and BEFORE, FOR, and AFTER names. I think its just trying to bandaid a broken method of only 2 passes by adding 3 more.

What about a way to link to link each file with @NODE definitions in it to a MOD and and define interactions such as this cfg file must be loaded before/after any files linked to OTHERMOD if its installed. I feel like without a way to define interactions between mods the problem will resurface again as more complex configs are used. To keep things simpler make everything in the same file that uses the new format load at the same time. Does this make any sense how im trying to explain it?

I have a question about the version selection logic in this.


@PART[some_part]:FOR[MyMod,23]:NEEDS[ModuleManager,200]:NEEDS[!RealFuels,50] // this config patch will ONLY be applied if version 23 or greater of the MyMod mod is installed, AND version 200 or greater of ModuleManager is installed, AND version 50 or greater of RealFuels ISN'T installed.
{
// do stuff
}

Is there a way to say only load this if a version of MOD before X is installed or would that require something like this for only working on version 49 or lesser of RealFuels? Like this?


@PART[some_part]:FOR[MyMod,23]:NEEDS[RealFuels]:NEEDS[!RealFuels,50]
{
// do stuff
}

Why not just support comparison options with it defaulting to >= so something like this could be done? Make it support >= (default if none entered), >, =, <, and <=.


@PART[some_part]:FOR[MyMod,23]:NEEDS[RealFuels,<50]
{
// do stuff
}

Have you added in the abiltiy to use :HAS on subnodes? So we can select from multiple nodes without having to use the tags that nobody does or without knowing exactly which number references it. For example like this.


@PART[*]
{
@MODULE[foo]:HAS[#bar=baz]
{
// do stuff
}
}

And I think some people wanted the ability to use other operators in #bar=baz to so stuff like #mass<0.05.

Link to comment
Share on other sites

Looking forward to 2.0.0!

Quick question though: I want to move all RCS tanks to the Control tab, so I use the following

@PART[*]:HAS[@RESOURCE[MonoPropellant]]:Final
{
@category = Control
}

However, this moves all the command pods there as well. What syntax should I put that includes things with resource MonoPropellant but excludes ModuleCommand?

EDIT: Am I right in thinking it would be

@PART[*]:HAS[@RESOURCE[MonoPropellant]]:HAS[!MODULE[ModuleCommand]:Final
{
@category = Control
}

?

Edited by ObsessedWithKSP
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...