Cephei

The official unoffical "help a fellow plugin developer" thread

Recommended Posts

5 hours ago, wile1411 said:

I

I'd also like to know how to spawn a part to a part in scene. From my mucking around I've been using the following to kill a via a button, however it also kills anything that was surface attached as well. Anything node attached becomes debris. Is there something that will keep surface attached items?


thisPart.part.Die();

 

I think you need to part.Decouple every part you want to keep. You might also want to rename decoupled parts. That's how KAS/KIS does it.

However, if you want to remove part in middle but keep ship intact, part coupling will be necessary, and that is overly complicated.

I picked different approach in my project.

  1. Save game
  2. Edit .sfs
  3. Save modified .sfs
  4. Load game

(mods can save and load saves)

Of course there are other problems, as .sfs format can hardly be called format. More than half of its content does nothing. I am making little ugly .sfs editor library. As of now I can recursively delete parts from part tree with it. Sometimes modified .sfs can even be loaded correctly in game :wink:

Using that sfs library I managed to delete parts from cargo bay inner nodes (named top2 and bottom2), sum their mass (I still need to find way to get weight of resources in part), add/remove resources and so on. Oh, I also can .Open() .Close() on cargo bay parts :)

After playing with first version of my lib I learned enough to make second edition, which should be able to delete parts without breaking part tree. Which is not a tree. It is list, with references to other item. And of course references are in various forms. Node attachments are both ways (parent<->child) while surface attachments are one way (child->parent). Oh, and of course there are no part id's specified in part itself, it is just index of PART entry in VESSEL entry in save file, so order is important (looks like someone hit graph with sledgehammer few dozen times to flatten it). All this trash means that removing part requires recalculating id's of every part in order to attachment node info. Ugh.

Adding parts will require calculating position, but it does not seem hard as it is relative to root or parent part if I am not mistaken.

If you are interested I can share my proof of concept code. Proper release must wait for code I won't be ashamed of.

Share this post


Link to post
Share on other sites
32 minutes ago, PT said:

Adding parts will require calculating position, but it does not seem hard as it is relative to root or parent part if I am not mistaken.

If you are interested I can share my proof of concept code. Proper release must wait for code I won't be ashamed of.

If you do have a proof of concept bit of code, it'd be great to have a look.

Is all the clean up necessary only if you want to keep a tree intact when pruning from the middle? I figures with craft always being broken via various impacts, that killing off a parts should be within scope of the default process.

I'm looking to use a part I've built and add a part to that known piece as an "end node". Depending on when the player does down the track, I plan to remove it later using the part.die command. I was hoping with the information I already know about my parts location and orientation, spawning in a part attached to it could be a matter of working out the location if reference to the known part rather than from the root part.

 

Share this post


Link to post
Share on other sites
8 minutes ago, wile1411 said:

If you do have a proof of concept bit of code, it'd be great to have a look.

Is all the clean up necessary only if you want to keep a tree intact when pruning from the middle? I figures with craft always being broken via various impacts, that killing off a parts should be within scope of the default process.

I'm looking to use a part I've built and add a part to that known piece as an "end node". Depending on when the player does down the track, I plan to remove it later using the part.die command. I was hoping with the information I already know about my parts location and orientation, spawning in a part attached to it could be a matter of working out the location if reference to the known part rather than from the root part.

 

Part tree in game is almost proper tree. You have Part->AttachmentNode->Part, no id's at all. However editing this tree is horror from what I saw in KAS/KIS code. Check out CreatePart (line 306) in KIS_Sharded.cs. It appears KIS is chasing targeted attachment node with new part until game allows coupling to happen. Part tree will get more and more convulted with each docking, decoupling or KAS/KIS operation. And then you risk awakening some sort of kraken when physics kicks in again for changed vessel.

In .sfs save file part tree is flattened to list, just open it in notepad and search for your VESSEL by name ( in my lib it is root.Find("GAME\\FLIGHTSTATE").Select("VESSEL","name","<vessel name")[0].Select("PART"), or you can just use LINQ instead of those ugly constructs, but LINQ/foreach in Mono leaves tons of garbage in memory ). In PART entries you have "parent = <parent_id>" , "attN = <node name>, <other_part_id>" and "surfaceN = surface, <parent_id>" (I forgot exact keys for surface attachment). "parent" is most important, required to build proper part tree when save is loaded, with incorrect values you get uncontrollable rocket. Proper values "attN" are needed only for rocket to retain its shape. With incorrect part id in "attN"  I got, for example, quad-symmetry radial Thuds to decouple from rocket (zero force) and sit all in same spot (in place where I clicked in VAB).

Cleaning will always be necessary when editing .sfs, because removing part in middle changes id's of every part after it. So you need to update parent id in children and its attachment nodes as well as child id in parent attachment nodes. Not the best format I had to work with. 

There is problem with finding part you clicked on in .sfs. There is no unique id to use, we would need to first add custom field with our own unique id to part you clicked, then find it in .sfs. From what i understand it should be doable. If all else fails, it could be done by custom resource (hide id in amount)..

I'll upload my code later today, when I'll be at my computer.

Share this post


Link to post
Share on other sites
5 hours ago, PT said:

I picked different approach in my project.

  1. Save game
  2. Edit .sfs
  3. Save modified .sfs
  4. Load game

(mods can save and load saves)

*cringe*

Decoupling is easy. ( part.decouple(0)  or part.parent.decouple(0) depending on where you want to cut). Doing it by hand is a bit more complex but not that much and has been done in other mods.

There are many mods that does it and many other that adds part (KIS and Extraplanetary-Launchpads being the two that do the most).

So why use a complex solution that may break horribly with future update and could easily corrupt save ???

 

Share this post


Link to post
Share on other sites
15 minutes ago, sarbian said:

*cringe*

Decoupling is easy. ( part.decouple(0)  or part.parent.decouple(0) depending on where you want to cut). Doing it by hand is a bit more complex but not that much and has been done in other mods.

There are many mods that does it and many other that adds part (KIS and Extraplanetary-Launchpads being the two that do the most).

So why use a complex solution that may break horribly with future update and could easily corrupt save ???

 

I do not want to decouple. I want to remove many parts in many places. And them add parts back again when I will feel like doing so.

As I wrote earlier, I did read KIS sources where adding parts is complex solution, working in complex and weird api of KSP intestines, where child is not certain to be child of its parent.

Haven't thought about stealing ideas from launchpad mod.

Share this post


Link to post
Share on other sites

I'm looking to programmatically identify the celestial body (CB) a science experiment is related to. Note, I have the Experiment data first - the CB isn't known in this context. Ie the player isn't landed on the body the experiment came from.

Looking at the resulting data object from ModuleScienceContainer, I can see there is a SubjectID. From that I found the function that specifically uses subjectID to get some information about the science status of a given experiment. However I can't find any direct reference to which celestial body it is related to.
Example:

SubjectID = "surfaceSample@KerbinSrfLandedLaunchPad"
ScienceSubject scisub;
scisub = ResearchAndDevelopment.GetSubjectByID(subjectID);
print(scisub.title);

This outputs the title of the experiment to the debug screen - in the example this would be: "Surface Sample from LaunchPad" but does contain the name as a property or within the text. There is also no property for "body" that I found.

Only thing I did see was ScienceSubject.IsFromBody(cb) - Am I safe to say there is no direct reference and it has to be looked up via this method anytime I want the CB info/name?

EDIT: 

Received an answer from @ShotgunNinja via msg (Thank you!) - the answer was surprisingly simple - even how stock does it.

20 hours ago, ShotgunNinja said:

The science system is a bit of a mess. The key to understand it is the 'subject_id'. You ask "where is the body stored?" and the answer is "the subject_id". If you'd ask "where is the situation stored?" the answer will be the same.

So, how does the stock game recognize when some science data is from a particular body? What is ScienceSubject.IsFromBody(db) doing? A substring search like this:


bool IsFromBody(CelestialBody cb)
{
  return this.id.Contains(cb.bodyName);
}

 

Edited by wile1411
added answer that was msg'ed to me

Share this post


Link to post
Share on other sites

Is there any way to alter the range of docking port magnetics?

Please quote or mention me if you answer.

Edited by RocketSquid

Share this post


Link to post
Share on other sites
26 minutes ago, RocketSquid said:

Is there any way to alter the range of docking port magnetics?

Please quote or mention me if you answer.

Yes, there's a field acquireRange on ModuleDockingNode which defaults to 0.5

I don't think this strictly has to do with plugin development though

Share this post


Link to post
Share on other sites
42 minutes ago, blowfish said:

Yes, there's a field acquireRange on ModuleDockingNode which defaults to 0.5

I don't think this strictly has to do with plugin development though

I'm working on a plugin for docking port tractor beams.

  • Like 1

Share this post


Link to post
Share on other sites

Getting an engine normalized thrust.

I get the engine this way:

                    if (part.Modules.OfType<ModuleEnginesFX>().Count() == 0)
                    {
                        if (part.Modules.OfType<ModuleEngines>().Count() == 0)
                        {
                            return;
                        }
                        else
                        {
                            myEngine = part.Modules.OfType<ModuleEngines>().FirstOrDefault();
                        }
                    }
                    else
                    {
                            myEngine = part.Modules.OfType<ModuleEnginesFX>().FirstOrDefault();
                    }

When I do this:

                        MPLog.Writelog("[Maritime Pack] (aniEngine) normalized Thrust " + myEngine.normalizedThrustOutput);
                        MPLog.Writelog("[Maritime Pack] (aniEngine) normalized Output " + myEngine.normalizedOutput);
                        MPLog.Writelog("[Maritime Pack] (aniEngine) Current Thrust " + myEngine.finalThrust);
                        MPLog.Writelog("[Maritime Pack] (aniEngine) Max Thrust " + myEngine.GetMaxThrust());
                        MPLog.Writelog("[Maritime Pack] (aniEngine) Current Throttle " + myEngine.currentThrottle);

Everything except GetMaxThrust and currentThrottle constantly read 0 

I'm hoping to get a normalizedThrust, which I assume reads 0-1.

Edited by Fengist

Share this post


Link to post
Share on other sites

@Fengist not sure about your issue, but you could simplify that code quite a bit.  part.Modules.OfType<ModuleEngines> will give you both ModuleEngines and ModuleEnginesFX, since ModuleEnginesFX derives from ModuleEngines.  Also probably easier to check whether FirstOrDefault gave you anything rather than looking at the count as well.  Also, part has a method for doing this:

myEngine = part.FindModuleImplementing<ModuleEngines>();

if (myEngine == null) return;

 

Share this post


Link to post
Share on other sites
7 minutes ago, blowfish said:

@Fengist not sure about your issue, but you could simplify that code quite a bit.  part.Modules.OfType<ModuleEngines> will give you both ModuleEngines and ModuleEnginesFX, since ModuleEnginesFX derives from ModuleEngines.  Also probably easier to check whether FirstOrDefault gave you anything rather than looking at the count as well.  Also, part has a method for doing this:


myEngine = part.FindModuleImplementing<ModuleEngines>();

if (myEngine == null) return;

 

Yea, I was trying different things to find out why I wasn't getting data from the engine.  That's pretty much how I originally had it, assuming engine would inherit the data from engineFX

I know why now, my own utter stupidity.

I was doing this just before the log output:

(partType == "Engine" && !myEngine.EngineIgnited)

So, the only time it showed any data was when the engine was shut down.

Duh!

Thanks :wink:

Share this post


Link to post
Share on other sites
2 hours ago, RocketSquid said:

What version of unity does KSP currently use?

That depends on what version of KSP you are referring to.
If you go to the output_log.txt you will find a line like this:
Initialize engine version: 5.2.4f1


That is the Unity version number (in this case KSP1.1.3)

Share this post


Link to post
Share on other sites

I have a method in a plugin I'm working on that will go through a list of vessels and return a list of vessels that implement my plugin.  I've been trying to troubleshoot the problem and it looks like the list is not returning correctly.  The list as Start() sees it has 0 elements, instead of the number of ships (2).  I've added some log writes at different points.  The code I am running:

namespace CivilianPopulationRevamp
{
  [KSPAddon (KSPAddon.Startup.Flight, false)]
  public class BackgroundGrowth : CivilianPopulationRegulator
  {
    List<Vessel> listCivilianVessels = new List<Vessel> ();

    public void Start ()
    {
      Debug.Log (debuggingClass.modName + "Running OnStart in BackgroundGrowth!");
      List<Vessel> listOfVessels = FlightGlobals.Vessels;
      listCivilianVessels = getCivilianVessels (listOfVessels);
      Debug.Log (debuggingClass.modName + listCivilianVessels.Count);
      foreach (Vessel civvieVessel in listCivilianVessels) {
        Debug.Log (debuggingClass.modName + civvieVessel.vesselName);
      }
    }//end start
      
      
    public List<Vessel> getCivilianVessels (List<Vessel> listOfVessels)
    {
      bool hasCivModule = false;//used to flag whether or not my plugin is on that craft
      List<Vessel> civilianDockList = new List<Vessel> ();//list of civilian vessels to be returned

      foreach (Vessel sumVes in listOfVessels) {
        hasCivModule = false;
        if (!sumVes.isActiveVessel && !sumVes.loaded) {
          var partSnapshots = sumVes.protoVessel.protoPartSnapshots;
          foreach (var partSnapshot in partSnapshots) {
            var protoPartModules = partSnapshot.modules;
            foreach (var protoPartModule in protoPartModules) {
              if (protoPartModule.moduleName == "CivilianDockGrowth") {
                hasCivModule = true;
              }//end if plugin name matches
            }//end foreach ProtoPartModuleSnapshot
          }//end foreach protoPartSnapshot
        }//end if vessel active
        if (hasCivModule) {
          listCivilianVessels.Add (sumVes);
          Debug.Log (debuggingClass.modName + "Added " + sumVes.vesselName + " to civilian list, implementing CivilianDockGrowth");
        }
      }//end foreach Vessel
      return civilianDockList;
    }//end getCivilianVessels
  }//end class
}//end namespace

This returns the following in the KSP log:

[LOG 17:08:25.604] [CivilianPop Revamp]Running OnStart in BackgroundGrowth!
[LOG 17:08:25.605] [CivilianPop Revamp]Added Untitled Space Craft to civilian list, implementing CivilianDockGrowth
[LOG 17:08:25.605] [CivilianPop Revamp]Added ActiveTestVessel1 to civilian list, implementing CivilianDockGrowth
[LOG 17:08:25.605] [CivilianPop Revamp]0
[LOG 17:08:25.615] [CivilianPop Revamp]civieDock (Untitled Space Craft) is running OnStart()!

Note:  The last line is from another portion of code, meaning that the above Start() method has finished executing.  Has anyone seen something similar to this?  The searching I've done says that the above (return variable_name) is the correct way to return an object.  Is there something I'm missing (like a need to return by reference)?

Edited by Tralfagar

Share this post


Link to post
Share on other sites

You Add to listCivilianVessels but you return civilianDockList.

Share this post


Link to post
Share on other sites

Is it possible to make an opaque Gui like the KSPedia, or Astronaut complex Gui? I want it to be open and closable via the appLauncher from the KSC. I thought it would be possible with this new KSPAsset system, but I just can't get my head round how to do it, and from what I've read, it's not easy to get gui's made in this way to be dynamic, which is something I'm looking for. In an ideal world, I'd actually like to add it to the Administration building as another tab, but I'm guessing this is impossible, or more effort that it's worth.

Any help would be greatly appreciated.

Share this post


Link to post
Share on other sites

@sarbian Thank you!  Now I feel like a real dunce.:confused:

Looking back, I'm trying to make my code more readable/less resource intensive.  But I'm having trouble figuring out if what I want to do is possible in C#.  It probably is, but I don't know the technical term for it.

Right now, I am going through a list of each inactive vessel and selecting each that has one of my part modules.  Form there, I cycle through each part to find the specific part implementing my mod.  Then I use the part snapshot (another array nested in the above part) to obtain the resourceSnapshot.  This is a lot of lists to go through in order to find something that should not be changing much (this section only runs on vessels that are unloaded).  I would like to save that memory location in a new class so I can directly modify it without having to set up several nested foreach loops.  In C/C++, I'd use pointers to keep that information and bundle them in a struct.  From there, if I want to change the value, I need only pass the pointer a new value.

So instead of this in the start AND update section of code:

foreach (Vessel civilianVessel in listCivilianVessels) {
  bool appliedVesselChanges = false;
  var civilianPartSnapshots = civilianVessel.protoVessel.protoPartSnapshots;
  foreach (ProtoPartSnapshot civilianPartSnapshot in civilianPartSnapshots) {
    foreach (ProtoPartModuleSnapshot civilianPartSnapshotModule in civilianPartSnapshot.modules) {
      Debug.Log (debuggingClass.modName + civilianPartSnapshotModule.moduleName);
      if (civilianPartSnapshotModule.moduleName == "populationGrowthModifier" && !appliedVesselChanges) {
        var civilianResourceList = civilianPartSnapshot.resources;
        foreach (ProtoPartResourceSnapshot civilianResource in civilianResourceList) {
          //apply logic to increment resources here
          appliedVesselChanges = true;
        }
      }
    }
  }
}

The above would be run once to collect information and log it into an array of classes My code could look like this:

public class myBackgroundClass
{
  List<UnloadedVesselClass> listUnloadedVessels = new List<UnloadedVesselClass>();

  OnStart(startState state)
  {
    //apply logic to find appropriate vessel resource, as above
    var snapshotResourceAmount//one of the variables I want to edit within the snapshot
    var newResourceInfo = new UnloadedVesselClass(snapshotResourceAmount);
    listUnloadedVessels.Add(newResourceInfo);
  }
  
  OnUpdate()
  {
    foreach(civilianVessel in listUnloadedVessels){
    //Apply logic to increment resources here; i.e. civilianVesel.resourceAmount++
  }
}

I've tried using the "ref" keyword, but it does not seem to work with Lists.  When I try using an intermediate variable, I store the location of *that* variable instead of the member of the list.  Does anyone have any ideas?

Share this post


Link to post
Share on other sites
12 hours ago, Cramblesquash said:

Is it possible to make an opaque Gui like the KSPedia, or Astronaut complex Gui? I want it to be open and closable via the appLauncher from the KSC. I thought it would be possible with this new KSPAsset system, but I just can't get my head round how to do it, and from what I've read, it's not easy to get gui's made in this way to be dynamic, which is something I'm looking for. In an ideal world, I'd actually like to add it to the Administration building as another tab, but I'm guessing this is impossible, or more effort that it's worth.

Any help would be greatly appreciated.

It is possible but as you said it is not easy. As for the "not easy to be dynamic" part it depends what you call dynamics. If it s mostly button and text you re fine. Design your UI, use different names for each item and then you can load it as a bundle (see here for inspiration)

Share this post


Link to post
Share on other sites
5 hours ago, sarbian said:

It is possible but as you said it is not easy. As for the "not easy to be dynamic" part it depends what you call dynamics. If it s mostly button and text you re fine. Design your UI, use different names for each item and then you can load it as a bundle (see here for inspiration)

As Sarbian says, if dynamic is just adding set piece elements at run time it's relatively easy to do (you just need multiple prefabs). I wouldn't neccesarily limit that to buttons and text either. It's all just a matter of finding the correct component (which Unity provides plenty of options to do) and setting it up the way you want / adding applicable callbacks.

tl;dr: Identify repeated components, make prefabs of them (I make a class for each prefab to make the C# side obvious)

Share this post


Link to post
Share on other sites

Where are the sound effects currently used by the game? I figured it'd be easier to change those files than the configs of every engine mod I have, but I can't find them. Are they accessible?

Share this post


Link to post
Share on other sites
29 minutes ago, String Witch said:

Where are the sound effects currently used by the game? I figured it'd be easier to change those files than the configs of every engine mod I have, but I can't find them. Are they accessible?

you mean the files located here? GameData\Squad\Sounds

Edited by wile1411

Share this post


Link to post
Share on other sites
13 hours ago, Tralfagar said:

I've tried using the "ref" keyword, but it does not seem to work with Lists.  When I try using an intermediate variable, I store the location of *that* variable instead of the member of the list.  Does anyone have any ideas?

If I understand what you're trying to do, you're trying real hard to get something you already have.  They're already pointers.

For example if you do:

List<PartResource> myres;
 
myres.Add(vessel.parts[7].Resources[0])

myres[0].amount+=1;

Then myres[0] isn't a copy of the original resource; it is the original resource.  In C terms, it's already a pointer to the original resource.  If you modify myres[0].amount, you're really modifying vessel.parts[7].Resources[0].amount.

For a simple example, SafeChute gathers all the part modules from a ship that are parachutes, and then saves them in a List, and then accesses that List to read values from the original part:

https://github.com/henrybauer/SafeChute/blob/master/SafeChute.cs#L113-L119

Closer to what you want to do, TacFuelBalancer's fuel controller builds a "map" of the ship and then caches it.  It then accesses the parts (and their resources) through that map:

Map building:

https://github.com/thewebbooth/TacFuelBalancer/blob/master/Source/FuelBalanceController.cs#L409-L562

Accessing the real parts via the map:

https://github.com/thewebbooth/TacFuelBalancer/blob/master/Source/FuelBalanceController.cs#L598-L617

 

Share this post


Link to post
Share on other sites
9 hours ago, wile1411 said:

you mean the files located here? GameData\Squad\Sounds

No; those files aren't actually used by the current build of the game. A number of filenames referenced in engine configs aren't in that folder, besides.

Share this post


Link to post
Share on other sites
2 hours ago, String Witch said:

No; those files aren't actually used by the current build of the game. A number of filenames referenced in engine configs aren't in that folder, besides.

You can't as the sounds are distributed as part of the Unity deployed game package. They are not individual files. The EULA prohibits you from touching them. To do what you want to do your only option IS to MM the sounds out of config files and replace them with others that way.

Edited by JPLRepo
  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now