Jump to content

0.15 code update - PartModule, KSPField, KSPEvent, ConfigNode and PartResource


thorfinn

Recommended Posts

Very very very interesting, but just one thing about the formatting for now: did you notice that the code boxes inside spoilers collapse to a single line? I suppose you didn\'t want them to do that.

Link to comment
Share on other sites

Very very very interesting, but just one thing about the formatting for now: did you notice that the code boxes inside spoilers collapse to a single line? I suppose you didn\'t want them to do that.

They aren\'t doing that for me. I get about a 15 line code box.

Arrr!

Capt\'n Skunky

Link to comment
Share on other sites

  • 2 weeks later...

I checked on my own machine here. The code looking like that seems to be caused by google chrome. I tried firefox and IE (shudder) they both show about 15 lines instead of 1.

I have noticed that on chrome as well, it\'s because the code tag is having a style attribute attached to it ('style='height: 20px;'), where as the one\'s that are fine don\'t have this.

it\'s caused by this code in the theme.js?fin20


function smf_codeBoxFix()
{
var codeFix = document.getElementsByTagName(\'code\');
for (var i = codeFix.length - 1; i >= 0; i--)
{
if (is_webkit && codeFix[i].offsetHeight < 20)
codeFix[i].style.height = (codeFix[i].offsetHeight + 20) + \'px\';

else if (is_ff && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0))
codeFix[i].style.overflow = \'scroll\';

else if (\'currentStyle\' in codeFix[i] && codeFix[i].currentStyle.overflow == \'auto\' && (codeFix[i].currentStyle.height == \'\' || codeFix[i].currentStyle.height == \'auto\') && (codeFix[i].scrollWidth > codeFix[i].clientWidth || codeFix[i].clientWidth == 0) && (codeFix[i].offsetHeight != 0))
codeFix[i].style.height = (codeFix[i].offsetHeight + 24) + \'px\';
}
}

The problem occurs when the spoiler tag is wrapped around the code tag.

The easiest fix would be to change the line

if (is_webkit && codeFix[i].offsetHeight < 20)

to

if (is_webkit && codeFix[i].offsetHeight < 20 && codeFix[i].parentNode.style.display != 'none')

Link to comment
Share on other sites

Thanks Mu, I\'m still a bit dazed processing this, but great job taking the time to explain things for us.

I had a couple initial questions/issues that came to mind:

1. Is there a way to manipulate or read which modules are running on a Part runtime? Specifically, is it possible to add a module, or retrieve a list of modules that are attached to an arbitrary Part reference through code?

Looks like there\'s

public PartModule AddModule(ConfigNode node);
public PartModule AddModule(string moduleName);
and
public PartModuleList Modules { get; }
and
public void RemoveModule(PartModule module);

@->2. How are conflicting resource definitions handled? As in multiple mod/plugins specifying the same resource name (LOX or LH2) with differing parameters (lbs/ft^3 or kg/m^3 or kilobananas/doo-hickey). Also, is there anything in place to allow differently named resources (LOX and LiquidOxygen) that should resolve to the same physical substance 'object' to do so?

3. Perhaps I\'m still a bit out of it, but in your 'generic reaction engine' example, how exactly are you calling part.RequestResource(p.id, p.currentRequirement); Seems the module class has a 'part' member that presumably gets set to the relevant part.

@->4. The fuel feed system seems to be massively bugged in the initial .15 release. Even once that is fixed, some of us prefer to specify our own 'resource distribution' scheme. If we use part.RequestResource(), are we locked into the default distribution rules? Can we explicitly call a something-or-other on a specific part reference to get it to specifically handle resource...stuff only within itself(as in not propagating the task beyond itself)?

I think that\'s it, but you\'re finally forcing me to learn reflection, attributes, and a whole bunch of that other silly C# stuff I\'d been ignoring, so um...what was I saying again? Anyway, thanks again for explaining it to us.

Edit: Thought of another thing, is there a way to add a new resource entry in code at runtime?

Looks like there\'s


public PartResource AddResource(ConfigNode node);
public PartResourceList Resources { get; }

@->Edit 2: In light of the changes to the structure of the part cfg files, does the new paradigm extend to the actual Part definition, and if so, could you please give us an example of what a blank part cfg should look like? As in just a static do-nothing part before any module stuff is added?

@->Edit 3: Fuel handling is usually done by mass flow, not volume flow(At least in calculations or stat listings of rocket engines, ie Isp, dry/wet mass, stoichiometrics). Having the resources specified in tank and requests in units of volume rather than mass necessitates more spreadsheet work when setting up parts.

@->Edit 4: This post looks like a huge mess now, but I was wondering how the part GUI\'s, and icon infoboxes are to be handled. Still working on creating your generic rocket module example, but how/if it interacts with the game GUI is still a mystery. Same goes for a modularized fueltank, especially as I have it set up for dual fuel storage and burn right now.

Link to comment
Share on other sites

Am I getting it right that the list of KSPFields cannot be altered at run-time?

Is there some other way to implement a dynamic list of items in part\'s context menu (e.g. a list of nearest vessels with an ability to click one of them)? We can surely add, well, 5 pre-defined fields 'vessel1, vessel2, ... vessel5' and change their guiActive/guiName as the list changes; but is it the right way? o_O

Link to comment
Share on other sites

I checked on my own machine here. The code looking like that seems to be caused by google chrome. I tried firefox and IE (shudder) they both show about 15 lines instead of 1.

Safari displays it incorrectly aswell if that helps.

Link to comment
Share on other sites

Am I getting it right that the list of KSPFields cannot be altered at run-time?

Is there some other way to implement a dynamic list of items in part\'s context menu (e.g. a list of nearest vessels with an ability to click one of them)? We can surely add, well, 5 pre-defined fields 'vessel1, vessel2, ... vessel5' and change their guiActive/guiName as the list changes; but is it the right way? o_O

I haven\'t gotten around to messing with the GUI stuff yet, but I think we ran into the same underlying issue. The [KSPField] attribute requires one of the listed types, or a class that implements the IConfigNode interface. What you\'re specifically asking for is to pass a List<T> to whatever it is that handles the GUI. I am also 'losing' my list through nonpersistance, as [KSPField] doesn\'t handle List<T> objects.

What I think will work, and am going to try is to wrap my list inside a class that implements IConfigNode, and the Load/Save members to resolve the strings back into a list. If that works like I think it should, I can then apply [KSPField] to an object of my wrapper class type, and it\'ll persist properly. What you will probably need to do is add more stuff to the wrapper class to let whatever the GUI code is resolve it to whatever it needs to.

As far as actually adding [KSPField] stuff at runtime, I found something here that may not work due to it\'s specific limitations, and here that may work using reflection.emit to assemble and manufacture a custom class at runtime.

In general though, because [KSPField] is an attribute, it\'s in no way dynamic at runtime, unless a reflection.emit class assembler and factory class scheme work out. But that is really, really more involved than modding a game should ever become.

Link to comment
Share on other sites

In general though, because [KSPField] is an attribute, it\'s in no way dynamic at runtime, unless a reflection.emit class assembler and factory class scheme work out. But that is really, really more involved than modding a game should ever become.
Well, I bet it\'s PartModule.Fields iterator used when building context menu. And it may (or may not) be just a static list initialized in constructor with all the KSPFields available at that moment, and may be not updated in run-time...

Needs testing ::)

Link to comment
Share on other sites

You might very well be right, but there is Part.AddModule(), which I was assuming is available at runtime to plugins, which would necessitate the list of fields being dynamic. If it\'s not going to work properly, that\'s another problem in itself.

I think the fields list is actually a 'public BaseFieldList Fields { get; }', I tried looking in BaseFieldList, but couldn\'t make much sense of it, too much hidden code.

Link to comment
Share on other sites

I still don\'t understand how the data loaded into an IConfigNode class is supposed to persist through scenes once it gets loaded from part.cfg, but I have a basic order of operations for the PartModule in action, so I\'ll share that.

->part is created(added to scene, or created from part.cfg)

1. PartModule constructor called

2. PartModule OnAwake called

3. : IConfigNode class constructors for [KSPField] fields in PartModule class

4a. : IConfigNode class Load() called '' if this is the part.cfg reading scene <-This is not done elsewhere

4b. : IConfigNode class Save() called '' this seems to happen when launching from VAB, and starting flightscene. It appears to be tied to the flightstate saving. It is not called after reading part.cfg.

5a. PartModule OnLoad() called '' <-This is done when reading part.cfg, nowhere else

5b. PartModule OnSave() called '' <-This is done when saving the flightstate, after the [KSPField] Save() calls

6. PartModule OnStart called <-This is not done when reading part.cfg, but is done in VAB/Flightscene. Flightscene happens just before

PQS (KerbinOcean) UpdateInit=3.098877s (1252 quads)

PQS (OceanPQS) UpdateInit=0.001693726s (44 quads)

7. OnUpdate etc called once the part is activated. This could be problematic for parts that should update when inactive.

Link to comment
Share on other sites

My workaround for custom type persistence:

1. Add a [KSPField] string to your PartModule, and override OnAwake and OnLoad like this:


public class MyModule : PartModule
{
[KSPField]
public string MyPersistantData;

public MyDataStructure MDS0 = new MyDataStructure();

public override void OnAwake()
{
if (MDS0 == null)
MDS0 = new MyDataStructure();
DeSerialize(); //read all the data stored in MyPersistantData back into the relevant places.
base.OnAwake();
}

public override void OnLoad(ConfigNode node)
{
foreach (ConfigNode subnode in node.nodes)
{
switch (subnode.name)
{
case 'DuelingInts':
{
MDS0.Load(subnode);
break;
}
}
}
Serialize(); //store all necessary data into MyPersistantData
base.OnLoad(node);
}

public void Serialize()
{
MyPersistantData = string.Empty;
if (MDS0 != null)
MyPersistantData += MDS0.ToString();
//can store more data here, just use a delimiter like in the class
}

public void DeSerialize()
{
if (string.IsNullOrEmpty(MyPersistantData))
return;//fail if nothing was saved
//can split the string into substrings here like in the class if you\'re packing multiple objects into it
MDS0.Read(MyPersistantData);
//this returns a bool for handling multiple object packing
}
}

2. Setup your class you need persistent data storage for like so:


//need this for culture normalization, crazy Euros use all kinds of funny stuff ;)
using System.Globalization;
//

public class MyDataStructure
{
public int Data0;
public int Data1;

//These two just keep things consistent
private static CultureInfo cult = CultureInfo.InvariantCulture;
private static NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands;
//
private static string[] Delimiter = new string[] { '*' };
private static int NumEntries = 2;

/* Just an example of validating data
private bool ValidateMe(int i)
{
if (i < 17 && i > 3)
return true;
return false;
}
*/

//as this class does not use the IConfigNode interface or loading mechanism, this method can be named anything
public void Load(ConfigNode node)
{
if (node == null)
return;
//used tryparse here as that does not throw exceptions
int.TryParse(node.GetValue('inta'), out Data0);
int.TryParse(node.GetValue('intb'), out Data1);
}

public bool Read(string s)
{
if (string.IsNullOrEmpty(s))
return false;//Failure
string[] strarr = s.Split(Delimiter, System.StringSplitOptions.RemoveEmptyEntries);//Holds array of string encoded values
if (strarr.Length != NumEntries)
return false;//Failure, incorrect number of serialized elements, or corruption
int[] intvals = new int[NumEntries];//will hold array of decoded values
bool valid = true;
for (int i = 0; i < NumEntries; i++)
{
valid &= int.TryParse(strarr[i], style, cult, out intvals[i]);//Try to decode values, and keep a running failure check
//valid &= ValidateMe(intvals[i]); //Example of another check to make sure the value is valid.
}
if (valid) //if all the data decoded and validated right, fill it in
{
Data0 = intvals[0];
Data1 = intvals[1];
}
return valid;
}

public override string ToString()
{
string s = string.Empty;
s += Data0.ToString(cult);
s += Delimiter;
s += Data1.ToString(cult);
return s;
}
}

3. Setup part.cfg like so:


// Kerbal Space Program - Part Config
//
//

// --- general parameters ---
name = KISConnect
module = Strut
author = Kellven

// --- asset parameters ---
mesh = ConnectV2.dae
scale = 1
texture = ConnectV2.png
specPower = 0.1
rimFalloff = 3
alphaCutoff = 0

// --- node definitions ---
node_attach = 0.0, 0.0, -0.10, 0.0, 0.0, 1.0


// --- editor parameters ---
cost = 1000
category = 1
subcategory = 0
title = KIS Connect v2
manufacturer = Kellvonic Imperial Spaceyard
description = Stick just one of these on your ship, and it will automagically make the parts connect better. Adding more than one will not increase connection betterness.

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

// --- standard part parameters ---
mass = 0.000001
dragModelType = default
maximum_drag = 0
minimum_drag = 0
angularDrag = 0
crashTolerance = 100
breakingForce = 1000
breakingTorque = 1000
maxTemp = 2900

fullExplosionPotential = 0.0
emptyExplosionPotential = 0.0

MODULE
{
name = MyModule

DuelingInts
{
inta = 1
intb = 2
}
}

Just used my own part as an example. It\'s a strut, shows up in strut section, and does nothing but read two ints from part.cfg into a data structure, but it should persist that structure through the various scenes. There might be a couple of errors in here still, I did a quick rewrite of my code into a general purpose example, but the general workaround idea does work in game.

Link to comment
Share on other sites

I tried the resource system with my solar panels and some power consuming modules. It works, but the tank gives/takes the resources only in case the modules can reach it through crossfeed capable parts. However for some systems like electricity it could be better to have access RCS way. Maybe there could be additional parameter that changes request logic implemented?

Link to comment
Share on other sites

Alchemist, I assume you\'re using the part.RequestResource thing. Have you tried modifying part.Resources[0].amount directly? I\'m curious as to whether or not that also modifies the return value of part.GetResourceMass.

The part.mass remains constant (as in the old drymass value), and I\'m not entirely sure whether or not the mass from resources is thrown into the gravity, CoM, and applyforce calculations, or is just ignored. I was going to add micro-resource tanks to my part-engines for air, oxidizer, and fuel, so they would work as a complete-engine-in-a-part instead of the multiple-parts-per-engine we seem to be having now. Probably not a big thing, but would be nice is the wetmass of the engine was used when applying the force, instead of the drymass, empty/full fuel tanks are something else entirely.

If there\'s an additional 'RequestMode' param added to the part.RequestResource, I\'d like to see an option to lock it to a specific part, so it would assume there\'s no crossfeed paths connected, and just pull from itself. To pull from a tank, you\'d have to call the method on a ref to the specific tank you wanted to draw from.

Link to comment
Share on other sites

That was interesting, it seems the [KSPField] persistence might still be having problems, but a separate static class with a static ConfigNode field will persist (Just doesn\'t get cleaned up I guess).

If that will work with a List<ConfigNode> also, can just call a static member inside OnLoad to check if the part is already in the list, and if not, add a new ConfigNode('PartName here') to the list. Then in OnStart call another member to return a node by 'PartName here', and send it to whatever Load(ConfigNode) members need it to re-initialize the part data. Eliminates the need to read/write from disk.

Link to comment
Share on other sites

  • 5 weeks later...

guys I\'m currently developing a system where all parts will consume a resource when the recive certain in flight messages, (like the ones to fire RCS, or move canards, or lower legs, or... (you get the idea)) I\'m havving to problems with this:

1. how do I override the default PartModual classes without overriding there existing contents, or how do I obtain the existing code so I can paste it in with what I want to add.

2. I have no idea what the message strings are, what are they? (all of them)

Link to comment
Share on other sites

  • 10 months later...

Since this thread is now in the correct forum, I'll go ahead and reply to it and get discussion started again.

I had a solution to the following, but it was bugged, so I'm scrapping it and starting over. Knowing how "official" modules handle this would be ideal.

1. How do modules like moduleEngines currently store/retrieve List<>s like moduleEngines' Propellants? Clearly it works for these modules, so I'd like to know how it's being done.

2. Secondarily, if I change a part's Resources in the editor, how do I get those changes to propagate to the version that spawns on the launchpad?

Here's my code, if it helps:

modularFuelTanks.cs

Edited by ialdabaoth
Link to comment
Share on other sites

  • 4 weeks later...
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...