Jump to content

The official unoffical "help a fellow plugin developer" thread


Recommended Posts

Just wanted to give everyone a heads up about the Part.FlightID value.

It seems to be the field that is generally used to uniquely identify a part and while this is true almost all of the time, I just wanted to warn people that it is not guaranteed unique.

I programmed my mod under the assumption that it was guaranteed unique and while it seemed to be true for months, I just recently had a bug report filed where the cause of my mod crashing was the fact that two parts had the same FlightID. (The player uploaded his persistent.sfs and I double-checked this myself.)

So while rare, make sure to add an exception clause to your code for what will happen if two parts have the same flightID. It is rare, but it does happen.

I suppose I could also ask if anyone has a better idea on how to uniquely identify parts? FlightID is what I have always been told but is there another method out there?

D.

Link to comment
Share on other sites

I don't think so.

The persistence file looks normal (no corruption) and it is two different launched vessels as per the player's description of what he was doing. (In that KSP has not cloned the same vessel somehow.)

One thing to keep in mind is that this is rare. I have been using the FlightID extensively in my action groups mod for months as a unique identifier and this is the first report of this happening.

Also of note is that the parts are on two different vessels. It is quite possible that using a combination of vessel id (the GUID string) and part.FlightID would work as a guaranteed unique identifier, or that the combination of those two values is random enough that it is close enough to unique for our purposes.

D.

Link to comment
Share on other sites

Being a Guid there are 2^128 different possible FlightIDs, and I don't know if you know how big 2^128 is, but it's really huge. It's about 3.4x10^38. So huge, that if you created a different Guids on an 8 core computer running at 4GHz (meaning 32 billion new Guids per second), it would take you about 25.5 billion times the current age of the universe to create them all. The probability of two FlightIDs being the same is (1/(2^128))^2, or 8.6x10^-78. It's really small. The probability of two parts on the same persistence file containing 1000 parts being the same is 4.3x10^-72. Really, it's far too low to even consider.

All of this to say: keep using FlightID and tell the person who sent you this persistence file he should be popping some champagne about this. He's special.

Edited by stupid_chris
Link to comment
Share on other sites

Just a point of order, it is the Vessel ID (not the FlightID) that is a GUID object. The Part.FlightID is a uint of which there is 4,294,967,295 possibilities. (Assuming SQUAD uses the entire range, I don't know if we can check that.)

It is still a really low possibility that two parts in the same persistence file would have the same FlightID so I'm not sure it is worth coding for this situation, this was more for awareness so that if you are trying to track down an error where data seems to be jumping around, check if your mod is processing different parts with the same FlightIDs and crossing data over between the two parts.

However, as the Vessel ID is a GUID and there are a lot fewer parts on a vessel than in an entire persistence file, if you design your mod so that you work with vessels via the vessel ID and then only the parts within that vessel via the FlightID, you drastically reduce your chances of running into this.

I'm not up on my statistics, but I think on the uint FlightID and 1000 parts in a persistence file, it is roughly a 1 in 4 million chance that two parts have the same FlightID. Still rare but not as unlikely as if it were a GUID. (Assuming SQUAD uses the entire range of the uint object.)

D.

edit: Sneaky DMagic, sneaky.

Link to comment
Share on other sites

Well Squad is remarquably dumb.

And no, actually, the chance of two parts having the same FlightID would then be around one over thirty six trillions (2.707x10^-14). Still too big to bother about it. But to make sure of it you could store both VesselID and PartID, and then check if both are identical. That'd do it.

Link to comment
Share on other sites

I'm writing a plugin for a part and was wondering how to get the amount of electricity (or any resource for that matter) in a current vessel. When called, the function will find out how much electric charge is in the current vessel, and return with an integer.

Here's a pseudo-code example of what I'm looking for:


[B]int[/B] electricityAmount = vessel.getElectricCharge();

Link to comment
Share on other sites

You want the total resource or how much is available for a part ?

Total :


PartResourceDefinition definition = PartResourceLibrary.Instance.GetDefinition("ElectricCharge");
List<Part> parts = (HighLogic.LoadedSceneIsEditor ? EditorLogic.fetch.ship.parts : vessel.parts);

double electricityAmount = 0;
foreach (Part p in parts)
{
r = p.Resources.Get(definition.id);
if (r != null)
electricityAmount += r.amount;
}

Available for a part :


PartResourceDefinition definition = PartResourceLibrary.Instance.GetDefinition("ElectricCharge");
Vessel.ActiveResource result = vessel.GetActiveResource(definition);
double electricityAmount = result.amount;

Link to comment
Share on other sites

Starting from the KSPApiExtensions FloatEdit tweakable, I wrote my own tweakable for TweakScale and fail to separate it.

The main dependency to KAE are these two lines from UIPartActionsExtended.cs, which somehow lead to the prefab registration.

controller.fieldPrefabs.Add(UIPartActionScaleEdit.CreateTemplate());
fieldPrefabTypes.Add(typeof(UI_ScaleEdit));

The first line seems straightforward, since I do not need KAE to reach the controller object. However I do not get what the second one does, other than adding something to a local list that is never used afterwards. I get the following error in the log:

ItemPrefab for control type 'UI_ScaleEdit' not found.

So it is missing some sort of registration for the type/prefab. Can anyone tell me how to do this? If it helps, here are the code versions I am talking about (trying to move UIPartActionScaleEdit.cs):

https://github.com/pellinor0/TweakScale/tree/dev/

https://github.com/pellinor0/KSPAPIExtensions/

EDIT: Solved! I managed to copy the registration code and sort out the things which should not be done twice.

Edited by pellinor
Link to comment
Share on other sites

  • 2 weeks later...

Back with what is likely another dumb question: How do you prorammatically unlock a tech node and all associated parts? Here's my existing code:


public static void UnlockNode(string techID)
{
foreach (RDTech r in AssetBase.RnDTechTree.GetTreeTechs())
{
if (r.techID == techID)
{
r.state = RDTech.State.Available;
ResearchAndDevelopment.Instance.SetTechState(techID, ResearchAndDevelopment.Instance.GetTechState(techID));
}
}
}

This marginally works. If I activate this with a button, it first entirely kills the window with the button so THAT'S an issue, but it also seems to unlock the node when you go into Research and Development. However, the node shows the parts are available for purchase instead of entirely unlocked and the panel for Research and Development does not go away when exited.

It broke rather spectacularly, and I'd love to know how to do this properly.

Link to comment
Share on other sites

Hi, I'm a beginner to modding

I have experience in python and javascript along with some experiance in c++ and objective C.

I've recently been learning C#, and for sure have all the basics down.

As far as I know I have my IDE set up correctly.

How do I set up a simple gui, say, on the KSC Screen.

Thanks,

Link to comment
Share on other sites

Useful references for starting out (including setting up the required references and making a simple part module)

To get gui elements to show at the space center scene, which I assume is what you meant by the KSC screen


//using statements, namespace, etc.

[KSPAddon(KSPAddon.Startup.SpaceCenter, false)] // this plugin will be run every time we enter the space center scene
public class IDrawABox : Monobehaviour
{
public void OnGUI
{
GUI.Box(new Rect(100,100,100,100), "I'm a Box");
}
}

Look up the Unity GUI / GUILayout documentation for how to make a useful GUI

Link to comment
Share on other sites

I'm finding myself posting too often in here, I think. Next question: GameEvents.OnPartPurchased. Not sure I'm actually using this correctly, but it does not seem to be firing when I test my code.


[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class SpaceRaceMain : MonoBehaviour
{
public void Start()
{
if (Instance != null)
{
Destroy(Instance);
}
Instance = this;
Debug.Log("SpaceRace: Adding events");
GameEvents.OnPartPurchased.Add(AddEngineeringProject);
}
public static void AddEngineeringProject(AvailablePart part)
{
Debug.Log("Firing AddEngineeringProject");
}
}

I get the "adding events" entry in the log, so I know Start() is being called as expected. The other entry doesn't get written when I go into RnD and buy a part.

Link to comment
Share on other sites

Looking at that, my first thought is that the AddEngineeringProject can't be static.

That is the only difference I can see between your example code and where I'm using GameEvents myself.

The only other possibility I can think of is that the PartPurchased event is not implemented where you think it is. Looking at the name, I would assume it fires when you purchase (not research) a part on the R&D screen, but has anyone actually confirmed that?

D.

Link to comment
Share on other sites

Looking at that, my first thought is that the AddEngineeringProject can't be static.

That is the only difference I can see between your example code and where I'm using GameEvents myself.

The only other possibility I can think of is that the PartPurchased event is not implemented where you think it is. Looking at the name, I would assume it fires when you purchase (not research) a part on the R&D screen, but has anyone actually confirmed that?

D.

Just tried it, you're right. It cannot be static. It does seem to fire when you actually purchase the part in the RnD screen, I'm going to see if it fires for each part in standard Career mode as well. I think it will.

EDIT: Oh, and thank you!

Link to comment
Share on other sites

That does not look that different from my own code too

The R&D screen is actually in the SpaceCentrer scene, so the only thing I see too is the static but I m quite sure I did static event before ...

To test did you unlock a tech node or did you purchase a part ?

Edit : ninjaed :)

Link to comment
Share on other sites

Back with another one, and it's a doozy:

I'm trying to set tech nodes to Unavailable (or Available) during several points. I'm getting errors that prevent leaving the Research Center, or entering it depending on where we came into the Space Center from.

Here's the thing: it only happens for nodes that were loaded from a file, but not for any nodes freshly researched. Those shut down just fine, and the system works fine until the next save/load cycle. This includes entering/leaving the tracking center, etc.

The code is all over the place as it's getting rather large, but I have narrowed the problem down.

Loading function, called from OnLoad():


public void LoadData()
{
researchList.Clear();
Debug.Log("SpaceRace: Calling LoadData");
if (File.Exists(SpaceRaceMain.spaceracefolder + "RnDList"))
{
using (StreamReader reader = new StreamReader(SpaceRaceMain.spaceracefolder + "RnDList"))
{
string line = "";
while ((line = reader.ReadLine()) != null)
{
researchList.Add(line);
}
Debug.Log(String.Format("SpaceRace: Loaded {0} lines into researchList", researchList.Count));
reader.Close();
}
}


foreach (string line in researchList)
{
string[] processor = line.Split(';');
SpaceRaceMain.Instance.researchProjects.Clear();
ProtoCrewMember crew = HighLogic.CurrentGame.CrewRoster.Crew.FirstOrDefault(c => c.name == processor[2]);
ScienceProject project = new ScienceProject();
project.node = ResearchAndDevelopment.Instance.GetTechState(processor[1]);
project.UTTimeCompleted = Convert.ToDouble(processor[3]);
project.KerbalAssigned = processor[2];
project.TechNode = processor[1];
project.TechName = processor[0];
project.Cost = Convert.ToInt32(processor[5]);
project.InProgress = Boolean.Parse(processor[4]);
if (!project.CheckList())
{
Debug.Log("SpaceRace: Loading science project to list from file.");
Debug.Log("SpaceRace: Project " + project.TechName + " loaded. Kerbal assigned: " + crew.name + ".");
SpaceRaceMain.Instance.researchProjects.Add(project);
crew.rosterStatus = ProtoCrewMember.RosterStatus.Assigned;
project.Lock();
}
}


public class ScienceProject
{
public string KerbalAssigned { get; set; } //Kerbal assigned to project. Kerbal is unavailable during project time.
public double UTTimeCompleted { get; set; } //Exact time project will complete and the part and Kerbal assigned will both become available.
public bool InProgress { get; set; } //Boolean for progress status.
public string TechNode { get; set; } //Tech node internal name
public string TechName { get; set; }
public ProtoTechNode node = new ProtoTechNode();
public int Cost { get; set; }
public void Unlock()
{
node.state = RDTech.State.Available;
ResearchAndDevelopment.Instance.SetTechState(TechNode, node);
}
public void Lock()
{
Debug.Log(node.techID);
node.state = RDTech.State.Unavailable;
ResearchAndDevelopment.Instance.SetTechState(TechNode, node);
Debug.Log("SpaceRace: Locked project.");
}
public bool CheckList()
{
return SpaceRaceMain.Instance.researchProjects.FirstOrDefault(t => t.TechNode == this.TechNode) != null;
}
}

I get the dreaded "object reference not set to an instance of an object" error any time Lock is called, or when processing the same code directly in the calling functions. The same goes for Unlock as well. I suspect it's how I'm accessing the node states, but I get the same error with either line commented out.

EDIT: I should make it clear: researchProjects is a public ScienceProjects list.

public List<ScienceProject> researchProjects = new List<ScienceProject>();

Link to comment
Share on other sites

I've got this narrowed further now, it seems that's failing before Lock() is called, when I go to recreate the object. Specifically, when the ProtoTechNode is being recreated:


public override void OnLoad(ConfigNode node)
{
string line = "basicRocketry;Basic Rocketry;5";
string[] processor = line.Split(';');
project.node = new ProtoTechNode();

project.node = ResearchAndDevelopment.Instance.GetTechState(processor[0]);
Debug.Logger("Added node " + project.node.techID);
SRSProjects.researchProjects.Add(project);
SRSMain.Instance.Logger("Added project " + project.techName + " to list");
}
}

I don't know why this is failing. Effectively the same code is called at on GameEvents.OnTechnologyResearched under Start(), with no errors.

Link to comment
Share on other sites

My first thought is that I think OnLoad runs before Start doesn't it?

In my (limited) understanding of how unity works, Awake() runs when an object is created, then OnLoad runs to load values, (insert other methods I'm forgetting about here), then once everything is ready, Start runs as the first Update frame, then Update and FixedUpdate take over.

So are you referencing something that is not created until the Start method runs maybe?

All the help I can offer, I've not done anything like what you are trying.

D.

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