Jump to content

The official unoffical "help a fellow plugin developer" thread


Recommended Posts

Still stuck on this.

I can get the names changed when the crew panel is first opened, then if I change anything, they revert to normal. But, if I then fill the available seats (or use the 'Fill' button), the names change back to how I want them. If I move a Kerbal out of a seat, they go back to the original names.

 

Any advice?

Link to comment
Share on other sites

2 hours ago, WelshSteW said:

I can get the names changed when the crew panel is first opened, then if I change anything, they revert to normal. But, if I then fill the available seats (or use the 'Fill' button), the names change back to how I want them. If I move a Kerbal out of a seat, they go back to the original names.

Kerbals are essentially special one part crafts and, so, have a prototype or "template" on the prefab from where they are instantiated on the Scene.

And, exactly as any other part, any change you do on an instantiated ("living") Kerbal is lost once the part is destroyed or recovered.

With a twist - there's a list of available Kerbals on a place called ROSTER where the currently instantiated Kerbals are kept (including the killed ones!). Do a test: open a SFS file on a good Text Editor (or better, make a copy of a SFS file and open it - just in case...), search for the word "ROSTER" (all upcase) and see how this data structure is stored on the file.

You will have something similar on the memory once you load the savegame.

If you want to permanently rename a Kerbal on a savegame you need to change its name on the ROSTER, and from that point every time you "select" the Kerbal on the SPH or VAB (where the available Kerbals are already instantiated), they will be spawned with the new name.

There's an old Add'On,  CrewManifest, that creates and spawns new Kerbals on a Scene. Check its code to see how they do it.

Alternatively, you can just directly edit the SFS file (being extremely careful to prevent screwing up something - always do backups!). :)

(remember: it's pointless to rename a spawned Kerbal, you need to rename the template on the ROSTER before it is spawned)

Link to comment
Share on other sites

18 hours ago, Lisias said:

Kerbals are essentially special one part crafts and, so, have a prototype or "template" on the prefab from where they are instantiated on the Scene.

And, exactly as any other part, any change you do on an instantiated ("living") Kerbal is lost once the part is destroyed or recovered.

With a twist - there's a list of available Kerbals on a place called ROSTER where the currently instantiated Kerbals are kept (including the killed ones!). Do a test: open a SFS file on a good Text Editor (or better, make a copy of a SFS file and open it - just in case...), search for the word "ROSTER" (all upcase) and see how this data structure is stored on the file.

You will have something similar on the memory once you load the savegame.

If you want to permanently rename a Kerbal on a savegame you need to change its name on the ROSTER, and from that point every time you "select" the Kerbal on the SPH or VAB (where the available Kerbals are already instantiated), they will be spawned with the new name.

There's an old Add'On,  CrewManifest, that creates and spawns new Kerbals on a Scene. Check its code to see how they do it.

Alternatively, you can just directly edit the SFS file (being extremely careful to prevent screwing up something - always do backups!). :)

(remember: it's pointless to rename a spawned Kerbal, you need to rename the template on the ROSTER before it is spawned)

Ok, I think I understand that. I could rename them all permanently. Well, I'm saying I could. What I mean is, that would be something I could fumble around with, that would solve the problem. But I'm worried that may break other things. Still it's something I'll keep in mind.

16 hours ago, HebaruSan said:

Maybe GameEvents.onEditorShipCrewModified?

 

Now, I have a confession to make. I jumped into this without a lot (any) experience with C# programming, and to be 100% honest, any experience I did have with programming was a long time ago. Like, decades. So, I didn't do the basics. I had no logging going on.

So, I fixed that. I spammed the log like crazy. What I found out was that the code was trying to run, but it was hitting errors, and stopping.

Just in case you're the curious type, the errors were this -

 

Spoiler

[ERR 18:26:08.061] Exception handling event onEditorShipCrewModified in class CrewPanelMonitor:System.NullReferenceException: Object reference not set to an instance of an object
  at EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) [0x00023] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.MyNameUpdater () [0x00014] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) [0x0000a] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EventData`1[T].Fire (T data) [0x000b0] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 18:26:08.138] NullReferenceException: Object reference not set to an instance of an object
    EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.MyNameUpdater () (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EventData`1[T].Fire (T data) (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventData`1:Fire(VesselCrewManifest)
    PartCrewManifest:AddCrewToSeat(ProtoCrewMember, Int32)
    KSP.UI.BaseCrewAssignmentDialog:GetManifest(Boolean)
    EditorLogic:UpdateCrewManifest()
    KSP.UI.CrewAssignmentDialog:EditorLogicUpdateCrew()
    KSP.UI.CrewAssignmentDialog:MoveCrewToAvail(UIList, UIList, UIListItem)
    KSP.UI.BaseCrewAssignmentDialog:ButtonClear()
    UnityEngine.EventSystems.EventSystem:Update()

and this -

Spoiler

[ERR 18:26:08.154] Exception handling event onEditorShipCrewModified in class CrewPanelMonitor:System.NullReferenceException: Object reference not set to an instance of an object
  at EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) [0x00023] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.MyNameUpdater () [0x00014] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) [0x0000a] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EventData`1[T].Fire (T data) [0x000b0] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 18:26:08.155] NullReferenceException: Object reference not set to an instance of an object
    EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.MyNameUpdater () (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EventData`1[T].Fire (T data) (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventData`1:Fire(VesselCrewManifest)
    EditorLogic:UpdateCrewManifest()
    KSP.UI.CrewAssignmentDialog:EditorLogicUpdateCrew()
    KSP.UI.CrewAssignmentDialog:MoveCrewToAvail(UIList, UIList, UIListItem)
    KSP.UI.BaseCrewAssignmentDialog:ButtonClear()
    UnityEngine.EventSystems.EventSystem:Update()

 

Which meant it wasn't failing to run, as I'd thought, it was just having problems.

So yeah, I apologise for not actually knowing what the problem was, so not being able to ask the right question, and so wasting your time. Sorry.

 

After finding this out, I decided to try and bring the learning experience to an end as quickly as possible. Rather than trying to learn how to fix the error, I decided to learn how to handle the error. Just accept it, and move on.

 

So, after adding a try and catch to the code, it's now 90% working. It modifies the names in all circumstances, except one. If no Kerbals are assigned to the ship, it doesn't work. But as soon as one Kerbal is assigned, it's fine.

I can live with that. For now.

 

But this has been fun. Frustrating. But fun. How you guys go about creating stuff from scratch amazes me. All I wanted to so was slightly modify an existing mod, and it nearly ruined me :D

Edited by WelshSteW
Link to comment
Share on other sites

1 hour ago, WelshSteW said:

Now, I have a confession to make. I jumped into this without a lot (any) experience with C# programming

Congratulations on getting so far! Yeah, logging can be extremely helpful. Sometimes the cause of a bug can be pinpointed by carefully reading KSP.log (is it renamed on Windows?) alongside the code.

1 hour ago, WelshSteW said:

Just in case you're the curious type, the errors were this -

For what it's worth, null reference exceptions are probably the most common problems that come up in C# (thanks to the lack of static analysis tools for implicitly nullable types prior to C# version 8). It means some code needs an object to work and it isn't given one, and in Unity that's often because your function is being called before or after you expected it would be. A few "if (x != null)" statements may help (though there is a Unity quirk with that statement that you hopefully won't hit doing this). Another personal favorite when applicable is to use "object?.property" instead of "object.property", which will just return null instead of throwing an exception if "object" is null.

1 hour ago, WelshSteW said:

So yeah, I apologise for not actually knowing what the problem was, so not being able to ask the right question, and so wasting your time. Sorry.

No problem; I see you edited that post, so there must have been a previous revision that you now think of that way, but I didn't even see it.

1 hour ago, WelshSteW said:

So, after adding a try and catch to the code, it's now 90% working. It modifies the names in all circumstances, except one. If no Kerbals are assigned to the ship, it doesn't work. But as soon as one Kerbal is assigned, it's fine.

I can live with that. For now.

Cool! I know you chatted a bit with the mod author and submitted an issue, but did you know you can submit your changes to be added to the main codebase on GitHub?

1 hour ago, WelshSteW said:

How you guys go about creating stuff from scratch amazes me.

I'm not so sure about "from scratch"; before I write one line of code, I've already got this amazing, complex rocketry game running on my PC, and all I have to do is figure out which tweaks will make it work the way I want. ;p Folks who make entirely new self-contained systems in the game (comms, life support, etc.) have my amazement as well, just as you say.

Link to comment
Share on other sites

Hello and sorry if this has already been asked here, research is quite verbose...

I'm trying to add parts to an inventory stack, I'm using the API's StoreCargoPartAtSlot(string,-1) but I'm running into some problems

  • The CheckPartStorage(craftPart) returns false even if a stack is not complete, the API says "Checks if the part can be stored Without actually storing it.", this is confusing...
  • StoreCargoPartAtSlot(craftPart, -1) adds the part the next empty slot. But once the inventory has no empty slots but still stackable space it fails, but leaves an error "Unable to store part - there are no free slots. Unable find part with name=evaRepairKit"
  • I tried to completely rewrite the stack storing behavior. It works but I run into two other problems: mass and volume and not checked and most of all... the PAW is not updated! I have to move an inventory part to "wake it" up.

StoreStack example

Spoiler

 


private static bool RealStoreCargoPartAtSlot(ModuleInventoryPart inventory, string partName, int inventorySlot)
        {
            if (String.IsNullOrEmpty(partName))
            {
                return false;
            }
            if (inventory == null)
            {
                return false;
            }
            int availableInventorySlot = -1;
            if (inventorySlot == -1)
            {
                // find first available inventory slot
                for (int i = 0; i < inventory.InventorySlots; i++)
                {
                    if (!inventory.storedParts.ContainsKey(i))
                    {
                        myDebug("Found undefined empty slot : " + i);

                        availableInventorySlot = i;
                        break;
                    }
                    StoredPart myStoredPart = inventory.storedParts[i];
                    if (myStoredPart != null && myStoredPart.IsEmpty)
                    {
                        myDebug("Found empty slot : " + i);

                        availableInventorySlot = i;
                        break;
                    }
                  // incomplete stacks act weird with isFull and isEmpty...
                  if (myStoredPart != null && myStoredPart.partName.Equals(partName) && myStoredPart.CanStack && myStoredPart.stackCapacity > myStoredPart.quantity)
                    {
                        myDebug("Found stackable slot : " + i);
                        availableInventorySlot = i;
                        break;
                    }
                }
            }
            else
            {
                if (inventory.storedParts.ContainsKey(inventorySlot) && inventory.storedParts[inventorySlot].IsFull)
                {
                    return false;
                }
                availableInventorySlot = inventorySlot;
            }
            if (availableInventorySlot == -1)
            {
                myDebug("No slot found!");
                return false;
            }
            try
            {
                if (!inventory.storedParts.ContainsKey(availableInventorySlot) || inventory.storedParts[availableInventorySlot].IsEmpty)
                {
                    myDebug("Full add in " + availableInventorySlot);
                    bool succes = inventory.StoreCargoPartAtSlot(partName, availableInventorySlot);
                    return succes;
                }
                else
                {
                    myDebug("Quantity add in " + availableInventorySlot);
                    inventory.storedParts[availableInventorySlot].quantity++;
                    return true;
                }

            }
            catch (Exception e)
            {
                print("Cannot augment quantity " + partName + " " + e.Message);
                return false;
            }

        }

 

 

 

Example (inserting a evaRepairKit)

Spoiler

 

7Vago3T.png

 

 

 

Did somebody tried this already?

Thanks for your help!

Edited by Goufalite
typo
Link to comment
Share on other sites

12 hours ago, WelshSteW said:

Now, I have a confession to make. I jumped into this without a lot (any) experience with C# programming, and to be 100% honest, any experience I did have with programming was a long time ago. Like, decades. So, I didn't do the basics. I had no logging going on.

So, I fixed that. I spammed the log like crazy. What I found out was that the code was trying to run, but it was hitting errors, and stopping.

Just in case you're the curious type, the errors were this -

 

  Reveal hidden contents

[ERR 18:26:08.061] Exception handling event onEditorShipCrewModified in class CrewPanelMonitor:System.NullReferenceException: Object reference not set to an instance of an object
  at EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) [0x00023] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.MyNameUpdater () [0x00014] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) [0x0000a] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EventData`1[T].Fire (T data) [0x000b0] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 18:26:08.138] NullReferenceException: Object reference not set to an instance of an object
    EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.MyNameUpdater () (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EventData`1[T].Fire (T data) (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventData`1:Fire(VesselCrewManifest)
    PartCrewManifest:AddCrewToSeat(ProtoCrewMember, Int32)
    KSP.UI.BaseCrewAssignmentDialog:GetManifest(Boolean)
    EditorLogic:UpdateCrewManifest()
    KSP.UI.CrewAssignmentDialog:EditorLogicUpdateCrew()
    KSP.UI.CrewAssignmentDialog:MoveCrewToAvail(UIList, UIList, UIListItem)
    KSP.UI.BaseCrewAssignmentDialog:ButtonClear()
    UnityEngine.EventSystems.EventSystem:Update()

and this -

  Reveal hidden contents

[ERR 18:26:08.154] Exception handling event onEditorShipCrewModified in class CrewPanelMonitor:System.NullReferenceException: Object reference not set to an instance of an object
  at EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) [0x00023] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.MyNameUpdater () [0x00014] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) [0x0000a] in <885ebe40736b4527a7b178d47e2cc771>:0 
  at EventData`1[T].Fire (T data) [0x000b0] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 18:26:08.155] NullReferenceException: Object reference not set to an instance of an object
    EarnYourStripes.CrewPanelMonitor.GetCrewToOverwrite (System.Collections.Generic.IReadOnlyCollection`1[T] crewItemContainers) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.MyNameUpdater () (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EarnYourStripes.CrewPanelMonitor.OnEditorShipCrewModified (VesselCrewManifest data) (at <885ebe40736b4527a7b178d47e2cc771>:0)
    EventData`1[T].Fire (T data) (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventData`1:Fire(VesselCrewManifest)
    EditorLogic:UpdateCrewManifest()
    KSP.UI.CrewAssignmentDialog:EditorLogicUpdateCrew()
    KSP.UI.CrewAssignmentDialog:MoveCrewToAvail(UIList, UIList, UIListItem)
    KSP.UI.BaseCrewAssignmentDialog:ButtonClear()
    UnityEngine.EventSystems.EventSystem:Update()

 

Which meant it wasn't failing to run, as I'd thought, it was just having problems.

Please be cautious with this.

Consider the following meta-program (python style - I completely pulled it out of my hat, I'm trying to make a point, not to solve a problem):

class EarnYourStripes:
    class CrewPanelMonitor:
        @KspEvent
        def OnEditorShipCrewModified(self, ship:ShipConstruction):
            try:
                modifiedCrewList = self.buildModifiedCrewList()
                overwrittenCrewList = self.GetCrewToOverwrite(modifiedCrewList)

                for crew in modifiedCrewList:
                    self.announce_crew_modified(crew)

                for crew in overwrittenCrewList:
                    self.handle_crew_overwritten(crew)
            except:
                # In God we trust! :P
                pass
            
        def GetCrewToOverwrite(crewList:list) -> list:
            r = list()
            # yada yada yada yada
            if self.something_wrong_happens_randomly:
                throw RandomException("duh")
            return r
            
        def handle_crew_overwritten(crew:Kerbal):
            self.remove_crew_from_roster(crew)
            self.announce_crew_removed(crew)
            new_crew = self.rename_crew(crew)
            self.announce_crew_modified(new_crew)

        def handle_crew_modified(crew:Kerbal):
            self.warn_kerbalism_about(crew)
            self.warn_courageoustourists_about(crew)
            # etc, etc, etc

        def announce_crew_removed(crew:Kerbal):
            # yada yada yada yada
            # etc, etc, etc

That pseudo-code kinda mimics what you said you are doing, but using hypothetical tasks to make things easier to explain.

By that hypothesis, the OnEditorShipCrewModified does what follows:

  1. Build a list of Kerbals that were modified, but were not "overwritten" (whatever is this)
  2. Build another list of Kerbals that were "overwritten"
  3. Announce every Kerbal that was modified to Kerbalism, KourageousTourists, etc (again, I'm pulling this from my hat - I don't know if you need to announce these changes to anyone)
  4. Handle the crew members that were "overwritten"
    1. delete it from roster
    2. announce to everybody that that crew member were removed (so they will not crash by trying to access it)
    3. get a new member based with the removed one's data (but renamed)
    4. announce this new crew member to everybody, so they know there's a new kid on the block

Now imagine that we get an Exception on GetCrewToOverwrite as it's happening to you in real life. What this means?

Well, code will abort execution on that line, and everything until the end of the caller's try block will not be executed - Kerbalism and KourageousTourists (on this example) will not be notified about the changes and then may behave erratically!!

So, if you really don't know how to fix this, but somehow this new code fixes something even worse on the wild - and so, you are replacing a nasty bug with one less nasty - the less horrible way to cope with this problem is, so, to do as follows:

        @KspEvent
        def OnEditorShipCrewModified(self, ship:ShipConstruction):
            modifiedCrewList = self.buildModifiedCrewList()
            try:
                overwrittenCrewList = self.GetCrewToOverwrite(modifiedCrewList)
            except:
                # oh, well... FIXME ASAP!
                overwrittenCrewList = list()

            for crew in modifiedCrewList:
                self.announce_crew_modified(crew)

            for crew in overwrittenCrewList:
                self.handle_crew_overwritten(crew)

Doing things this way will, at least, allow the modifiedCrewList to be handled correctly.

Did I made myself understandable? This is really important, you may be inducing third parties to bork doing what you are doing if you are doing the way I think you are doing... :P 

This is not only a hypothetical situation - these things really screwed me up on my first year on maintaining TweakScale: things go badly somewhere else, but TweakScale was the one borking because it needs something that was not done somewhere else due that bork. And this is a proverbial Pain in the SAS to diagnose!

 

10 hours ago, HebaruSan said:

Cool! I know you chatted a bit with the mod author and submitted an issue, but did you know you can submit your changes to be added to the main codebase on GitHub?

I would advise against a Pull Request with the empty Try Catch he is using at this moment to overcome that Exception - unless this is really the only way out of the mess.

Things may appears to work on his machine, but once this stunt is published, it may (probably will) induce undesired collateral effects on third parties that will rely on that code to be fully ran. Once that process is aborted on the middle, things that should had been done will not.

Edited by Lisias
better choose of words.
Link to comment
Share on other sites

That's certainly a few things I'll keep in mind, and to be honest something I hadn't fully considered. It's also why I tried to stay away from the permanent renaming that you mentioned. I assumed, if I'm playing around with the instance of the Kerbal, anything I did wouldn't be permanent? So it's unlikely to break my game (or at least, not break it permanently). I hope I've understood your points, and that my assumption is correct.

 

I haven't looked into the Try Except stuff yet, but I will.

 

As for submitting it to the mod author, I don't think I will. As I said, I'm still very new to this, and to be honest, I'm curious to see how they do it, so I can compare it to how I did it. I know that's a bit selfish, and that I'm making them do work they (possibly) don't have to. I'm hoping that I can pay it back in the future if I manage to get a decent understanding of it all, either by managing to make a mod all by myself, or by actually being able to help other mod authors out with issues or requests.

Link to comment
Share on other sites

On 6/18/2021 at 8:05 AM, WelshSteW said:

That's certainly a few things I'll keep in mind, and to be honest something I hadn't fully considered. It's also why I tried to stay away from the permanent renaming that you mentioned. I assumed, if I'm playing around with the instance of the Kerbal, anything I did wouldn't be permanent? So it's unlikely to break my game (or at least, not break it permanently). I hope I've understood your points, and that my assumption is correct.

I'll try to simplify things to allow easy understanding - once we understand the basics, we could dig into the gore details (and I don't know all of them anyway.. :P ).

There're info on the Kerbal that are meant to be changed and preserved; there're info that can be changed but will be thrown away later; and there're some info that just can't change, as they are used as primary keys inside the game.

Things that are preserved are saved automatically by KSP into the ROSTER once you recover the Kerbal. If you want to save additional info, usually you write a custom PartModule, patch it into the Kerbal's prefab, and on your code you handle the OnSave event (what to save and where to save it will be handle on that custom module!).

Some other things are just thrown away when you recover the Kerbal- for example, the amount of EVA fuel on the older KSP versions.

But some few things cannot be changed otherwise KSP (and everybody else) will lose track of the objects - by reading your logs, I'm concluding that the Kerbal's name is one of that things.

When you correlate information from different objects, you need a Primary Key (something that univocally identifies a thing): Kebal A is piloting Vessel B, Kerbal C is a passenger on Vessel D, etc - so you can't have two kerbals named A, or two Vessels named B... There's a thingy called InstanceID that univocally identifies an object at runtime, but this is only valid at runtime - once you recover the object (or save the savegame), these InstanceIDs are thrown away and the next time you launch that Kerbal (or anything else), it will gain a new one. So the InstanceID is kinda a temporary Surrogate Key valid during a session (like a token on a webpage).

So we are back to the Primary Key problem (i.e., that thing that will identify the Kerbal between gaming sessions): it happens that the Kerbal's name is that Primary Key!

So, if you change the Kerbal's name at runtime, when you recover the Kerbal  (or save the game), the KSP's code that would preserve the persistent data from the Kerbal will not find it on the ROSTER, and since this is a situation that was considered out of scope of the implementation (we just don't change a Primary Key of an object), the code doesn't handle this exception and so BADABOOM!! #leelooFeelings

Since I don't know exactly what you are planning to do, I don't know what to suggest - but on a wild guess, perhaps creating a new Kerbal for the job (using some other as a template) would not do the job?

 

On 6/18/2021 at 8:05 AM, WelshSteW said:

I haven't looked into the Try Except stuff yet, but I will.

It's important, believe me.

In the past, KSP was "protecting" us by doing every callback inside a try-catch block - so we could bork as we wish without screwing up things:

def execute_physics_frame(world):
    for game_object in GameDatabase.AllGameObjects:
        for module in game_object.modules:
            for callback in module.callbacks:
                try:
                    execute(callback, parms)
                except ex as Exception:
                    Log.exception(ex)

But..

Try-catches are terrible expensive: every time you enter a try block, the runtime needs to create a new stackframe in order to handle the block, and once you leave the block, the stackframe needs to be cleaned. A lot of nested stackframes on every call on every animation frame of the game, and you will lose easily about 5 to 10% of the CPU juice only handling stackframes!

So ideally we need to get rid of all try blocks to save a lot of CPU power, that so can be used on new shinny things:

def execute_physics_frame(world):
    for game_object in GameDatabase.AllGameObjects:
        for module in game_object.modules:
            for callback in module.callbacks:
                execute(callback, parms)

But that have an undesired effect: since an uncaught exception will halt the whole execution until the stackframes's top level, if we bork something down here the caller of the execute_physics_frame will be halted - and so, the whole game can crash!!

(and yeah, you just learnt why sometimes we have a CTD - Crash To Desktop - on some games!)

So KSP is getting rid of try-catches only on hot code (i.e., parts of the program that are called all the time!) in order to prevent a CTD. But yet, now we can't just bork things as we were used to do without injecting collateral effects on third-parties:

def execute_physics_frame(world):
    try:
        for game_object in GameDatabase.AllGameObjects:
            for module in game_object.modules:
                for callback in module.callbacks:
                    execute(callback, parms)
    except ex as Exception:
        Log.exception(ex)

(I don't know if this is really how KSP handle things, but my empirical tests suggests it's something near this)

Now, knowing that KSP needs a Primary Key to keep track of things at runtime, and knowing that missing Primary Keys throw Exceptions inside your callbacks, we can see why, usually, it's a bad idea to just shove an empty try-catch on borking code to mask exceptions, instead of properly handle them (or prevent them to happening).

 

On 6/18/2021 at 8:05 AM, WelshSteW said:

As for submitting it to the mod author, I don't think I will. As I said, I'm still very new to this, and to be honest, I'm curious to see how they do it, so I can compare it to how I did it. I know that's a bit selfish, and that I'm making them do work they (possibly) don't have to. I'm hoping that I can pay it back in the future if I manage to get a decent understanding of it all, either by managing to make a mod all by myself, or by actually being able to help other mod authors out with issues or requests.

IMHO, that's the way to go (at least, it was what I did when I was starting here).

Everybody was a beginner in the beginning :P and the best way to learn things is... Making mistakes and then fixing them.

And, frankly, fixing bugs is a hell of an effective way to get your feet wet: What's broken? Why it broke? How I fix this? And, now, how in hell I proper fix that damned thing without creating new bugs? :D 

Once you are able to answer these questions, things start to make sense.

And then you write a Mod, are bitten by the modding bug and now you are doomed like the rest of us. :sticktongue:

Edited by Lisias
Tyops, of cuorse...
Link to comment
Share on other sites

  • 4 weeks later...

I have a module that inherits from ModuleEngines, but I do not need the "Activate Engine" or "Shutdown Engine" toggle in the PAW.  Is there any way to disable this toggle in the PAW so that it doesn't appear?  I would prefer to disable it in the plugin code itself, if at all possible.

Edited by TheShadow1138
Link to comment
Share on other sites

6 hours ago, TheShadow1138 said:

I have a module that inherits from ModuleEngines, but I do not need the "Activate Engine" or "Shutdown Engine" toggle in the PAW.  Is there any way to disable this toggle in the PAW so that it doesn't appear?  I would prefer to disable it in the plugin code itself, if at all possible.

You can find the event by name (base.Event["Shutdown"] or base.Event["Activate"]) and then ".guiActive = false" them in update.

I'm intersted in your inheritage, do you have to pass all the KSPField in the cfg file? How do you tell existing parts to use this inheritance?

Edited by Goufalite
typo
Link to comment
Share on other sites

On 6/17/2021 at 10:56 PM, Goufalite said:

I'm trying to add parts to an inventory stack, I'm using the API's StoreCargoPartAtSlot(string,-1) but I'm running into some problems

  • The CheckPartStorage(craftPart) returns false even if a stack is not complete, the API says "Checks if the part can be stored Without actually storing it.", this is confusing...
  • StoreCargoPartAtSlot(craftPart, -1) adds the part the next empty slot. But once the inventory has no empty slots but still stackable space it fails, but leaves an error "Unable to store part - there are no free slots. Unable find part with name=evaRepairKit"
  • I tried to completely rewrite the stack storing behavior. It works but I run into two other problems: mass and volume and not checked and most of all... the PAW is not updated! I have to move an inventory part to "wake it" up.

Self answering after a few research ;)

First of all, I created a bug with reproduction steps, this is not a normal behavior...

Then I had to recode all the checking and storing behavior with some other tweaks:

  • You need to manually check mass and volume before addind a stackable part (and the cheat override for kerbal inventory in 1.12)
if (!CheatOptions.IgnoreKerbalInventoryLimits)
            {

                if (inventory.HasMassLimit && inventory.massCapacity + PartMass(myPartString) > inventory.massLimit + 0.00001f)
                {
                    ScreenMessages.PostScreenMessage(Localizer.Format("#boomass"), 2.0f, ScreenMessageStyle.UPPER_CENTER);
                    return false;
                }
                if (inventory.HasPackedVolumeLimit && inventory.volumeCapacity + PartVolume(myPartString) > inventory.packedVolumeLimit + 0.00001f)
                {
                    ScreenMessages.PostScreenMessage(Localizer.Format("#boovolume"), 2.0f, ScreenMessageStyle.UPPER_CENTER);
                    return false;
                }
            }
  • To add something to a stack (after checking if the stack isn't complete of course) and update the UI :
return inventory.UpdateStackAmountAtSlot(availableInventorySlot, inventory.storedParts[availableInventorySlot].quantity + 1);
  • To remove a part from a stack and update the UI
this.ModuleInventoryPartReference.RemoveNPartsFromInventory(myPartString, 1);
GameEvents.onModuleInventoryChanged.Fire(this.ModuleInventoryPartReference);

 

Link to comment
Share on other sites

6 hours ago, Goufalite said:

You can find the event by name (base.Event["Shutdown"] or base.Event["Activate"]) and then ".guiActive = false" them in update.

I'm intersted in your inheritage, do you have to pass all the KSPField in the cfg file? How do you tell existing parts to use this inheritance?

Thank you so much.  I put the statements in the OnStart() function and it worked like a charm.

I'm only using "engineID" in the CFG file for this particular module.  I do set the "maxThrust" KSPField in my module's code to ensure that it has 0 thrust without needing to expose it in the CFG, and to ensure that no extraneous forces are applied.  At the same time it allows me to use any other member or method of ModuleEngines without explicitly declaring them, as expected.

The part is a warp drive that I wanted to add a Waterfall FX node to, which requires an "engineID" to work.  So, I needed to switch my module from inheriting from PartModule to inheriting from ModuleEngines so I could get an "engineID".  The only thing I ended up changing was adding the statements to activate and shutdown the engine, and update the throttle so that Waterfall would work as expected.  I had already coded the activation and deactivation of the warp drive and so just added "this.Activate()" and "this.Shutdown()" wherever the drive was being activated/deactivated, and inserted "this.UpdateThrottle()" and everything worked.  I just didn't need that PAW button to activate/shutdown because clicking it wouldn't do anything anyway.  I hope that answers your question, and thank you again for your help.

Link to comment
Share on other sites

On 7/15/2021 at 6:38 PM, TheShadow1138 said:

I'm only using "engineID" in the CFG file for this particular module.  I do set the "maxThrust" KSPField in my module's code to ensure that it has 0 thrust without needing to expose it in the CFG, and to ensure that no extraneous forces are applied.  At the same time it allows me to use any other member or method of ModuleEngines without explicitly declaring them, as expected.

I'm a little confused... Can I see your CFG file?

Link to comment
Share on other sites

30 minutes ago, Goufalite said:

I'm a little confused... Can I see your CFG file?

Here's the config:  (SW_ModuleWarpGenerator is the module that inherits from ModuleEngines)

PART
{
	name = SWPhoenixCore
	module = Part
	author = TheShadow1138
	MODEL
	{
		model = TrekDrive/Parts/Phoenix/PhoenixCore
	}
	rescaleFactor = 1
	node_stack_top = 0.0, 0.530495, 0.0, 0.0, 1.0, 0.0, 2
	node_stack_bottom = 0.0, -0.493999, 0.0, 0.0, -1.0, 0.0, 2
	node_attach = 1.25, 0.0, 0.0, 1.0, 0.0, 0.0, 1
	TechRequired = fuelSystems
	entryCost = 8200
	cost = 1550
	category = Electrical
	subcategory = 0
	title = Phoenix Warp Core/Field Generator
	manufacturer = ShadowWorks
	description = The warp core and warp field generator of the Phoenix warp ship.  Designed by Zephram Kerman as part of his warp drive project.  Uses a matter-antimatter reaction to generate massive amounts of warp plasma and electric charge to power the Phoenix and its warp drive.  The built-in warp field generator controls the flow of warp plasma and the warp coils to generate the warp field and drive the vessel at warp velocities.
	attachRules = 1,1,1,1,0
	mass = 3.5
	dragModelType = default
	maximum_drag = 0.2
	minimum_drag = 0.3
	angularDrag = 2
	crashTolerance = 6
	breakingForce = 200
	breakingTorque = 200
	maxTemp = 2000 // = 2900
	bulkheadProfiles = size2, srf
	stageOffset = 1
	tags = trekdrive phoenix shadowworks antimatter warp field generator core

	MODULE
	{
		name = SW_ModuleWarpCore
		minAntimatter = 0.1
		minMatter = 0.1
		warpPlasmaProdRate = 10
		ecProdRate = 2500
	}

	MODULE
	{
		name = SW_ModuleWarpGenerator
		engineID = phoenixWarp
		maxWarp = 1
		minNacelles = 2
		electricityReq = 500.000
	}

}

 

Link to comment
Share on other sites

  • 2 weeks later...

Hi, I need a little help on how to access a ConfigNode in a module when a ship is loaded

Here's my module description

@PART[stuff]
{
	@MODULE[myModule]
	{
		field = myField
		TABLE
		{
			ITEM
			{
				name = myName1
				ratio = 1
			}
			ITEM
			{
				name = myName2
				ratio = 2
			}
		}
	}
}

I'm using this code to access the nodes in the override onLoad(ConfigNode) of my module

base.onLoad(node);

var nodesResource = node.GetNode("TABLE");
if (nodesResource == null)
{
	UnityEngine.MonoBehaviour.print("No TABLE node");
  	throw new Exception("No TABLE node");
}
var resources = nodesResource.GetNodes("ITEM");
UnityEngine.MonoBehaviour.print("Found resources : " + resources.Length);
for (int i = 0; i < resources.Length; i++)
{
	resourcesDict.Add(resources[i].GetValue("name"), float.Parse(resources[i].GetValue("ratio")));
}

I've put some logs and when the game loads there's no problem (I can see "Found resources..."), but when I focus on a ship it throws "No TABLE node"...

TABLE is not intented to be persistent. How can I access this table at flight?

Thanks!

Link to comment
Share on other sites

31 minutes ago, Goufalite said:

How can I access this table at flight?

Self answering, again ;) Source

if (part.partInfo != null)
            {
                node =
                    GameDatabase.Instance.GetConfigs("PART")
                                .Single(c => part.partInfo.name == c.name.Replace('_', '.'))
                                .config.GetNodes("MODULE")
                                .Single(n => n.GetValue("name") == moduleName);
            }

Copy the code above in your OnAwake.

Link to comment
Share on other sites

  • 1 month later...

I'm wanting to add a slider to the PAW for my warp drive part module so that the player can choose their desired maximum warp factor with an absolute upper limit warp factor defined in the part CFG.  So, I need to be able to set the upper limit of the slider from a variable that is set in the part CFG.

As an example, if the part CFG defines the drive with a maxWarp = 5, then the slider needs to go from 0 - 5, but if the CFG defines maxWarp = 7, the slider needs to go from 0 - 7.

Monodevelop tells me that "An object reference is required to access non-static member 'TrekDrive.SW_ModuleWarpGenerator.maxWarp'".  So, I conclude that I must reference the object to which the module is applied, but cannot because the KSPField is used before the module is actually added to the part.  I did try moving the KSPField definition for the slider to the OnStart() function, but it seems that I cannot place a KSPField definition there.  I haven't been able to access the KSP API Documentation because it's been undergoing maintenance for over a day now, so I can't really go digging through that for insight, so any help y'all can provide would be greatly appreciated.

Edit (found the answer):  I was able to find the answer, or at least a working solution.

Edited by TheShadow1138
Link to comment
Share on other sites

  • 2 weeks later...

Okay. So. I am currently developing a music mod for KSP and would anyone know how to toggle it? Okay let me explain. Some mods have a icon that shows below all the apps. Well. What should I type if i wanted to toggle the songs by clicking the icon? 

Link to comment
Share on other sites

26 minutes ago, Dr. Kerbal said:

Okay. So. I am currently developing a music mod for KSP and would anyone know how to toggle it? Okay let me explain. Some mods have a icon that shows below all the apps. Well. What should I type if i wanted to toggle the songs by clicking the icon? 

You need to register an event handler on the icon. Not that hard, but a bit convoluted. Check this code:

  • It starts on every Editor (you can define in which scenes you want your buttons on the KSPAddon thingy.
  • Then the Awake method is called by Unity, and the code hooks it up to be called when the Launcher App (the KSP code than handles the thingy) is ready.
  • Then the Start method is called by Unity, that effectivelly creates the button for your application.
    • Check how it defines when it will be displayed (VAB? SPH? Somewhere else?)
    • Check how it defines the method to be called when you click on the icon (useToolbarButton on this example)
  • Your code must decide if your window is to be displayed on every Update(). If yes, draw it (it's done on every frame)
  • Don't forget to release and destroy anything you had allocated or created on OnDestroy() !!

There's probably cleaner codes around to do the task, but this one is the one I recalled from heart (as I had played a bit with it on my rookie times!).

Cheers!

Link to comment
Share on other sites

12 hours ago, Lisias said:

You need to register an event handler on the icon. Not that hard, but a bit convoluted. Check this code:

  • It starts on every Editor (you can define in which scenes you want your buttons on the KSPAddon thingy.
  • Then the Awake method is called by Unity, and the code hooks it up to be called when the Launcher App (the KSP code than handles the thingy) is ready.
  • Then the Start method is called by Unity, that effectivelly creates the button for your application.
    • Check how it defines when it will be displayed (VAB? SPH? Somewhere else?)
    • Check how it defines the method to be called when you click on the icon (useToolbarButton on this example)
  • Your code must decide if your window is to be displayed on every Update(). If yes, draw it (it's done on every frame)
  • Don't forget to release and destroy anything you had allocated or created on OnDestroy() !!

There's probably cleaner codes around to do the task, but this one is the one I recalled from heart (as I had played a bit with it on my rookie times!).

Cheers!

Thanks I will se what I can make with this! Thanks for the help!

 

I read the code and it just seems to show where and when the button/icon appears. It was really useful and maybe I could figure out how to make it work. I always just start up with a little plan. 

But a lot of "ifs" are just needed it seems. But thanks for the help! This was really helpful!

Edited by Dr. Kerbal
Link to comment
Share on other sites

So what do I type to have when I type Shift + R do something? Is it something like this? This is just a guess (the guess is keypressed = r ) Other lines of code are just for example. 

        playWhen
        {
        keyPressed = r
        }
}
playlist
{
        name = 
        loop =
        shuffle =
        preloadTime =

And can i do this?

        playWhen
        {
        keyPressed = r
        scene = MainMenu
        }
}
playlist
{
        name = ExampleSong
        loop = False
        shuffle = False
        preloadTime = 1

 

Edited by Dr. Kerbal
Link to comment
Share on other sites

Trying to write a simple calculator that does the math for this:

 

I'm trying to get the burn time, which I can do with Tsiolkovsky's rocket equation, (calculate the amount of fuel burned for a given ΔV , and divide by the burn rate)

but for that I need the exhaust velocity (Isp), and burn rate for the active vessel, how do I get those?

I've done a bit of searching on here and google, but it's not clear. I don't think I need to worry about atmosphere just vacuum.

 

Link to comment
Share on other sites

How might I find the resource concentration for resources in an atmosphere? E.g. on kerbin, what are the values for intakeAir? Is that purely a check for hasOxygen followed by a pressure/speed calculation, or is the quantity something that can be modified?

 

EDIT:
After several hours spent trawling the documentation, I got it figured out. For anyone else wondering how on kerbin to find those values:

ResourceCache cache = ResourceCache.Instance;
// These are lists of all applicable resources.
cache.GlobalResources 
cache.PlanetaryResources
cache.BiomeResources

 

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