├── GameData
└── Pilot Assistant
│ ├── Icon
│ ├── BlizzyIcon.png
│ └── AppLauncherIcon.png
│ ├── PilotAssistant.dll
│ └── PilotAssistant.version
├── .gitattributes
├── PilotAssistant
├── Presets
│ ├── CraftPreset.cs
│ └── AsstPreset.cs
├── Properties
│ └── AssemblyInfo.cs
├── Toolbar
│ ├── AppLauncherFlight.cs
│ ├── ToolbarMod.cs
│ └── ToolbarWrapper.cs
├── Logger.cs
├── PilotAssistant.csproj
├── FlightModules
│ ├── AsstVesselModule.cs
│ ├── VesselData.cs
│ └── PilotAssistant.cs
├── BindingManager.cs
├── PID_Controller.cs
├── Utility
│ ├── Utils.cs
│ └── GeneralUI.cs
├── PilotAssistantFlightCore.cs
└── PresetManager.cs
├── Pilot Assistant.sln
└── .gitignore
/GameData/Pilot Assistant/Icon/BlizzyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crzyrndm/Pilot-Assistant/HEAD/GameData/Pilot Assistant/Icon/BlizzyIcon.png
--------------------------------------------------------------------------------
/GameData/Pilot Assistant/PilotAssistant.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crzyrndm/Pilot-Assistant/HEAD/GameData/Pilot Assistant/PilotAssistant.dll
--------------------------------------------------------------------------------
/GameData/Pilot Assistant/Icon/AppLauncherIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Crzyrndm/Pilot-Assistant/HEAD/GameData/Pilot Assistant/Icon/AppLauncherIcon.png
--------------------------------------------------------------------------------
/GameData/Pilot Assistant/PilotAssistant.version:
--------------------------------------------------------------------------------
1 | {"NAME":"Pilot Assistant","URL":"https://raw.githubusercontent.com/Crzyrndm/Pilot-Assistant/master/GameData/Pilot%20Assistant/PilotAssistant.version","DOWNLOAD":"https://github.com/Crzyrndm/Pilot-Assistant/releases","VERSION":{"MAJOR":1,"MINOR":13,"PATCH":3},"KSP_VERSION":{"MAJOR":1,"MINOR":3,"PATCH":0}}
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/PilotAssistant/Presets/CraftPreset.cs:
--------------------------------------------------------------------------------
1 | namespace PilotAssistant.Presets
2 | {
3 | ///
4 | /// This can be made obselete. All that is needed now with a single module is a dictionary associating craft names with PA presets
5 | ///
6 | [System.Obsolete("Store association as a craft-PA preset Dict", true)]
7 | public class CraftPreset
8 | {
9 | string name;
10 | AsstPreset pa;
11 |
12 | public CraftPreset(string Name, AsstPreset PA)
13 | {
14 | name = Name;
15 | pa = PA;
16 | }
17 |
18 | public string Name
19 | {
20 | get { return name; }
21 | set { name = value; }
22 | }
23 |
24 | public AsstPreset AsstPreset
25 | {
26 | get { return pa; }
27 | set { pa = value; }
28 | }
29 |
30 | public bool Dead
31 | {
32 | get { return AsstPreset == null; }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Pilot Assistant.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30723.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PilotAssistant", "PilotAssistant\PilotAssistant.csproj", "{D283D199-0B7A-4DCD-A991-C82091190104}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D283D199-0B7A-4DCD-A991-C82091190104}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {D283D199-0B7A-4DCD-A991-C82091190104}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {D283D199-0B7A-4DCD-A991-C82091190104}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D283D199-0B7A-4DCD-A991-C82091190104}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/PilotAssistant/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("AutoPilot")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("AutoPilot")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("d3531cc8-0fa6-422a-9f4d-36e0142d63c4")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PilotAssistant/Toolbar/AppLauncherFlight.cs:
--------------------------------------------------------------------------------
1 | namespace PilotAssistant.Toolbar
2 | {
3 | using KSP.UI.Screens;
4 |
5 | public class AppLauncherFlight
6 | {
7 | private static ApplicationLauncherButton btnLauncher;
8 |
9 | private static AppLauncherFlight instance;
10 | public static AppLauncherFlight Instance
11 | {
12 | get
13 | {
14 | if (instance == null)
15 | {
16 | instance = new AppLauncherFlight();
17 | }
18 |
19 | return instance;
20 | }
21 | }
22 |
23 | public void Awake()
24 | {
25 | GameEvents.onGUIApplicationLauncherReady.Add(AddButton);
26 | GameEvents.onGUIApplicationLauncherUnreadifying.Add(RemoveButton);
27 | }
28 |
29 | public void AddButton()
30 | {
31 | if (btnLauncher == null)
32 | {
33 | btnLauncher = ApplicationLauncher.Instance.AddModApplication(
34 | OnToggleTrue, OnToggleFalse,
35 | null, null, null, null,
36 | ApplicationLauncher.AppScenes.FLIGHT,
37 | GameDatabase.Instance.GetTexture("Pilot Assistant/Icon/AppLauncherIcon", false));
38 | }
39 | }
40 |
41 | public void RemoveButton(GameScenes scene)
42 | {
43 | ApplicationLauncher.Instance.RemoveModApplication(btnLauncher);
44 | }
45 |
46 | private void OnToggleTrue()
47 | {
48 | PilotAssistantFlightCore.bDisplayAssistant = true;
49 | }
50 |
51 | private void OnToggleFalse()
52 | {
53 | PilotAssistantFlightCore.bDisplayAssistant = false;
54 | }
55 |
56 | public static void SetBtnState(bool state, bool click = false)
57 | {
58 | if (state)
59 | {
60 | btnLauncher.SetTrue(click);
61 | }
62 | else
63 | {
64 | btnLauncher.SetFalse(click);
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/PilotAssistant/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 |
4 | namespace PilotAssistant
5 | {
6 | internal static class Logger
7 | {
8 | public static readonly Version version = new Version(1, 13, 3);
9 |
10 | internal enum LogLevel
11 | {
12 | Debug,
13 | Warn,
14 | Error
15 | }
16 |
17 | ///
18 | /// format the string to be logged and prefix with mod id + version
19 | ///
20 | /// string format to be logged
21 | /// params for the format
22 | ///
23 | static string LogString(string format, params object[] o)
24 | {
25 | return $"[Pilot Assistant {version}]: {string.Format(format, o)}";
26 | }
27 |
28 | ///
29 | /// Debug messages only compiled in debug build. Also much easier to search for once debugging/development complete...
30 | ///
31 | ///
32 | ///
33 | [System.Diagnostics.Conditional("DEBUG")]
34 | internal static void Dev(object o, LogLevel level = LogLevel.Debug)
35 | {
36 | Log(o, level);
37 | }
38 |
39 | [System.Diagnostics.Conditional("DEBUG")]
40 | internal static void Dev(string format, LogLevel level = LogLevel.Debug, params object[] o)
41 | {
42 | Log(format, level, o);
43 | }
44 |
45 | ///
46 | /// Debug.Log with FE id/version inserted
47 | ///
48 | ///
49 | internal static void Log(object o, LogLevel level = LogLevel.Debug)
50 | {
51 | Log(o.ToString(), level);
52 | }
53 |
54 | internal static void Log(string format, LogLevel level = LogLevel.Debug, params object[] o)
55 | {
56 | if (level == LogLevel.Debug)
57 | {
58 | Debug.Log(LogString(format, o));
59 | }
60 | else if (level == LogLevel.Warn)
61 | {
62 | Debug.LogWarning(LogString(format, o));
63 | }
64 | else
65 | {
66 | Debug.LogError(LogString(format, o));
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/PilotAssistant/Toolbar/ToolbarMod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace PilotAssistant.Toolbar
7 | {
8 | public class ToolbarMod
9 | {
10 | private static ToolbarMod instance;
11 | public static ToolbarMod Instance
12 | {
13 | get
14 | {
15 | if (instance == null)
16 | {
17 | instance = new ToolbarMod();
18 | }
19 |
20 | return instance;
21 | }
22 | }
23 |
24 | private IButton asstButton;
25 | private IButton SSASButton;
26 | private IButton stockButton;
27 | private IButton menuButton;
28 | public void Awake()
29 | {
30 | menuButton = ToolbarManager.Instance.Add("PilotAssistant", "PilotAssistantMenuBtn");
31 | menuButton.TexturePath = PilotAssistantFlightCore.Instance.blizMenuTexPath;
32 | menuButton.ToolTip = "Open Pilot Assistant Menu";
33 | menuButton.OnClick += (e) => PilotAssistantFlightCore.bDisplayOptions = !PilotAssistantFlightCore.bDisplayOptions;
34 |
35 | asstButton = ToolbarManager.Instance.Add("PilotAssistant", "PilotAssistantAsstBtn");
36 | asstButton.TexturePath = PilotAssistantFlightCore.Instance.blizAsstTexPath;
37 | asstButton.ToolTip = "Open Pilot Assistant Window";
38 | asstButton.OnClick += (e) => PilotAssistantFlightCore.bDisplayAssistant = !PilotAssistantFlightCore.bDisplayAssistant;
39 |
40 | SSASButton = ToolbarManager.Instance.Add("PilotAssistant", "PilotAssistantSSASBtn");
41 | SSASButton.TexturePath = PilotAssistantFlightCore.Instance.blizSSASTexPath;
42 | SSASButton.ToolTip = "Open SSAS Window";
43 | SSASButton.OnClick += (e) => PilotAssistantFlightCore.bDisplaySSAS = !PilotAssistantFlightCore.bDisplaySSAS;
44 |
45 | stockButton = ToolbarManager.Instance.Add("PilotAssistant", "PilotAssistantStockBtn");
46 | stockButton.TexturePath = PilotAssistantFlightCore.Instance.blizSASTexPath;
47 | stockButton.ToolTip = "Open Stock SAS tuning Window";
48 | stockButton.OnClick += (e) => PilotAssistantFlightCore.bDisplaySAS = !PilotAssistantFlightCore.bDisplaySAS;
49 | }
50 |
51 | public void OnDestroy()
52 | {
53 | menuButton.Destroy();
54 | asstButton.Destroy();
55 | SSASButton.Destroy();
56 | stockButton.Destroy();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/PilotAssistant/Presets/AsstPreset.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace PilotAssistant.Presets
7 | {
8 | ///
9 | /// Holds all the PID tuning values for the 7 (or more if required) controllers involved.
10 | ///
11 | public class AsstPreset
12 | {
13 | public string name;
14 | public List PIDGains = new List();
15 |
16 | public AsstPreset(List controllers, string Name) // used for adding a new preset, can clone the current values
17 | {
18 | name = Name;
19 | Update(controllers);
20 | }
21 |
22 | public AsstPreset(Asst_PID_Controller[] controllers, string Name) // used for adding a new preset, can clone the current values
23 | {
24 | name = Name;
25 | Update(controllers);
26 | }
27 |
28 | public AsstPreset(List gains, string Name) // used for loading presets from file
29 | {
30 | name = Name;
31 | PIDGains = gains;
32 | }
33 |
34 | public void Update(List controllers)
35 | {
36 | PIDGains.Clear();
37 | foreach (Asst_PID_Controller controller in controllers)
38 | {
39 | double[] gains = new double[9];
40 | gains[0] = controller.K_proportional;
41 | gains[1] = controller.K_integral;
42 | gains[2] = controller.K_derivative;
43 | gains[3] = controller.OutMin;
44 | gains[4] = controller.OutMax;
45 | gains[5] = controller.IntegralClampLower;
46 | gains[6] = controller.IntegralClampUpper;
47 | gains[7] = controller.Scalar;
48 | gains[8] = controller.Easing;
49 |
50 | PIDGains.Add(gains);
51 | }
52 | }
53 |
54 | public void Update(Asst_PID_Controller[] controllers)
55 | {
56 | PIDGains.Clear();
57 | foreach (Asst_PID_Controller controller in controllers)
58 | {
59 | double[] gains = new double[9];
60 | gains[0] = controller.K_proportional;
61 | gains[1] = controller.K_integral;
62 | gains[2] = controller.K_derivative;
63 | gains[3] = controller.OutMin;
64 | gains[4] = controller.OutMax;
65 | gains[5] = controller.IntegralClampLower;
66 | gains[6] = controller.IntegralClampUpper;
67 | gains[7] = controller.Scalar;
68 | gains[8] = controller.Easing;
69 |
70 | PIDGains.Add(gains);
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
198 | PartDatabase.cfg
199 | Physics.cfg
200 |
--------------------------------------------------------------------------------
/PilotAssistant/PilotAssistant.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D283D199-0B7A-4DCD-A991-C82091190104}
8 | Library
9 | Properties
10 | PilotAssistant
11 | PilotAssistant
12 | v3.5
13 | 512
14 |
15 |
16 |
17 | false
18 | none
19 | false
20 | ..\GameData\Pilot Assistant\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | none
27 | true
28 | ..\GameData\Pilot Assistant\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 | Always
35 |
36 |
37 |
38 | ..\..\..\Desktop\Kerbal Space Program Dev\KSP_x64_Data\Managed\Assembly-CSharp.dll
39 | False
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ..\..\..\Desktop\Kerbal Space Program Dev\KSP_x64_Data\Managed\UnityEngine.dll
49 | False
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | set SOURCE="D:\Libraries\GitHub\Pilot-Assistant\GameData"
74 | set DESTINATION="D:\Libraries\Desktop\Kerbal Space Program Dev\GameData"
75 | xcopy %25SOURCE%25 %25DESTINATION%25 /e /c /r /y
76 |
77 |
84 |
--------------------------------------------------------------------------------
/PilotAssistant/FlightModules/AsstVesselModule.cs:
--------------------------------------------------------------------------------
1 | using PilotAssistant.Utility;
2 | using System;
3 | using UnityEngine;
4 |
5 | namespace PilotAssistant.FlightModules
6 | {
7 | public class AsstVesselModule : VesselModule
8 | {
9 | public PilotAssistant vesselAsst;
10 | public VesselData vesselData;
11 |
12 | public override Activation GetActivation()
13 | {
14 | return Activation.LoadedVessels;
15 | }
16 |
17 | public override bool ShouldBeActive()
18 | {
19 | return Vessel.loaded;
20 | }
21 |
22 | protected override void OnStart()
23 | {
24 | base.OnStart();
25 | try
26 | {
27 | vesselAsst = new PilotAssistant(this);
28 | vesselData = new VesselData(this);
29 | PilotAssistantFlightCore.Instance.AddVessel(this);
30 |
31 | vesselAsst.Start();
32 |
33 | Vessel.OnPreAutopilotUpdate += new FlightInputCallback(PreAutoPilotUpdate);
34 | Vessel.OnPostAutopilotUpdate += new FlightInputCallback(PostAutoPilotUpdate);
35 |
36 | GameEvents.onVesselChange.Add(VesselSwitch);
37 | GameEvents.onTimeWarpRateChanged.Add(WarpHandler);
38 |
39 | if (FlightGlobals.ActiveVessel == Vessel)
40 | {
41 | VesselSwitch(Vessel);
42 | }
43 | }
44 | catch (Exception ex)
45 | {
46 | Logger.Log("Startup error", Logger.LogLevel.Error);
47 | Logger.Log(ex.Message, Logger.LogLevel.Error);
48 | Logger.Log(ex.InnerException, Logger.LogLevel.Error);
49 | Logger.Log(ex.StackTrace, Logger.LogLevel.Error);
50 | }
51 | }
52 |
53 | public void Update()
54 | {
55 | if (ReferenceEquals(Vessel, null))
56 | {
57 | return;
58 | }
59 |
60 | vesselAsst.Update();
61 | }
62 |
63 | public void WarpHandler()
64 | {
65 | vesselAsst.WarpHandler();
66 | }
67 |
68 | public void VesselSwitch(Vessel v)
69 | {
70 | if (v == Vessel)
71 | {
72 | vesselAsst.VesselSwitch(v);
73 | }
74 | }
75 |
76 | public void PreAutoPilotUpdate(FlightCtrlState state)
77 | {
78 | if (Vessel.HoldPhysics)
79 | {
80 | return;
81 | }
82 |
83 | vesselData.UpdateAttitude();
84 | }
85 |
86 | public void PostAutoPilotUpdate(FlightCtrlState state)
87 | {
88 | if (Vessel.HoldPhysics)
89 | {
90 | return;
91 | }
92 |
93 | vesselAsst.VesselController(state);
94 | }
95 |
96 | public void OnGUI()
97 | {
98 | if (PilotAssistantFlightCore.bHideUI || PilotAssistantFlightCore.Instance == null
99 | || PilotAssistantFlightCore.Instance.selectedVesselIndex >= PilotAssistantFlightCore.Instance.controlledVessels.Count
100 | || PilotAssistantFlightCore.Instance.controlledVessels[PilotAssistantFlightCore.Instance.selectedVesselIndex] != this)
101 | {
102 | return;
103 | }
104 |
105 | vesselAsst.DrawGUI();
106 | }
107 |
108 | public void OnDestroy()
109 | {
110 | if (Vessel != null)
111 | {
112 | GameEvents.onVesselChange.Remove(VesselSwitch);
113 | GameEvents.onTimeWarpRateChanged.Remove(WarpHandler);
114 |
115 | Vessel.OnPreAutopilotUpdate -= PreAutoPilotUpdate;
116 | Vessel.OnPostAutopilotUpdate -= PostAutoPilotUpdate;
117 | if (!ReferenceEquals(vesselAsst, null))
118 | {
119 | vesselAsst.OnDestroy();
120 | if (!ReferenceEquals(PilotAssistantFlightCore.Instance, null))
121 | {
122 | PilotAssistantFlightCore.Instance.RemoveVessel(this);
123 | }
124 | }
125 | vesselAsst = null;
126 | vesselData = null;
127 | }
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/PilotAssistant/BindingManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace PilotAssistant
7 | {
8 | using Utility;
9 |
10 | enum Display
11 | {
12 | Asst,
13 | SSAS
14 | }
15 |
16 | enum BindingIndex
17 | {
18 | Pause,
19 | HdgTgl,
20 | VertTgl,
21 | ThrtTgl,
22 | ArmSSAS
23 | }
24 |
25 | class BindingManager
26 | {
27 | static BindingManager instance;
28 | public static BindingManager Instance
29 | {
30 | get
31 | {
32 | if (ReferenceEquals(instance, null))
33 | {
34 | instance = new BindingManager();
35 | }
36 |
37 | return instance;
38 | }
39 | }
40 |
41 | public Rect windowRect = new Rect();
42 | Display selector = Display.Asst;
43 | GUIContent[] selectorLabels = new GUIContent[2] { new GUIContent("Pilot Assistant"), new GUIContent("SSAS") };
44 |
45 | public static Binding[] bindings;
46 |
47 | public void Start()
48 | {
49 | bindings = new Binding[Enum.GetNames(typeof(BindingIndex)).GetLength(0)];
50 | bindings[(int)BindingIndex.Pause] = new Binding("Pause Control", KeyCode.Tab, KeyCode.None, Display.Asst);
51 | bindings[(int)BindingIndex.HdgTgl] = new Binding("Toggle Heading Control", KeyCode.Keypad9, KeyCode.LeftAlt, Display.Asst);
52 | bindings[(int)BindingIndex.VertTgl] = new Binding("Toggle Vert Control", KeyCode.Keypad6, KeyCode.LeftAlt, Display.Asst);
53 | bindings[(int)BindingIndex.ThrtTgl] = new Binding("Toggle Throttle Control", KeyCode.Keypad3, KeyCode.LeftAlt, Display.Asst);
54 | bindings[(int)BindingIndex.ArmSSAS] = new Binding("Arm SSAS", GameSettings.SAS_TOGGLE.primary.code, KeyCode.LeftAlt, Display.SSAS);
55 | }
56 |
57 | public void Draw()
58 | {
59 | if (PilotAssistantFlightCore.bDisplayBindings)
60 | {
61 | windowRect = GUILayout.Window(6849762, windowRect, DrawWindow, string.Empty);
62 | }
63 | }
64 |
65 | void DrawWindow(int id)
66 | {
67 | selector = (Display)GUILayout.SelectionGrid((int)selector, selectorLabels, 2);
68 | if (selector == Display.Asst)
69 | {
70 | DrawLabelsInRow("Reduce Target Heading", GameSettings.YAW_LEFT.primary.code);
71 | DrawLabelsInRow("Increase Target Heading", GameSettings.YAW_RIGHT.primary.code);
72 | DrawLabelsInRow("Reduce Vert Target", GameSettings.PITCH_DOWN.primary.code);
73 | DrawLabelsInRow("Increase Vert Target", GameSettings.PITCH_UP.primary.code);
74 | DrawLabelsInRow("Reduce Target Speed", GameSettings.THROTTLE_DOWN.primary.code);
75 | DrawLabelsInRow("Increase Target Speed", GameSettings.THROTTLE_UP.primary.code);
76 | DrawLabelsInRow("Toggle Fine Mode", GameSettings.PRECISION_CTRL.primary.code);
77 | DrawLabelsInRow("Rate x10", GameSettings.MODIFIER_KEY.primary.code);
78 | GUILayout.Space(20);
79 | foreach (Binding b in bindings)
80 | {
81 | if (b.ToDisplay != Display.Asst)
82 | {
83 | continue;
84 | }
85 |
86 | DrawSetKey(b);
87 | }
88 | }
89 | else if (selector == Display.SSAS)
90 | {
91 | DrawSetKey(bindings[(int)BindingIndex.ArmSSAS]);
92 | DrawLabelsInRow("Toggle SSAS", GameSettings.SAS_TOGGLE.primary.code);
93 | }
94 | GUI.DragWindow();
95 | }
96 |
97 | void DrawLabelsInRow(string Action, KeyCode Keycode)
98 | {
99 | GUILayout.BeginHorizontal();
100 | GUILayout.Label(Action, GUILayout.Width(150));
101 | GUILayout.Label(Keycode.ToString(), GUILayout.Width(120));
102 | GUILayout.EndHorizontal();
103 | }
104 |
105 | void DrawSetKey(Binding keybind)
106 | {
107 | GUILayout.BeginHorizontal();
108 | GUILayout.Label(keybind.BindingDescription, GUILayout.Width(150));
109 | keybind.WaitingToSetPrimary = GUILayout.Toggle(keybind.WaitingToSetPrimary, keybind.PrimaryBindingCode.ToString(), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(100));
110 | if (keybind.WaitingToSetPrimary)
111 | {
112 | if (Input.GetMouseButton(0) || Event.current.keyCode == KeyCode.Escape)
113 | {
114 | keybind.WaitingToSetPrimary = false;
115 | }
116 | else if (Event.current.type == EventType.KeyDown)
117 | {
118 | keybind.PrimaryBindingCode.code = Event.current.keyCode;
119 | keybind.WaitingToSetPrimary = false;
120 | }
121 | }
122 | keybind.WaitingToSetSecondary = GUILayout.Toggle(keybind.WaitingToSetSecondary, keybind.SecondaryBindingCode.ToString(), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(100));
123 | if (keybind.WaitingToSetSecondary)
124 | {
125 | if (Input.GetMouseButton(0) || Event.current.keyCode == KeyCode.Escape)
126 | {
127 | keybind.SecondaryBindingCode.code = KeyCode.None;
128 | keybind.WaitingToSetSecondary = false;
129 | }
130 | else if (Event.current.type == EventType.KeyDown)
131 | {
132 | keybind.SecondaryBindingCode.code = Event.current.keyCode;
133 | keybind.WaitingToSetSecondary = false;
134 | }
135 | }
136 | GUILayout.EndHorizontal();
137 | }
138 |
139 | public void OnDestroy()
140 | {
141 | bindings = null;
142 | instance = null;
143 | }
144 |
145 | public class Binding
146 | {
147 | public Display ToDisplay { get; set; }
148 | public string BindingDescription { get; set; }
149 | public KeyCodeExtended PrimaryBindingCode { get; set; }
150 | public bool WaitingToSetPrimary { get; set; }
151 | public KeyCodeExtended SecondaryBindingCode { get; set; }
152 | public bool WaitingToSetSecondary { get; set; }
153 | public Binding(string description, KeyCode primary, KeyCode secondary, Display display)
154 | {
155 | BindingDescription = description;
156 | PrimaryBindingCode = new KeyCodeExtended(primary);
157 | SecondaryBindingCode = new KeyCodeExtended(secondary);
158 | ToDisplay = display;
159 | }
160 |
161 | public bool IsPressed
162 | {
163 | get
164 | {
165 | return Input.GetKeyDown(PrimaryBindingCode.code) && (SecondaryBindingCode.isNone ? true : Input.GetKey(SecondaryBindingCode.code));
166 | }
167 | }
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/PilotAssistant/FlightModules/VesselData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace PilotAssistant.FlightModules
7 | {
8 | using Utility;
9 |
10 | public class VesselData
11 | {
12 | public VesselData(AsstVesselModule avm)
13 | {
14 | vesModule = avm;
15 | }
16 |
17 | public AsstVesselModule vesModule;
18 |
19 | public double radarAlt;
20 | public double pitch;
21 | public double bank;
22 | public double yaw;
23 | public double AoA;
24 | public double heading;
25 | public double progradeHeading;
26 | public double vertSpeed;
27 | public double acceleration;
28 | public Vector3d planetUp;
29 | public Vector3d surfVelForward;
30 | public Vector3d surfVelRight;
31 | public Vector3d surfVesForward;
32 | public Vector3d surfVesRight;
33 | public Vector3d lastVelocity;
34 | public Vector3d velocity;
35 | public Vector3 obtRadial;
36 | public Vector3 obtNormal;
37 | public Vector3 srfRadial;
38 | public Vector3 srfNormal;
39 |
40 | ///
41 | /// Called in OnPreAutoPilotUpdate. Do not call multiple times per physics frame or the "lastPlanetUp" vector will not be correct and VSpeed will not be calculated correctly
42 | /// Can't just leave it to a Coroutine becuase it has to be called before anything else
43 | ///
44 | public void UpdateAttitude()
45 | {
46 | //if (PilotAssistantFlightCore.calculateDirection)
47 | //findVesselFwdAxis(vRef.Vessel);
48 | //else
49 | vesselFacingAxis = vesModule.Vessel.transform.up;
50 | planetUp = (vesModule.Vessel.rootPart.transform.position - vesModule.Vessel.mainBody.position).normalized;
51 |
52 | // 4 frames of reference to use. Orientation, Velocity, and both of the previous parallel to the surface
53 | radarAlt = vesModule.Vessel.altitude - (vesModule.Vessel.mainBody.ocean ? Math.Max(vesModule.Vessel.pqsAltitude, 0) : vesModule.Vessel.pqsAltitude);
54 | velocity = vesModule.Vessel.rootPart.Rigidbody.velocity + Krakensbane.GetFrameVelocity();
55 | acceleration = (velocity - lastVelocity).magnitude / TimeWarp.fixedDeltaTime;
56 | acceleration *= Math.Sign(Vector3.Dot(velocity - lastVelocity, velocity));
57 | vertSpeed = Vector3d.Dot(planetUp, (velocity + lastVelocity) / 2);
58 | lastVelocity = velocity;
59 |
60 | // Velocity forward and right vectors parallel to the surface
61 | surfVelRight = Vector3d.Cross(planetUp, vesModule.Vessel.srf_velocity).normalized;
62 | surfVelForward = Vector3d.Cross(surfVelRight, planetUp).normalized;
63 |
64 | // Vessel forward and right vectors parallel to the surface
65 | surfVesRight = Vector3d.Cross(planetUp, vesselFacingAxis).normalized;
66 | surfVesForward = Vector3d.Cross(surfVesRight, planetUp).normalized;
67 |
68 | obtNormal = Vector3.Cross(vesModule.Vessel.obt_velocity, planetUp).normalized;
69 | obtRadial = Vector3.Cross(vesModule.Vessel.obt_velocity, obtNormal).normalized;
70 | srfNormal = Vector3.Cross(vesModule.Vessel.srf_velocity, planetUp).normalized;
71 | srfRadial = Vector3.Cross(vesModule.Vessel.srf_velocity, srfNormal).normalized;
72 |
73 | pitch = 90 - Vector3d.Angle(planetUp, vesselFacingAxis);
74 | heading = (Vector3d.Angle(surfVesForward, vesModule.Vessel.north) * Math.Sign(Vector3d.Dot(surfVesForward, vesModule.Vessel.east))).HeadingClamp(360);
75 | progradeHeading = (Vector3d.Angle(surfVelForward, vesModule.Vessel.north) * Math.Sign(Vector3d.Dot(surfVelForward, vesModule.Vessel.east))).HeadingClamp(360);
76 | bank = Vector3d.Angle(surfVesRight, vesModule.Vessel.ReferenceTransform.right) * Math.Sign(Vector3d.Dot(surfVesRight, -vesModule.Vessel.ReferenceTransform.forward));
77 |
78 | if (vesModule.Vessel.srfSpeed > 1)
79 | {
80 | Vector3d AoAVec = vesModule.Vessel.srf_velocity.ProjectOnPlane(vesModule.Vessel.ReferenceTransform.right);
81 | AoA = Vector3d.Angle(AoAVec, vesselFacingAxis) * Math.Sign(Vector3d.Dot(AoAVec, vesModule.Vessel.ReferenceTransform.forward));
82 |
83 | Vector3d yawVec = vesModule.Vessel.srf_velocity.ProjectOnPlane(vesModule.Vessel.ReferenceTransform.forward);
84 | yaw = Vector3d.Angle(yawVec, vesselFacingAxis) * Math.Sign(Vector3d.Dot(yawVec, vesModule.Vessel.ReferenceTransform.right));
85 | }
86 | else
87 | {
88 | AoA = yaw = 0;
89 | }
90 | }
91 |
92 | private Vector3 vesselFacingAxis = new Vector3();
93 | ///
94 | /// Find the vessel orientation at the CoM by interpolating from surrounding part transforms
95 | /// This orientation should be significantly more resistant to vessel flex/wobble than the vessel transform (root part) as a free body rotates about it's CoM
96 | ///
97 | /// Has an issue with the origin shifter causing random bounces
98 | ///
99 | public void FindVesselFwdAxis(Vessel v)
100 | {
101 | Part closestPart = v.rootPart;
102 | float offset = (closestPart.transform.position - v.CurrentCoM).sqrMagnitude; // only comparing magnitude, sign and actual value don't matter
103 |
104 | foreach (Part p in v.Parts)
105 | {
106 | float partOffset = (p.partTransform.position - v.CurrentCoM).sqrMagnitude;
107 | if (partOffset < offset)
108 | {
109 | closestPart = p;
110 | offset = partOffset;
111 | }
112 | }
113 | ///
114 | /// now require two things, accounting for any rotation in part placement, and interpolating with surrounding parts (parent/children/symmetry counterparts) to "shift" the location to the CoM
115 | /// accounting for rotation is the most important, the nearby position will work for now.
116 | /// Vector3 location = closestPart.partTransform.position - v.CurrentCoM;
117 | ///
118 | vesselFacingAxis = closestPart.transform.localRotation * Quaternion.Inverse(closestPart.orgRot) * Vector3.up;
119 | if (!ReferenceEquals(closestPart.symmetryCounterparts, null))
120 | {
121 | for (int i = 0; i < closestPart.symmetryCounterparts.Count; i++)
122 | {
123 | vesselFacingAxis += closestPart.symmetryCounterparts[i].transform.localRotation * Quaternion.Inverse(closestPart.symmetryCounterparts[i].orgRot) * Vector3.up;
124 | }
125 | vesselFacingAxis /= (closestPart.symmetryCounterparts.Count + 1);
126 | }
127 | }
128 |
129 | private ArrowPointer pointer;
130 | public void DrawArrow(Vector3 dir, Transform t)
131 | {
132 | if (ReferenceEquals(pointer, null))
133 | {
134 | pointer = ArrowPointer.Create(t, Vector3.zero, dir, 100, Color.red, true);
135 | }
136 | else
137 | {
138 | pointer.Direction = dir;
139 | }
140 | }
141 |
142 | public void DestroyArrow()
143 | {
144 | UnityEngine.Object.Destroy(pointer);
145 | pointer = null;
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/PilotAssistant/PID_Controller.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 |
4 | namespace PilotAssistant
5 | {
6 | using Utility;
7 | using FlightModules;
8 |
9 | public class Asst_PID_Controller
10 | {
11 | public AsstList CtrlID { get; set; }
12 | public double Target_setpoint { get; set; } // target setpoint
13 | public double Active_setpoint { get; set; } // setpoint being shifted to the target
14 | public double K_proportional { get; set; }
15 | public double K_integral { get; set; }
16 | public double K_derivative { get; set; }
17 | protected double scale;
18 | public double InMin { private get; set; }
19 | public double InMax { private get; set; }
20 | public double OutMin { get; set; }
21 | public double OutMax { get; set; }
22 | public double IntegralClampUpper { get; set; }
23 | public double IntegralClampLower { get; set; }
24 | protected double increment = 0; // increment stored because it grows with each frame
25 | protected double easing = 1; // speed of increment growth
26 |
27 | protected double sum = 0; // integral sum
28 | protected double previous = 0; // previous value stored for derivative action
29 | protected double rolling_diff = 0; // used for rolling average difference
30 | protected double rollingFactor = 0.5; // rolling average proportion. 0 = all new, 1 = never changes
31 |
32 | public double LastOutput { get; protected set; }
33 | public bool InvertInput { get; set; }
34 | public bool InvertOutput { get; set; }
35 | public bool BShow { get; set; }
36 | public bool SkipDerivative { get; set; }
37 | public bool IsHeadingControl { get; set; }
38 |
39 | public Asst_PID_Controller(AsstList ID, double Kp, double Ki, double Kd, double OutputMin, double OutputMax, double intClampLower, double intClampUpper, double scalar = 1, double shiftRate = 1)
40 | {
41 | CtrlID = ID;
42 | K_proportional = Kp;
43 | K_integral = Ki;
44 | K_derivative = Kd;
45 | OutMin = OutputMin;
46 | OutMax = OutputMax;
47 | IntegralClampLower = intClampLower;
48 | IntegralClampUpper = intClampUpper;
49 | scale = scalar;
50 | easing = shiftRate;
51 | InMin = -double.MaxValue;
52 | InMax = double.MaxValue;
53 | }
54 |
55 | public Asst_PID_Controller(AsstList ID, double[] gains)
56 | {
57 | CtrlID = ID;
58 | K_proportional = gains[0];
59 | K_integral = gains[1];
60 | K_derivative = gains[2];
61 | OutMin = gains[3];
62 | OutMax = gains[4];
63 | IntegralClampLower = gains[5];
64 | IntegralClampUpper = gains[6];
65 | scale = gains[7];
66 | easing = gains[8];
67 | InMin = -double.MaxValue;
68 | InMax = double.MaxValue;
69 | }
70 |
71 | public virtual double ResponseD(double input, bool useIntegral)
72 | {
73 | input = Utils.Clamp((InvertInput ? -1 : 1) * input, InMin, InMax);
74 | if (Active_setpoint != Target_setpoint)
75 | {
76 | increment += easing * TimeWarp.fixedDeltaTime * 0.01;
77 | Active_setpoint += Utils.Clamp(Target_setpoint - Active_setpoint, -increment, increment);
78 | }
79 | double error;
80 | if (!IsHeadingControl)
81 | {
82 | error = input - Active_setpoint;
83 | }
84 | else
85 | {
86 | error = Utils.CurrentAngleTargetRel(input, Active_setpoint, 180) - Active_setpoint;
87 | }
88 |
89 | if (SkipDerivative)
90 | {
91 | SkipDerivative = false;
92 | previous = input;
93 | }
94 | LastOutput = ProportionalError(error) + IntegralError(error, useIntegral) + DerivativeError(input);
95 | LastOutput *= (InvertOutput ? -1 : 1);
96 | LastOutput = Utils.Clamp(LastOutput, OutMin, OutMax);
97 | return LastOutput;
98 | }
99 |
100 | public virtual float ResponseF(double input, bool useIntegral)
101 | {
102 | return (float)ResponseD(input, useIntegral);
103 | }
104 |
105 | protected virtual double ProportionalError(double error)
106 | {
107 | return error * K_proportional / scale;
108 | }
109 |
110 | protected virtual double IntegralError(double error, bool useIntegral)
111 | {
112 | if (K_integral == 0 || !useIntegral)
113 | {
114 | sum = 0;
115 | return sum;
116 | }
117 | sum += error * TimeWarp.fixedDeltaTime * K_integral / scale;
118 | sum = Utils.Clamp(sum, IntegralClampLower, IntegralClampUpper); // AIW
119 | return sum;
120 | }
121 |
122 | protected virtual double DerivativeError(double input)
123 | {
124 | double difference = 0;
125 | if (IsHeadingControl)
126 | {
127 | difference = (Utils.CurrentAngleTargetRel(input, previous, 180) - previous) / TimeWarp.fixedDeltaTime;
128 | }
129 | else
130 | {
131 | difference = (input - previous) / TimeWarp.fixedDeltaTime;
132 | }
133 |
134 | previous = input;
135 |
136 | rolling_diff = rolling_diff * rollingFactor + difference * (1 - rollingFactor); // rolling average sometimes helps smooth out a jumpy derivative response
137 | return rolling_diff * K_derivative / scale;
138 | }
139 |
140 | protected virtual double DerivativeErrorRate(double rate)
141 | {
142 | return rate * K_derivative / scale;
143 | }
144 |
145 | public virtual void Clear()
146 | {
147 | sum = 0;
148 | }
149 |
150 | ///
151 | /// Set the integral up to resume from its last loop. Used to smoothly resume control
152 | ///
153 | /// set true if control is reversed for some reason
154 | public virtual void Preset(bool invert = false)
155 | {
156 | sum = LastOutput * (invert ? 1 : -1);
157 | }
158 |
159 | ///
160 | /// Set the integral to resume from a new target. Used to smoothly resume control
161 | ///
162 | ///
163 | ///
164 | public virtual void Preset(double target, bool invert = false)
165 | {
166 | sum = target * (invert ? 1 : -1);
167 | }
168 |
169 | public virtual void UpdateSetpoint(double newSetpoint, bool smooth = false, double smoothStart = 0)
170 | {
171 | Target_setpoint = Utils.Clamp(newSetpoint, InMin, InMax);
172 | Active_setpoint = smooth ? smoothStart : newSetpoint;
173 | increment = 0;
174 | }
175 |
176 | public virtual void IncreaseSetpoint(double increaseBy)
177 | {
178 | Target_setpoint += increaseBy;
179 | }
180 |
181 | #region properties
182 | public virtual double Scalar
183 | {
184 | get
185 | {
186 | return scale;
187 | }
188 | set
189 | {
190 | scale = Math.Max(value, 0.01);
191 | }
192 | }
193 |
194 | public virtual double Easing
195 | {
196 | get
197 | {
198 | return easing;
199 | }
200 | set
201 | {
202 | easing = Math.Max(value, 0.01);
203 | }
204 | }
205 | #endregion
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/PilotAssistant/Utility/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace PilotAssistant.Utility
7 | {
8 | using FlightModules;
9 |
10 | public static class Utils
11 | {
12 | public static T Clamp(this T val, T min, T max) where T : System.IComparable
13 | {
14 | if (val.CompareTo(min) < 0)
15 | {
16 | return min;
17 | }
18 | else if (val.CompareTo(max) > 0)
19 | {
20 | return max;
21 | }
22 | else
23 | {
24 | return val;
25 | }
26 | }
27 |
28 | public static Asst_PID_Controller GetAsst(this AsstList id, PilotAssistant instance)
29 | {
30 | return instance.controllers[(int)id];
31 | }
32 |
33 | public static bool IsFlightControlLocked()
34 | {
35 | return (InputLockManager.IsLocked(ControlTypes.PITCH) && !PilotAssistant.pitchLockEngaged) || InputLockManager.IsLocked(ControlTypes.ROLL)
36 | || (InputLockManager.IsLocked(ControlTypes.YAW) && !PilotAssistant.yawLockEngaged) || InputLockManager.IsLocked(ControlTypes.THROTTLE);
37 | }
38 |
39 | ///
40 | /// Circular rounding to keep compass measurements within a 360 degree range
41 | /// maxHeading is the top limit, bottom limit is maxHeading - 360
42 | ///
43 | public static double HeadingClamp(this double valToClamp, double maxHeading, double range = 360)
44 | {
45 | double temp = (valToClamp - (maxHeading - range)) % range;
46 | return (maxHeading - range) + (temp < 0 ? temp + range : temp);
47 | }
48 |
49 | ///
50 | /// Plane normal vector from a given heading (surface right vector)
51 | ///
52 | public static Vector3 VecHeading(double target, AsstVesselModule avm)
53 | {
54 | double angleDiff = target - avm.vesselData.heading;
55 | return Quaternion.AngleAxis((float)(angleDiff + 90), (Vector3)avm.vesselData.planetUp) * avm.vesselData.surfVesForward;
56 | }
57 |
58 | ///
59 | /// calculate current heading from plane normal vector
60 | ///
61 | public static double CalculateTargetHeading(Vector3 direction, AsstVesselModule avm)
62 | {
63 | var fwd = Vector3.Cross(direction, avm.vesselData.planetUp);
64 | double heading = Vector3.Angle(fwd, avm.Vessel.north) * Math.Sign(Vector3.Dot(fwd, avm.Vessel.east));
65 | return heading.HeadingClamp(360);
66 | }
67 |
68 | ///
69 | /// calculate current heading from plane rotation
70 | ///
71 | public static double CalculateTargetHeading(Quaternion rotation, AsstVesselModule avm)
72 | {
73 | var fwd = Vector3.Cross(GetPlaneNormal(rotation, avm), avm.vesselData.planetUp);
74 | double heading = Vector3.Angle(fwd, avm.Vessel.north) * Math.Sign(Vector3.Dot(fwd, avm.Vessel.east));
75 | return heading.HeadingClamp(360);
76 | }
77 |
78 | ///
79 | /// calculates the angle to feed corrected for 0/360 crossings
80 | /// eg. if the target is 350 and the current is 10, it will return 370 giving a diff of -20 degrees
81 | /// else you get +ve 340 and the turn is in the wrong direction
82 | ///
83 | public static double CurrentAngleTargetRel(double current, double target, double maxAngle)
84 | {
85 | double diff = target - current;
86 | if (diff < maxAngle - 360)
87 | {
88 | return current - 360;
89 | }
90 | else if (diff > maxAngle)
91 | {
92 | return current + 360;
93 | }
94 | else
95 | {
96 | return current;
97 | }
98 | }
99 |
100 | ///
101 | /// calculate the planet relative rotation from the plane normal vector
102 | ///
103 | public static Quaternion GetPlaneRotation(Vector3 planeNormal, AsstVesselModule avm)
104 | {
105 | return Quaternion.FromToRotation(avm.Vessel.mainBody.transform.right, planeNormal);
106 | }
107 |
108 | public static Quaternion GetPlaneRotation(double heading, AsstVesselModule avm)
109 | {
110 | Vector3 planeNormal = VecHeading(heading, avm);
111 | return GetPlaneRotation(planeNormal, avm);
112 | }
113 |
114 | public static Vector3 GetPlaneNormal(Quaternion rotation, AsstVesselModule avm)
115 | {
116 | return rotation * avm.Vessel.mainBody.transform.right;
117 | }
118 |
119 | public static bool IsNeutral(AxisBinding axis)
120 | {
121 | return axis.IsNeutral() && Math.Abs(axis.GetAxis()) < 0.00001;
122 | }
123 |
124 | public static bool HasYawInput()
125 | {
126 | return GameSettings.YAW_LEFT.GetKey() || GameSettings.YAW_RIGHT.GetKey() || !Utils.IsNeutral(GameSettings.AXIS_YAW);
127 | }
128 |
129 | public static bool HasPitchInput()
130 | {
131 | return GameSettings.PITCH_DOWN.GetKey() || GameSettings.PITCH_UP.GetKey() || !Utils.IsNeutral(GameSettings.AXIS_PITCH);
132 | }
133 |
134 | public static bool HasRollInput()
135 | {
136 | return GameSettings.ROLL_LEFT.GetKey() || GameSettings.ROLL_RIGHT.GetKey() || !Utils.IsNeutral(GameSettings.AXIS_ROLL);
137 | }
138 |
139 | public static bool HasThrottleInput()
140 | {
141 | return GameSettings.THROTTLE_UP.GetKey() || GameSettings.THROTTLE_DOWN.GetKey() || (GameSettings.THROTTLE_CUTOFF.GetKeyDown() && !GameSettings.MODIFIER_KEY.GetKey()) || GameSettings.THROTTLE_FULL.GetKeyDown();
142 | }
143 |
144 | public static Vector3d ProjectOnPlane(this Vector3d vector, Vector3d planeNormal)
145 | {
146 | return vector - Vector3d.Project(vector, planeNormal);
147 | }
148 |
149 | public static double SpeedUnitTransform(SpeedUnits units, double soundSpeed)
150 | {
151 | switch (units)
152 | {
153 | case SpeedUnits.mSec:
154 | return 1;
155 | case SpeedUnits.knots:
156 | return 1.943844492440604768413343347219;
157 | case SpeedUnits.kmph:
158 | return 3.6;
159 | case SpeedUnits.mph:
160 | return 2.236936;
161 | case SpeedUnits.mach:
162 | return 1 / soundSpeed;
163 | }
164 | return 1;
165 | }
166 |
167 | public static double SpeedTransform(SpeedRef refMode, AsstVesselModule avm)
168 | {
169 | switch (refMode)
170 | {
171 | case SpeedRef.Indicated:
172 | double stagnationPres = Math.Pow(((avm.Vessel.mainBody.atmosphereAdiabaticIndex - 1) * avm.Vessel.mach * avm.Vessel.mach * 0.5) + 1, avm.Vessel.mainBody.atmosphereAdiabaticIndex / (avm.Vessel.mainBody.atmosphereAdiabaticIndex - 1));
173 | return Math.Sqrt(avm.Vessel.atmDensity / 1.225) * stagnationPres;
174 | case SpeedRef.Equivalent:
175 | return Math.Sqrt(avm.Vessel.atmDensity / 1.225);
176 | case SpeedRef.True:
177 | default:
178 | return 1;
179 | }
180 | }
181 |
182 | public static string UnitString(SpeedUnits unit)
183 | {
184 | switch(unit)
185 | {
186 | case SpeedUnits.mSec:
187 | return " m/s";
188 | case SpeedUnits.mach:
189 | return " mach";
190 | case SpeedUnits.knots:
191 | return " kn";
192 | case SpeedUnits.kmph:
193 | return " km/h";
194 | case SpeedUnits.mph:
195 | return " mph";
196 | }
197 | return string.Empty;
198 | }
199 |
200 |
201 | public static string TryGetValue(this ConfigNode node, string key, string defaultValue)
202 | {
203 | if (node.HasValue(key))
204 | {
205 | return node.GetValue(key);
206 | }
207 |
208 | return defaultValue;
209 | }
210 |
211 | public static bool TryGetValue(this ConfigNode node, string key, bool defaultValue)
212 | {
213 | if (node.HasValue(key) && bool.TryParse(node.GetValue(key), out bool val))
214 | {
215 | return val;
216 | }
217 |
218 | return defaultValue;
219 | }
220 |
221 | public static int TryGetValue(this ConfigNode node, string key, int defaultValue)
222 | {
223 | if (node.HasValue(key) && int.TryParse(node.GetValue(key), out int val))
224 | {
225 | return val;
226 | }
227 |
228 | return defaultValue;
229 | }
230 |
231 | public static float TryGetValue(this ConfigNode node, string key, float defaultValue)
232 | {
233 | if (node.HasValue(key) && float.TryParse(node.GetValue(key), out float val))
234 | {
235 | return val;
236 | }
237 |
238 | return defaultValue;
239 | }
240 |
241 | public static double TryGetValue(this ConfigNode node, string key, double defaultValue)
242 | {
243 | if (node.HasValue(key) && double.TryParse(node.GetValue(key), out double val))
244 | {
245 | return val;
246 | }
247 |
248 | return defaultValue;
249 | }
250 |
251 | public static KeyCode TryGetValue(this ConfigNode node, string key, KeyCode defaultValue)
252 | {
253 | if (node.HasValue(key))
254 | {
255 | try
256 | {
257 | var val = (KeyCode)System.Enum.Parse(typeof(KeyCode), node.GetValue(key));
258 | return val;
259 | }
260 | catch { }
261 | }
262 | return defaultValue;
263 | }
264 |
265 | public static Rect TryGetValue(this ConfigNode node, string key, Rect defaultValue)
266 | {
267 | var val = new Rect();
268 | if (node.TryGetValue(key, ref val))
269 | {
270 | return val;
271 | }
272 | return defaultValue;
273 | }
274 | }
275 | }
--------------------------------------------------------------------------------
/PilotAssistant/Utility/GeneralUI.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace PilotAssistant.Utility
6 | {
7 | enum MyStyles
8 | {
9 | labelAlert,
10 | numBoxLabel,
11 | numBoxText,
12 | btnPlus,
13 | btnMinus,
14 | btnToggle,
15 | greenTextBox,
16 | redButtonText,
17 | lblToggle
18 | }
19 |
20 | static class GeneralUI
21 | {
22 | public static Color stockBackgroundGUIColor;
23 | public static Color ActiveBackground = XKCDColors.BrightOrange;
24 | public static Color InActiveBackground = XKCDColors.BrightSkyBlue;
25 | public static Color HeaderButtonBackground = XKCDColors.BlueBlue;
26 |
27 | // save the skin so other windows can't interfere
28 | public static GUISkin UISkin;
29 |
30 | public static GUIContent KpLabel = new GUIContent("Kp", "Kp is the proportional response factor. The greater the error between the current state and the target, the greater the impact it has. \r\n\r\nP_res = Kp * error");
31 | public static GUIContent KiLabel = new GUIContent("Ki", "Ki is the integral response factor. The integral response is the sum of all previous errors and depends on both the magnitude and the duration for which the error remained.\r\n\r\nI_res = Ki * sumOf(error)");
32 | public static GUIContent KdLabel = new GUIContent("Kd", "Kd is the derivative response factor. The derivative response acts to prevent the output from changing and will dampen out oscillations when used in moderation.\r\n\r\nD_res = Kd * (error - prev_error)");
33 | public static GUIContent ScalarLabel = new GUIContent("Scalar", "The scalar factor increase/decrease the impact of Kp, Ki, and Kd. This is used to accomodate variations in flight conditions.\r\n\r\nOutput = (P_res + I_res + D_res) / Scalar");
34 | public static GUIContent IMaxLabel = new GUIContent("I Max", "The maximum value the integral sum can reach. This is mostly used to prevent excessive buildup when the setpoint is changed");
35 | public static GUIContent IMinLabel = new GUIContent("I Min", "The minimum value the integral sum can reach. This is mostly used to prevent excessive buildup when the setpoint is changed");
36 | public static GUIContent EasingLabel = new GUIContent("Easing", "The rate of change of the setpoint when a new target is set. Higher gives a faster change, lower gives a smoother change");
37 | public static GUIContent DelayLabel = new GUIContent("Delay", "The time in ms between there being no input on the axis and the axis attitude being locked");
38 |
39 | public static void CustomSkin()
40 | {
41 | UISkin = (GUISkin)MonoBehaviour.Instantiate(UnityEngine.GUI.skin);
42 | UISkin.customStyles = new GUIStyle[Enum.GetValues(typeof(MyStyles)).GetLength(0)];
43 | stockBackgroundGUIColor = GUI.backgroundColor;
44 |
45 | // style for the paused message (big, bold, and red)
46 | UISkin.customStyles[(int)MyStyles.labelAlert] = new GUIStyle(GUI.skin.box);
47 | UISkin.customStyles[(int)MyStyles.labelAlert].normal.textColor = XKCDColors.Red;
48 | UISkin.customStyles[(int)MyStyles.labelAlert].fontSize = 21;
49 | UISkin.customStyles[(int)MyStyles.labelAlert].fontStyle = FontStyle.Bold;
50 | UISkin.customStyles[(int)MyStyles.labelAlert].alignment = TextAnchor.MiddleCenter;
51 |
52 | // style for label to align with increment buttons
53 | UISkin.customStyles[(int)MyStyles.numBoxLabel] = new GUIStyle(UISkin.label) {
54 | alignment = TextAnchor.MiddleLeft,
55 | margin = new RectOffset(4, 4, 5, 3)
56 | };
57 |
58 | // style for text box to align with increment buttons better
59 | UISkin.customStyles[(int)MyStyles.numBoxText] = new GUIStyle(UISkin.textField) {
60 | alignment = TextAnchor.MiddleLeft,
61 | margin = new RectOffset(4, 0, 5, 3)
62 | };
63 |
64 | // style for increment button
65 | UISkin.customStyles[(int)MyStyles.btnPlus] = new GUIStyle(UISkin.button) {
66 | margin = new RectOffset(0, 4, 2, 0)
67 | };
68 | UISkin.customStyles[(int)MyStyles.btnPlus].hover.textColor = Color.yellow;
69 | UISkin.customStyles[(int)MyStyles.btnPlus].onActive.textColor = Color.green;
70 |
71 | // style for derement button
72 | UISkin.customStyles[(int)MyStyles.btnMinus] = new GUIStyle(UISkin.button) {
73 | margin = new RectOffset(0, 4, 0, 2)
74 | };
75 | UISkin.customStyles[(int)MyStyles.btnMinus].hover.textColor = Color.yellow;
76 | UISkin.customStyles[(int)MyStyles.btnMinus].onActive.textColor = Color.green;
77 |
78 | // A toggle that looks like a button
79 | UISkin.customStyles[(int)MyStyles.btnToggle] = new GUIStyle(UISkin.button);
80 | UISkin.customStyles[(int)MyStyles.btnToggle].normal.textColor = UISkin.customStyles[(int)MyStyles.btnToggle].focused.textColor = Color.white;
81 | UISkin.customStyles[(int)MyStyles.btnToggle].onNormal.textColor = UISkin.customStyles[(int)MyStyles.btnToggle].onFocused.textColor = UISkin.customStyles[(int)MyStyles.btnToggle].onHover.textColor
82 | = UISkin.customStyles[(int)MyStyles.btnToggle].active.textColor = UISkin.customStyles[(int)MyStyles.btnToggle].hover.textColor = UISkin.customStyles[(int)MyStyles.btnToggle].onActive.textColor = Color.green;
83 | UISkin.customStyles[(int)MyStyles.btnToggle].onNormal.background = UISkin.customStyles[(int)MyStyles.btnToggle].onHover.background = UISkin.customStyles[(int)MyStyles.btnToggle].onActive.background
84 | = UISkin.customStyles[(int)MyStyles.btnToggle].active.background = HighLogic.Skin.button.onNormal.background;
85 | UISkin.customStyles[(int)MyStyles.btnToggle].hover.background = UISkin.customStyles[(int)MyStyles.btnToggle].normal.background;
86 |
87 | UISkin.customStyles[(int)MyStyles.lblToggle] = new GUIStyle(UISkin.customStyles[(int)MyStyles.btnToggle]);
88 |
89 | UISkin.customStyles[(int)MyStyles.greenTextBox] = new GUIStyle(UISkin.textArea);
90 | UISkin.customStyles[(int)MyStyles.greenTextBox].active.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].hover.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].focused.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].normal.textColor
91 | = UISkin.customStyles[(int)MyStyles.greenTextBox].onActive.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].onHover.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].onFocused.textColor = UISkin.customStyles[(int)MyStyles.greenTextBox].onNormal.textColor = XKCDColors.Green;
92 |
93 | UISkin.customStyles[(int)MyStyles.redButtonText] = new GUIStyle(UISkin.button);
94 | UISkin.customStyles[(int)MyStyles.redButtonText].active.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].hover.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].focused.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].normal.textColor
95 | = UISkin.customStyles[(int)MyStyles.redButtonText].onActive.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].onHover.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].onFocused.textColor = UISkin.customStyles[(int)MyStyles.redButtonText].onNormal.textColor = XKCDColors.Red;
96 |
97 | UISkin.box.onActive.background = UISkin.box.onFocused.background = UISkin.box.onHover.background = UISkin.box.onNormal.background =
98 | UISkin.box.active.background = UISkin.box.focused.background = UISkin.box.hover.background = UISkin.box.normal.background = UISkin.window.normal.background;
99 | }
100 |
101 | ///
102 | /// Draws a label and text box of specified widths with +/- 10% increment buttons. Returns the numeric value of the text box
103 | ///
104 | /// text for the label
105 | /// number to display in text box
106 | ///
107 | ///
108 | /// edited value of the text box
109 | public static double LabPlusNumBox(string labelText, string boxText, float labelWidth = 100, float boxWidth = 60)
110 | {
111 | double val;
112 | GUILayout.BeginHorizontal();
113 |
114 | GUILayout.Label(labelText, UISkin.customStyles[(int)MyStyles.numBoxLabel], GUILayout.Width(labelWidth));
115 | val = double.Parse(boxText);
116 | boxText = val.ToString(",0.0#####");
117 | string text = GUILayout.TextField(boxText, UISkin.customStyles[(int)MyStyles.numBoxText], GUILayout.Width(boxWidth));
118 | //
119 | try
120 | {
121 | val = double.Parse(text);
122 | }
123 | catch
124 | {
125 | val = double.Parse(boxText);
126 | }
127 | //
128 | GUILayout.BeginVertical();
129 | if (GUILayout.Button("+", UISkin.customStyles[(int)MyStyles.btnPlus], GUILayout.Width(20), GUILayout.Height(13)))
130 | {
131 | if (val != 0)
132 | {
133 | val *= 1.1;
134 | }
135 | else
136 | {
137 | val = 0.01;
138 | }
139 | }
140 | if (GUILayout.Button("-", UISkin.customStyles[(int)MyStyles.btnMinus], GUILayout.Width(20), GUILayout.Height(13)))
141 | {
142 | val /= 1.1;
143 | }
144 | GUILayout.EndVertical();
145 | //
146 | GUILayout.EndHorizontal();
147 | return val;
148 | }
149 |
150 | public static double LabPlusNumBox(GUIContent labelText, string boxText, float labelWidth = 100, float boxWidth = 60)
151 | {
152 | double val;
153 | GUILayout.BeginHorizontal();
154 |
155 | GUILayout.Label(labelText, UISkin.customStyles[(int)MyStyles.numBoxLabel], GUILayout.Width(labelWidth));
156 | val = double.Parse(boxText);
157 | boxText = val.ToString(",0.0#####");
158 | string text = GUILayout.TextField(boxText, UISkin.customStyles[(int)MyStyles.numBoxText], GUILayout.Width(boxWidth));
159 | //
160 | try
161 | {
162 | val = double.Parse(text);
163 | }
164 | catch
165 | {
166 | val = double.Parse(boxText);
167 | }
168 | //
169 | GUILayout.BeginVertical();
170 | if (GUILayout.Button("+", UISkin.customStyles[(int)MyStyles.btnPlus], GUILayout.Width(20), GUILayout.Height(13)))
171 | {
172 | if (val != 0)
173 | {
174 | val *= 1.1;
175 | }
176 | else
177 | {
178 | val = 0.01;
179 | }
180 | }
181 | if (GUILayout.Button("-", UISkin.customStyles[(int)MyStyles.btnMinus], GUILayout.Width(20), GUILayout.Height(13)))
182 | {
183 | val /= 1.1;
184 | }
185 | GUILayout.EndVertical();
186 | //
187 | GUILayout.EndHorizontal();
188 | return val;
189 | }
190 |
191 | public static void PostMessage(string message)
192 | {
193 | ScreenMessages.PostScreenMessage(message);
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/PilotAssistant/PilotAssistantFlightCore.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using UnityEngine;
4 |
5 | namespace PilotAssistant
6 | {
7 | using FlightModules;
8 | using Toolbar;
9 | using Utility;
10 |
11 | [KSPAddon(KSPAddon.Startup.Flight, false)]
12 | internal class PilotAssistantFlightCore : MonoBehaviour
13 | {
14 | private static PilotAssistantFlightCore instance;
15 | public static PilotAssistantFlightCore Instance
16 | {
17 | get
18 | {
19 | return instance;
20 | }
21 | }
22 |
23 | public static bool showTooltips = true;
24 | public static bool bHideUI = false;
25 | public static ConfigNode config;
26 | public Rect window;
27 | public bool bUseStockToolbar = true;
28 |
29 | public static bool bDisplayBindings = false;
30 | public static bool bDisplayOptions = false;
31 | public static bool bDisplayAssistant = false;
32 | public static bool bDisplaySAS = false;
33 | public static bool bDisplaySSAS = false;
34 |
35 | public static bool calculateDirection = true;
36 |
37 | public string blizMenuTexPath;
38 | public string blizAsstTexPath;
39 | public string blizSSASTexPath;
40 | public string blizSASTexPath;
41 |
42 | public List controlledVessels = new List();
43 | public int selectedVesselIndex = 0;
44 |
45 | public void Awake()
46 | {
47 | instance = this;
48 | bHideUI = false;
49 |
50 | config = ConfigNode.Load(KSP.IO.IOUtils.GetFilePathFor(GetType(), "Settings.cfg")) ?? new ConfigNode(string.Empty);
51 |
52 | bUseStockToolbar = config.TryGetValue("UseStockToolbar", true);
53 |
54 | blizMenuTexPath = config.TryGetValue("blizMenuIcon", "Pilot Assistant/Icon/BlizzyIcon");
55 | blizAsstTexPath = config.TryGetValue("blizAsstIcon", "Pilot Assistant/Icon/BlizzyIcon");
56 | blizSSASTexPath = config.TryGetValue("blizSSASIcon", "Pilot Assistant/Icon/BlizzyIcon");
57 | blizSASTexPath = config.TryGetValue("blizSASIcon", "Pilot Assistant/Icon/BlizzyIcon");
58 |
59 | if (!bUseStockToolbar && ToolbarManager.ToolbarAvailable)
60 | {
61 | ToolbarMod.Instance.Awake();
62 | }
63 | else
64 | {
65 | AppLauncherFlight.Instance.Awake();
66 | }
67 | }
68 |
69 | public void Start()
70 | {
71 | BindingManager.Instance.Start();
72 | LoadConfig();
73 |
74 | // don't put these in awake or they trigger on loading the vessel and everything gets wierd
75 | GameEvents.onHideUI.Add(HideUI);
76 | GameEvents.onShowUI.Add(ShowUI);
77 | }
78 |
79 | public void AddVessel(AsstVesselModule avm)
80 | {
81 | controlledVessels.Add(avm);
82 | }
83 |
84 | public void RemoveVessel(AsstVesselModule avm)
85 | {
86 | if (selectedVesselIndex >= controlledVessels.Count)
87 | {
88 | return;
89 | }
90 |
91 | if (avm.Vessel != controlledVessels[selectedVesselIndex].Vessel)
92 | {
93 | Vessel ves = controlledVessels[selectedVesselIndex].Vessel;
94 | controlledVessels.Remove(avm);
95 | selectedVesselIndex = controlledVessels.FindIndex(vm => vm.Vessel == ves);
96 | }
97 | else
98 | {
99 | controlledVessels.RemoveAt(selectedVesselIndex);
100 | selectedVesselIndex = 0;
101 | }
102 | }
103 |
104 | public void LoadConfig()
105 | {
106 | try
107 | {
108 | config = ConfigNode.Load(KSP.IO.IOUtils.GetFilePathFor(GetType(), "Settings.cfg"));
109 | if (ReferenceEquals(config, null))
110 | {
111 | config = new ConfigNode(string.Empty);
112 | }
113 |
114 | if (!ReferenceEquals(config, null))
115 | {
116 | showTooltips = config.TryGetValue("AsstTooltips", true);
117 |
118 | PilotAssistant.doublesided = config.TryGetValue("AsstDoublesided", false);
119 | PilotAssistant.showPIDLimits = config.TryGetValue("AsstLimits", false);
120 | PilotAssistant.showControlSurfaces = config.TryGetValue("AsstControlSurfaces", false);
121 | PilotAssistant.maxHdgScrollbarHeight = config.TryGetValue("maxHdgHeight", 55);
122 | PilotAssistant.maxVertScrollbarHeight = config.TryGetValue("maxVertHeight", 55);
123 | PilotAssistant.maxThrtScrollbarHeight = config.TryGetValue("maxThrtHeight", 55);
124 |
125 | // windows
126 | PilotAssistant.window = config.TryGetValue("AsstWindow", new Rect(300, 300, 0, 0));
127 | BindingManager.Instance.windowRect = config.TryGetValue("BindingWindow", new Rect(300, 50, 0, 0));
128 | window = config.TryGetValue("AppWindow", new Rect(100, 300, 0, 0));
129 |
130 | // key bindings
131 | BindingManager.bindings[(int)BindingIndex.Pause].PrimaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("pausePrimary", KeyCode.Tab.ToString()));
132 | BindingManager.bindings[(int)BindingIndex.Pause].SecondaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("pauseSecondary", KeyCode.None.ToString()));
133 | BindingManager.bindings[(int)BindingIndex.HdgTgl].PrimaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("hdgTglPrimary", KeyCode.Keypad9.ToString()));
134 | BindingManager.bindings[(int)BindingIndex.HdgTgl].SecondaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("hdgTglSecondary", KeyCode.LeftAlt.ToString()));
135 | BindingManager.bindings[(int)BindingIndex.VertTgl].PrimaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("vertTglPrimary", KeyCode.Keypad6.ToString()));
136 | BindingManager.bindings[(int)BindingIndex.VertTgl].SecondaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("vertTglSecondary", KeyCode.LeftAlt.ToString()));
137 | BindingManager.bindings[(int)BindingIndex.ThrtTgl].PrimaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("thrtTglPrimary", KeyCode.Keypad3.ToString()));
138 | BindingManager.bindings[(int)BindingIndex.ThrtTgl].SecondaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("thrtTglSecondary", KeyCode.LeftAlt.ToString()));
139 | BindingManager.bindings[(int)BindingIndex.ArmSSAS].PrimaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("SSASArmPrimary", GameSettings.SAS_TOGGLE.primary.ToString()));
140 | BindingManager.bindings[(int)BindingIndex.ArmSSAS].SecondaryBindingCode.code = (KeyCode)System.Enum.Parse(typeof(KeyCode), config.TryGetValue("SSASArmSecondary", KeyCode.LeftAlt.ToString()));
141 | }
142 | else
143 | {
144 | Logger.Log("Failed to create settings node", Logger.LogLevel.Error);
145 | }
146 | }
147 | catch
148 | {
149 | Logger.Log("Config load failed", Logger.LogLevel.Error);
150 | }
151 | }
152 |
153 | public void SaveConfig()
154 | {
155 | try
156 | {
157 | if (ReferenceEquals(config, null))
158 | {
159 | config = new ConfigNode(string.Empty);
160 | }
161 |
162 | if (!ReferenceEquals(config, null))
163 | {
164 | config.SetValue("AsstTooltips", showTooltips.ToString(), true);
165 | config.SetValue("UseStockToolbar", bUseStockToolbar.ToString(), true);
166 |
167 | config.SetValue("AsstDoublesided", PilotAssistant.doublesided.ToString(), true);
168 | config.SetValue("AsstLimits", PilotAssistant.showPIDLimits.ToString(), true);
169 | config.SetValue("AsstControlSurfaces", PilotAssistant.showControlSurfaces.ToString(), true);
170 | config.SetValue("maxHdgHeight", PilotAssistant.maxHdgScrollbarHeight.ToString(), true);
171 | config.SetValue("maxVertHeight", PilotAssistant.maxVertScrollbarHeight.ToString(), true);
172 | config.SetValue("maxThrtHeight", PilotAssistant.maxThrtScrollbarHeight.ToString(), true);
173 |
174 | // window rects
175 | config.SetValue("AsstWindow", PilotAssistant.window.ToString(), true);
176 | config.SetValue("AppWindow", window.ToString(), true);
177 | config.SetValue("BindingWindow", BindingManager.Instance.windowRect.ToString(), true);
178 |
179 | // key bindings
180 | config.SetValue("pausePrimary", BindingManager.bindings[(int)BindingIndex.Pause].PrimaryBindingCode.code.ToString(), true);
181 | config.SetValue("pauseSecondary", BindingManager.bindings[(int)BindingIndex.Pause].SecondaryBindingCode.code.ToString(), true);
182 | config.SetValue("hdgTglPrimary", BindingManager.bindings[(int)BindingIndex.HdgTgl].PrimaryBindingCode.code.ToString(), true);
183 | config.SetValue("hdgTglSecondary", BindingManager.bindings[(int)BindingIndex.HdgTgl].SecondaryBindingCode.code.ToString(), true);
184 | config.SetValue("vertTglPrimary", BindingManager.bindings[(int)BindingIndex.VertTgl].PrimaryBindingCode.code.ToString(), true);
185 | config.SetValue("vertTglSecondary", BindingManager.bindings[(int)BindingIndex.VertTgl].SecondaryBindingCode.code.ToString(), true);
186 | config.SetValue("thrtTglPrimary", BindingManager.bindings[(int)BindingIndex.ThrtTgl].PrimaryBindingCode.code.ToString(), true);
187 | config.SetValue("thrtTglSecondary", BindingManager.bindings[(int)BindingIndex.ThrtTgl].SecondaryBindingCode.code.ToString(), true);
188 | config.SetValue("SSASArmPrimary", BindingManager.bindings[(int)BindingIndex.ArmSSAS].PrimaryBindingCode.code.ToString(), true);
189 | config.SetValue("SSASArmSecondary", BindingManager.bindings[(int)BindingIndex.ArmSSAS].SecondaryBindingCode.code.ToString(), true);
190 |
191 | // bliz toolbar icons
192 | config.SetValue("blizMenuIcon", blizMenuTexPath, true);
193 | config.SetValue("blizAsstIcon", blizAsstTexPath, true);
194 | config.SetValue("blizSSASIcon", blizSSASTexPath, true);
195 | config.SetValue("blizSASIcon", blizSASTexPath, true);
196 |
197 | Directory.CreateDirectory(KSP.IO.IOUtils.GetFilePathFor(GetType(), string.Empty));
198 | config.Save(KSP.IO.IOUtils.GetFilePathFor(GetType(), "Settings.cfg"));
199 | }
200 | }
201 | catch
202 | {
203 | Logger.Log("Pilot Assistant save failed", Logger.LogLevel.Error);
204 | }
205 | }
206 |
207 | public void OnGUI()
208 | {
209 | if (ReferenceEquals(GeneralUI.UISkin, null))
210 | {
211 | GeneralUI.CustomSkin();
212 | }
213 |
214 | if (bHideUI)
215 | {
216 | return;
217 | }
218 |
219 | GUI.skin = GeneralUI.UISkin;
220 | GUI.backgroundColor = GeneralUI.stockBackgroundGUIColor;
221 | Draw();
222 | BindingManager.Instance.Draw();
223 | }
224 |
225 | public void Draw()
226 | {
227 | if (bDisplayOptions)
228 | {
229 | window = GUILayout.Window(0984653, window, OptionsWindow, string.Empty, GUILayout.Width(60), GUILayout.Height(0));
230 | }
231 | }
232 |
233 | private void OptionsWindow(int id)
234 | {
235 | if (GUI.Button(new Rect(window.width - 16, 2, 14, 14), string.Empty))
236 | {
237 | bDisplayOptions = false;
238 | }
239 |
240 | if (GUILayout.Button("Update Defaults"))
241 | {
242 | PresetManager.Instance.UpdateDefaultAsstPreset(controlledVessels[selectedVesselIndex].vesselAsst.activePreset);
243 | }
244 |
245 | if (controlledVessels.Count > 1)
246 | {
247 | GUILayout.Box(string.Empty, GUILayout.Height(10));
248 | for (int i = 0; i < controlledVessels.Count; i++)
249 | {
250 | if (controlledVessels[i].Vessel.isActiveVessel)
251 | {
252 | GUI.backgroundColor = Color.green;
253 | }
254 |
255 | bool tmp = GUILayout.Toggle(i == selectedVesselIndex, controlledVessels[i].Vessel.vesselName, GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(120));
256 | if (tmp)
257 | {
258 | selectedVesselIndex = i;
259 | }
260 |
261 | GUI.backgroundColor = GeneralUI.stockBackgroundGUIColor;
262 | }
263 | }
264 | GUI.DragWindow();
265 | }
266 |
267 | private void HideUI()
268 | {
269 | bHideUI = true;
270 | }
271 |
272 | private void ShowUI()
273 | {
274 | bHideUI = false;
275 | }
276 |
277 | public void OnDestroy()
278 | {
279 | SaveConfig();
280 | if (Toolbar.ToolbarManager.ToolbarAvailable && !bUseStockToolbar)
281 | {
282 | ToolbarMod.Instance.OnDestroy();
283 | }
284 |
285 | BindingManager.Instance.OnDestroy();
286 |
287 | GameEvents.onHideUI.Remove(HideUI);
288 | GameEvents.onShowUI.Remove(ShowUI);
289 |
290 | PresetManager.SaveToFile();
291 | instance = null;
292 | }
293 | }
294 | }
--------------------------------------------------------------------------------
/PilotAssistant/PresetManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEngine;
5 |
6 | namespace PilotAssistant
7 | {
8 | using Utility;
9 | using Presets;
10 | using FlightModules;
11 |
12 | [KSPAddon(KSPAddon.Startup.MainMenu, true)]
13 | public class PresetManager : MonoBehaviour
14 | {
15 | public static PresetManager Instance
16 | {
17 | get;
18 | private set;
19 | }
20 |
21 | //
22 | // A list of all the loaded PA presets and the one currently loaded
23 | public List AsstPresetList = new List();
24 |
25 | //
26 | // stores all craft presets by name
27 | public Dictionary craftPresetDict = new Dictionary();
28 |
29 | //
30 | // save/load paths
31 | public const string presetsPath = "GameData/Pilot Assistant/Presets.cfg";
32 | public const string defaultsPath = "GameData/Pilot Assistant/Defaults.cfg";
33 |
34 | //
35 | // names of default presets
36 | public const string craftDefaultName = "default";
37 | public const string asstDefaultName = "default";
38 |
39 | //
40 | // node ID's for craft and PA presets
41 | public const string craftPresetNodeName = "CraftPreset";
42 | public const string asstPresetNodeName = "PIDPreset";
43 |
44 | //
45 | // PA preset name ID in the craft preset
46 | public const string craftAsstKey = "pilot";
47 |
48 | //
49 | // controller node ID's for the PA preset
50 | public const string hdgCtrlr = "HdgBankController";
51 | public const string yawCtrlr = "HdgYawController";
52 | public const string aileronCtrlr = "AileronController";
53 | public const string rudderCtrlr = "RudderController";
54 | public const string altCtrlr = "AltitudeController";
55 | public const string vertCtrlr = "AoAController";
56 | public const string elevCtrlr = "ElevatorController";
57 | public const string speedCtrlr = "SpeedController";
58 | public const string accelCtrlr = "AccelController";
59 |
60 | //
61 | // controller property keys for PA
62 | public const string pGain = "PGain";
63 | public const string iGain = "IGain";
64 | public const string dGain = "DGain";
65 | public const string min = "MinOut";
66 | public const string max = "MaxOut";
67 | public const string iLower = "ClampLower";
68 | public const string iUpper = "ClampUpper";
69 | public const string scalar = "Scalar";
70 | public const string ease = "Ease";
71 |
72 | public void Start()
73 | {
74 | // only ever a single instance of this class created upon reaching the main menu for the first time
75 | Instance = this;
76 | // make sure that instance is never recovered while loading
77 | DontDestroyOnLoad(this);
78 | // load preset data saved from a previous time
79 | LoadPresetsFromFile();
80 | }
81 |
82 | public void OnDestroy()
83 | {
84 | // probably not ever called but if it is, changes are saved
85 | SaveToFile();
86 | }
87 |
88 | ///
89 | /// process previously saved data loading PA and craft presets into a usable format
90 | ///
91 | public void LoadPresetsFromFile()
92 | {
93 | // PA nodes
94 | foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes(asstPresetNodeName)) // want to move this outside GameDatabase at some point
95 | {
96 | string name = node.GetValue("name");
97 | if (ReferenceEquals(node, null) || Instance.AsstPresetList.Any(p => p.name == name))
98 | {
99 | continue;
100 | }
101 |
102 | // process controller nodes to a more easily accesible array format.
103 | // Could possibly do this a bit neater by iterating through the nodes and doing a switch on the node name. Downside would be trying to keep the order intact
104 | var gains = new List {
105 | GainsArrayFromNode(node.GetNode(hdgCtrlr), AsstList.HdgBank),
106 | GainsArrayFromNode(node.GetNode(yawCtrlr), AsstList.BankToYaw),
107 | GainsArrayFromNode(node.GetNode(aileronCtrlr), AsstList.Aileron),
108 | GainsArrayFromNode(node.GetNode(rudderCtrlr), AsstList.Rudder),
109 | GainsArrayFromNode(node.GetNode(altCtrlr), AsstList.Altitude),
110 | GainsArrayFromNode(node.GetNode(vertCtrlr), AsstList.VertSpeed),
111 | GainsArrayFromNode(node.GetNode(elevCtrlr), AsstList.Elevator),
112 | GainsArrayFromNode(node.GetNode(speedCtrlr), AsstList.Speed),
113 | GainsArrayFromNode(node.GetNode(accelCtrlr), AsstList.Acceleration)
114 | };
115 |
116 | AsstPresetList.Add(new AsstPreset(gains, name));
117 | }
118 |
119 | // craft nodes are just a list of craft/preset pairs with a comma delimiter
120 | char[] delimiter = new char[] { ',' };
121 | foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes(craftPresetNodeName)) // want to move this outside GameDatabase at some point
122 | {
123 | if (ReferenceEquals(node, null))
124 | {
125 | continue;
126 | }
127 |
128 | string[] values = node.GetValues();
129 | for (int i = 0; i < values.Length; ++i )
130 | {
131 | string[] tmp = values[i].Split(delimiter, 2, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
132 | if (tmp.Length != 2)
133 | {
134 | continue;
135 | }
136 |
137 | if (!craftPresetDict.ContainsKey(tmp[0]))
138 | {
139 | craftPresetDict.Add(tmp[0], tmp[1]);
140 | }
141 | else if (tmp[0] == craftDefaultName)
142 | {
143 | craftPresetDict[craftDefaultName] = tmp[1];
144 | }
145 | }
146 | }
147 | }
148 |
149 | ///
150 | /// saves user created and default presets for next run
151 | ///
152 | public static void SaveToFile()
153 | {
154 | var node = new ConfigNode();
155 | // dummy value is required incase nothing else will be added to the file. KSP doesn't like blank .cfg's
156 | node.AddValue("dummy", "do not delete me");
157 | foreach (AsstPreset p in Instance.AsstPresetList)
158 | {
159 | node.AddNode(AsstPresetToNode(p));
160 | }
161 |
162 | var vesselNode = new ConfigNode(craftPresetNodeName);
163 | foreach (KeyValuePair cP in Instance.craftPresetDict)
164 | {
165 | vesselNode.AddValue("pair", string.Concat(cP.Key, ",", cP.Value)); // pair = craft,preset
166 | }
167 |
168 | node.AddNode(vesselNode);
169 |
170 | node.Save(KSPUtil.ApplicationRootPath.Replace("\\", "/") + presetsPath);
171 | }
172 |
173 | ///
174 | /// Sets the current active PA preset to be the default
175 | ///
176 | public void UpdateDefaultAsstPreset(AsstPreset preset)
177 | {
178 | craftPresetDict[craftDefaultName] = preset.name;
179 | SaveToFile();
180 | }
181 |
182 | ///
183 | /// Processes a config node for a controller into a more accessible array of doubles
184 | ///
185 | /// A controller node
186 | /// An ID to use for referencing the default values in cases of null input
187 | /// an array of doubles containing the gains for a controller
188 | public static double[] GainsArrayFromNode(ConfigNode node, AsstList type)
189 | {
190 | if (ReferenceEquals(node, null))
191 | {
192 | return DefaultControllerGains(type);
193 | }
194 |
195 | double[] gains = new double[9];
196 | double.TryParse(node.GetValue(pGain), out gains[0]);
197 | double.TryParse(node.GetValue(iGain), out gains[1]);
198 | double.TryParse(node.GetValue(dGain), out gains[2]);
199 | double.TryParse(node.GetValue(min), out gains[3]);
200 | double.TryParse(node.GetValue(max), out gains[4]);
201 | double.TryParse(node.GetValue(iLower), out gains[5]);
202 | double.TryParse(node.GetValue(iUpper), out gains[6]);
203 | double.TryParse(node.GetValue(scalar), out gains[7]);
204 | double.TryParse(node.GetValue(ease), out gains[8]);
205 |
206 | return gains;
207 | }
208 |
209 | ///
210 | /// Processes an array of gains into a Config node ready to be saved
211 | ///
212 | /// Node name
213 | /// index of the array in the preset storage
214 | /// object to source the array from
215 | /// A config node holding the gains for a controller
216 | public static ConfigNode GainsArrayToNode(string name, int index, AsstPreset preset)
217 | {
218 | var node = new ConfigNode(name);
219 | node.AddValue(pGain, preset.PIDGains[index][0]);
220 | node.AddValue(iGain, preset.PIDGains[index][1]);
221 | node.AddValue(dGain, preset.PIDGains[index][2]);
222 | node.AddValue(min, preset.PIDGains[index][3]);
223 | node.AddValue(max, preset.PIDGains[index][4]);
224 | node.AddValue(iLower, preset.PIDGains[index][5]);
225 | node.AddValue(iUpper, preset.PIDGains[index][6]);
226 | node.AddValue(scalar, preset.PIDGains[index][7]);
227 | node.AddValue(ease, preset.PIDGains[index][8]);
228 | return node;
229 | }
230 |
231 | ///
232 | /// Turns a PA preset into a config node holding nodes of all its controllers
233 | ///
234 | /// preset to process
235 | /// config node holding all PA controller values
236 | public static ConfigNode AsstPresetToNode(AsstPreset preset)
237 | {
238 | var node = new ConfigNode(asstPresetNodeName);
239 | node.AddValue("name", preset.name);
240 | node.AddNode(GainsArrayToNode(hdgCtrlr, (int)AsstList.HdgBank, preset));
241 | node.AddNode(GainsArrayToNode(yawCtrlr, (int)AsstList.BankToYaw, preset));
242 | node.AddNode(GainsArrayToNode(aileronCtrlr, (int)AsstList.Aileron, preset));
243 | node.AddNode(GainsArrayToNode(rudderCtrlr, (int)AsstList.Rudder, preset));
244 | node.AddNode(GainsArrayToNode(altCtrlr, (int)AsstList.Altitude, preset));
245 | node.AddNode(GainsArrayToNode(vertCtrlr, (int)AsstList.VertSpeed, preset));
246 | node.AddNode(GainsArrayToNode(elevCtrlr, (int)AsstList.Elevator, preset));
247 | node.AddNode(GainsArrayToNode(speedCtrlr, (int)AsstList.Speed, preset));
248 | node.AddNode(GainsArrayToNode(accelCtrlr, (int)AsstList.Acceleration, preset));
249 |
250 | return node;
251 | }
252 |
253 | ///
254 | /// returns the default gains for the controller
255 | ///
256 | /// controller ID
257 | /// default gains array
258 | public static double[] DefaultControllerGains(AsstList type)
259 | {
260 | switch(type)
261 | {
262 | case AsstList.HdgBank:
263 | return PilotAssistant.defaultHdgBankGains;
264 | case AsstList.BankToYaw:
265 | return PilotAssistant.defaultBankToYawGains;
266 | case AsstList.Aileron:
267 | return PilotAssistant.defaultAileronGains;
268 | case AsstList.Rudder:
269 | return PilotAssistant.defaultRudderGains;
270 | case AsstList.Altitude:
271 | return PilotAssistant.defaultAltitudeGains;
272 | case AsstList.VertSpeed:
273 | return PilotAssistant.defaultVSpeedGains;
274 | case AsstList.Elevator:
275 | return PilotAssistant.defaultElevatorGains;
276 | case AsstList.Speed:
277 | return PilotAssistant.defaultSpeedGains;
278 | case AsstList.Acceleration:
279 | return PilotAssistant.defaultAccelGains;
280 | default:
281 | return PilotAssistant.defaultAileronGains;
282 | }
283 | }
284 |
285 | ///
286 | /// Creates a preset from an array of controllers. Can't access Asst controllers directly because more than one instance can be active
287 | ///
288 | /// preset name
289 | /// controllers to build from
290 | /// vessel to associate with
291 | public static bool NewAsstPreset(string name, Asst_PID_Controller[] controllers, Vessel v)
292 | {
293 | if (string.IsNullOrEmpty(name))
294 | {
295 | return false;
296 | }
297 |
298 | if (Instance.AsstPresetList.Any(p => p.name == name))
299 | {
300 | GeneralUI.PostMessage("Failed to add preset with duplicate name");
301 | return false;
302 | }
303 | var newPreset = new AsstPreset(controllers, name);
304 | Instance.UpdateCraftPreset(newPreset, v);
305 | Instance.AsstPresetList.Add(newPreset);
306 | SaveToFile();
307 |
308 | return true; // new preset created successfully, can clear the string
309 | }
310 |
311 | ///
312 | /// loads a preset into the controllers of a PA instance
313 | ///
314 | /// the preset to load
315 | /// the PA instance to load to
316 | public static void LoadAsstPreset(AsstPreset p, PilotAssistant asstInstance)
317 | {
318 | if (ReferenceEquals(p, null))
319 | {
320 | return;
321 | }
322 |
323 | Asst_PID_Controller[] c = asstInstance.controllers;
324 | for (int i = 0; i < 8; i++)
325 | {
326 | c[i].K_proportional = p.PIDGains[i][0];
327 | c[i].K_integral = p.PIDGains[i][1];
328 | c[i].K_derivative = p.PIDGains[i][2];
329 | c[i].OutMin = p.PIDGains[i][3];
330 | c[i].OutMax = p.PIDGains[i][4];
331 | c[i].IntegralClampLower = p.PIDGains[i][5];
332 | c[i].IntegralClampUpper = p.PIDGains[i][6];
333 | c[i].Scalar = p.PIDGains[i][7];
334 | c[i].Easing = p.PIDGains[i][8];
335 | }
336 |
337 | asstInstance.activePreset = p;
338 | GeneralUI.PostMessage("Loaded preset " + p.name);
339 |
340 | if (asstInstance.activePreset.name != Instance.craftPresetDict[craftDefaultName])
341 | {
342 | Instance.UpdateCraftPreset(asstInstance.activePreset, asstInstance.vesModule.Vessel);
343 | }
344 |
345 | SaveToFile();
346 | }
347 |
348 | ///
349 | /// loads a preset into the controllers of a PA instance
350 | ///
351 | /// the preset to load
352 | /// the PA instance to load to
353 | public static void LoadAsstPreset(string presetName, PilotAssistant asstInstance)
354 | {
355 | if (string.IsNullOrEmpty(presetName))
356 | {
357 | return;
358 | }
359 |
360 | AsstPreset p = Instance.AsstPresetList.FirstOrDefault(pr => pr.name == presetName);
361 | if (ReferenceEquals(p , null))
362 | {
363 | return;
364 | }
365 |
366 | Asst_PID_Controller[] c = asstInstance.controllers;
367 | for (int i = 0; i < 8; i++)
368 | {
369 | c[i].K_proportional = p.PIDGains[i][0];
370 | c[i].K_integral = p.PIDGains[i][1];
371 | c[i].K_derivative = p.PIDGains[i][2];
372 | c[i].OutMin = p.PIDGains[i][3];
373 | c[i].OutMax = p.PIDGains[i][4];
374 | c[i].IntegralClampLower = p.PIDGains[i][5];
375 | c[i].IntegralClampUpper = p.PIDGains[i][6];
376 | c[i].Scalar = p.PIDGains[i][7];
377 | c[i].Easing = p.PIDGains[i][8];
378 | }
379 |
380 | asstInstance.activePreset = p;
381 | GeneralUI.PostMessage("Loaded preset " + p.name);
382 |
383 | if (asstInstance.activePreset.name != Instance.craftPresetDict[craftDefaultName])
384 | {
385 | Instance.UpdateCraftPreset(asstInstance.activePreset, asstInstance.vesModule.Vessel);
386 | }
387 |
388 | SaveToFile();
389 | }
390 |
391 | ///
392 | /// remove a preset from the stored list and remove any references to it on active vessels
393 | ///
394 | ///
395 | public void DeleteAsstPreset(AsstPreset p)
396 | {
397 | GeneralUI.PostMessage("Deleted preset " + p.name);
398 | foreach (AsstVesselModule avm in PilotAssistantFlightCore.Instance.controlledVessels)
399 | {
400 | if (avm.vesselAsst.activePreset == p)
401 | {
402 | avm.vesselAsst.activePreset = null;
403 | }
404 | }
405 | var toRemove = new List();
406 | foreach (KeyValuePair kvp in craftPresetDict)
407 | {
408 | if (kvp.Value == p.name)
409 | {
410 | toRemove.Add(kvp.Key);
411 | }
412 | }
413 | foreach (string s in toRemove)
414 | {
415 | craftPresetDict.Remove(s);
416 | }
417 |
418 | AsstPresetList.Remove(p);
419 |
420 | p = null;
421 |
422 | SaveToFile();
423 | }
424 |
425 | ///
426 | /// called on vessel load to load the correct preset for the vessel being flown
427 | ///
428 | /// The instance to load for
429 | public void LoadCraftAsstPreset(PilotAssistant instance)
430 | {
431 | Logger.Log("loading preset for craft " + instance.Vessel.name);
432 | Logger.Log(craftPresetDict.TryGetValue(instance.Vessel.vesselName, out string presetName) + " " + presetName);
433 | if (craftPresetDict.TryGetValue(instance.Vessel.vesselName, out presetName))
434 | {
435 | LoadAsstPreset(presetName, instance);
436 | }
437 | else
438 | {
439 | LoadAsstPreset(Instance.craftPresetDict[craftDefaultName], instance);
440 | }
441 | }
442 |
443 | ///
444 | /// updates the craft/preset references
445 | ///
446 | /// preset
447 | /// craft
448 | public void UpdateCraftPreset(AsstPreset p, Vessel v)
449 | {
450 | if (!Instance.craftPresetDict.ContainsKey(v.vesselName))
451 | {
452 | craftPresetDict.Add(v.vesselName, string.Empty);
453 | }
454 |
455 | Instance.craftPresetDict[v.vesselName] = p.name;
456 | }
457 |
458 | public void InitDefaultPreset(AsstPreset p)
459 | {
460 | if (!craftPresetDict.TryGetValue(craftDefaultName, out string defaultName) || defaultName == string.Empty)
461 | {
462 | AsstPresetList.Add(p);
463 | Instance.craftPresetDict[craftDefaultName] = p.name;
464 | }
465 | SaveToFile();
466 | // loadAsstPreset(Instance.craftPresetDict[craftDefaultName], instance);
467 | }
468 | }
469 | }
470 |
--------------------------------------------------------------------------------
/PilotAssistant/Toolbar/ToolbarWrapper.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2013-2015, Maik Schreiber
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | */
26 | using System;
27 | using System.Collections.Generic;
28 | using System.Linq;
29 | using System.Reflection;
30 | using UnityEngine;
31 |
32 |
33 | // TODO: Change to your plugin's namespace here.
34 | namespace PilotAssistant.Toolbar
35 | {
36 |
37 |
38 |
39 | /**********************************************************\
40 | * --- DO NOT EDIT BELOW THIS COMMENT --- *
41 | * *
42 | * This file contains classes and interfaces to use the *
43 | * Toolbar Plugin without creating a hard dependency on it. *
44 | * *
45 | * There is nothing in this file that needs to be edited *
46 | * by hand. *
47 | * *
48 | * --- DO NOT EDIT BELOW THIS COMMENT --- *
49 | \**********************************************************/
50 |
51 |
52 |
53 | ///
54 | /// The global tool bar manager.
55 | ///
56 | public partial class ToolbarManager : IToolbarManager
57 | {
58 | ///
59 | /// Whether the Toolbar Plugin is available.
60 | ///
61 | public static bool ToolbarAvailable
62 | {
63 | get
64 | {
65 | if (toolbarAvailable == null)
66 | {
67 | toolbarAvailable = Instance != null;
68 | }
69 | return (bool)toolbarAvailable;
70 | }
71 | }
72 |
73 | ///
74 | /// The global tool bar manager instance.
75 | ///
76 | public static IToolbarManager Instance
77 | {
78 | get
79 | {
80 | if ((toolbarAvailable != false) && (instance_ == null))
81 | {
82 | Type type = ToolbarTypes.GetType("Toolbar.ToolbarManager");
83 | if (type != null)
84 | {
85 | object realToolbarManager = ToolbarTypes.GetStaticProperty(type, "Instance").GetValue(null, null);
86 | instance_ = new ToolbarManager(realToolbarManager);
87 | }
88 | }
89 | return instance_;
90 | }
91 | }
92 | }
93 |
94 | #region interfaces
95 |
96 | ///
97 | /// A toolbar manager.
98 | ///
99 | public interface IToolbarManager
100 | {
101 | ///
102 | /// Adds a new button.
103 | ///
104 | ///
105 | /// To replace an existing button, just add a new button using the old button's namespace and ID.
106 | /// Note that the new button will inherit the screen position of the old button.
107 | ///
108 | /// The new button's namespace. This is usually the plugin's name. Must not include special characters like '.'
109 | /// The new button's ID. This ID must be unique across all buttons in the namespace. Must not include special characters like '.'
110 | /// The button created.
111 | IButton Add(string ns, string id);
112 | }
113 |
114 | ///
115 | /// Represents a clickable button.
116 | ///
117 | public interface IButton
118 | {
119 | ///
120 | /// The text displayed on the button. Set to null to hide text.
121 | ///
122 | ///
123 | /// The text can be changed at any time to modify the button's appearance. Note that since this will also
124 | /// modify the button's size, this feature should be used sparingly, if at all.
125 | ///
126 | ///
127 | string Text
128 | {
129 | set;
130 | get;
131 | }
132 |
133 | ///
134 | /// The color the button text is displayed with. Defaults to Color.white.
135 | ///
136 | ///
137 | /// The text color can be changed at any time to modify the button's appearance.
138 | ///
139 | Color TextColor
140 | {
141 | set;
142 | get;
143 | }
144 |
145 | ///
146 | /// The path of a texture file to display an icon on the button. Set to null to hide icon.
147 | ///
148 | ///
149 | ///
150 | /// A texture path on a button will have precedence over text. That is, if both text and texture path
151 | /// have been set on a button, the button will show the texture, not the text.
152 | ///
153 | ///
154 | /// The texture size must not exceed 24x24 pixels.
155 | ///
156 | ///
157 | /// The texture path must be relative to the "GameData" directory, and must not specify a file name suffix.
158 | /// Valid example: MyAddon/Textures/icon_mybutton
159 | ///
160 | ///
161 | /// The texture path can be changed at any time to modify the button's appearance.
162 | ///
163 | ///
164 | ///
165 | string TexturePath
166 | {
167 | set;
168 | get;
169 | }
170 |
171 | ///
172 | /// The button's tool tip text. Set to null if no tool tip is desired.
173 | ///
174 | ///
175 | /// Tool Tip Text Should Always Use Headline Style Like This.
176 | ///
177 | string ToolTip
178 | {
179 | set;
180 | get;
181 | }
182 |
183 | ///
184 | /// Whether this button is currently visible or not. Can be used in addition to or as a replacement for .
185 | ///
186 | ///
187 | /// Setting this property to true does not affect the player's ability to hide the button using the configuration.
188 | /// Conversely, setting this property to false does not enable the player to show the button using the configuration.
189 | ///
190 | bool Visible
191 | {
192 | set;
193 | get;
194 | }
195 |
196 | ///
197 | /// Determines this button's visibility. Can be used in addition to or as a replacement for .
198 | ///
199 | ///
200 | /// The return value from IVisibility.Visible is subject to the same rules as outlined for
201 | /// .
202 | ///
203 | IVisibility Visibility
204 | {
205 | set;
206 | get;
207 | }
208 |
209 | ///
210 | /// Whether this button is currently effectively visible or not. This is a combination of
211 | /// and .
212 | ///
213 | ///
214 | /// Note that the toolbar is not visible in certain game scenes, for example the loading screens. This property
215 | /// does not reflect button invisibility in those scenes. In addition, this property does not reflect the
216 | /// player's configuration of the button's visibility.
217 | ///
218 | bool EffectivelyVisible
219 | {
220 | get;
221 | }
222 |
223 | ///
224 | /// Whether this button is currently enabled (clickable) or not. This does not affect the player's ability to
225 | /// position the button on their toolbar.
226 | ///
227 | bool Enabled
228 | {
229 | set;
230 | get;
231 | }
232 |
233 | ///
234 | /// Whether this button is currently "important." Set to false to return to normal button behaviour.
235 | ///
236 | ///
237 | ///
238 | /// This can be used to temporarily force the button to be shown on screen regardless of the toolbar being
239 | /// currently in auto-hidden mode. For example, a button that signals the arrival of a private message in
240 | /// a chat room could mark itself as "important" as long as the message has not been read.
241 | ///
242 | ///
243 | /// Setting this property does not change the appearance of the button. Use to
244 | /// change the button's icon.
245 | ///
246 | ///
247 | /// Setting this property to true does not affect the player's ability to hide the button using the
248 | /// configuration.
249 | ///
250 | ///
251 | /// This feature should be used only sparingly, if at all, since it forces the button to be displayed on
252 | /// screen even when it normally wouldn't.
253 | ///
254 | ///
255 | bool Important
256 | {
257 | set;
258 | get;
259 | }
260 |
261 | ///
262 | /// A drawable that is tied to the current button. This can be anything from a popup menu to
263 | /// an informational window. Set to null to hide the drawable.
264 | ///
265 | IDrawable Drawable
266 | {
267 | set;
268 | get;
269 | }
270 |
271 | ///
272 | /// Event handler that can be registered with to receive "on click" events.
273 | ///
274 | ///
275 | ///
276 | /// IButton button = ...
277 | /// button.OnClick += (e) => {
278 | /// Debug.Log("button clicked, mouseButton: " + e.MouseButton);
279 | /// };
280 | ///
281 | ///
282 | event ClickHandler OnClick;
283 |
284 | ///
285 | /// Event handler that can be registered with to receive "on mouse enter" events.
286 | ///
287 | ///
288 | ///
289 | /// IButton button = ...
290 | /// button.OnMouseEnter += (e) => {
291 | /// Debug.Log("mouse entered button");
292 | /// };
293 | ///
294 | ///
295 | event MouseEnterHandler OnMouseEnter;
296 |
297 | ///
298 | /// Event handler that can be registered with to receive "on mouse leave" events.
299 | ///
300 | ///
301 | ///
302 | /// IButton button = ...
303 | /// button.OnMouseLeave += (e) => {
304 | /// Debug.Log("mouse left button");
305 | /// };
306 | ///
307 | ///
308 | event MouseLeaveHandler OnMouseLeave;
309 |
310 | ///
311 | /// Permanently destroys this button so that it is no longer displayed.
312 | /// Should be used when a plugin is stopped to remove leftover buttons.
313 | ///
314 | void Destroy();
315 | }
316 |
317 | ///
318 | /// A drawable that is tied to a particular button. This can be anything from a popup menu
319 | /// to an informational window.
320 | ///
321 | public interface IDrawable
322 | {
323 | ///
324 | /// Update any information. This is called once per frame.
325 | ///
326 | void Update();
327 |
328 | ///
329 | /// Draws GUI widgets for this drawable. This is the equivalent to the OnGUI() message in
330 | /// .
331 | ///
332 | ///
333 | /// The drawable will be positioned near its parent toolbar according to the drawable's current
334 | /// width/height.
335 | ///
336 | /// The left/top position of where to draw this drawable.
337 | /// The current width/height of this drawable.
338 | Vector2 Draw(Vector2 position);
339 | }
340 |
341 | #endregion
342 |
343 | #region events
344 |
345 | ///
346 | /// Event describing a click on a button.
347 | ///
348 | public partial class ClickEvent : EventArgs
349 | {
350 | ///
351 | /// The button that has been clicked.
352 | ///
353 | public readonly IButton Button;
354 |
355 | ///
356 | /// The mouse button which the button was clicked with.
357 | ///
358 | ///
359 | /// Is 0 for left mouse button, 1 for right mouse button, and 2 for middle mouse button.
360 | ///
361 | public readonly int MouseButton;
362 | }
363 |
364 | ///
365 | /// An event handler that is invoked whenever a button has been clicked.
366 | ///
367 | /// An event describing the button click.
368 | public delegate void ClickHandler(ClickEvent e);
369 |
370 | ///
371 | /// Event describing the mouse pointer moving about a button.
372 | ///
373 | public abstract partial class MouseMoveEvent
374 | {
375 | ///
376 | /// The button in question.
377 | ///
378 | public readonly IButton button;
379 | }
380 |
381 | ///
382 | /// Event describing the mouse pointer entering a button's area.
383 | ///
384 | public partial class MouseEnterEvent : MouseMoveEvent
385 | {
386 | }
387 |
388 | ///
389 | /// Event describing the mouse pointer leaving a button's area.
390 | ///
391 | public partial class MouseLeaveEvent : MouseMoveEvent
392 | {
393 | }
394 |
395 | ///
396 | /// An event handler that is invoked whenever the mouse pointer enters a button's area.
397 | ///
398 | /// An event describing the mouse pointer entering.
399 | public delegate void MouseEnterHandler(MouseEnterEvent e);
400 |
401 | ///
402 | /// An event handler that is invoked whenever the mouse pointer leaves a button's area.
403 | ///
404 | /// An event describing the mouse pointer leaving.
405 | public delegate void MouseLeaveHandler(MouseLeaveEvent e);
406 |
407 | #endregion
408 |
409 | #region visibility
410 |
411 | ///
412 | /// Determines visibility of a button.
413 | ///
414 | ///
415 | public interface IVisibility
416 | {
417 | ///
418 | /// Whether a button is currently visible or not.
419 | ///
420 | ///
421 | bool Visible
422 | {
423 | get;
424 | }
425 | }
426 |
427 | ///
428 | /// Determines visibility of a button in relation to the currently running game scene.
429 | ///
430 | ///
431 | ///
432 | /// IButton button = ...
433 | /// button.Visibility = new GameScenesVisibility(GameScenes.EDITOR, GameScenes.FLIGHT);
434 | ///
435 | ///
436 | ///
437 | public class GameScenesVisibility : IVisibility
438 | {
439 | public bool Visible
440 | {
441 | get
442 | {
443 | return (bool)visibleProperty.GetValue(realGameScenesVisibility, null);
444 | }
445 | }
446 |
447 | private object realGameScenesVisibility;
448 | private PropertyInfo visibleProperty;
449 |
450 | public GameScenesVisibility(params GameScenes[] gameScenes)
451 | {
452 | Type gameScenesVisibilityType = ToolbarTypes.GetType("Toolbar.GameScenesVisibility");
453 | realGameScenesVisibility = Activator.CreateInstance(gameScenesVisibilityType, new object[] { gameScenes });
454 | visibleProperty = ToolbarTypes.GetProperty(gameScenesVisibilityType, "Visible");
455 | }
456 | }
457 |
458 | #endregion
459 |
460 | #region drawable
461 |
462 | ///
463 | /// A drawable that draws a popup menu.
464 | ///
465 | public partial class PopupMenuDrawable : IDrawable
466 | {
467 | ///
468 | /// Event handler that can be registered with to receive "any menu option clicked" events.
469 | ///
470 | public event Action OnAnyOptionClicked
471 | {
472 | add
473 | {
474 | onAnyOptionClickedEvent.AddEventHandler(realPopupMenuDrawable, value);
475 | }
476 | remove
477 | {
478 | onAnyOptionClickedEvent.RemoveEventHandler(realPopupMenuDrawable, value);
479 | }
480 | }
481 |
482 | private object realPopupMenuDrawable;
483 | private MethodInfo updateMethod;
484 | private MethodInfo drawMethod;
485 | private MethodInfo addOptionMethod;
486 | private MethodInfo addSeparatorMethod;
487 | private MethodInfo destroyMethod;
488 | private EventInfo onAnyOptionClickedEvent;
489 |
490 | public PopupMenuDrawable()
491 | {
492 | Type popupMenuDrawableType = ToolbarTypes.GetType("Toolbar.PopupMenuDrawable");
493 | realPopupMenuDrawable = Activator.CreateInstance(popupMenuDrawableType, null);
494 | updateMethod = ToolbarTypes.GetMethod(popupMenuDrawableType, "Update");
495 | drawMethod = ToolbarTypes.GetMethod(popupMenuDrawableType, "Draw");
496 | addOptionMethod = ToolbarTypes.GetMethod(popupMenuDrawableType, "AddOption");
497 | addSeparatorMethod = ToolbarTypes.GetMethod(popupMenuDrawableType, "AddSeparator");
498 | destroyMethod = ToolbarTypes.GetMethod(popupMenuDrawableType, "Destroy");
499 | onAnyOptionClickedEvent = ToolbarTypes.GetEvent(popupMenuDrawableType, "OnAnyOptionClicked");
500 | }
501 |
502 | public void Update()
503 | {
504 | updateMethod.Invoke(realPopupMenuDrawable, null);
505 | }
506 |
507 | public Vector2 Draw(Vector2 position)
508 | {
509 | return (Vector2)drawMethod.Invoke(realPopupMenuDrawable, new object[] { position });
510 | }
511 |
512 | ///
513 | /// Adds a new option to the popup menu.
514 | ///
515 | /// The text of the option.
516 | /// A button that can be used to register clicks on the menu option.
517 | public IButton AddOption(string text)
518 | {
519 | object realButton = addOptionMethod.Invoke(realPopupMenuDrawable, new object[] { text });
520 | return new Button(realButton, new ToolbarTypes());
521 | }
522 |
523 | ///
524 | /// Adds a separator to the popup menu.
525 | ///
526 | public void AddSeparator()
527 | {
528 | addSeparatorMethod.Invoke(realPopupMenuDrawable, null);
529 | }
530 |
531 | ///
532 | /// Destroys this drawable. This must always be called before disposing of this drawable.
533 | ///
534 | public void Destroy()
535 | {
536 | destroyMethod.Invoke(realPopupMenuDrawable, null);
537 | }
538 | }
539 |
540 | #endregion
541 |
542 | #region private implementations
543 |
544 | public partial class ToolbarManager : IToolbarManager
545 | {
546 | private static bool? toolbarAvailable = null;
547 | private static IToolbarManager instance_;
548 |
549 | private object realToolbarManager;
550 | private MethodInfo addMethod;
551 | private Dictionary