Jump to content

Noob getting started - tutorial questions


Recommended Posts

 So I'm trying to get started here. I took CS101 with C++ >20 years ago so bare with me. Maybe this will help someone else too. 

I got VS Community 2019 installed... figured out I need dotnet workload, not just C# alone. I also installed the Unity plugin. I got passed that.

I'm trying to follow this guide. I realize it's outdated and I'm targeting the default dotnet 4.7.2 which should work for KSP 1.8+ according to 1.8 modder notes

First issue was I needed to reference UnityEngine.CoreModule.dll because the editor told me MonoBehaviour moved there. I did that.

Looks like this now.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HelloWorld
{
    [KSPAddon(KSPAddon.Startup.MainMenu, false)]
    public class Hello : MonoBehaviour
    {
        public void Update()
        {
            Debug.Log("Hello world! " + Time.realtimeSinceStartup);
        }
    }
}

I didn't try it yet - I want to understand it first...

  1. Why does it need "using UnityEngine" to define MonoBehaviour?  Why doesn't "using  UnityEngine.CoreModule" work?  There's no CoreModule in UnityEngine namespace - OK. I need to reference UnityEngine.CoreModule.dll but don't need UnityEngine.dll.  Why aren't the "using" and DLL references simply the same?
  2. How does KSPAddon know what to run? Well, it's not a function - it's an attribute... need to study that. Also, there's no explicit call to Update() so why does it run? and why would it run repeatedly? I don't see the flow control. I'm looking for something like:
while(KSPAddon.Startup.MainMenu())
{	
	Hello.Update();
}

 

Link to comment
Share on other sites

55 minutes ago, Krazy1 said:

Why does it need "using UnityEngine" to define MonoBehaviour? 

Technically it doesn't; you could inherit from UnityEngine.MonoBehaviour instead with no "using" directive at all, and that would still work. (I realize that isn't what you're asking, but it might still contribute some understanding...)

55 minutes ago, Krazy1 said:

Why doesn't "using  UnityEngine.CoreModule" work?  There's no CoreModule in UnityEngine namespace - OK. I need to reference UnityEngine.CoreModule.dll but don't need UnityEngine.dll.  Why aren't the "using" and DLL references simply the same?

Namespaces (the in-code names) and DLL names just aren't the same; it's one of the more confusing things  about .NET. A DLL is free to contain code in whatever namespaces it wants, regardless of its own name, and many DLLs can all share one namespace if they like. It would probably be better if Unity was designed to make them the same, but it isn't (possibly because the DLL names got refactored somewhat recently).

55 minutes ago, Krazy1 said:

How does KSPAddon know what to run? Well, it's not a function - it's an attribute... need to study that.

Right, that attribute tags the class as a KSP plugin. When KSP loads your DLL, that attribute tells it to create one instance of that class when the conditions defined in the parameters are met (so, when the main menu loads, in your example code).

55 minutes ago, Krazy1 said:

Also, there's no explicit call to Update() so why does it run? and why would it run repeatedly? I don't see the flow control. I'm looking for something like:

Unity magic. The Unity engine uses code reflection to analyze MonoBehaviour objects to see which of its standard access points they have defined, then calls them as it progresses through its own core loop. This works even without inheritance, and even if your member functions are private. Not very good practice IMHO, but it probably helps take a lot of speed bumps out of the way of inexperienced programmers trying to get started in Unity.

https://docs.unity3d.com/ScriptReference/MonoBehaviour.html

Link to comment
Share on other sites

Thanks for all the good info.

1 hour ago, HebaruSan said:

This works even without inheritance, and even if your member functions are private.

Wow. Is this a game or a virus? I work in aerospace IRL and devs have to meet DO-178 standard. I doubt they'd let this fly. 

Link to comment
Share on other sites

7 hours ago, Krazy1 said:

Thanks for all the good info.

You're welcome, I hope it helps, and good luck. Do you have the API docs bookmarked yet?

And don't hesitate to exploit the huge library of source code produced by previous modders (required to announce a mod in the forum). If you can think of a mod that does the tricky thing you want to do, you can look up how they did it to learn (or even borrow, license permitting).

7 hours ago, Krazy1 said:

Wow. Is this a game or a virus?

Well, Unity itself isn't written in or designed for .NET; it's just using .NET as a scripting tool, so there's a translation layer between what it's really doing and what it presents to C# code. I haven't tried this myself, but I believe it's also possible to use Javascript instead of C#, so some of the quirks may stem from the underlying engine having to support that.

I think sometimes a Unity customer needs to write a very small amount of C# just to make a simple behavior happen in a game. With that use case in mind, I can understand the advantages in such a user not needing to be proficient with keywords like "public" and "override".

7 hours ago, Krazy1 said:

I work in aerospace IRL and devs have to meet DO-178 standard. I doubt they'd let this fly. 

If that bothers you, beware the big gotcha that trips everyone up at least once: Comparing a Unity GameObject (from which MonoBehaviour inherits) with null will sometimes return true even if you know and can prove that it cannot be null based on what your code does. A GameObject in C# has a private pointer to a C++ object in the engine that represents its "true" state. Unity overrides GameObject.operator==(null) to first check null, and then if it's not null, also check whether the underlying C++ object has been destroyed, which happens according to Unity's own event system rather than C#'s usual garbage collection with reference counting. I think the place where this usually trips us up is in teardown code, where it may look like some of your cleanup has already happened before it possibly could have.

One last thing that sank in slowly for me: Try to think of a GameObject or MonoBehaviour not as existing on its own but rather as part of a "scene" alongside other GameObjects, all of whom are running their own Update, FixedUpdate, etc., functions every tick. Together, those objects make up a Unity game.

Link to comment
Share on other sites

So I got the "hello world" code working and the next step seems like a brick wall right now. I'm trying to learn more C# ("this" and constructors and delegates and why use a Class instead of a Struct). The KSP API documentation is nothing but a list - no explanation of what anything does.  I'm trying to read the state of the editor part list sorter: name/ mass/ cost/ size. This compiles but gives NREs. 

using UnityEngine;

namespace Rememberer
{
    [KSPAddon(KSPAddon.Startup.EditorAny, false)]
    public class RememEditor : MonoBehaviour
    {
      
        public void Start()
        { 
            //Debug.Log("Editor Start " + Time.realtimeSinceStartup);
            bool partListSortAscStart = new KSP.UI.Screens.EditorPartList().partListSorter.startSortingAsc;
            Debug.Log("Editor Start: Part List Sort Asc = " + partListSortAscStart);
            int partListSortIndexStart = new KSP.UI.Screens.EditorPartList().partListSorter.StartSortingIndex;
            Debug.Log("Editor Start: Part List Sort Index = " + partListSortIndexStart);
        }

        public void OnDisable()
        {
            //Debug.Log("Editor OnDisable " + Time.realtimeSinceStartup);
            bool partListSortAscOnDisable = new KSP.UI.Screens.EditorPartList().partListSorter.startSortingAsc;
            Debug.Log("Editor Disable: Part List Sort Asc = " + partListSortAscOnDisable);
            int partListSortIndexOnDisable = new KSP.UI.Screens.EditorPartList().partListSorter.StartSortingIndex;
            Debug.Log("Editor Disable: Part List Sort Index = " + partListSortIndexOnDisable);
        }
    }
}

 

Spoiler

[LOG 01:44:32.482] [HighLogic]: =========================== Scene Change : From SPACECENTER to EDITOR (Async) =====================
[LOG 01:44:32.661] [UIApp] OnDestroy: KSPedia
[LOG 01:44:32.750] [UIMasterController]: HideUI
[LOG 01:44:33.456] UICanvasPrefabSpawner SceneLogic spawning Editor
[LOG 01:44:33.479] No Input Locks in effect right now
[LOG 01:44:33.566] [AddonLoader]: Instantiating addon 'RememEditor' from assembly 'Rememberer'
[LOG 01:44:33.573] [UIMasterController]: HideUI
[LOG 01:44:33.882] [UIMasterController]: ShowUI
[WRN 01:44:34.012] The loaded level has a different lightmaps mode than the current one. Current: Directional. Loaded: Non-Directional. Will use: Directional.
[LOG 01:44:34.028] [UIMasterController]: ShowUI
[LOG 01:44:34.039] ------------------- initializing editor mode... ------------------
[LOG 01:44:34.039] editor started
[LOG 01:44:34.056] Loading Depletion Nodes
[LOG 01:44:34.056] DepNodeCount:  0
[LOG 01:44:34.056] Loading Biome Nodes
[LOG 01:44:34.056] BiomeNodeCount:  0
[LOG 01:44:34.056] Loading Planet Nodes
[LOG 01:44:34.056] PlanetNodeCount:  0
[LOG 01:44:34.057] [ScenarioDestructibles]: Loading... 0 objects registered
[EXC 01:44:34.173] NullReferenceException: Object reference not set to an instance of an object
    Rememberer.RememEditor.Start () (at <b0d9770ef1a94a0a940a5dfce2cacc22>:0)
[WRN 01:44:34.240] HighlightingSystem : Framebuffer depth data is not available and can't be used to occlude highlighting. Highlighting occluders enabled.
[LOG 01:44:34.257] [UiApp] Awake: EngineersReport
[LOG 01:44:34.257] [UiApp] Awake: KSPedia
[LOG 01:44:34.257] [UiApp] Awake: Missions App
[LOG 01:44:34.257] [UiApp] Awake: DeltaVApp
[LOG 01:44:34.257] [UiApp] Awake: ActionGroupsApp
[LOG 01:44:34.257] [ApplicationLauncher] OnSceneLoadedGUIReady: scene EDITOR ShouldBeVisible() True ShouldBeOnTop() False iIsPositionedAtTop False
[LOG 01:44:34.264] [UIApp] OnDestroy: Contracts
[LOG 01:44:34.268] [MessageSystem] Reposition 0.02 117143
[LOG 01:44:34.268] [GenericAppFrame] Reposition 0.02 117143
[LOG 01:44:34.268] [GenericAppFrame] Reposition 0.02 117143
[LOG 01:44:34.357] [UIApp] Adding ActionGroupsApp to Application Launcher
[LOG 01:44:34.358] [ApplicationLauncher] SetHidden: 
[LOG 01:44:34.358] [UIApp] Adding Missions App to Application Launcher
[LOG 01:44:34.359] [UIApp] Adding EngineersReport to Application Launcher
[LOG 01:44:34.374] [ActionGroupsApp] OnAppStarted(): id: -357456
[LOG 01:44:34.376] [GenericAppFrame] Reposition 0.125728 117149
[LOG 01:44:34.377] [UIApp] Adding DeltaVApp to Application Launcher
[LOG 01:44:34.377] [MissionsApp] OnAppStarted(): id: -357444
[LOG 01:44:34.377] MissionsApp does not execute in this game mode, destroying this instance
[LOG 01:44:34.377] [UIApp] OnDestroy: Missions App
[LOG 01:44:34.379] [GenericAppFrame] Reposition 0.125728 117149
[LOG 01:44:34.402] [GenericAppFrame] Reposition 0.1534238 117150
[LOG 01:44:34.410] [UIApp] Adding KSPedia to Application Launcher
[LOG 01:44:34.490] [UIMasterController]: ShowUI
[LOG 01:45:11.073] Saving Achievements Tree...
[LOG 01:45:11.073] Saving Achievements Tree...
[LOG 01:45:11.073] [MessageSystem] Save Messages
[LOG 01:45:11.084] Game State Saved to saves/default/persistent
[LOG 01:45:11.087] [UIMasterController]: HideUI
[LOG 01:45:11.187] [HighLogic]: =========================== Scene Change : From EDITOR to SPACECENTER =====================
[LOG 01:45:11.387] [UIApp] OnDestroy: KSPedia
[LOG 01:45:11.387] [UIApp] OnDestroy: ActionGroupsApp
[EXC 01:45:11.388] NullReferenceException: Object reference not set to an instance of an object
    Rememberer.RememEditor.OnDisable () (at <b0d9770ef1a94a0a940a5dfce2cacc22>:0)
[LOG 01:45:11.389] [UIApp] OnDestroy: DeltaVApp
[LOG 01:45:11.391] [UIApp] OnDestroy: EngineersReport
[LOG 01:45:14.149] [UIMasterController]: ShowUI
[LOG 01:45:14.404] Loading Depletion Nodes
[LOG 01:45:14.404] DepNodeCount:  0
[LOG 01:45:14.404] Loading Biome Nodes
[LOG 01:45:14.404] BiomeNodeCount:  0
[LOG 01:45:14.404] Loading Planet Nodes
[LOG 01:45:14.404] PlanetNodeCount:  0
[LOG 01:45:14.405] [ScenarioDestructibles]: Loading... 0 objects registered
[LOG 01:45:14.432] [UiApp] Awake: KSPedia
[LOG 01:45:14.432] [UiApp] Awake: Missions App
[LOG 01:45:14.432] [ApplicationLauncher] OnSceneLoadedGUIReady: scene SPACECENTER ShouldBeVisible() True ShouldBeOnTop() False iIsPositionedAtTop False

 

Note that this part:  ".EditorPartList().partListSorter." without the "()" gives an editor error - partListSorter is not a part of EditorPartList. I don't really understand that.  Any help appreciated!

Edited by Krazy1
Link to comment
Share on other sites

5 hours ago, Krazy1 said:

Note that this part:  ".EditorPartList().partListSorter." without the "()" gives an editor error - partListSorter is not a part of EditorPartList. I don't really understand that.  Any help appreciated!

Instead of making your own empty instance of the part list, try accessing the one the game uses through the Instance property:

bool partListSortAscStart = KSP.UI.Screens.EditorPartList.Instance.partListSorter.startSortingAsc;
Debug.Log("Editor Start: Part List Sort Asc = " + partListSortAscStart);
int partListSortIndexStart = KSP.UI.Screens.EditorPartList.Instance.partListSorter.StartSortingIndex;
Debug.Log("Editor Start: Part List Sort Index = " + partListSortIndexStart);

This is an example of the singleton pattern, in which a class is designed to have just one globally accessible instance. It's fairly common in KSP's code, usually via a property called Instance or fetch.

https://kerbalspaceprogram.com/api/class_k_s_p_1_1_u_i_1_1_screens_1_1_editor_part_list.html

Quote

Properties

static EditorPartList  Instance [get, set]
Link to comment
Share on other sites

Thanks @HebaruSan That worked. I didn't notice you removed the "new" but I figured that out because it's a Static. 

So next task is to write new values but I'm afraid that won't work because the constructor (?) has no "set"

public static EditorPartList Instance { get; }

But it's not Readonly. I'm not sure. I'll try it.

Edited by Krazy1
Link to comment
Share on other sites

3 hours ago, Krazy1 said:

Thanks @HebaruSan That worked. I didn't notice you removed the "new" but I figured that out because it's a Static. 

Yup, you don't want a new object in this case.

3 hours ago, Krazy1 said:

So next task is to write new values but I'm afraid that won't work because the constructor (?) has no "set"

It's a property, a .NET thing that's like a member variable except it runs code; alternately, it's like a method except you don't use "()" to call it. The lack of a "set" only means you can't do this:

KSP.UI.Screens.EditorPartList.Instance = blahblahblah;

... which you don't want to do anyway. But you can do anything you like with the instance's public member variables and functions.

Link to comment
Share on other sites

OK so I got the basic function working in the game to sort parts by something other than "name".  It just mimics pressing the button like you would do with the mouse. I can't get to read the state but that's a problem  for later. Now I'm trying read the setting from a text file. I'm just trying to read this tiny file  it's not been easy so far:

partListSortAsc, True
partListSortIndex, 1

And the .cs (sorry about  all the  comments)

Spoiler

using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;

using UnityEngine;
using KSP.UI.Screens; // has class EditorPartList


namespace Rememberer
{
    [KSPAddon(KSPAddon.Startup.EditorAny, false)]
    public class RememEditor : MonoBehaviour
    {
        
        private bool sortAsc; // true: ascending, false: descending
        private int sortIndex; // 0: name, 1: mass, 2: cost, 3: size

        public void Start()
        { 
            Debug.Log("RememEditor Start " + Time.realtimeSinceStartup);

            // load initial values

            string dataFileName = "Rememberer.txt";
            Assembly execAssembly = Assembly.GetExecutingAssembly();
            string pluginDirectory = Path.GetDirectoryName(execAssembly.Location);
            string dataFilePath = Path.Combine(pluginDirectory, dataFileName);
            Debug.Log("RememEditor Start File Path: " + dataFilePath);                                     //   <---- this seems to work

            //List<string> lines = new List<string>();

            //Dictionary<string, string> dict = new Dictionary<string, string>();
            //dict = File.ReadAllLines(dataFilePath).ToDictionary(x => x, x=> x);

            Dictionary<string, string> dict = File.ReadAllLines(dataFilePath).ToDictionary(x => x, x => x);   //   <--- not working???
            Debug.Log("RememEditor Start dict " + dict);

            //Dict.Add("partListSortAsc" , "True");
            //Dict.Add("partListSortIndex", "1");

            sortAsc = Convert.ToBoolean(dict["partListSortAsc"]);
            Debug.Log("RememEditor Start sortAsc " + sortAsc);
            sortIndex = Convert.ToInt32(dict["partListSortIndex"]);
            Debug.Log("RememEditor Start sortIndex " + sortIndex);

            for (int click=0; click <= Convert.ToInt32(!sortAsc); click++)
            {
                Debug.Log("RememEditor Start clicking button " + sortIndex);
                EditorPartList.Instance.partListSorter.ClickButton(sortIndex);
            }

 *snip*

And excerpt from the KSP log:

[LOG 18:20:12.474] ------------------- initializing editor mode... ------------------
[LOG 18:20:12.474] editor started
[LOG 18:20:12.485] Loading Depletion Nodes
[LOG 18:20:12.485] DepNodeCount:  0
[LOG 18:20:12.485] Loading Biome Nodes
[LOG 18:20:12.485] BiomeNodeCount:  0
[LOG 18:20:12.485] Loading Planet Nodes
[LOG 18:20:12.485] PlanetNodeCount:  0
[LOG 18:20:12.487] [ScenarioDestructibles]: Loading... 0 objects registered
[LOG 18:20:12.487] [ScenarioUpgradeableFacilities]: Loading... 0 objects registered
[LOG 18:20:12.598] RememEditor Start 6202.018
[LOG 18:20:12.598] RememEditor Start File Path: C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\GameData\_Local\Rememberer.txt
[LOG 18:20:12.599] RememEditor Start dict System.Collections.Generic.Dictionary`2[System.String,System.String]
[EXC 18:20:12.603] KeyNotFoundException: The given key was not present in the dictionary.
	System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <ad04dee02e7e4a85a1299c7ee81c79f6>:0)
	Rememberer.RememEditor.Start () (at <c2786458e8db4538ba8ce3cf296b4868>:0)

It has the right file path but the data for "dict" is garbage. I got the lambda function part here (without really understanding it):

https://stackoverflow.com/questions/11581101/c-sharp-convert-liststring-to-dictionarystring-string

 

Link to comment
Share on other sites

37 minutes ago, Krazy1 said:


            Dictionary<string, string> dict = File.ReadAllLines(dataFilePath).ToDictionary(x => x, x => x);   //   <--- not working???

That will have the same effect as doing this:

dict["partListSortAsc, True"] = "partListSortAsc, True";
dict["partListSortIndex, 1"] = "partListSortIndex, 1";

The reason is that File.ReadAllLines returns an array containing one string per line of the file. The ToDictionary call doesn't parse the lines at all (I'm guessing that you want to treat the comma as a separator between key and value), it just uses the whole line as both key and value.

Quote

It has the right file path but the data for "dict" is garbage. I got the lambda function part here (without really understanding it):

https://stackoverflow.com/questions/11581101/c-sharp-convert-liststring-to-dictionarystring-string

Yeah, they're trying to do something somewhat different from what you're trying to do.

Normally in a programming tutorial the next topic would be how to use string.Split or regular expressions to parse your data, but going that route will involve re-inventing a lot of wheels and spending your time on things that aren't really core to your mod, and it will only get more complex as you think of more things to save and load. Consider using KSP's built-in parser instead. If you put a .cfg file in KSP's config file format in your mod's folder, KSP can automatically parse it for you and provide it as ConfigNode objects. For example, if your file contains this:

REMEMBERERSETTINGS
{
    partListSortAsc = True
    partListSortIndex = 1
}

Then you can access its values via GameDatabase.Instance.GetConfigs("REMEMBERERSETTINGS").

Edited by HebaruSan
Link to comment
Share on other sites

Thanks again. Very helpful. 

6 hours ago, HebaruSan said:

I'm guessing that you want to treat the comma as a separator between key and value

Yeah, that's the idea. I'm not too surprised it worked like you described. I wish I had a better way to test it. Maybe command line builds would help in some cases. It's slow loading/ exiting KSP every iteration.

6 hours ago, HebaruSan said:

KSP can automatically parse it for you and provide it as ConfigNode objects

OK - parsing data isn't much fun. Maybe I'll come back to learn how to do it myself but right now I'd much prefer to have it working. Can I still use the Unity hook KSPAddon? The Getting Started thread

says: "Note: There is no integrated save/loading of data in the KSPAddon hook, you must do that manually." 

I inferred that "manually" meant there was no built-in method at all. 

 

Link to comment
Share on other sites

3 hours ago, Krazy1 said:

Can I still use the Unity hook KSPAddon? The Getting Started thread

says: "Note: There is no integrated save/loading of data in the KSPAddon hook, you must do that manually." 

I inferred that "manually" meant there was no built-in method at all. 

Hmm, that's fairly misleading if you interpret "in the KSPAddon hook" to mean modding in general. The ScenarioModule parent class and KSPScenario attribute provide a pretty easy, integrated way to load and save data to a save file (again, using ConfigNodes). Remember Planning Node? It remembers the nodes you've created between sessions by having a ScenarioModule class hold the current list of them:

https://github.com/HebaruSan/PlanningNode/blob/e9f6809dbb0100d70f037d8fcdbeb2bccd4ebb4e/Source/PlanningNodesManager.cs#L11-L44

You'll still need a KSPAddon class to be the main driver of your class's activity, but adding a ScenarioModule minimizes the pain of working with persistent data (assuming you're OK with making that data save-specific).

Link to comment
Share on other sites

10 hours ago, HebaruSan said:

Hmm, that's fairly misleading if you interpret "in the KSPAddon hook" to mean modding in general. 

The guide was clear about using config nodes in ScenarioModule. Gee, I didn't think about using both KSPAddon and ScenarioModule. One very minor issue with data saved in the savegame: if you remove the mod, say to debug mod interactions , the KSP.log will show new errors because there is data left in the savegame and there's nothing using it. Anyway, I have better options than what I was doing before. 

10 hours ago, HebaruSan said:

Remember Planning Node?

Yes - it's great. I have a ship going to Moho. Just aimed for the bullseye and I was lined up for an intercept. 

Your code has comments! So many comments. Wow. :0.0:

Link to comment
Share on other sites

4 minutes ago, Krazy1 said:

Yes - it's great. I have a ship going to Moho. Just aimed for the bullseye and I was lined up for an intercept. 

That is really satisfying to hear. :joy::valjoy:

4 minutes ago, Krazy1 said:

Your code has comments! So many comments. Wow. :0.0:

Sure, how else am I supposed to keep track of how anything works? :confused:

More seriously, I put this in the csproj file to force myself to write documentation for the public interfaces:

<DocumentationFile>$(IntermediateOutputPath)$(AssemblyName).xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

The first line generates a warning if anything isn't documented, and the second line transforms those warnings into red-text errors that make compilation fail. Some would probably regard this as programmer masochism, but I like knowing that a successful build means I've not only written the code, but also a blurb justifying why things are structured as they are. Helps me catch things that are temporary or half-baked.

Link to comment
Share on other sites

I hope this good news for you rather than bad news, but @FruitGoose has released a mod that I think does roughly what you're aiming for:

I would suggest browsing its source code to see how it works, but I was not able to find it yet. I'm guessing it will become available soon though.

Link to comment
Share on other sites

So I got the part sorting  working . Yay.

But I want it to remember another setting in flight. Can I structure the code like:

namespace MyMod {
	[KSPAddon(KSPAddon.Startup.EditorAny, false)]
	Class EditorStuff {blah blah}
	[KSPAddon(KSPAddon.Startup.Flight, false)]
	Class FlightStuff {blah blah}
}
	

Update: simple test, looks like this works as expected

Edited by Krazy1
Link to comment
Share on other sites

45 minutes ago, Krazy1 said:

I want it to remember another setting in flight. Can I structure the code like:


namespace MyMod {
	[KSPAddon(KSPAddon.Startup.EditorAny, false)]
	class EditorStuff {blah blah}
	[KSPAddon(KSPAddon.Startup.Flight, false)]
	class FlightStuff {blah blah}
}

Pretty much, yeah. PlanningNode's flight mode addon:

PlanningNode's trackin station addon:

Link to comment
Share on other sites

Ok so I'm trying to toggle the vessel type filter panel at the top of the map view. It's new in KSP 1.11. I wish it would stay open. First I'm just trying to toggle it open every time the map view is entered. I found a trigger for the map view: GameEvents.OnMapEntered.Add(Callback) and the button is pressed with MapViewFiltering.Instance.ToggleFiltersPanel() I believe.

A debug.log() message confirms the callback is triggered when map view is entered. But nothing happens with ToggleFiltersPanel. No error or anything.

I could further verify that ToggleFiltersPanel is right by checking that it is not in the KSP 1.10 API... but they keep updating the same API documentation link. Did someone archive the old versions?   I'm pretty sure I have the right function though.

I think there's a execution order issue- it's trying to toggle the filter panel before it's available. I can see the pull-down button is dark gray initially when the map view loads and turns light gray maybe 1/2 second later. The intro tutorial mentions Start() timing may be an issue and suggests a Coroutine (which I'd have to go figure out how it works).  Does that sound plausible? Thanks.

 

Link to comment
Share on other sites

17 hours ago, Krazy1 said:

Did someone archive the old versions?

Not to my knowledge.

17 hours ago, Krazy1 said:

Does that sound plausible?

Uhh... maybe? You've progressed past the things I understand well, or have a broad knowledge of, into things that I have never attempted to investigate. Sounds like you're on the right track, though.

I would probably set up a temporary toolbar button in the map view to call the code I wanted to try out; if the button does what you want, then you can safely blame a timing issue.

Link to comment
Share on other sites

OK. I'll see what I can do. Another discovery - entering the Tracking Station also triggers the same Map view trigger and then it does give NREs because the button I'm trying to "press" really does not exist in the TS. I don't know why it fired 7 times though. 

Spoiler

[LOG 13:24:58.173] [ApplicationLauncher] OnSceneLoadedGUIReady: scene TRACKSTATION ShouldBeVisible() True ShouldBeOnTop() False iIsPositionedAtTop False
[LOG 13:24:58.176] ScaleModList: listSize 164 maxListSize 1630
[LOG 13:24:58.176] ScaleModList: listSize 164 maxListSize 1630
[LOG 13:24:58.177] ScaleModList: listSize 205 maxListSize 1630
[LOG 13:24:58.179] ScaleModList: listSize 246 maxListSize 1630
[LOG 13:24:58.181] ScaleModList: listSize 287 maxListSize 1630
[LOG 13:24:58.183] ScaleModList: listSize 328 maxListSize 1630
[LOG 13:24:58.189] ScaleModList: listSize 369 maxListSize 1630
[LOG 13:24:58.194] ScaleModList: listSize 410 maxListSize 1630
[LOG 13:24:58.194] ScaleModList: listSize 451 maxListSize 1630
[LOG 13:24:58.195] ScaleModList: listSize 410 maxListSize 1630
[LOG 13:24:58.195] [KnowledgeBase] OnAppLauncherReady 740516
[LOG 13:24:58.203] ScaleModList: listSize 410 maxListSize 1630
[WRN 13:24:58.204] UIList: RemoveItem didn't find any item to remove.
[LOG 13:24:58.204] ScaleModList: listSize 410 maxListSize 1630
[LOG 13:24:58.204] QuickGoTo(QStockToolbar)[1.40]: Destroy
[LOG 13:24:58.204] ScaleModList: listSize 451 maxListSize 1630
[LOG 13:24:58.205] QuickGoTo(QStockToolbar)[1.40]: Init
[LOG 13:24:58.205] QuickGoTo(QStockToolbar)[1.40]: AppLauncherReady
[LOG 13:24:58.205] [4/11/2021 1:24:58 PM [x] Science!]: <Trace> (ScienceChecklistAddon) - Load
[LOG 13:24:58.205] [4/11/2021 1:24:58 PM [x] Science!]: <Trace> (ScienceChecklistAddon) - Already loaded.
[LOG 13:24:58.260] 4/11/2021 1:24:58 PM,KerbalAlarmClock,Contracts System Ready
[LOG 13:24:58.262] ScaleModList: listSize 492 maxListSize 1630
[LOG 13:24:58.279] [MessageSystem] Reposition 0.02 740517
[LOG 13:24:58.279] [GenericAppFrame] Reposition 0.02 740517
[LOG 13:24:58.279] [GenericAppFrame] Reposition 0.02 740517
[LOG 13:24:58.295] RememFlight - Entering Map view
[LOG 13:24:58.295] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.295] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.296] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.297] RememFlight - Entering Map view
[LOG 13:24:58.297] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.297] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.298] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.299] RememFlight - Entering Map view
[LOG 13:24:58.299] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.299] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.300] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.301] RememFlight - Entering Map view
[LOG 13:24:58.301] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.301] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.302] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.303] RememFlight - Entering Map view
[LOG 13:24:58.303] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.303] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.304] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.305] RememFlight - Entering Map view
[LOG 13:24:58.305] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.305] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.306] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.307] RememFlight - Entering Map view
[LOG 13:24:58.307] RememFlight - ToggleFiltersPanel
[ERR 13:24:58.307] Exception handling event OnMapEntered in class RememFlight:System.NullReferenceException: Object reference not set to an instance of an object
  at MapViewFiltering.ToggleFiltersPanel () [0x00000] in <06f13185617646e5bc801baeab53ab75>:0 
  at Rememberer.RememFlight.EnterMapCB () [0x00022] in <2251746e8ae547cda1947e5953861f59>:0 
  at EventVoid.Fire () [0x00127] in <06f13185617646e5bc801baeab53ab75>:0 

[EXC 13:24:58.308] NullReferenceException: Object reference not set to an instance of an object
    MapViewFiltering.ToggleFiltersPanel () (at <06f13185617646e5bc801baeab53ab75>:0)
    Rememberer.RememFlight.EnterMapCB () (at <2251746e8ae547cda1947e5953861f59>:0)
    EventVoid.Fire () (at <06f13185617646e5bc801baeab53ab75>:0)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    ModuleManager.UnityLogHandle.InterceptLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    EventVoid:Fire()
    <Start>d__79:MoveNext()
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
[LOG 13:24:58.309] [PlanetariumCamera]: Focus: Kerbin

 

Link to comment
Share on other sites

On 4/3/2021 at 1:12 AM, Krazy1 said:

why use a Class instead of a Struct

For perspective: object-oriented is about data/method encapsulation.  A struct is only a collection of data.  A class defines a collection of data along with the methods that operate on that data: an 'object'.  (An object is analogous to the biological cell in which receptors on the cell's wall may receive and act on external signals, but only the cell may operate directly upon on its own internal state: its 'instance' data.)  Seen this way, your struct (which does exist in C#), is so poor a subset of the object (of a class) that no one is often interested in using it.  Constructors guarantee the integrity of the initial state (and are a class method again roughly analogous to cell division) for ensuring that a new object is created in a 'healthy', consistent state.

So you might better ask, "why use a struct when it is such a poor cousin to a class?".  Especially when you consider that your for-now-wisely-chosen-struct may need to become a richer class in the rapidly-changing future.

To complete the answer, delegates are a modern-fangled (and class-entangled) re-interpretation of "call-backs" [and more; e.g. 'dependency-injection'].  Progress is good.

Edited by Hotel26
Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

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