Cephei

The official unoffical "help a fellow plugin developer" thread

Recommended Posts

@katateochi build your own with an if statement looking at Vessel.Situations. 

https://kerbalspaceprogram.com/api/class_vessel.html#acea90b73d9b4523383503ec82f2f63bb

bool vesselDocked;
OnFixedUpdate()
{
	if(Vessel.Situations == 128)
	{
  		vesselDocked = true;
	}
}

(Note: i don’t know if the above code will work, but its my best guess. I have never worked with enums, I’m just relaying what I found. Official MS documentation here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum)

Share this post


Link to post
Share on other sites

@Benjamin Kerman @Aelfhe1m thanks guys! I think thats got me in the right direction.  Still not quite got it, now when I dock the button is shown on the context menu, but it's shown for all docking ports on the vessel, not just the one that's actually docked. But I think I'm at least on the right path now!

Share this post


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

@Benjamin Kerman @Aelfhe1m thanks guys! I think thats got me in the right direction.  Still not quite got it, now when I dock the button is shown on the context menu, but it's shown for all docking ports on the vessel, not just the one that's actually docked. But I think I'm at least on the right path now!

 

Take a look at this - it should all be inside your PartModule:

 

ModuleDockingNode pm;
            
// on startup save the docking node PartModule
for (int i = currentVessel.Parts.Count - 1; i >= 0; --i)
{
    for (int j = currentVessel.parts[i].Modules.Count - 1; j >= 0; --j)
	{
		if (currentVessel.parts[i].Modules[j].moduleName == "ModuleDockingNode")
		{
          pm = (ModuleDockingNode)currentVessel.parts[i].Modules[j];
		}
	}   
}

// set up trigger for docking
GameEvents.onVesselStandardModification.Add(purpleMonkeyDishwasher);

// FYI GameEvents.onVesselStandardModification collects various vessel events and fires them off with a single one.
// Specifically -onPartAttach,onPartRemove,onPartCouple,onPartDie,onPartUndock,onVesselWasModified,onVesselPartCountChanged

// set up button

[KSPEvent(guiActive = true, guiName = "Magic!!!")]
        public void MyMagicButton()
        {
        }

// when event triggered, change button state
public void purpleMonkeyDishwasher(Vessel gameEventVessel = null)
{
  Events["MyMagicButton"].active = (pm.state.StartsWith("Docked") || pm.state.StartsWith("PreAttached"))
}

 

You can't just capture the docking event; you need to capture the undocking event too, so you can set it back to not visible.

 

Share this post


Link to post
Share on other sites

@hab136 thanks, that `onVesselStandardModification` Event is useful. 

The problem that I have with all the approaches I've tried is that the state of the ModuleDockingNode during the call to the Event is the state proceeding the state it ends up in.  So for example if I have a undocked port which has the state of 'ready', when it docks and the `onVesselStandardModification` fires, the state that it has is "Acquire", although moments later it's state changes to docked. Similarly a port which is docked, when undocking the state that is present during the Event is still "Docked".  

So it seems like onVesselStandardModification fires just before the dock/undock (or the change in the state comes slightly after that).  What I need is something that fires after the state has changed. 

As an experiment I used a Coroutine so I could add in a short delay (of just 1 ms) before it checks the state.  With that short delay the state after docking/undocking is what I'd expect, but the downside is even that short delay causes a slight pause in the game.  Also depending on a set time delay is not an good approach.

Share this post


Link to post
Share on other sites

In the end I've solved it like this, might not be an ideal solution but I can't see a better way, but this seems to work;
I'm still pretty new to C# and plugin development so any feedback on the approach is welcome!
Thanks @Benjamin Kerman, @Aelfhe1m and @hab136 for your answers.

The idea is that in onVesselStandardModification it raises a flag to check the state of ModuleDockingNode and sets a counter. The counter is decremented by 1 on each call of onUpdate and when it hits 0 then it checks the state again and compares it to the result of the last check.  If the state has changed since the last check then it resets the counter, if it has remained the same then it removes the flag and takes the required action based on the state.  So basically it waits a few cycles of onUpdate for the state to stop changing before taking the required action.
I wanted to avoid using onUpdate for this as that's called constantly, but at least this way it only does anything in onUpdate if the flag to check state is raised.

internal ModuleDockingNode docking_module;
internal bool state_changed = false;
internal int state_check_delay;
internal string last_state;

public override void OnStart(StartState state){
    docking_module = this.part.FindModuleImplementing<ModuleDockingNode>();
    onVesselModify();
    GameEvents.onVesselStandardModification.Add(onVesselModify);
}

public void onDestroy(){
    GameEvents.onVesselStandardModification.Remove(onVesselModify);
}

public void onVesselModify(Vessel ves = null){
    check_state(true);
}

internal virtual void check_state(bool force_check = false){
    if (force_check) {
        last_state = "";
    }
    if (docking_module.state != last_state) {
        state_changed = true;
        state_check_delay = 10;
        last_state = docking_module.state;
    } else {
        state_changed = false;
        Events["fuel_pump"].active = (docking_module.state.StartsWith("Docked") || docking_module.state.StartsWith("PreAttached") );
    }
}

public override void OnUpdate(){
    if (state_changed) {
        state_check_delay -= 1;
        if (state_check_delay <= 0) {
            check_state();
        }
    }
}




 

Share this post


Link to post
Share on other sites

Is there a way to get a list of installed mods in game?  
So far all I've got is to get a list of parts from `PartLoader.LoadedPartsList`. From that I can iterate over each AvailablePart and for each one get its path in GameData and take the top level folder as the name of the mod for the part, but that is an assumption that doesn't work with all mods (I can also lookup the part_name in KerbalX's mod database, but that only helps for mods which KerbalX already knows about).
Also this doesn't yield any information about mods which don't add any parts.  So does the game have any internal notion of which mods are installed?  I'm guessing not as I don't see where that information would come from, but asking anyway just incase there is some sneaky solution.

Share this post


Link to post
Share on other sites
1 hour ago, katateochi said:

Is there a way to get a list of installed mods in game?  
So far all I've got is to get a list of parts from `PartLoader.LoadedPartsList`. From that I can iterate over each AvailablePart and for each one get its path in GameData and take the top level folder as the name of the mod for the part, but that is an assumption that doesn't work with all mods (I can also lookup the part_name in KerbalX's mod database, but that only helps for mods which KerbalX already knows about).
Also this doesn't yield any information about mods which don't add any parts.  So does the game have any internal notion of which mods are installed?  I'm guessing not as I don't see where that information would come from, but asking anyway just incase there is some sneaky solution.

Problem is that there isn't really a standard notion of what a mod is.  Most mods are self-contained within a directory in GameData, but some mod authors choose to put all their mods in the same GameData directory and then have the individual mods' directories under that.  And then there's ModuleManager which is installed directly into GameData without a directory.  KSP pretty much just loads everything in GameData without considering how it's organized.

Share this post


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

Problem is that there isn't really a standard notion of what a mod is.  Most mods are self-contained within a directory in GameData, but some mod authors choose to put all their mods in the same GameData directory and then have the individual mods' directories under that.  And then there's ModuleManager which is installed directly into GameData without a directory.  KSP pretty much just loads everything in GameData without considering how it's organized.

I thought that would be the case. It's a pity that from the ground up they didn't build in some process where mods "register" themselves with the game and set their name/version....but then again, at least this is better than the dark days where all parts just got dumped into the Parts folder and likewise for plugins (speaking of which, why do those folders still exist?).  

Share this post


Link to post
Share on other sites
4 hours ago, katateochi said:

I thought that would be the case. It's a pity that from the ground up they didn't build in some process where mods "register" themselves with the game and set their name/version....but then again, at least this is better than the dark days where all parts just got dumped into the Parts folder and likewise for plugins (speaking of which, why do those folders still exist?).  

Is there anything useful in Game.loaderInfo? I just noticed this interesting part of a save file:

        LoaderInfo
        {
                ModuleManager.2.7.5 v2.7.5.0 = False
                KerbalEngineer.Unity v1.0.0.0 = False
                KerbalEngineer v1.1.2.8 = False
                MiniAVC v1.0.3.2 = False
                ModularFlightIntegrator v1.0.0.0 / v1.2.2.0 = False
                Kopernicus.Components v1.0.0.0 = True
                Kopernicus.OnDemand v1.0.0.0 = True
                Kopernicus.Parser v1.0.0.0 = True
                Kopernicus v1.0.0.0 = True
                TacLib v0.0.0.0 = False
                USAFOrion v1.0.0.0 = False
                KerbalEngineer = True
                Kopernicus = True
                ModularFlightIntegrator = True
                PartOverhauls = False
                USAFOrionTD = False

And a search of the API for that name turns up loaderInfo = new Dictionary<string, bool>() in Game. Sounds like it might be the same structure.

Share this post


Link to post
Share on other sites

What is PartLab? Where is it? On the wiki, its mentioned, and seems important to the process but I cant seem to find any links or mention of it outside of that through google.

Edited by USB4
Spelling

Share this post


Link to post
Share on other sites
21 hours ago, HebaruSan said:

Is there anything useful in Game.loaderInfo? I just noticed this interesting part of a save file:

oh! interesting. yeah I think that is exactly the info I'm looking for.  It seems to keep track of mods which have been removed too, (those are listed as = false). Not quite sure how it sets the version numbers, some seem to correlate to the mod's version and others (and mods with no version info) just seem to get assigned something.
But that is some useful info, thanks for finding it! 

Share this post


Link to post
Share on other sites

I am trying to create a contract type and I want the contracts to generate only for bodies other than the home planet. How would i do this?

Share this post


Link to post
Share on other sites
On 20/11/2017 at 12:25 AM, DeltaDizzy said:

I am trying to create a contract type and I want the contracts to generate only for bodies other than the home planet. How would i do this?

Are you using Contract Configurator for the contract manager?

If so then:

targetBody = AllBodies().Where(cb => !cb.IsHomeWorld()).Random()

That will select a random body that can be ANYTHING except the home world (suns, planets, moons, sigma binary barycentres etc). There are a lot of options in the celestial body expressions to filter it further - see wiki.

Share this post


Link to post
Share on other sites

I am trying to modify some settings for destructible buildings and found some older code / mods that have done this before. I tried implementing the following but when I use

  [KSPAddon(KSPAddon.Startup.Flight, true)] I get an exception when using

  [KSPAddon(KSPAddon.Startup.MainMenu, true)] , I get no errors however, the settings do not seem to persist

    public class BuildingDamage : MonoBehaviour
    {
        void Awake()
        {
            Debug.Log("[BDArmory]: Modifying Buildings");

            foreach (KeyValuePair<string, ScenarioDestructibles.ProtoDestructible> des in ScenarioDestructibles.protoDestructibles)
            {
                DestructibleBuilding building = des.Value.dBuildingRefs[0];
                building.damageDecay = 600f;
                building.impactMomentumThreshold *= 150;
            }

Anyone happen to know the proper way to persist these settings?

Edited by gomker

Share this post


Link to post
Share on other sites

Hello, I hope this is the place to ask this.

 

I recently re-built my mod BDDMP, to run with the latest version of KSP.

 

While doing so, one of my users got a "System.Reflection.ReflectionTypeLoadException" while loading my mod. This doesn't occur when I run the mod, andI normally develop in Java not C#. so am curious how to fix it (what issues I should be looking for, etc.)

 

Error text:

[ERR 20:51:50.643] AssemblyLoader: Exception loading 'BDDMP': System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded.
  at (wrapper managed-to-native) System.Reflection.Assembly:GetTypes (bool)
  at System.Reflection.Assembly.GetTypes () [0x00000] in <filename unknown>:0 
  at AssemblyLoader.LoadAssemblies () [0x00000] in <filename unknown>:0 

Additional information about this exception:

 System.TypeLoadException: Could not load type 'BDDMP.BDDMPSynchronizer' from assembly 'BDDMP, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.

 System.IO.FileNotFoundException: Could not load file or assembly 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
File name: 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null'

 System.IO.FileNotFoundException: Could not load file or assembly 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
File name: 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null'

 System.IO.FileNotFoundException: Could not load file or assembly 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
File name: 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null'

 System.IO.FileNotFoundException: Could not load file or assembly 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
File name: 'BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null'

 System.TypeLoadException: Could not load type '<>c__DisplayClass42_0' from assembly 'BDDMP, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.

 [... Duplicates removed to abreviate log ...]
 
 System.TypeLoadException: Could not load type '<>c__DisplayClass52_0' from assembly 'BDDMP, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.

 

When I encountered this issue on my end while re-porting my code it was because it was loaded before the other DLLs, but the log does say it is located in "\GameData\zzBDDMP\BDDMP.dll" which should load after other assemblies.

Thanks

Edited by jediminer543

Share this post


Link to post
Share on other sites
1 hour ago, jediminer543 said:

Hello, I hope this is the place to ask this.

 

I recently re-built my mod BDDMP, to run with the latest version of KSP.

 

While doing so, one of my users got a "System.Reflection.ReflectionTypeLoadException" while loading my mod. This doesn't occur when I run the mod, andI normally develop in Java not C#. so am curious how to fix it (what issues I should be looking for, etc.)

 

Error text:

Spoiler

 

 

When I encountered this issue on my end while re-porting my code it was because it was loaded before the other DLLs, but the log does say it is located in "\GameData\zzBDDMP\BDDMP.dll" which should load after other assemblies.

Thanks

It looks like the plugin is built against BDArmory, so it's expecting to find that plugin too but it's not there (or it did not load properly)

Share this post


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

It looks like the plugin is built against BDArmory, so it's expecting to find that plugin too but it's not there (or it did not load properly)

[LOG 20:51:50.514] Load(Assembly): /ModuleManager.2.8.1
[LOG 20:51:50.515] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\ModuleManager.2.8.1.dll
[LOG 20:51:50.541] AssemblyLoader: KSPAssembly 'ModuleManager' V2.5
[LOG 20:51:50.541] Load(Assembly): BDArmory/Plugins/BDArmory.Core
[LOG 20:51:50.542] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\BDArmory\Plugins\BDArmory.Core.dll
[LOG 20:51:50.543] AssemblyLoader: KSPAssembly 'BDArmory.Core' V1.0
[LOG 20:51:50.544] Load(Assembly): BDArmory/Plugins/BDArmory
[LOG 20:51:50.544] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\BDArmory\Plugins\BDArmory.dll
[LOG 20:51:50.547] AssemblyLoader: KSPAssembly 'BDArmory' V1.0
[LOG 20:51:50.548] Load(Assembly): DarkMultiPlayer/Plugins/DarkMultiPlayer-Common
[LOG 20:51:50.548] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\DarkMultiPlayer\Plugins\DarkMultiPlayer-Common.dll
[LOG 20:51:50.550] Load(Assembly): DarkMultiPlayer/Plugins/DarkMultiPlayer
[LOG 20:51:50.550] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\DarkMultiPlayer\Plugins\DarkMultiPlayer.dll
[LOG 20:51:50.552] Load(Assembly): DarkMultiPlayer/Plugins/ICSharpCode.SharpZipLib
[LOG 20:51:50.553] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\DarkMultiPlayer\Plugins\ICSharpCode.SharpZipLib.dll
[LOG 20:51:50.555] Load(Assembly): DarkMultiPlayer/Plugins/MessageWriter2
[LOG 20:51:50.555] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\DarkMultiPlayer\Plugins\MessageWriter2.dll
[LOG 20:51:50.557] Load(Assembly): PhysicsRangeExtender/Plugins/PhysicsRangeExtender
[LOG 20:51:50.558] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\PhysicsRangeExtender\Plugins\PhysicsRangeExtender.dll
[LOG 20:51:50.560] Load(Assembly): Squad/Plugins/KSPSteamCtrlr
[LOG 20:51:50.560] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\Squad\Plugins\KSPSteamCtrlr.dll
[LOG 20:51:50.561] Load(Assembly): Squad/Plugins/Steamworks.NET
[LOG 20:51:50.562] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\Squad\Plugins\Steamworks.NET.dll
[LOG 20:51:50.564] Load(Assembly): zzBDDMP/BDDMP
[LOG 20:51:50.564] AssemblyLoader: Loading assembly at E:\Games\KSP\Kerbal.Space.Program.v1.3.1.1891.part1\Kerbal.Space.Program.v1.3.1.1891\GameData\zzBDDMP\BDDMP.dll
[LOG 20:51:50.583] AssemblyLoader: KSPAssembly 'BDDMP' V1.0
[LOG 20:51:50.584] AssemblyLoader: Loading assemblies
[WRN 20:51:50.641] ADDON BINDER: Ingoring binding redirect due to incompatible versions: BDArmory, Version=1.0.0.39495, Culture=neutral, PublicKeyToken=null => BDArmory, Version=1.0.0.21477, Culture=neutral, PublicKeyToken=null

This is the loading sequence from their addon loading, which clearly states that BDA loaded correctly.

It says there is a version mismatch, but I explicitly ensured that BDDMP doesn't request a specific version of BDA (They SHOULD both be using the V1.0 release, since no new releases have occured since that time). Is there something else that I need to toggle.

 

Thanks

 

Edit: Recompiling against CKAN version of BDA worked, but my point of not checking versions still stands.

Edited by jediminer543

Share this post


Link to post
Share on other sites
7 hours ago, jediminer543 said:

This is the loading sequence from their addon loading, which clearly states that BDA loaded correctly.

It says there is a version mismatch, but I explicitly ensured that BDDMP doesn't request a specific version of BDA (They SHOULD both be using the V1.0 release, since no new releases have occured since that time). Is there something else that I need to toggle.

 

Thanks

 

Edit: Recompiling against CKAN version of BDA worked, but my point of not checking versions still stands.

AFAIK, if you compile against an assembly, then the AssemblyVersion of it is baked into that reference and it will refuse to use any other version.  Makes it kind of difficult to add dependencies.

Share this post


Link to post
Share on other sites

I've got a couple questions in the context of the editors:

1) How can I tell if I am in the VAB or the SPH?
I can check HighLogic.LoadedScene.CompareTo(GameScenes.EDITOR) which returns 0 when in either editor, or more simply HighLogic.LoadedSceneIsEditor which returns a bool
but they don't differentiate between either editor. 

I found the answer before posting, but I thought I'd include what I found in case anyone else was looking for this too;
use EditorDriver.editorFacility ie: EditorDriver.editorFacility.CompareTo(EditorFacility.SPH)

2) How can I check if the currently loaded craft has unsaved changes?
I've found the GameEvent `GameEvents.onEditorShipModified` which is triggered after any changes to the ship so if that event has fired I can assume the ship has unsaved changes. But then I'd also need to hook into the ship being saved, or a ship being loaded or a new ship being created.  onEditorRestart seems to be called when a new ship is created and when a ship is loaded, but in the case of a ship being loaded onEditorShipModified is also called just after.  I can also use EditorLogic.fetch.saveBtn.onClick.AddListener(<stuff>) to detect save being clicked.  
But doing this just seems horribly convoluted and isn't an airtight solution. 

In a previous attempt I used File.Exists(ShipConstruction.GetSavePath("craftname")) to check if a file for the craft existed (returned false if not) and then if there was a file I used EditorLogic.fetch.ship.SaveShip().Save(temp_path) to save the currently loaded craft to a temporary location, then loaded both files and did a hexdigest of each and compared them.  That approach also seems sloppy, but honestly I think it's better than the above solution and has less chance of being wrong.

But surely there must be something simple like EditorLogic.fetch.ship.isSaved 

Share this post


Link to post
Share on other sites
21 hours ago, Benjamin Kerman said:

@katateochi I found EditorLogic.saveShip(). Could be used for saving. I was also looking into Part.onAttach, but I can’t figure out the usage of it…

I'd also found that to create a save, but can't find anyway to find out if the currently loaded craft is saved. Short of setting up a whole load of Event callbacks and trying to track the state (which just feels like a totally wrong way to go about it) or saving the current craft to a temp location and comparing the file contents with the existing craft file (which also feels like a poor solution).  
There must be a boolean property somewhere that says if the current craft is saved or not. surely? I wonder how the stock solution works. 

Share this post


Link to post
Share on other sites

I was thinking a little bool in your program that flips if EditorLogic.saveShip is called @katateochi

class ShipSaveTracker : EditorLogic
{
  
  bool saved;

  public void EditorLogic.saveShip()
    saved = true;
  
}

 

Share this post


Link to post
Share on other sites

@katateochi

EditorLogic.fetch.editorType should also tell you VAB/SPH.  https://anatid.github.io/XML-Documentation-for-the-KSP-API/class_editor_logic.html

The oddly-named EditorLogic.fetch.SetBackup() causes the currently active ship to be marked as dirty (needs to be saved), as if someone has modified it.  AFAIK there's no public variable you can check to see if the ship is dirty; you'd have to catch the ship modification events and set your own variable.  GameEvents.onEditorShipModified.Add() and GameEvents.onGameStateSaved.Add(). https://kerbalspaceprogram.com/api/class_game_events.html

Unless you're making a save-status indicator, perhaps there's another way to do what you want without checking the save status?

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