Jump to content

The official unoffical "help a fellow plugin developer" thread


Recommended Posts

Running into a little bit of confusion with Module Manager filtering. I'm working on a mod right now that needs to be able to target ONLY parts with crew capacity greater than 0—i.e., only crew-able parts. From what it looks like, there are no < or > operators in the MM syntax. Would this work?

@PART[*]:HAS[CrewCapacity]:HAS[!CrewCapacity[0]]]
{
  //myModStuff
}

 

Edited by blorgon
Link to comment
Share on other sites

Just now, blowfish said:

@blorgon Also this should probably go in the ModuleManager thread.  Not really related to plugins.

Well, I'm writing a plugin that I need to apply to those specific parts, and from what I understand, this is how you apply plugins in the game, correct? I'm brand new to this stuff, so... sorry about that!

Link to comment
Share on other sites

Okay, here's one I'm pretty sure belongs here. Just a heads up, I'm very new to C# and programming in general. I've actually got about 60% of the C# part of program written. Right now I'm trying to figure out how to interface with the game itself.

Here's my question: how can I get my plugin to read a .craft file? Specifically, I need to define a string using a value that will be entered by the user via an in-game GUI, which will be "stored" in a module in the .craft file.

Edited by blorgon
Link to comment
Share on other sites

How to detect collisions with the active vessel?

This code spawns a collider in vessel CoM, but trigger does not trigger

    public class collisionTest : MonoBehaviour
    {
        public BoxCollider boxCollider1 = new GameObject().AddComponent<BoxCollider>();

        public collisionTest()
        {
            boxCollider1.isTrigger = true;
            boxCollider1.enabled = true;
            boxCollider1.transform.position = FlightGlobals.ActiveVessel.CoM;
        }

        void OnTriggerEnter(Collider other)
        {
            Debug.Log(other.gameObject.name);
        }

        void OnCollisionEnter(Collision collision)
        {
            Debug.Log(collision.gameObject.name);
        }
    }

Wen boxCollider1.isTrigger is set to false, pushes the vessel, but there is no log.

All I can find about colliders uses the unity editor thing (like this one http://unity3d.com/es/learn/tutorials/modules/beginner/physics/colliders-as-triggers?playlist=17120 ) I use Visual Studio

Link to comment
Share on other sites

You should to :

  • set the collider size
  • set its layer.
  • add a RigidBody (Adding the BoxCollider may do it, not sure)

And do not use constructor to spawn gameobject and init them. Use Awake/Start or you will run into strange Unity behaviors.

And I have doubt that it will trigger if spawned inside anyway.

 

Link to comment
Share on other sites

8 hours ago, sarbian said:

You should to :

  • set the collider size
  • set its layer.
  • add a RigidBody (Adding the BoxCollider may do it, not sure)

And do not use constructor to spawn gameobject and init them. Use Awake/Start or you will run into strange Unity behaviors.

And I have doubt that it will trigger if spawned inside anyway.

 

It works. Thanks!

Link to comment
Share on other sites

I'm fairly well-versed in scientific code development but not well-versed in programming in this kind of environment. After an hour of googling I'm not sure I'm any closer to understanding how to get started.

I'm not trying to develop an addon from scratch, I'm trying to set up an environment where I can test code in existing addons to aid in bug squashing for my favorite addons.

Can anyone point me to an up-to-date tutorial for getting started with this kind of testing? How can one inspect code during execution in KSP? Basically how does one setup a testing and debugging environment? This feels like a noob question but most of the stuff that's coming up in my google searches is for designing parts and such, which I'm not really interested in.

edit: I see sarbian's sticky in this very forum. I had read a.g.'s stickied post, which warns that it's only for experienced developers, and in my head I thought the two posts were the same because the titles are so similar. Still, if there's any up-to-date slightly more beginner's tutorials that you guys know of, please pass it along. Getting started feels like the most daunting part here and I don't want it to discourage me so much that I decide to just throw my hands up and not do it. I have a 2 month old baby at home and am only able to work part-time at the moment, so I could be easily dissuaded :P

edit2: the add-on development sub-forum structure confused me a little, but now I'm working over the thread from the forum one-up from this sub-forum. I would have thought the links compilation would have been in this sub-forum.

Edited by drhay53
Link to comment
Share on other sites

9 hours ago, drhay53 said:

edit: I see sarbian's sticky in this very forum. I had read a.g.'s stickied post, which warns that it's only for experienced developers, and in my head I thought the two posts were the same because the titles are so similar. Still, if there's any up-to-date slightly more beginner's tutorials that you guys know of, please pass it along.

The instructions for setting up debugging in Sarbian's sticky in this forum are a lot more simple than they may appear.  You should have no difficulties setting it up.  You will probably have more hassle building some mods from their source (which will be required for the source level debugging) as some mods may not include solution or project files and all mod projects will probably need the references to be reset to point at your path to KSP.

9 hours ago, drhay53 said:

edit2: the add-on development sub-forum structure confused me a little, but now I'm working over the thread from the forum one-up from this sub-forum. I would have thought the links compilation would have been in this sub-forum.

This sub-forum is specifically for plugin DLL development where the parent forum is for mod development as a whole.  It might make more sense if all the info about plugins were moved to a new thread in this sub-forum and the thread in the parent just pointed to it.

Link to comment
Share on other sites

I'm starting now, therefore I have no previous experience with modding. 

How would i go to calculate if two parts are touching each other?

First thing i thought was to calculate their distance, however: how do i get the CoM? I checked the API in the Part class but i got really confused. Then how would i go to check the boundaries? And see if they are touching?

Would collision be a better way? How would i go for that?

Thanks in advance 

Link to comment
Share on other sites

@RattiRatto

A part CoM is part.rb.worldCenterOfMass

Then for the bounds you best option would be to use the static calls of PartGeometryUtil.  PartGeometryUtil.GetColliderBounds(part.go) or PartGeometryUtil.GetPartRendererBound (part.go)  depending on what you need and how often you do it (Rendered will be slower).

Then you use Bounds.Intersects

If you want something more precise you should use the call that gives you each bounds and check then against each of the other part bounds.

Link to comment
Share on other sites

 

1 hour ago, sarbian said:

@RattiRatto

A part CoM is part.rb.worldCenterOfMass

Then for the bounds you best option would be to use the static calls of PartGeometryUtil.  PartGeometryUtil.GetColliderBounds(part.go) or PartGeometryUtil.GetPartRendererBound (part.go)  depending on what you need and how often you do it (Rendered will be slower).

Then you use Bounds.Intersects

If you want something more precise you should use the call that gives you each bounds and check then against each of the other part bounds.

First, thanks for the detailed answer!

I'm not proficient with Unity nor with gaming in general, so my questions might sound silly (i really never had to program something that was in 3d!).

Why should i use the static calls to get the boundaries more than once? I mean once i have the shape of it, why do i need to keep calling it? Can't all the movement from here be handled just by knowing the position of the CoM?

 

Link to comment
Share on other sites

I can't remember the coordinate system used by the bounds. I think they are in local coord and you would have to rotate/translate them. If that the case you would only to get them once.

Link to comment
Share on other sites

On 31/12/2015 at 2:57 AM, xEvilReeperx said:

@JPLRepoI noticed that ProgressTree has a Deploy and Stow method which traverses the tree and runs some callbacks that hook/unhook each ProgressNode listener from their respective events. You could skip the ProgressNode (CelestialBodyScience) you're accidentally tripping by playing a little GameEvent shell game inside your contract using those callbacks... of course, it's a bit ugly :0.0:


// TSTTelescopeContract

private void FakeCallback(float f, ScienceSubject s, ProtoVessel p, bool b)
{
    GameEvents.OnScienceRecieved.Remove(FakeCallback);
}

protected override void AwardCompletion()
{
    base.AwardCompletion();

    var bodyName = GetParameter<TSTTelescopeContractParam>().target.name;
    var targetBody = FlightGlobals.Bodies.FirstOrDefault(cb => bodyName == cb.name);

    if (targetBody == null)
    {
        Debug.LogWarning("Couldn't find CelestialBody with name " + bodyName + " that " +
                                        typeof(TSTTelescopeContractParam).Name + " specifies");
        return;
    }

    var subtree = ProgressTracking.Instance.GetBodyTree(targetBody);

    if (subtree.science.Subtree.Count > 0)
    {
        Debug.LogWarning("Multiple science subtree nodes for " + bodyName + " -- investigate");
        return;
    }


    subtree.science.OnStow(); // removes it from onScienceReceived
    GameEvents.OnScienceRecieved.Add(FakeCallback); // we need a fake callback because the one that triggered this method call was removed by 
                                                    // TSTScienceParam.OnUnregister. GameEvent callbacks are stored in a list and called in reverse order
                                                    // so the subtree.science callback we're about to add would otherwise be the next called
    subtree.science.OnDeploy(); // adds to end of onScienceReceived
}

 

Hoping for some help again from the expert... @xEvilReeperx Seems in 1.1+ this doesn't work any more.
Investigations I have conducted so far appears the FakeCallback is not being called... and the subtree.science.OnDeploy() is...
The AwardCompletion() is being called before GameEvent.OnScienceReceived is fired as it always has. A debug run sees the OnStow removes the callback for ProgressTracker from the GameEvents list and the FakeCallback and OnDeply re-adding. but something isn't right.
I'll keep trying to figure it out. On the surface has the GameEvents order changed?

Link to comment
Share on other sites

@JPLRepo

The order hasn't changed, but now the event list gets copied into a local value so it can't be affected while the event is firing (meaning all the previous code is useless now). You could either rebuild the ConfigNode as was suggested earlier or else devise a way to control GameEvents.onScienceReceived somehow. Since that field is actually writable and you can force ProgressNode to add/remove callbacks at will, it's possible to mickey mouse a solution by tricking ProgressNode into registering for a proxy GameEvent which you can then control as you want.

Here's my proof of concept:

[KSPAddon(KSPAddon.Startup.Instantly, true)]
class ScienceProgressionBlocker : MonoBehaviour
{
    public static readonly EventData<float, ScienceSubject, ProtoVessel, bool> ProxyOnScienceReceived =
        new EventData<float, ScienceSubject, ProtoVessel, bool>("Proxy.OnScienceReceived");

    private static ScienceProgressionBlocker Instance { get; set; }
    private static bool _block = false;

    public static void BlockSingleEvent()
    {
        _block = true;
    }


    private void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(this); // GameEvents throws an exception on static methods, so we need a reference ;\

        // We want this event to be the very first one, if possible. That will ensure it runs last
        GameEvents.OnScienceRecieved.Add(OnScienceReceived);
    }


    private void OnScienceReceived(float data0, ScienceSubject data1, ProtoVessel data2, bool data3)
    {
        if (!_block)
            ProxyOnScienceReceived.Fire(data0, data1, data2, data3);

        _block = false;
    }
}


[KSPScenario(ScenarioCreationOptions.AddToAllGames, GameScenes.FLIGHT, GameScenes.TRACKSTATION, GameScenes.SPACECENTER)]
public class ProgressTracker_TrapTest : ScenarioModule
{
    private IEnumerator Start()
    {
        yield return new WaitForEndOfFrame(); // ProgressTracker might not have started, wait and make sure

        if (ProgressTracking.Instance == null)
        {
            Debug.LogError("ProgressTracking instance not found!");
            yield break;
        }

        foreach (var cb in FlightGlobals.Bodies)
        {
            var tree = ProgressTracking.Instance.GetBodyTree(cb);
            var scienceAchievement = tree.science;

            // remove science callbacks to onScienceReceived and onScienceDataTransmitted
            scienceAchievement.OnStow();

            // wrap the add/remove callbacks inside another little method that tricks them into
            // registering with the proxy GameEvents
            var originalStow = scienceAchievement.OnStow;
            var originalDeploy = scienceAchievement.OnDeploy;

            scienceAchievement.OnStow = () =>
            {
                SwapInProxyEvents(originalStow);
            };

            scienceAchievement.OnDeploy = () =>
            {
                SwapInProxyEvents(originalDeploy);
            };

            // restore science callbacks (although now they'll register with ScienceProgressionBlocker instead of the real ones)
            scienceAchievement.OnDeploy();
        }
    }


    private static void SwapInProxyEvents(Callback call)
    {
        var original = GameEvents.OnScienceRecieved;

        try
        {
            GameEvents.OnScienceRecieved = ScienceProgressionBlocker.ProxyOnScienceReceived;
            call();
        }
        finally
        {
            GameEvents.OnScienceRecieved = original;
        }
    }
}

And then your contract award method becomes this:

        protected override void AwardCompletion()
        {
            ScienceProgressionBlocker.BlockSingleEvent();
            base.AwardCompletion();
        }

As is this will only prevent science progression if the related contract is active and submitting science (whether recovery or transmission) completes it. You might not want the telescope to trip any progress nodes at all ever in which case you could filter out any events involving a particular experiment or however you want to do it

Link to comment
Share on other sites

4 hours ago, xEvilReeperx said:

@JPLRepo

The order hasn't changed, but now the event list gets copied into a local value so it can't be affected while the event is firing (meaning all the previous code is useless now). You could either rebuild the ConfigNode as was suggested earlier or else devise a way to control GameEvents.onScienceReceived somehow. Since that field is actually writable and you can force ProgressNode to add/remove callbacks at will, it's possible to mickey mouse a solution by tricking ProgressNode into registering for a proxy GameEvent which you can then control as you want.

Here's my proof of concept:


[KSPAddon(KSPAddon.Startup.Instantly, true)]
class ScienceProgressionBlocker : MonoBehaviour
{
    public static readonly EventData<float, ScienceSubject, ProtoVessel, bool> ProxyOnScienceReceived =
        new EventData<float, ScienceSubject, ProtoVessel, bool>("Proxy.OnScienceReceived");

    private static ScienceProgressionBlocker Instance { get; set; }
    private static bool _block = false;

    public static void BlockSingleEvent()
    {
        _block = true;
    }


    private void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(this); // GameEvents throws an exception on static methods, so we need a reference ;\

        // We want this event to be the very first one, if possible. That will ensure it runs last
        GameEvents.OnScienceRecieved.Add(OnScienceReceived);
    }


    private void OnScienceReceived(float data0, ScienceSubject data1, ProtoVessel data2, bool data3)
    {
        if (!_block)
            ProxyOnScienceReceived.Fire(data0, data1, data2, data3);

        _block = false;
    }
}


[KSPScenario(ScenarioCreationOptions.AddToAllGames, GameScenes.FLIGHT, GameScenes.TRACKSTATION, GameScenes.SPACECENTER)]
public class ProgressTracker_TrapTest : ScenarioModule
{
    private IEnumerator Start()
    {
        yield return new WaitForEndOfFrame(); // ProgressTracker might not have started, wait and make sure

        if (ProgressTracking.Instance == null)
        {
            Debug.LogError("ProgressTracking instance not found!");
            yield break;
        }

        foreach (var cb in FlightGlobals.Bodies)
        {
            var tree = ProgressTracking.Instance.GetBodyTree(cb);
            var scienceAchievement = tree.science;

            // remove science callbacks to onScienceReceived and onScienceDataTransmitted
            scienceAchievement.OnStow();

            // wrap the add/remove callbacks inside another little method that tricks them into
            // registering with the proxy GameEvents
            var originalStow = scienceAchievement.OnStow;
            var originalDeploy = scienceAchievement.OnDeploy;

            scienceAchievement.OnStow = () =>
            {
                SwapInProxyEvents(originalStow);
            };

            scienceAchievement.OnDeploy = () =>
            {
                SwapInProxyEvents(originalDeploy);
            };

            // restore science callbacks (although now they'll register with ScienceProgressionBlocker instead of the real ones)
            scienceAchievement.OnDeploy();
        }
    }


    private static void SwapInProxyEvents(Callback call)
    {
        var original = GameEvents.OnScienceRecieved;

        try
        {
            GameEvents.OnScienceRecieved = ScienceProgressionBlocker.ProxyOnScienceReceived;
            call();
        }
        finally
        {
            GameEvents.OnScienceRecieved = original;
        }
    }
}

And then your contract award method becomes this:


        protected override void AwardCompletion()
        {
            ScienceProgressionBlocker.BlockSingleEvent();
            base.AwardCompletion();
        }

As is this will only prevent science progression if the related contract is active and submitting science (whether recovery or transmission) completes it. You might not want the telescope to trip any progress nodes at all ever in which case you could filter out any events involving a particular experiment or however you want to do it

Now how did I miss that. :sticktongue::cool:@xEvilReeperx thanks for that pick-up. I'll work to develop what I need from that. Thanks again.

Link to comment
Share on other sites

  • 2 weeks later...

Hi, been busting my ass for the last 6 hours trying to get my code, making a single engine craft hover, to work. I've tried like 7 methods on how to make it hover around a target altitude, but it just isn't stable enough (the methods make the altitude vary between everything from 10 meters to 200). I need some sort of formula to set the throttle, and I've tried so many I can't even remember them. The latest one I have tried is:

throttle = (1 / (currentAlt / targetAlt)) * ((vesselmass * localg) / thrustAvailable);

But it is very unstable, craft goes up and down a lot. Has anyone done something like this before and can help me out? Also, MechJebs KEEP VERT is unstable too, so can't borrow anything from there.

 

Also why doesn't this work? It's the same way (or very close to how) it's done by MechJeb.

(Vector3d)CoM = vessel.findWorldCenterOfMass();
(float)localg = FlightGlobals.getGeeForceAtPosition(CoM).magnitude;

When I set localg this way, nothing works at all, I don't get any KSPEvents/Fields displayed, nothing. If I just set localg = 9.81f, it works.

 

Thanks in advance!

Link to comment
Share on other sites

I've done pretty much exactly this for my Vertical Velocity mod and I have 2 questions.

First, how are you calculating the thrustAvailable variable?

Second, especially at low altitudes, this is going to be very coarse in it's throttle control.

Target alt = 10m, current alt = 20m, difference of 10m but your calculation will see a factor of 2 for your throttle.

Target alt = 100m, current alt = 110m, difference of 10m, but your calculation will see a factor of 1.1 for your throttle for the same 10m absolute distance.

You need to make the throttle relative to the distance to your target, your current absolute altitude shouldn't be in there.

D.

 

 

 

Edited by Diazo
Link to comment
Share on other sites

13 hours ago, Diazo said:

I've done pretty much exactly this for my Vertical Velocity mod and I have 2 questions.

First, how are you calculating the thrustAvailable variable?

Second, especially at low altitudes, this is going to be very coarse in it's throttle control.

Target alt = 10m, current alt = 20m, difference of 10m but your calculation will see a factor of 2 for your throttle.

Target alt = 100m, current alt = 110m, difference of 10m, but your calculation will see a factor of 1.1 for your throttle for the same 10m absolute distance.

You need to make the throttle relative to the distance to your target, your current absolute altitude shouldn't be in there.

D.

 

 

 

Thanks for the reply!

thrustAvailable is right now just set to engine.maxThrust but it's wrong since maxThrust is set to 100 while I can get 115-120 kN of thrust at full throttle. The problem is I don't know how to calculate it.

I know about the flaw in my formula, it's just I've tried so many different ways so now I decided to try that one.

Also if I don't have any altitude restrictions or so, just solely go for TWR, the TWR can be 1.000 but I still get +3m/s vertical speed. Is vessel.getTotalMass() flawed or is it the localg that is wrong (9.81)?

Link to comment
Share on other sites

Okay, the fundamental error you are making is that TWR is not tied to speed, it is tied to acceleration.

So a TWR value of 1 means "cancel gravity's pull" and will maintain your current speed. If that speed is +50m/s going up, a TWR of 1 will maintain that +50m/s velocity.

How I deal with this in my mod is I don't set the throttle based on my current altitude relative to target, I calculate the velocity I should be going for how far away I am from my target altitude and my available thrust, then set the throttle relative to that velocity.

 

On the max thrust, you have to check the thrustCurve value. My code for this is:

TWR1MaxThrust += (double)((TWR1EngineModule.maxFuelFlow * TWR1EngineModule.g * TWR1EngineModule.atmosphereCurve.Evaluate((float)(TWR1EngineModule.vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres)) * TWR1EngineModule.thrustPercentage / 100F) * offsetMultiplier); //add engine thrust to MaxThrust

MaxThrust is the max thrust available from the engine.

EngineModule is the reference to the ModuleEngine I am working with.

I multiply by the .thrustPercentage in case the engine has been thrust limited in the part's right-click menu and offsetMultiplier is how I account for how far off vertical the engine is angled.

Hope that helps,

D.

Edited by Diazo
Link to comment
Share on other sites

5 hours ago, Diazo said:

Okay, the fundamental error you are making is that TWR is not tied to speed, it is tied to acceleration.

So a TWR value of 1 means "cancel gravity's pull" and will maintain your current speed. If that speed is +50m/s going up, a TWR of 1 will maintain that +50m/s velocity.

How I deal with this in my mod is I don't set the throttle based on my current altitude relative to target, I calculate the velocity I should be going for how far away I am from my target altitude and my available thrust, then set the throttle relative to that velocity.

 

On the max thrust, you have to check the thrustCurve value. My code for this is:


TWR1MaxThrust += (double)((TWR1EngineModule.maxFuelFlow * TWR1EngineModule.g * TWR1EngineModule.atmosphereCurve.Evaluate((float)(TWR1EngineModule.vessel.staticPressurekPa * PhysicsGlobals.KpaToAtmospheres)) * TWR1EngineModule.thrustPercentage / 100F) * offsetMultiplier); //add engine thrust to MaxThrust

MaxThrust is the max thrust available from the engine.

EngineModule is the reference to the ModuleEngine I am working with.

I multiply by the .thrustPercentage in case the engine has been thrust limited in the part's right-click menu and offsetMultiplier is how I account for how far off vertical the engine is angled.

Hope that helps,

D.

Alright, that helps a lot, thank you. Didn't know that about TWR actually, but now that I think about it it seems pretty obvious.

I actually looked at your source code for TWR1 and I got some *cough*inspiration*cough*. Well anyway, I was just loading my craft and nothing happens, because the debug is getting filled with:

"[Exception]: TypeLoadException: Could not load type 'Vector3d' from assembly 'Assembly-CSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.".

Any idea why this happens? I am using UnityEngine and System, I have Assembly-CSharp and UnityEngine in my references, plus my CoM is set to type Vector3, not Vector3d.

Thanks for taking the time to help me on this, I'll be sure to pay it forward once I get a lot better with C#

Loading gif

:)

Link to comment
Share on other sites

You'll have to update your references. In KSP 1,1 the Vector3D class moved from Assembly-CSharp to the KSUtil reference.

You're still telling it to look in the old location.

All the .dll files in the KSP_Managed folders are KSP version specific, you need to compile against the .dlls from the KSP version you are trying to run them in.

D.

Edited by Diazo
Link to comment
Share on other sites

10 hours ago, Diazo said:

You'll have to update your references. In KSP 1,1 the Vector3D class moved from Assembly-CSharp to the KSUtil reference.

You're still telling it to look in the old location.

All the .dll files in the KSP_Managed folders are KSP version specific, you need to compile against the .dlls from the KSP version you are trying to run them in.

D.

Duh. Thanks lol. Is there a thread or something somewhere with things that mod developers should know, considering the update? What with the move to Unity 5 and all that, or is everything basically the same?

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