Jump to content

The official unoffical "help a fellow plugin developer" thread


Recommended Posts

I'm looking through some sciencedata in a foreach loop, and for some reason or the other, it stopped working after docking.

foreach (ScienceData data in FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>().First().GetData()) // search through all the data we have collected onboard to check if we've collected it already
{
isincontainer = false;

Debug.Log(data.subjectID + currentScienceSubject.id);
if (currentScienceSubject.id == data.subjectID) // we already have this data in our container, so we skip it
{
#if DEBUG
Debug.Log("[For Science] Skipping: Found existing experiment data: " + currentScienceSubject.id);
isincontainer = true;
break;
#endif
}
}

I check in several places, but I don't think that FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>().First().GetData() is returning a value to iterate through after docking.

I can run a crew report and store it and it will start working. It is just wierd, and I figure its some obscurity of object references that i don't understand or something, but i tried it a bunch of different ways. I'm made a new arrays, tried calling the object before the loop.

To be clear, it works fine before docking, and while it is docked, but stops working after undocking on the vessel with no data (the data is transferred to one single container during docking). And debug log of the object says sciencedata as expected, but I get no errors whatsoever, so I don't know what else to inspect or how to try and figure it out.

Here's the relevant transfer code.


private void TransferScience()
{
if (ActiveContainer().GetActiveVesselDataCount() != ActiveContainer().GetScienceCount()) // only actually transfer if there is data to move to active container
{
#if DEBUG
Debug.Log("[For Science] Tranfering science to container.");
#endif
ActiveContainer().StoreData(GetExperimentList().Cast<IScienceDataContainer>().ToList(), true); // this is what actually moves the data to the active container
List<ModuleScienceContainer> containerstotransfer = GetContainerList(); // a temporary list of our containers
containerstotransfer.Remove(ActiveContainer()); // we need to remove the container we storing the data in because that would be wierd and buggy
ActiveContainer().StoreData(containerstotransfer.Cast<IScienceDataContainer>().ToList(), true); // now store all data from other containers
}
}

Yes, I talk to myself a lot in my code. :P

Edited by WaveFunctionP
Link to comment
Share on other sites

Hmmm, the first oddity that jumps out at me is this line:

foreach (ScienceData data in FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>().First().GetData())

You are only looking at the .GetData() in the "first" ModuleScienceContainer on the vessel.

Then when you dock, the "first" ModuleScienceContainer is now a different module in actuality but because your code has no way to look at different modules, it can no longer find the data.

Would need some testing to prove that, but it is a place to start.

D.

Link to comment
Share on other sites

Hmmm, the first oddity that jumps out at me is this line:

foreach (ScienceData data in FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>().First().GetData())

You are only looking at the .GetData() in the "first" ModuleScienceContainer on the vessel.

Then when you dock, the "first" ModuleScienceContainer is now a different module in actuality but because your code has no way to look at different modules, it can no longer find the data.

Would need some testing to prove that, but it is a place to start.

D.

I just tried iterating a foreach loop through all the containers and the result was the same behavior.


foreach (ModuleScienceContainer container in GetContainerList())
{
foreach (ScienceData data in container.GetData()) // search through all the data we have collected onboard to check if we've collected it already
{
isincontainer = false;
if (currentScienceSubject.id == data.subjectID) // we already have this data in our container, so we skip this experiment
{
#if DEBUG
Debug.Log("[For Science] Skipping: Found existing experiment data: " + currentScienceSubject.id);
isincontainer = true; // if this never goes off, this flag is used to actually fire off the experiment
break;
#endif
}
}
}

There are only two modulesciencecontainers between the two ships that I'm testing with. When I undock, the first one the list SHOULD be the only one on the list. I think. When I dock, it seems a random pod is chose to be at the top of the part tree and so it seem random which pod will get chosen, but still, only ever one pod get the data. And the pod with the data is the only one that continues to collect data properly. (wild guess inc!) I don't think that getdata()'s ScienceData[] object is getting reinitialized properly for the module after the data is removed, and I don't know how to force initialization.

edit:

Here's the error I get if I try to access the first data in the sciencedata array when the problem is occurring.

InvalidOperationException: Operation is not valid due to the current state of the object

at System.Linq.Enumerable.First[scienceData] (IEnumerable`1 source) [0x00000] in <filename unknown>:0

at ForScience.ForScience.RunScience () [0x00000] in <filename unknown>:0

at ForScience.ForScience.Update () [0x00000] in <filename unknown>:0

And ofcourse, it works fine as soon as i run a report and put some data into the container.

Edited by WaveFunctionP
Link to comment
Share on other sites

How does the GetContainerList method work?

It seems there is still a First() in there somewhere. You should probably never use First() on something that may have no elements, use FirstOrDefault() then check if the return is null.

Link to comment
Share on other sites

Hey DMagic,

I ended up doing it this way with some reference to the code you provided, thanks again.


private int GetActiveFactoryCount(Vessel v)
{
int c = 0;
var factories = from pref in v.protoVessel.protoPartSnapshots where pref.modules.Any(a => a.moduleName == "KSP_Factory") select pref;

if (factories.Count() != 0)
{
foreach (var p in factories)
{
ConfigNode node = p.modules[0].moduleValues;

if (node.HasValue("isActive"))
{
if (bool.Parse(node.GetValue("isActive")))
{
c++;
}
}
}
}

return c;
}

Link to comment
Share on other sites

How does the GetContainerList method work?

It seems there is still a First() in there somewhere. You should probably never use First() on something that may have no elements, use FirstOrDefault() then check if the return is null.

GetContainerList() just returns FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>().First().GetData()

I've tried FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>()[0].GetData() as well as checking for null.

I just ended up skipping then container check with getstoreddata() == 0, and flagging to run the experiments.

edit: The firstordefault thing is new to me, thanks for the tip.

edit edit: I just tried the firstordefault and i'm finally getting null errors. That works like a charm!

Edited by WaveFunctionP
Link to comment
Share on other sites

What you need is something like:


var containers = FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>();

foreach (var container in containers)
{
if (container == null)
continue;

var data = container.GetData();
}

containers will be an IEnumerable with all of the ModuleScienceContainers in the vessel. You can just check each of them for data.

Link to comment
Share on other sites

What you need is something like:


var containers = FlightGlobals.ActiveVessel.FindPartModulesImplementing<ModuleScienceContainer>();

foreach (var container in containers)
{
if (container == null)
continue;

var data = container.GetData();
}

containers will be an IEnumerable with all of the ModuleScienceContainers in the vessel. You can just check each of them for data.

I could probably clean my logic up quite a bit with switch, continue and break in general. But after finding out the issue with nulls, I've been able to simplify that particular portion quite a bit with methods that I've tried but failed at before. This is the full method now.


private void RunScience()
{
if (GetExperimentList() == null)
{
#if DEBUG
Debug.Log("[For Science] GetExperiment() was null.");
#endif
}
else
{
completedExperiments.Clear(); // clear our list of experiments that have been run so we start with a fresh list.

foreach (ModuleScienceExperiment currentExperiment in GetExperimentList())
{
var fixBiome = string.Empty; // some biomes don't have 4th string, so we just put an empty in to compare strings later
if (currentExperiment.experiment.BiomeIsRelevantWhile(currentSituation())) fixBiome = currentBiome();// for those that do, we add it to the string

// we build the current string to check against the available experiements
var currentScienceSubject = ResearchAndDevelopment.GetExperimentSubject(currentExperiment.experiment, currentSituation(), currentBody(), fixBiome);// << squad's fancy string builder. ikr!, with all this work, we pretty much did all the work already

// and we check the value to see if it is worth running
var currentScienceValue = ResearchAndDevelopment.GetScienceValue(currentExperiment.experiment.baseValue * currentExperiment.experiment.dataScale, currentScienceSubject);

#if DEBUG
Debug.Log("[For Science] Checking experiment: " + currentScienceSubject.id);
#endif

if (completedExperiments.Contains(currentExperiment)) // do we have the same experiment onboard?
{
#if DEBUG
Debug.Log("[For Science] Skipping: Experiment duplicate detected.");
#endif
}
else if (!currentExperiment.rerunnable & !IsScientistOnBoard()) // no cheating goo and materials here
{
#if DEBUG
Debug.Log("[For Science] Skipping: Experiment is not repeatable.");
#endif
}
else if (!currentExperiment.experiment.IsAvailableWhile(currentSituation(), currentBody())) // this experiement isn't available here so we skip it
{
#if DEBUG
Debug.Log("[For Science] Skipping: Experiment is not available for this situation/atmosphere.");
#endif
}
else if (currentScienceValue == 0) // this experiment has little value so we skip it
{
#if DEBUG
Debug.Log("[For Science] Skipping: No more science is available: ");
#endif
}
else
{
ScienceData newdata = new ScienceData(
currentExperiment.experiment.baseValue * currentScienceSubject.dataScale,
currentExperiment.xmitDataScalar,
0f,
currentScienceSubject.id,
currentScienceSubject.title
);

if (ActiveContainer() == null || !ActiveContainer().HasData(newdata))
{
#if DEBUG
Debug.Log("[For Science] Running experiment: " + currentScienceSubject.id);
#endif
//manually add data to avoid deployexperiment state issues
ActiveContainer().AddData(newdata);
//}
}

completedExperiments.Add(currentExperiment); // add this to the list of experiments to check for duplicates earlier in the runscience logic
}

}
}
}

Edited by WaveFunctionP
Link to comment
Share on other sites

I have no idea how it would be done code-wise, but you could set the vessel to be unowned (like asteroids). I think there's a parameter for that in VESSEL{} in persistent.sfs

thanks, but i've tried setting the vessel's discovery info to unowned (vessel.DiscoveryInfo.SetLevel(DiscoveryLevels.Unowned)), that doesn't help much, I can still switch to it via the map view or the key that switches between nearby vessels.

Link to comment
Share on other sites

I have noticed that in the new resource definitions, there is a value called "hsp." My only guess as to what it does is that it has something to do with the heat capacity of the part that contains it. If someone knows what it does, please correct me.

Also, I was looking at Ore.cfg in the Squad Resources folder, and I was wondering if there are other places to define resources. In the config, global definitions, planetary definitions, and biome definitions were made. Is there a way to do atmospheric ones?

And about the tech tree- I have heard that I can use ModuleManager to add new nodes to the tree, kind of like what techManager used to do. If this is right, how do I go about doing it?

Link to comment
Share on other sites

Apologies in advance if this has been posted before. I am fiddling with a parts pack and am getting this error when my part model is loaded, and am wondering if anyone has seen it before and knows the remedy:

File error:
Failed to read past end of stream.
at System.IO.BinaryReader.ReadByte () [0x00000] in <filename unknown>:0
at System.IO.BinaryReader.Read7BitEncodedInt () [0x00000] in <filename unknown>:0
at System.IO.BinaryReader.ReadString () [0x00000] in <filename unknown>:0
at A..ReadTextures (System.IO.BinaryReader br, UnityEngine.GameObject o) [0x00000] in <filename unknown>:0
at A..ReadChild (System.IO.BinaryReader br, UnityEngine.Transform parent) [0x00000] in <filename unknown>:0
at A.. (.UrlFile ) [0x00000] in <filename unknown>:0

(Filename: C:/buildslave/unity/build/artifacts/StandalonePlayerGenerated/UnityEngineDebug.cpp Line: 56)

Model load error in 'C:\Program Files (x86)\Kerbal Space Program\GameData\BAE\Parts\Engines\BAEengine5mB1\BAEengine5mB1.mu'

Thanks in advance!

EDIT: Nevermind, I figured it out: just don't try to use DDS textures when you're exporting things from Unity.

Edited by greystork
Link to comment
Share on other sites

How do I add a new piece of data to the list of things that the game stores in persistent.sfs? Specifically, I want to keep track of what the in-game time was when certain events occurred, so I'm looking for something like this:

CurrentGame.SetPersistentData("myTimestamp", Planetarium.GetUniversalTime()); //function I wish existed. Suppose UT is 100 here
UnityEngine.Debug.Log(CurrentGame.GetPersistentData("myTimestamp")); //would print "100"
// -- user saves the game --
// -- time passes --
CurrentGame.SetPersistentData("myTimestamp", Planetarium.GetUniversalTime()); //Suppose UT is 200 here
UnityEngine.Debug.Log(CurrentGame.GetPersistentData("myTimestamp")); //would print "200"
// -- user reloads last save --
UnityEngine.Debug.Log(CurrentGame.GetPersistentData("myTimestamp")); //would print "100"

Link to comment
Share on other sites

How do I add a new piece of data to the list of things that the game stores in persistent.sfs? Specifically, I want to keep track of what the in-game time was when certain events occurred

The way of doing this would depend on wether you're writing a custom part (inherit from PartModule) or a plugin (inherit from MonoBehaviour). But in any case, you can't control when the game will be saved, so you'll need to store that time in a variable first, and set the persistent data whenever saving occurs.

If you're in a plugin, you can do it something like this:


void Start()
{
GameEvents.onGameStateSave.Add(new EventData<ConfigNode>.OnEvent(OnSave));
GameEvents.onGameStateLoad.Add(new EventData<ConfigNode>.OnEvent(OnLoad));
//(...) other startup code here
}

private void OnSave(ConfigNode node)
{
node.RemoveValues("MyCustomDataKey");
node.AddValue("MyCustomDataKey", "my saved data here (serialized)");
}

private void OnLoad(ConfigNode node)
{
string[] vals = node.GetValues("MyCustomDataKey");
if (vals.Length > 0)
{
string loadedData = vals[0];
//do whatever you want with the loaded data here (deserialize, etc.)
}
}

If you're in a PartModule, you just need to have a KspField (preferably a string or a number type), and store your value in that.

Set it up like this:


[KSPField(guiActive = false, isPersistant = true)]
public string MyStoredData;

Link to comment
Share on other sites

If you're in a plugin, you can do it something like this:

THANK YOU! :D

Follow-up:

private void OnSave(ConfigNode node)
{
node.RemoveValues("MyCustomDataKey");
node.AddValue("MyCustomDataKey", "my saved data here (serialized)");
}

Am I correct in guessing that the "node" variable here is a ConfigNode containing the entire save file? If so, would it be best practice to do something like this instead?

private void OnSave(ConfigNode node)
{
node.RemoveValues("MyModName");
ConfigNode data = new ConfigNode();
data.name = "MyModName";
data.AddValue("K1", "V1");
data.AddValue("K2", "V2");
node.SetNode("MyModName", data);
}

And am I even using those functions properly? Friggin' shoddy documentation...

Edit: or maybe it would be better to do it this way?

private void OnSave(ConfigNode node)
{
ConfigNode data = new ConfigNode();
data.name = "MyModName";
data.AddValue("K1", "V1");
data.AddValue("K2", "V2");

node.RemoveNode("MyModName");
node.AddNode(data);
}

Edited by Adamantium9001
Link to comment
Share on other sites

@Adamantium9001: I guess your last example would be fine, but I haven't actually tried that kind of thing yet. You can test it easily though, and see if the save file contains what you want it to contain.

I hope I don't sound like I'm nagging, but does anyone have any answers to the questions I asked earlier? I feel overlooked...

I can't help you with that. I feel like this thread is the best place to get overlooked. If it's important, why don't you make a separate thread for it?

Link to comment
Share on other sites

@janecondo: I try to not post unless I have a decent answer, but maybe my speculation can at least point you in the right direction in this case.

hsp: No clue. To check if it is heat, grab a heat shield and see what it's RESOURCES are. Checking the PART.cfg might be enough, or you might have to use a pluging to monitor the RESROUCES on the part in-game in real time.

atmospheric resource: This is possible but no clue how to do it. I'd start by finding the definition for OXYGEN on Kerbin and see if that works for you. If OXYGEN is a special case and not really a resource, you'd probably be looking at a plug-in of some sort to return resource calls looking for atmosphere.

Tech tree: Yes, the tree in KSP 1.0 is now ModuleManager configurable. I have no clue on the syntax needed, but check out the Tech Tree changing mods, I assume those would have reference code you could use. (There are two or three of those mods out there.)

Hopefully that at least points you in the right direction.

I can't help you with that. I feel like this thread is the best place to get overlooked. If it's important, why don't you make a separate thread for it?

This thread is for smaller questions that can be answered in a post, you are unlikely to get more then a couple line answer. For janecondo's questions, this is probably the right place for them.

If you have a more complex question, especially once you start posting example code, that is probably worth starting a thread for. It varies from person to person a lot of course, but that is how I see it.

D.

Edited by Diazo
Link to comment
Share on other sites

Hey all,

When leaving focus of a ship that's consuming/generating resources, where is the data stored when KSP processes background consumption. Example would be leaving a harvester mining for ore and changing back to the ship later, noting that the resource has been generated further.

Link to comment
Share on other sites

Correct, KSP does not process background resource changes. Some mods like TACLS (and IIRC the new stock ISRU) will calculate time-since-you-were-last-here and produce/consume resources all at once based on that time, but for as-time-passes background you need a mod like Background Processing.

Link to comment
Share on other sites

I'm developing a plugin to help plan flights by solar sail, and I could use some help. The main thread is here: http://forum.kerbalspaceprogram.com/threads/119579-WIP-1-0-2-SolarSailNavigator-v1-0-1-alpha

I am currently showing a preview in map mode of the future trajectory using the LineRender, but I'd like something nicer - and constant screen width - like the normal orbit lines in map mode. Is there a good, clear example showing how to do this?

Additionally, I want to render small meshes (e.g. squares) to show the sail attitude along the trajectory in map mode, but again, I'm having trouble finding some clear examples. Does anyone have suggestions?

Thanks.

Link to comment
Share on other sites

Does anyone have any idea for an easy way to check if service/cargo bay doors are open/closed?

For landing legs & solar panels I just used FindPartModulesImplementing to find each part and then looped through checking the state (legState/StateString), but <ModuleCargoBay> doesn't seem to have any state/status/etc value I can find.

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