├── 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 buttons = new Dictionary(); 552 | private ToolbarTypes types = new ToolbarTypes(); 553 | 554 | private ToolbarManager(object realToolbarManager) 555 | { 556 | this.realToolbarManager = realToolbarManager; 557 | 558 | addMethod = ToolbarTypes.GetMethod(types.iToolbarManagerType, "add"); 559 | } 560 | 561 | public IButton Add(string ns, string id) 562 | { 563 | object realButton = addMethod.Invoke(realToolbarManager, new object[] { ns, id }); 564 | IButton button = new Button(realButton, types); 565 | buttons.Add(realButton, button); 566 | return button; 567 | } 568 | } 569 | 570 | internal class Button : IButton 571 | { 572 | private object realButton; 573 | private ToolbarTypes types; 574 | private Delegate realClickHandler; 575 | private Delegate realMouseEnterHandler; 576 | private Delegate realMouseLeaveHandler; 577 | 578 | internal Button(object realButton, ToolbarTypes types) 579 | { 580 | this.realButton = realButton; 581 | this.types = types; 582 | 583 | realClickHandler = AttachEventHandler(types.button.onClickEvent, "clicked", realButton); 584 | realMouseEnterHandler = AttachEventHandler(types.button.onMouseEnterEvent, "mouseEntered", realButton); 585 | realMouseLeaveHandler = AttachEventHandler(types.button.onMouseLeaveEvent, "mouseLeft", realButton); 586 | } 587 | 588 | private Delegate AttachEventHandler(EventInfo @event, string methodName, object realButton) 589 | { 590 | MethodInfo method = GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); 591 | var d = Delegate.CreateDelegate(@event.EventHandlerType, this, method); 592 | @event.AddEventHandler(realButton, d); 593 | return d; 594 | } 595 | 596 | public string Text 597 | { 598 | set 599 | { 600 | types.button.textProperty.SetValue(realButton, value, null); 601 | } 602 | get 603 | { 604 | return (string)types.button.textProperty.GetValue(realButton, null); 605 | } 606 | } 607 | 608 | public Color TextColor 609 | { 610 | set 611 | { 612 | types.button.textColorProperty.SetValue(realButton, value, null); 613 | } 614 | get 615 | { 616 | return (Color)types.button.textColorProperty.GetValue(realButton, null); 617 | } 618 | } 619 | 620 | public string TexturePath 621 | { 622 | set 623 | { 624 | types.button.texturePathProperty.SetValue(realButton, value, null); 625 | } 626 | get 627 | { 628 | return (string)types.button.texturePathProperty.GetValue(realButton, null); 629 | } 630 | } 631 | 632 | public string ToolTip 633 | { 634 | set 635 | { 636 | types.button.toolTipProperty.SetValue(realButton, value, null); 637 | } 638 | get 639 | { 640 | return (string)types.button.toolTipProperty.GetValue(realButton, null); 641 | } 642 | } 643 | 644 | public bool Visible 645 | { 646 | set 647 | { 648 | types.button.visibleProperty.SetValue(realButton, value, null); 649 | } 650 | get 651 | { 652 | return (bool)types.button.visibleProperty.GetValue(realButton, null); 653 | } 654 | } 655 | 656 | public IVisibility Visibility 657 | { 658 | set 659 | { 660 | object functionVisibility = null; 661 | if (value != null) 662 | { 663 | functionVisibility = Activator.CreateInstance(types.functionVisibilityType, new object[] { new Func(() => value.Visible) }); 664 | } 665 | types.button.visibilityProperty.SetValue(realButton, functionVisibility, null); 666 | visibility_ = value; 667 | } 668 | get 669 | { 670 | return visibility_; 671 | } 672 | } 673 | private IVisibility visibility_; 674 | 675 | public bool EffectivelyVisible 676 | { 677 | get 678 | { 679 | return (bool)types.button.effectivelyVisibleProperty.GetValue(realButton, null); 680 | } 681 | } 682 | 683 | public bool Enabled 684 | { 685 | set 686 | { 687 | types.button.enabledProperty.SetValue(realButton, value, null); 688 | } 689 | get 690 | { 691 | return (bool)types.button.enabledProperty.GetValue(realButton, null); 692 | } 693 | } 694 | 695 | public bool Important 696 | { 697 | set 698 | { 699 | types.button.importantProperty.SetValue(realButton, value, null); 700 | } 701 | get 702 | { 703 | return (bool)types.button.importantProperty.GetValue(realButton, null); 704 | } 705 | } 706 | 707 | public IDrawable Drawable 708 | { 709 | set 710 | { 711 | object functionDrawable = null; 712 | if (value != null) 713 | { 714 | functionDrawable = Activator.CreateInstance(types.functionDrawableType, new object[] { 715 | new Action(() => value.Update()), 716 | new Func((pos) => value.Draw(pos)) 717 | }); 718 | } 719 | types.button.drawableProperty.SetValue(realButton, functionDrawable, null); 720 | drawable_ = value; 721 | } 722 | get 723 | { 724 | return drawable_; 725 | } 726 | } 727 | private IDrawable drawable_; 728 | 729 | public event ClickHandler OnClick; 730 | 731 | private void Clicked(object realEvent) 732 | { 733 | OnClick?.Invoke(new ClickEvent(realEvent, this)); 734 | } 735 | 736 | public event MouseEnterHandler OnMouseEnter; 737 | 738 | private void MouseEntered(object realEvent) 739 | { 740 | OnMouseEnter?.Invoke(new MouseEnterEvent(this)); 741 | } 742 | 743 | public event MouseLeaveHandler OnMouseLeave; 744 | 745 | private void MouseLeft(object realEvent) 746 | { 747 | OnMouseLeave?.Invoke(new MouseLeaveEvent(this)); 748 | } 749 | 750 | public void Destroy() 751 | { 752 | DetachEventHandler(types.button.onClickEvent, realClickHandler, realButton); 753 | DetachEventHandler(types.button.onMouseEnterEvent, realMouseEnterHandler, realButton); 754 | DetachEventHandler(types.button.onMouseLeaveEvent, realMouseLeaveHandler, realButton); 755 | 756 | types.button.destroyMethod.Invoke(realButton, null); 757 | } 758 | 759 | private void DetachEventHandler(EventInfo @event, Delegate d, object realButton) 760 | { 761 | @event.RemoveEventHandler(realButton, d); 762 | } 763 | } 764 | 765 | public partial class ClickEvent : EventArgs 766 | { 767 | internal ClickEvent(object realEvent, IButton button) 768 | { 769 | Type type = realEvent.GetType(); 770 | Button = button; 771 | MouseButton = (int)type.GetField("MouseButton", BindingFlags.Public | BindingFlags.Instance).GetValue(realEvent); 772 | } 773 | } 774 | 775 | public abstract partial class MouseMoveEvent : EventArgs 776 | { 777 | internal MouseMoveEvent(IButton button) 778 | { 779 | this.button = button; 780 | } 781 | } 782 | 783 | public partial class MouseEnterEvent : MouseMoveEvent 784 | { 785 | internal MouseEnterEvent(IButton button) 786 | : base(button) 787 | { 788 | } 789 | } 790 | 791 | public partial class MouseLeaveEvent : MouseMoveEvent 792 | { 793 | internal MouseLeaveEvent(IButton button) 794 | : base(button) 795 | { 796 | } 797 | } 798 | 799 | internal class ToolbarTypes 800 | { 801 | internal readonly Type iToolbarManagerType; 802 | internal readonly Type functionVisibilityType; 803 | internal readonly Type functionDrawableType; 804 | internal readonly ButtonTypes button; 805 | 806 | internal ToolbarTypes() 807 | { 808 | iToolbarManagerType = GetType("Toolbar.IToolbarManager"); 809 | functionVisibilityType = GetType("Toolbar.FunctionVisibility"); 810 | functionDrawableType = GetType("Toolbar.FunctionDrawable"); 811 | 812 | Type iButtonType = GetType("Toolbar.IButton"); 813 | button = new ButtonTypes(iButtonType); 814 | } 815 | 816 | internal static Type GetType(string name) 817 | { 818 | Type type = null; 819 | AssemblyLoader.loadedAssemblies.TypeOperation(t => 820 | { 821 | if (t.FullName == name) 822 | { 823 | type = t; 824 | } 825 | }); 826 | return type; 827 | } 828 | 829 | internal static PropertyInfo GetProperty(Type type, string name) 830 | { 831 | return type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); 832 | } 833 | 834 | internal static PropertyInfo GetStaticProperty(Type type, string name) 835 | { 836 | return type.GetProperty(name, BindingFlags.Public | BindingFlags.Static); 837 | } 838 | 839 | internal static EventInfo GetEvent(Type type, string name) 840 | { 841 | return type.GetEvent(name, BindingFlags.Public | BindingFlags.Instance); 842 | } 843 | 844 | internal static MethodInfo GetMethod(Type type, string name) 845 | { 846 | return type.GetMethod(name, BindingFlags.Public | BindingFlags.Instance); 847 | } 848 | } 849 | 850 | internal class ButtonTypes 851 | { 852 | internal readonly Type iButtonType; 853 | internal readonly PropertyInfo textProperty; 854 | internal readonly PropertyInfo textColorProperty; 855 | internal readonly PropertyInfo texturePathProperty; 856 | internal readonly PropertyInfo toolTipProperty; 857 | internal readonly PropertyInfo visibleProperty; 858 | internal readonly PropertyInfo visibilityProperty; 859 | internal readonly PropertyInfo effectivelyVisibleProperty; 860 | internal readonly PropertyInfo enabledProperty; 861 | internal readonly PropertyInfo importantProperty; 862 | internal readonly PropertyInfo drawableProperty; 863 | internal readonly EventInfo onClickEvent; 864 | internal readonly EventInfo onMouseEnterEvent; 865 | internal readonly EventInfo onMouseLeaveEvent; 866 | internal readonly MethodInfo destroyMethod; 867 | 868 | internal ButtonTypes(Type iButtonType) 869 | { 870 | this.iButtonType = iButtonType; 871 | 872 | textProperty = ToolbarTypes.GetProperty(iButtonType, "Text"); 873 | textColorProperty = ToolbarTypes.GetProperty(iButtonType, "TextColor"); 874 | texturePathProperty = ToolbarTypes.GetProperty(iButtonType, "TexturePath"); 875 | toolTipProperty = ToolbarTypes.GetProperty(iButtonType, "ToolTip"); 876 | visibleProperty = ToolbarTypes.GetProperty(iButtonType, "Visible"); 877 | visibilityProperty = ToolbarTypes.GetProperty(iButtonType, "Visibility"); 878 | effectivelyVisibleProperty = ToolbarTypes.GetProperty(iButtonType, "EffectivelyVisible"); 879 | enabledProperty = ToolbarTypes.GetProperty(iButtonType, "Enabled"); 880 | importantProperty = ToolbarTypes.GetProperty(iButtonType, "Important"); 881 | drawableProperty = ToolbarTypes.GetProperty(iButtonType, "Drawable"); 882 | onClickEvent = ToolbarTypes.GetEvent(iButtonType, "OnClick"); 883 | onMouseEnterEvent = ToolbarTypes.GetEvent(iButtonType, "OnMouseEnter"); 884 | onMouseLeaveEvent = ToolbarTypes.GetEvent(iButtonType, "OnMouseLeave"); 885 | destroyMethod = ToolbarTypes.GetMethod(iButtonType, "Destroy"); 886 | } 887 | } 888 | 889 | #endregion 890 | } 891 | -------------------------------------------------------------------------------- /PilotAssistant/FlightModules/PilotAssistant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using UnityEngine; 5 | 6 | namespace PilotAssistant.FlightModules 7 | { 8 | using Presets; 9 | using Utility; 10 | 11 | public enum AsstList 12 | { 13 | HdgBank, 14 | BankToYaw, 15 | Aileron, 16 | Rudder, 17 | Altitude, 18 | VertSpeed, 19 | Elevator, 20 | Speed, 21 | Acceleration 22 | } 23 | 24 | public enum VertMode 25 | { 26 | ToggleOn = -1, 27 | Pitch = 0, 28 | VSpeed = 1, 29 | Altitude = 2, 30 | RadarAltitude = 3 31 | } 32 | 33 | public enum HrztMode 34 | { 35 | ToggleOn = -1, 36 | Bank = 0, 37 | Heading = 1, 38 | HeadingNum = 2 39 | } 40 | 41 | public enum ThrottleMode 42 | { 43 | ToggleOn = -1, 44 | Direct = 0, 45 | Acceleration = 1, 46 | Speed = 2 47 | } 48 | 49 | public enum SpeedUnits 50 | { 51 | mSec, 52 | kmph, 53 | mph, 54 | knots, 55 | mach 56 | } 57 | 58 | public enum SpeedRef 59 | { 60 | True, 61 | Indicated, 62 | Equivalent, 63 | Mach 64 | } 65 | 66 | public class PilotAssistant 67 | { 68 | #region Globals 69 | public AsstVesselModule vesModule; 70 | void StartCoroutine(IEnumerator routine) // quick access to coroutine now it doesn't inherit Monobehaviour 71 | { 72 | vesModule.StartCoroutine(routine); 73 | } 74 | 75 | public Vessel Vessel 76 | { 77 | get 78 | { 79 | return vesModule.Vessel; 80 | } 81 | } 82 | 83 | public Asst_PID_Controller[] controllers = new Asst_PID_Controller[9]; 84 | double currentThrottlePct; // need to keep a record of this as the vessel ctrlstate value is not holding enough significance to slow down adjustments 85 | 86 | public bool bPause = false; 87 | public bool bLockInput = false; 88 | 89 | public bool HrztActive = false; 90 | public HrztMode CurrentHrztMode = HrztMode.Heading; 91 | static GUIContent[] hrztLabels = new GUIContent[3] { new GUIContent("Bank", "Mode: Bank Angle Control\r\n\r\nMaintains a targeted bank angle. Negative values for banking left, positive values for banking right"), 92 | new GUIContent("Dir", "Mode: Direction Control\r\n\r\nDirection control maintains a set facing as the vessel travels around a planet. Fly in a straight line long enough and you will get back to where you started so long as sideslip is minimal.\r\nLimits maximum bank angle"), 93 | new GUIContent("Hdg", "Mode: Heading control\r\n\r\nHeading control follows a constant compass heading. Useful for local navigation but is difficult to use over long distances due to the effects of planetary curvature.\r\nLimits maximum bank angle") }; 94 | 95 | public bool VertActive = false; 96 | public VertMode CurrentVertMode = VertMode.VSpeed; 97 | static GUIContent[] vertLabels = new GUIContent[3] { new GUIContent("Pitch", "Mode: Pitch Control\r\n\r\nMaintains a targeted pitch angle"), 98 | new GUIContent("VSpd", "Mode: Vertical Speed Control\r\n\r\nManages vessel angle of attack to control ascent rate.\r\nLimits vessel angle of attack"), 99 | new GUIContent("Alt", "Mode: Altitude Control\r\n\r\nManages vessel altitude ascent rate to attain a set altitude relative to sea level.\r\nLimits vessel ascent rate") }; 100 | //new GUIContent("RAlt", "Mode: Radar Altitude Control\r\n\r\nManages vessel altitude ascent rate to attain a set altitude relative to the terrain.\r\nLimits vessel ascent rate") }; 101 | 102 | public bool ThrtActive = false; 103 | public ThrottleMode CurrentThrottleMode = ThrottleMode.Speed; 104 | static GUIContent[] throttleLabels = new GUIContent[3] { new GUIContent("Dir", "Mode: Direct Throttle Control\r\n\r\nSets vessel throttle to specified percentage"), 105 | new GUIContent("Acc", "Mode: Acceleration Control\r\n\r\nManages vessel throttle to attain a desired acceleration"), 106 | new GUIContent("Spd", "Mode: Speed Control\r\n\r\nManages vessel acceleration to attain a set speed.\r\nLimits acceleration")}; 107 | 108 | public static Rect window = new Rect(10, 130, 10, 10); 109 | 110 | public static bool showPresets = false; 111 | public static bool showPIDLimits = false; 112 | public static bool showControlSurfaces = false; 113 | public static bool doublesided = false; 114 | 115 | string targetVert = "0.00"; 116 | string targetHeading = "0.00"; 117 | string targetSpeed = "0.00"; 118 | 119 | const string yawLockID = "Pilot Assistant Yaw Lock"; 120 | public static bool yawLockEngaged = false; 121 | const string pitchLockID = "Pilot Assistant Pitch Lock"; 122 | public static bool pitchLockEngaged = false; 123 | 124 | // rate values for keyboard input 125 | const double hrztScale = 0.4; 126 | const double vertScale = 0.4; // altitude rate is x10 127 | const double throttleScale = 0.4; // acceleration rate is x0.1 128 | 129 | // Direction control vars 130 | Quaternion currentTarget = Quaternion.identity; // this is the body relative rotation the control is aimed at 131 | Quaternion newTarget = Quaternion.identity; // this is the body relative rotation we are moving to 132 | double increment = 0; // this is the angle to shift per second 133 | bool hdgShiftIsRunning = false; 134 | bool stopHdgShift = false; 135 | 136 | // don't update hdg display if true 137 | bool headingEdit = true; 138 | 139 | bool bShowHdg = true; 140 | bool bShowVert = true; 141 | bool bShowThrottle = true; 142 | 143 | bool bMinimiseHdg = false; 144 | bool bMinimiseVert = false; 145 | bool bMinimiseThrt = false; 146 | 147 | Vector2 HdgScrollbar = Vector2.zero; 148 | public float hdgScrollHeight = 55; 149 | public static float maxHdgScrollbarHeight = 55; 150 | Vector2 VertScrollbar = Vector2.zero; 151 | public float vertScrollHeight = 55; 152 | public static float maxVertScrollbarHeight = 55; 153 | Vector2 ThrtScrollbar = Vector2.zero; 154 | public float thrtScrollHeight = 55; 155 | public static float maxThrtScrollbarHeight = 55; 156 | 157 | float dragStart = 0; 158 | float dragID = 0; // 0 = inactive, 1 = hdg, 2 = vert, 3 = thrt 159 | 160 | string newPresetName = string.Empty; 161 | static Rect presetWindow = new Rect(0, 0, 200, 10); 162 | 163 | float pitchSet = 0; 164 | 165 | // Kp, Ki, Kd, Min Out,Max Out,I Min, I Max, Scalar, Easing 166 | public static readonly double[] defaultHdgBankGains = { 2, 0, 0, -30, 30, -1, 1, 1, 1 }; 167 | public static readonly double[] defaultBankToYawGains = { 0, 0, 0, -2, 2, -0.5, 0.5, 1, 1 }; 168 | public static readonly double[] defaultAileronGains = { 0.02, 0.005, 0.01, -1, 1, -1, 1, 1, 1 }; 169 | public static readonly double[] defaultRudderGains = { 0.1, 0.025, 0.05, -1, 1, -1, 1, 1, 1 }; 170 | public static readonly double[] defaultAltitudeGains = { 0.15, 0, 0, -50, 50, 0, 0, 1, 100 }; 171 | public static readonly double[] defaultVSpeedGains = { 2, 0.8, 2, -15, 15, -15, 15, 1, 10 }; 172 | public static readonly double[] defaultElevatorGains = { 0.05, 0.01, 0.10, -1, 1, -1, 1, 2, 1 }; 173 | public static readonly double[] defaultSpeedGains = { 0.2, 0, 0.2, -10, 10, -10, 10, 1, 10 }; 174 | public static readonly double[] defaultAccelGains = { 0.2, 0.08, 0, 0, 1, -1, 1, 1, 1 }; 175 | 176 | // speed mode change 177 | bool speedSelectWindowVisible; 178 | Rect speedSelectWindow; 179 | 180 | SpeedRef speedRef = SpeedRef.True; 181 | GUIContent[] speedRefLabels = new GUIContent[3] {new GUIContent("TAS"), 182 | new GUIContent("IAS"), 183 | new GUIContent("EAS")}; 184 | SpeedUnits units = SpeedUnits.mSec; 185 | GUIContent[] speedUnitLabels = new GUIContent[5] {new GUIContent("m/s"), 186 | new GUIContent("km/h"), 187 | new GUIContent("mph"), 188 | new GUIContent("kn"), 189 | new GUIContent("mach")}; 190 | /** Speed and acceleration accounting for TAS/IAS/EAS since calculating acceleration for modes other than TAS is not just a simple multiplier **/ 191 | double adjustedAcceleration, adjustedSpeed; 192 | 193 | // instanced active preset 194 | public AsstPreset activePreset; 195 | 196 | #endregion 197 | public PilotAssistant(AsstVesselModule avm) 198 | { 199 | vesModule = avm; 200 | } 201 | 202 | public void Start() 203 | { 204 | Initialise(); 205 | 206 | if (Vessel == FlightGlobals.ActiveVessel) 207 | { 208 | InputLockManager.RemoveControlLock(pitchLockID); 209 | InputLockManager.RemoveControlLock(yawLockID); 210 | pitchLockEngaged = false; 211 | yawLockEngaged = false; 212 | } 213 | 214 | PresetManager.Instance.LoadCraftAsstPreset(this); 215 | } 216 | 217 | public void OnDestroy() 218 | { 219 | if (Vessel == FlightGlobals.ActiveVessel) 220 | { 221 | InputLockManager.RemoveControlLock(pitchLockID); 222 | InputLockManager.RemoveControlLock(yawLockID); 223 | pitchLockEngaged = false; 224 | yawLockEngaged = false; 225 | } 226 | } 227 | 228 | void Initialise() 229 | { 230 | controllers[(int)AsstList.HdgBank] = new Asst_PID_Controller(AsstList.HdgBank, defaultHdgBankGains); 231 | controllers[(int)AsstList.BankToYaw] = new Asst_PID_Controller(AsstList.BankToYaw, defaultBankToYawGains); 232 | controllers[(int)AsstList.Aileron] = new Asst_PID_Controller(AsstList.Aileron, defaultAileronGains); 233 | controllers[(int)AsstList.Rudder] = new Asst_PID_Controller(AsstList.Rudder, defaultRudderGains); 234 | controllers[(int)AsstList.Altitude] = new Asst_PID_Controller(AsstList.Altitude, defaultAltitudeGains); 235 | controllers[(int)AsstList.VertSpeed] = new Asst_PID_Controller(AsstList.VertSpeed, defaultVSpeedGains); 236 | controllers[(int)AsstList.Elevator] = new Asst_PID_Controller(AsstList.Elevator, defaultElevatorGains); 237 | controllers[(int)AsstList.Speed] = new Asst_PID_Controller(AsstList.Speed, defaultSpeedGains); 238 | controllers[(int)AsstList.Acceleration] = new Asst_PID_Controller(AsstList.Acceleration, defaultAccelGains); 239 | 240 | // Set up a default preset that can be easily returned to 241 | PresetManager.Instance.InitDefaultPreset(new AsstPreset(controllers, "default")); 242 | 243 | AsstList.HdgBank.GetAsst(this).InvertOutput = true; 244 | //AsstList.BankToYaw.GetAsst(this).invertInput = true; 245 | //AsstList.BankToYaw.GetAsst(this).invertOutput = true; 246 | AsstList.Aileron.GetAsst(this).InvertOutput = true; 247 | AsstList.Altitude.GetAsst(this).InvertOutput = true; 248 | AsstList.VertSpeed.GetAsst(this).InvertOutput = true; 249 | AsstList.Elevator.GetAsst(this).InvertOutput = true; 250 | AsstList.Speed.GetAsst(this).InvertOutput = true; 251 | AsstList.Acceleration.GetAsst(this).InvertOutput = true; 252 | 253 | AsstList.Aileron.GetAsst(this).InMax = 180; 254 | AsstList.Aileron.GetAsst(this).InMin = -180; 255 | AsstList.Altitude.GetAsst(this).InMin = 0; 256 | AsstList.Speed.GetAsst(this).InMin = 0; 257 | AsstList.Elevator.GetAsst(this).InMax = 180; 258 | AsstList.Elevator.GetAsst(this).InMin = -180; 259 | AsstList.Rudder.GetAsst(this).InMax = 180; 260 | AsstList.Rudder.GetAsst(this).InMin = -180; 261 | AsstList.HdgBank.GetAsst(this).IsHeadingControl = true; // fix for derivative freaking out when heading target flickers across 0/360 262 | AsstList.Aileron.GetAsst(this).IsHeadingControl = true; 263 | } 264 | 265 | public void WarpHandler() 266 | { 267 | // reset any setpoints on leaving warp 268 | if (TimeWarp.CurrentRateIndex == 0 && TimeWarp.CurrentRate != 1 && TimeWarp.WarpMode == TimeWarp.Modes.HIGH) 269 | { 270 | HdgModeChanged(CurrentHrztMode, false); 271 | VertModeChanged(CurrentVertMode, false); 272 | ThrottleModeChanged(CurrentThrottleMode, false); 273 | } 274 | } 275 | 276 | public void VesselSwitch(Vessel ves) 277 | { 278 | if (Vessel == ves) 279 | { 280 | HdgModeChanged(CurrentHrztMode, HrztActive, false); 281 | VertModeChanged(CurrentVertMode, VertActive, false); 282 | ThrottleModeChanged(CurrentThrottleMode, ThrtActive, false); 283 | } 284 | } 285 | 286 | #region Update / Input Monitoring 287 | public void Update() 288 | { 289 | InputResponse(); 290 | if (bPause) 291 | { 292 | return; 293 | } 294 | 295 | // Heading setpoint updates 296 | if (HrztActive) 297 | { 298 | if (Vessel.LandedOrSplashed) 299 | { 300 | newTarget = currentTarget = Utils.GetPlaneRotation(Vessel.transform.right, vesModule); 301 | } 302 | 303 | if (CurrentHrztMode == HrztMode.Heading) 304 | { 305 | AsstList.HdgBank.GetAsst(this).UpdateSetpoint(Utils.CalculateTargetHeading(currentTarget, vesModule)); 306 | if (!headingEdit) 307 | { 308 | targetHeading = AsstList.HdgBank.GetAsst(this).Target_setpoint.ToString("0.00"); 309 | } 310 | } 311 | } 312 | 313 | if (speedSelectWindowVisible && Input.GetMouseButtonDown(0) && !speedSelectWindow.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y))) 314 | { 315 | speedSelectWindowVisible = false; 316 | } 317 | } 318 | 319 | public void InputResponse() 320 | { 321 | if (!Vessel.isActiveVessel || bLockInput || Utils.IsFlightControlLocked() || Vessel.HoldPhysics) 322 | { 323 | return; 324 | } 325 | 326 | if (BindingManager.bindings[(int)BindingIndex.Pause].IsPressed && !MapView.MapIsEnabled) 327 | { 328 | TogglePauseCtrlState(); 329 | } 330 | 331 | if (BindingManager.bindings[(int)BindingIndex.HdgTgl].IsPressed) 332 | { 333 | HdgModeChanged(CurrentHrztMode, !HrztActive); 334 | } 335 | 336 | if (BindingManager.bindings[(int)BindingIndex.VertTgl].IsPressed) 337 | { 338 | VertModeChanged(CurrentVertMode, !VertActive); 339 | } 340 | 341 | if (BindingManager.bindings[(int)BindingIndex.ThrtTgl].IsPressed) 342 | { 343 | ThrottleModeChanged(CurrentThrottleMode, !ThrtActive); 344 | } 345 | 346 | if (bPause) 347 | { 348 | return; 349 | } 350 | 351 | double scale = GameSettings.MODIFIER_KEY.GetKey() ? 10 : 1; // normally *1, with LAlt is *10 352 | if (FlightInputHandler.fetch.precisionMode) 353 | { 354 | scale = 0.1 / scale; // normally *0.1, with alt is *0.01 355 | } 356 | 357 | // ============================================================ Hrzt Controls ============================================================ 358 | if (HrztActive && !Vessel.LandedOrSplashed && Utils.HasYawInput()) 359 | { 360 | double hdg = GameSettings.YAW_LEFT.GetKey() ? -hrztScale * scale : 0; 361 | hdg += GameSettings.YAW_RIGHT.GetKey() ? hrztScale * scale : 0; 362 | hdg += hrztScale * scale * GameSettings.AXIS_YAW.GetAxis(); 363 | 364 | switch (CurrentHrztMode) 365 | { 366 | case HrztMode.Bank: 367 | AsstList.Aileron.GetAsst(this).UpdateSetpoint(Utils.HeadingClamp(AsstList.Aileron.GetAsst(this).Target_setpoint + hdg / 4, 180)); 368 | targetHeading = AsstList.Aileron.GetAsst(this).Target_setpoint.ToString("0.00"); 369 | break; 370 | case HrztMode.Heading: 371 | StartCoroutine(ShiftHeadingTarget(Utils.CalculateTargetHeading(newTarget, vesModule) + hdg)); 372 | break; 373 | case HrztMode.HeadingNum: 374 | AsstList.HdgBank.GetAsst(this).UpdateSetpoint(Utils.HeadingClamp(AsstList.HdgBank.GetAsst(this).Target_setpoint + hdg, 360)); 375 | targetHeading = AsstList.HdgBank.GetAsst(this).Target_setpoint.ToString("0.00"); 376 | break; 377 | } 378 | } 379 | // ============================================================ Vertical Controls ============================================================ 380 | if (VertActive && Utils.HasPitchInput()) 381 | { 382 | double vert = GameSettings.PITCH_DOWN.GetKey() ? -vertScale * scale : 0; 383 | vert += GameSettings.PITCH_UP.GetKey() ? vertScale * scale : 0; 384 | vert += !Utils.IsNeutral(GameSettings.AXIS_PITCH) ? vertScale * scale * GameSettings.AXIS_PITCH.GetAxis() : 0; 385 | 386 | switch (CurrentVertMode) 387 | { 388 | case VertMode.Altitude: 389 | case VertMode.RadarAltitude: 390 | AsstList.Altitude.GetAsst(this).IncreaseSetpoint(vert * 10); 391 | targetVert = AsstList.Altitude.GetAsst(this).Target_setpoint.ToString("0.00"); 392 | break; 393 | case VertMode.VSpeed: 394 | AsstList.VertSpeed.GetAsst(this).IncreaseSetpoint(vert); 395 | targetVert = AsstList.VertSpeed.GetAsst(this).Target_setpoint.ToString("0.00"); 396 | break; 397 | case VertMode.Pitch: 398 | AsstList.Elevator.GetAsst(this).IncreaseSetpoint(vert); 399 | targetVert = AsstList.Elevator.GetAsst(this).Target_setpoint.ToString("0.00"); 400 | break; 401 | } 402 | } 403 | // ============================================================ Throttle Controls ============================================================ 404 | if (ThrtActive && Utils.HasThrottleInput()) 405 | { 406 | double speedScale = scale / (units != SpeedUnits.mach ? Utils.SpeedUnitTransform(units, Vessel.speedOfSound) : 1); 407 | double speed = GameSettings.THROTTLE_UP.GetKey() ? throttleScale * speedScale : 0; 408 | speed -= GameSettings.THROTTLE_DOWN.GetKey() ? throttleScale * speedScale : 0; 409 | speed += GameSettings.THROTTLE_FULL.GetKeyDown() ? 100 * speedScale : 0; 410 | speed -= (GameSettings.THROTTLE_CUTOFF.GetKeyDown() && !GameSettings.MODIFIER_KEY.GetKey()) ? 100 * speedScale : 0; 411 | 412 | switch (CurrentThrottleMode) 413 | { 414 | case ThrottleMode.Direct: 415 | currentThrottlePct = Utils.Clamp(currentThrottlePct + speed / 100, 0, 1); 416 | Vessel.ctrlState.mainThrottle = (float)currentThrottlePct; 417 | if (ReferenceEquals(Vessel, FlightGlobals.ActiveVessel)) 418 | { 419 | FlightInputHandler.state.mainThrottle = (float)currentThrottlePct; 420 | } 421 | 422 | targetSpeed = (currentThrottlePct * 100).ToString("0.00"); 423 | break; 424 | case ThrottleMode.Acceleration: 425 | AsstList.Acceleration.GetAsst(this).IncreaseSetpoint(speed / 10); 426 | targetSpeed = (AsstList.Acceleration.GetAsst(this).Target_setpoint * Utils.SpeedUnitTransform(units, Vessel.speedOfSound)).ToString("0.00"); 427 | break; 428 | case ThrottleMode.Speed: 429 | AsstList.Speed.GetAsst(this).UpdateSetpoint(Math.Max(AsstList.Speed.GetAsst(this).Target_setpoint + speed, 0)); 430 | targetSpeed = (AsstList.Speed.GetAsst(this).Target_setpoint * Utils.SpeedUnitTransform(units, Vessel.speedOfSound)).ToString("0.00"); 431 | break; 432 | } 433 | } 434 | } 435 | 436 | private void HdgModeChanged(HrztMode newMode, bool active, bool setTarget = true) 437 | { 438 | AsstList.HdgBank.GetAsst(this).SkipDerivative = true; 439 | AsstList.BankToYaw.GetAsst(this).SkipDerivative = true; 440 | AsstList.Aileron.GetAsst(this).SkipDerivative = true; 441 | AsstList.Rudder.GetAsst(this).SkipDerivative = true; 442 | 443 | if (!active) 444 | { 445 | InputLockManager.RemoveControlLock(yawLockID); 446 | yawLockEngaged = false; 447 | stopHdgShift = true; 448 | headingEdit = true; 449 | AsstList.HdgBank.GetAsst(this).Clear(); 450 | AsstList.BankToYaw.GetAsst(this).Clear(); 451 | AsstList.Aileron.GetAsst(this).Clear(); 452 | AsstList.Rudder.GetAsst(this).Clear(); 453 | } 454 | else 455 | { 456 | if (!yawLockEngaged) 457 | { 458 | InputLockManager.SetControlLock(ControlTypes.YAW, yawLockID); 459 | yawLockEngaged = true; 460 | } 461 | bPause = false; 462 | switch (newMode) 463 | { 464 | case HrztMode.HeadingNum: 465 | if (setTarget) 466 | { 467 | AsstList.HdgBank.GetAsst(this).UpdateSetpoint(vesModule.vesselData.heading); 468 | } 469 | 470 | targetHeading = AsstList.HdgBank.GetAsst(this).Target_setpoint.ToString("0.00"); 471 | break; 472 | case HrztMode.Heading: 473 | if (setTarget) 474 | { 475 | StartCoroutine(ShiftHeadingTarget(vesModule.vesselData.heading)); 476 | } 477 | 478 | break; 479 | case HrztMode.Bank: 480 | if (setTarget) 481 | { 482 | AsstList.Aileron.GetAsst(this).UpdateSetpoint(vesModule.vesselData.bank); 483 | } 484 | 485 | targetHeading = AsstList.Aileron.GetAsst(this).Target_setpoint.ToString("0.00"); 486 | break; 487 | } 488 | } 489 | HrztActive = active; 490 | CurrentHrztMode = newMode; 491 | } 492 | 493 | public double GetCurrentHrzt() 494 | { 495 | switch (CurrentHrztMode) 496 | { 497 | case HrztMode.HeadingNum: 498 | case HrztMode.Heading: 499 | return AsstList.HdgBank.GetAsst(this).Target_setpoint; 500 | case HrztMode.Bank: 501 | default: 502 | return AsstList.Aileron.GetAsst(this).Target_setpoint; 503 | } 504 | } 505 | 506 | /// 507 | /// 508 | /// 509 | /// Sets the state of the heading control system. True = enabled 510 | /// Whether to update the target value 511 | /// Mode to use. Heading, bank, etc. 512 | /// The new target value 513 | public void SetHrzt(bool active, bool setTarget, HrztMode mode, double target) 514 | { 515 | if (setTarget) 516 | { 517 | switch (mode) 518 | { 519 | case HrztMode.Bank: 520 | AsstList.Aileron.GetAsst(this).UpdateSetpoint(target.HeadingClamp(180), true, vesModule.vesselData.bank); 521 | break; 522 | case HrztMode.Heading: 523 | StartCoroutine(ShiftHeadingTarget(target.HeadingClamp(360))); 524 | break; 525 | case HrztMode.HeadingNum: 526 | AsstList.HdgBank.GetAsst(this).UpdateSetpoint(target.HeadingClamp(360), true, vesModule.vesselData.heading); 527 | break; 528 | } 529 | } 530 | HdgModeChanged(mode, active, !setTarget); 531 | } 532 | 533 | private void VertModeChanged(VertMode newMode, bool active, bool setTarget = true) 534 | { 535 | if (!active) 536 | { 537 | InputLockManager.RemoveControlLock(pitchLockID); 538 | pitchLockEngaged = false; 539 | AsstList.Altitude.GetAsst(this).Clear(); 540 | AsstList.VertSpeed.GetAsst(this).Clear(); 541 | AsstList.Elevator.GetAsst(this).Clear(); 542 | StartCoroutine(FadeOutPitch()); 543 | } 544 | else 545 | { 546 | if (!pitchLockEngaged) 547 | { 548 | InputLockManager.SetControlLock(ControlTypes.PITCH, pitchLockID); 549 | pitchLockEngaged = true; 550 | } 551 | bPause = false; 552 | 553 | //////////////////////////////////////////////////////////////////////////////////////// 554 | // Set the integral sums for the vertical control systems to improve transfer smoothness 555 | bool invert = Math.Abs(vesModule.vesselData.bank) > 90; 556 | if (VertActive) 557 | { 558 | AsstList.Elevator.GetAsst(this).Preset(invert); 559 | if (CurrentVertMode == VertMode.Altitude || CurrentVertMode == VertMode.RadarAltitude || CurrentVertMode == VertMode.VSpeed) 560 | { 561 | AsstList.VertSpeed.GetAsst(this).Preset(invert); 562 | if (CurrentVertMode == VertMode.Altitude || CurrentVertMode == VertMode.RadarAltitude) 563 | { 564 | AsstList.Altitude.GetAsst(this).Preset(invert); 565 | } 566 | else 567 | { 568 | AsstList.Altitude.GetAsst(this).Preset(vesModule.vesselData.vertSpeed, invert); 569 | } 570 | } 571 | else 572 | { 573 | AsstList.Altitude.GetAsst(this).Preset(vesModule.vesselData.vertSpeed, invert); 574 | AsstList.VertSpeed.GetAsst(this).Preset(vesModule.vesselData.AoA, invert); 575 | } 576 | } 577 | else 578 | { 579 | AsstList.Altitude.GetAsst(this).Preset(vesModule.vesselData.vertSpeed, invert); 580 | AsstList.VertSpeed.GetAsst(this).Preset(vesModule.vesselData.AoA, invert); 581 | AsstList.Elevator.GetAsst(this).Preset(pitchSet, invert); 582 | } 583 | 584 | switch (newMode) 585 | { 586 | case VertMode.Pitch: 587 | if (setTarget) 588 | { 589 | AsstList.Elevator.GetAsst(this).UpdateSetpoint(vesModule.vesselData.pitch); 590 | } 591 | targetVert = AsstList.Elevator.GetAsst(this).Target_setpoint.ToString("0.00"); 592 | break; 593 | case VertMode.VSpeed: 594 | if (setTarget) 595 | { 596 | AsstList.VertSpeed.GetAsst(this).UpdateSetpoint(vesModule.vesselData.vertSpeed, true, vesModule.vesselData.vertSpeed + vesModule.vesselData.AoA / AsstList.VertSpeed.GetAsst(this).K_proportional); 597 | } 598 | 599 | targetVert = AsstList.VertSpeed.GetAsst(this).Target_setpoint.ToString("0.00"); 600 | break; 601 | case VertMode.Altitude: 602 | if (setTarget) 603 | { 604 | AsstList.Altitude.GetAsst(this).UpdateSetpoint(Vessel.altitude, true, Vessel.altitude + vesModule.vesselData.vertSpeed / AsstList.Altitude.GetAsst(this).K_proportional); 605 | } 606 | 607 | targetVert = AsstList.Altitude.GetAsst(this).Target_setpoint.ToString("0.00"); 608 | break; 609 | case VertMode.RadarAltitude: 610 | if (setTarget) 611 | { 612 | AsstList.Altitude.GetAsst(this).UpdateSetpoint(vesModule.vesselData.radarAlt, true, vesModule.vesselData.radarAlt + vesModule.vesselData.vertSpeed / AsstList.Altitude.GetAsst(this).K_proportional); 613 | } 614 | 615 | targetVert = AsstList.Altitude.GetAsst(this).Target_setpoint.ToString("0.00"); 616 | break; 617 | } 618 | } 619 | VertActive = active; 620 | CurrentVertMode = newMode; 621 | } 622 | 623 | public double GetCurrentVert() 624 | { 625 | switch (CurrentVertMode) 626 | { 627 | case VertMode.Pitch: 628 | return AsstList.Elevator.GetAsst(this).Target_setpoint; 629 | case VertMode.VSpeed: 630 | return AsstList.VertSpeed.GetAsst(this).Target_setpoint; 631 | case VertMode.Altitude: 632 | case VertMode.RadarAltitude: 633 | default: 634 | return AsstList.Altitude.GetAsst(this).Target_setpoint; 635 | } 636 | } 637 | 638 | public void SetVert(bool active, bool setTarget, VertMode mode, double target) 639 | { 640 | if (setTarget) 641 | { 642 | switch (mode) 643 | { 644 | case VertMode.Altitude: 645 | AsstList.Altitude.GetAsst(this).UpdateSetpoint(target, true, Vessel.altitude + vesModule.vesselData.vertSpeed / AsstList.Altitude.GetAsst(this).K_proportional); 646 | break; 647 | case VertMode.RadarAltitude: 648 | AsstList.Altitude.GetAsst(this).UpdateSetpoint(target, true, vesModule.vesselData.radarAlt + vesModule.vesselData.vertSpeed / AsstList.Altitude.GetAsst(this).K_proportional); 649 | break; 650 | case VertMode.VSpeed: 651 | AsstList.VertSpeed.GetAsst(this).UpdateSetpoint(target, true, vesModule.vesselData.vertSpeed + vesModule.vesselData.AoA / AsstList.VertSpeed.GetAsst(this).K_proportional); 652 | break; 653 | case VertMode.Pitch: 654 | AsstList.Elevator.GetAsst(this).UpdateSetpoint(target, true, vesModule.vesselData.pitch); 655 | break; 656 | } 657 | } 658 | VertModeChanged(mode, active, !setTarget); 659 | } 660 | 661 | private void ThrottleModeChanged(ThrottleMode newMode, bool active, bool setTarget = true) 662 | { 663 | AsstList.Acceleration.GetAsst(this).SkipDerivative = true; 664 | AsstList.Speed.GetAsst(this).SkipDerivative = true; 665 | 666 | if (!active) 667 | { 668 | AsstList.Acceleration.GetAsst(this).Clear(); 669 | AsstList.Speed.GetAsst(this).Clear(); 670 | } 671 | else 672 | { 673 | bPause = false; 674 | switch (newMode) 675 | { 676 | case ThrottleMode.Speed: 677 | if (setTarget) 678 | { 679 | AsstList.Speed.GetAsst(this).UpdateSetpoint(Vessel.srfSpeed); 680 | } 681 | 682 | targetSpeed = (AsstList.Speed.GetAsst(this).Target_setpoint * Utils.SpeedUnitTransform(units, Vessel.speedOfSound)).ToString("0.00"); 683 | break; 684 | case ThrottleMode.Acceleration: 685 | if (setTarget) 686 | { 687 | AsstList.Acceleration.GetAsst(this).UpdateSetpoint(vesModule.vesselData.acceleration); 688 | } 689 | 690 | targetSpeed = (AsstList.Acceleration.GetAsst(this).Target_setpoint * Utils.SpeedUnitTransform(units, Vessel.speedOfSound)).ToString("0.00"); 691 | break; 692 | case ThrottleMode.Direct: 693 | if (setTarget) 694 | { 695 | currentThrottlePct = Vessel.ctrlState.mainThrottle; 696 | } 697 | 698 | targetSpeed = (currentThrottlePct * 100).ToString("0.00"); 699 | break; 700 | } 701 | } 702 | ThrtActive = active; 703 | CurrentThrottleMode = newMode; 704 | } 705 | 706 | public double GetCurrentThrottle() 707 | { 708 | switch(CurrentThrottleMode) 709 | { 710 | case ThrottleMode.Direct: 711 | return currentThrottlePct; 712 | case ThrottleMode.Acceleration: 713 | return AsstList.Acceleration.GetAsst(this).Target_setpoint; 714 | case ThrottleMode.Speed: 715 | default: 716 | return AsstList.Speed.GetAsst(this).Target_setpoint; 717 | } 718 | } 719 | 720 | public void SetThrottle(bool active, bool setTarget, ThrottleMode mode, double target) 721 | { 722 | if (setTarget) 723 | { 724 | switch (mode) 725 | { 726 | case ThrottleMode.Direct: 727 | currentThrottlePct = Utils.Clamp(target / 100, 0, 1); 728 | Vessel.ctrlState.mainThrottle = (float)currentThrottlePct; 729 | if (ReferenceEquals(Vessel, FlightGlobals.ActiveVessel)) 730 | { 731 | FlightInputHandler.state.mainThrottle = (float)currentThrottlePct; 732 | } 733 | 734 | break; 735 | case ThrottleMode.Acceleration: 736 | target /= Utils.SpeedUnitTransform(units, Vessel.speedOfSound); 737 | AsstList.Acceleration.GetAsst(this).UpdateSetpoint(target, true, vesModule.vesselData.acceleration); 738 | break; 739 | case ThrottleMode.Speed: 740 | target /= Utils.SpeedUnitTransform(units, Vessel.speedOfSound); 741 | AsstList.Speed.GetAsst(this).UpdateSetpoint(target, true, Vessel.srfSpeed); 742 | break; 743 | } 744 | } 745 | ThrottleModeChanged(mode, active, !setTarget); 746 | } 747 | 748 | public void ChangeSpeedRef(SpeedRef newRef) 749 | { 750 | if (ThrtActive) 751 | { 752 | switch (CurrentThrottleMode) 753 | { 754 | case ThrottleMode.Speed: 755 | double currentSpeed = AsstList.Speed.GetAsst(this).Target_setpoint / Utils.SpeedTransform(speedRef, vesModule); 756 | AsstList.Speed.GetAsst(this).UpdateSetpoint(currentSpeed * Utils.SpeedTransform(newRef, vesModule)); 757 | break; 758 | case ThrottleMode.Acceleration: 759 | double currentAccel = AsstList.Acceleration.GetAsst(this).Target_setpoint / Utils.SpeedTransform(speedRef, vesModule); 760 | AsstList.Acceleration.GetAsst(this).UpdateSetpoint(currentAccel * Utils.SpeedTransform(newRef, vesModule)); 761 | break; 762 | } 763 | } 764 | speedRef = newRef; 765 | adjustedSpeed = Vessel.srfSpeed * Utils.SpeedTransform(speedRef, vesModule); 766 | adjustedAcceleration = 0; 767 | 768 | ThrottleModeChanged(CurrentThrottleMode, ThrtActive, false); 769 | } 770 | 771 | public void ChangeSpeedUnit(SpeedUnits unit) 772 | { 773 | units = unit; 774 | ThrottleModeChanged(CurrentThrottleMode, ThrtActive, false); 775 | } 776 | 777 | public void TogglePauseCtrlState() 778 | { 779 | bPause = !bPause; 780 | 781 | if (bPause) 782 | { 783 | GeneralUI.PostMessage("Pilot Assistant: Control Paused"); 784 | InputLockManager.RemoveControlLock(yawLockID); 785 | InputLockManager.RemoveControlLock(pitchLockID); 786 | pitchLockEngaged = false; 787 | yawLockEngaged = false; 788 | } 789 | else 790 | { 791 | GeneralUI.PostMessage("Pilot Assistant: Control Unpaused"); 792 | HdgModeChanged(CurrentHrztMode, HrztActive); 793 | VertModeChanged(CurrentVertMode, VertActive); 794 | ThrottleModeChanged(CurrentThrottleMode, ThrtActive); 795 | } 796 | } 797 | 798 | public void SetLimit(double newLimit, AsstList controller) 799 | { 800 | Asst_PID_Controller c = controller.GetAsst(this); 801 | c.OutMax = Math.Abs(newLimit); 802 | c.OutMin = -Math.Abs(newLimit); 803 | } 804 | 805 | public double GetLimit(AsstList controller) 806 | { 807 | return controller.GetAsst(this).OutMax; 808 | } 809 | #endregion 810 | 811 | #region Control / Fixed Update 812 | public void VesselController(FlightCtrlState state) 813 | { 814 | pitchSet = state.pitch; // last pitch ouput, used for presetting the elevator 815 | UpdateAdjustedAcceleration(); // must run to update the UI readouts 816 | 817 | if (bPause) 818 | { 819 | return; 820 | } 821 | 822 | bool useIntegral = !Vessel.LandedOrSplashed; 823 | // Heading Control 824 | if (HrztActive && useIntegral) 825 | { 826 | switch (CurrentHrztMode) 827 | { 828 | case HrztMode.Heading: 829 | case HrztMode.HeadingNum: 830 | double tempResponse = AsstList.HdgBank.GetAsst(this).ResponseD(vesModule.vesselData.progradeHeading, useIntegral); 831 | AsstList.BankToYaw.GetAsst(this).UpdateSetpoint(tempResponse); 832 | AsstList.Aileron.GetAsst(this).UpdateSetpoint(tempResponse); 833 | AsstList.Rudder.GetAsst(this).UpdateSetpoint(AsstList.BankToYaw.GetAsst(this).ResponseD(vesModule.vesselData.yaw, useIntegral)); 834 | break; 835 | case HrztMode.Bank: 836 | default: 837 | AsstList.Rudder.GetAsst(this).UpdateSetpoint(0); 838 | break; 839 | } 840 | 841 | state.roll = AsstList.Aileron.GetAsst(this).ResponseF(vesModule.vesselData.bank, useIntegral).Clamp(-1, 1); 842 | state.yaw = AsstList.Rudder.GetAsst(this).ResponseF(vesModule.vesselData.yaw, useIntegral).Clamp(-1, 1); 843 | } 844 | 845 | if (VertActive) 846 | { 847 | if (CurrentVertMode != VertMode.Pitch) 848 | { 849 | switch (CurrentVertMode) 850 | { 851 | case VertMode.RadarAltitude: 852 | AsstList.VertSpeed.GetAsst(this).UpdateSetpoint(Utils.Clamp(GetClimbRateForConstAltitude() + AsstList.Altitude.GetAsst(this).ResponseD(vesModule.vesselData.radarAlt * Vector3.Dot(vesModule.vesselData.surfVelForward, Vessel.srf_velocity.normalized), useIntegral), -Vessel.srfSpeed * 0.9, Vessel.srfSpeed * 0.9)); 853 | break; 854 | case VertMode.Altitude: 855 | AsstList.VertSpeed.GetAsst(this).UpdateSetpoint(Utils.Clamp(AsstList.Altitude.GetAsst(this).ResponseD(Vessel.altitude, useIntegral), Vessel.srfSpeed * -0.9, Vessel.srfSpeed * 0.9)); 856 | break; 857 | } 858 | AsstList.Elevator.GetAsst(this).UpdateSetpoint(AsstList.VertSpeed.GetAsst(this).ResponseD(vesModule.vesselData.vertSpeed, useIntegral) * Utils.Clamp(Math.Cos(vesModule.vesselData.bank * Math.PI / 180) * 2.0, -1, 1)); 859 | state.pitch = AsstList.Elevator.GetAsst(this).ResponseF(vesModule.vesselData.AoA, useIntegral).Clamp(-1, 1); 860 | } 861 | else 862 | { 863 | state.pitch = AsstList.Elevator.GetAsst(this).ResponseF(vesModule.vesselData.pitch, useIntegral).Clamp(-1, 1); 864 | state.pitch *= (float)Utils.Clamp(Math.Cos(vesModule.vesselData.bank * Math.PI / 180) * 2.0, -1, 1); // only reduce control when bank angle exceeds ~60 degrees 865 | } 866 | } 867 | else if (pitchHold != 0) 868 | { 869 | state.pitch = Mathf.Clamp(state.pitch - pitchHold, -1, 1); 870 | } 871 | 872 | if (ThrtActive) 873 | { 874 | if (Vessel.ActionGroups[KSPActionGroup.Brakes] || (AsstList.Speed.GetAsst(this).Target_setpoint == 0 && Vessel.srfSpeed < -AsstList.Acceleration.GetAsst(this).OutMin)) 875 | { 876 | state.mainThrottle = 0; 877 | } 878 | else if (CurrentThrottleMode != ThrottleMode.Direct) 879 | { 880 | if (CurrentThrottleMode == ThrottleMode.Speed) 881 | { 882 | AsstList.Acceleration.GetAsst(this).UpdateSetpoint(AsstList.Speed.GetAsst(this).ResponseD(adjustedSpeed, useIntegral)); 883 | } 884 | 885 | state.mainThrottle = AsstList.Acceleration.GetAsst(this).ResponseF(adjustedAcceleration, useIntegral).Clamp(0, 1); 886 | } 887 | else 888 | { 889 | state.mainThrottle = (float)currentThrottlePct; 890 | } 891 | 892 | if (Vessel == FlightGlobals.ActiveVessel) 893 | { 894 | FlightInputHandler.state.mainThrottle = state.mainThrottle; // set throttle state permanently, but only if active vessel... 895 | } 896 | } 897 | } 898 | 899 | public void UpdateAdjustedAcceleration() 900 | { 901 | double newAdjustedSpeed = Vessel.srfSpeed * Utils.SpeedTransform(speedRef, vesModule); 902 | adjustedAcceleration = adjustedAcceleration * 0.8 + 0.2 * (newAdjustedSpeed - adjustedSpeed) / TimeWarp.fixedDeltaTime; 903 | adjustedSpeed = newAdjustedSpeed; 904 | } 905 | 906 | float pitchHold = 0; 907 | IEnumerator FadeOutPitch() 908 | { 909 | double val = AsstList.Elevator.GetAsst(this).LastOutput; 910 | double step = val * TimeWarp.fixedDeltaTime / 10; 911 | int sign = Math.Sign(val); 912 | yield return new WaitForFixedUpdate(); 913 | while (!VertActive && Math.Sign(val) == sign && Vessel.atmDensity != 0) 914 | { 915 | yield return new WaitForFixedUpdate(); 916 | val -= step; 917 | pitchHold = (float)val; 918 | } 919 | pitchHold = 0; 920 | } 921 | 922 | IEnumerator ShiftHeadingTarget(double newHdg) 923 | { 924 | headingEdit = false; 925 | stopHdgShift = false; 926 | if (hdgShiftIsRunning) 927 | { 928 | double remainder = Quaternion.Angle(newTarget, currentTarget); 929 | // set new direction 930 | newTarget = Utils.GetPlaneRotation(newHdg, vesModule); 931 | // get new remainder, reset increment only if the sign changed 932 | double tempRemainder = Quaternion.Angle(newTarget, currentTarget); 933 | if (tempRemainder < 0.5 * AsstList.HdgBank.GetAsst(this).OutMax && tempRemainder < 0.5 * remainder) 934 | { 935 | currentTarget = Utils.GetPlaneRotation((vesModule.vesselData.heading + vesModule.vesselData.bank / AsstList.HdgBank.GetAsst(this).K_proportional).HeadingClamp(360), vesModule); 936 | increment = 0; 937 | } 938 | yield break; 939 | } 940 | else 941 | { 942 | currentTarget = Utils.GetPlaneRotation((vesModule.vesselData.heading + vesModule.vesselData.bank / AsstList.HdgBank.GetAsst(this).K_proportional).HeadingClamp(360), vesModule); 943 | newTarget = Utils.GetPlaneRotation(newHdg, vesModule); 944 | increment = 0; 945 | hdgShiftIsRunning = true; 946 | } 947 | 948 | while (!stopHdgShift && Math.Abs(Quaternion.Angle(currentTarget, newTarget)) > 0.01) 949 | { 950 | currentTarget = Quaternion.RotateTowards(currentTarget, newTarget, (float)increment); 951 | increment += AsstList.HdgBank.GetAsst(this).Easing * TimeWarp.fixedDeltaTime * 0.2; 952 | yield return new WaitForFixedUpdate(); 953 | } 954 | if (!stopHdgShift) 955 | { 956 | currentTarget = newTarget; 957 | } 958 | 959 | hdgShiftIsRunning = false; 960 | } 961 | 962 | double GetClimbRateForConstAltitude() 963 | { 964 | // work out angle for ~1s to approach the point 965 | double angle = Math.Min(Math.Atan(4 * Vessel.horizontalSrfSpeed / vesModule.vesselData.radarAlt), 1.55); // 1.55 is ~89 degrees 966 | if (double.IsNaN(angle) || angle < 0.25) // 0.25 is 14.3 degrees 967 | { 968 | return 0; // fly without predictive if high/slow 969 | } 970 | else 971 | { 972 | TerrainSlope(angle, out double slope); 973 | return slope * Vessel.horizontalSrfSpeed; 974 | } 975 | } 976 | 977 | /// 978 | /// 979 | /// 980 | /// angle in radians to ping. 0 is straight down 981 | /// the calculated terrain slope 982 | /// true if an object was encountered 983 | bool TerrainSlope(double angle, out double slope) 984 | { 985 | slope = 0; 986 | angle += vesModule.vesselData.pitch * Math.PI / 180; 987 | double RayDist = FindTerrainDistAtAngle((float)(angle * 180 / Math.PI), 10000); 988 | double AltAhead = 0; 989 | if (RayDist == -1) 990 | { 991 | return false; 992 | } 993 | else 994 | { 995 | AltAhead = RayDist * Math.Cos(angle); 996 | if (Vessel.mainBody.ocean) 997 | { 998 | AltAhead = Math.Min(AltAhead, Vessel.altitude); 999 | } 1000 | } 1001 | slope = (vesModule.vesselData.radarAlt - AltAhead) / (AltAhead * Math.Tan(angle)); 1002 | return true; 1003 | } 1004 | 1005 | /// 1006 | /// raycast from vessel CoM along the given angle, returns the distance at which terrain is detected (-1 if never detected). Angle is degrees to rotate forwards from vertical 1007 | /// 1008 | float FindTerrainDistAtAngle(float angle, float maxDist) 1009 | { 1010 | Vector3 direction = Quaternion.AngleAxis(angle, -vesModule.vesselData.surfVelRight) * -vesModule.vesselData.planetUp; 1011 | Vector3 origin = Vessel.rootPart.transform.position; 1012 | if (!Vessel.HoldPhysics && Physics.Raycast(origin, direction, out RaycastHit hitInfo, maxDist, ~1)) // ~1 masks off layer 0 which is apparently the parts on the current vessel. Seems to work 1013 | { 1014 | return hitInfo.distance; 1015 | } 1016 | 1017 | return -1; 1018 | } 1019 | 1020 | #endregion 1021 | 1022 | #region GUI 1023 | public void DrawGUI() 1024 | { 1025 | if (!PilotAssistantFlightCore.bDisplayAssistant) 1026 | { 1027 | return; 1028 | } 1029 | 1030 | if (Event.current.type == EventType.Layout) 1031 | { 1032 | bMinimiseHdg = maxHdgScrollbarHeight == 10; 1033 | bMinimiseVert = maxVertScrollbarHeight == 10; 1034 | bMinimiseThrt = maxThrtScrollbarHeight == 10; 1035 | } 1036 | 1037 | // main window 1038 | #region Main Window resizing (scroll views dont work nicely with GUILayout) 1039 | // Have to put the width changes before the draw so the close button is correctly placed 1040 | float width; 1041 | if (showPIDLimits && controllers.Any(c => ControllerVisible(c))) // use two column view if show limits option and a controller is open 1042 | { 1043 | width = 340; 1044 | } 1045 | else 1046 | { 1047 | width = 210; 1048 | } 1049 | 1050 | if (bShowHdg && dragID != 1) 1051 | { 1052 | hdgScrollHeight = 0; // no controllers visible when in wing lvl mode unless ctrl surf's are there 1053 | if (CurrentHrztMode != HrztMode.Bank) 1054 | { 1055 | hdgScrollHeight += AsstList.HdgBank.GetAsst(this).BShow ? 168 : 29; 1056 | hdgScrollHeight += AsstList.BankToYaw.GetAsst(this).BShow ? 140 : 27; 1057 | } 1058 | if (showControlSurfaces) 1059 | { 1060 | hdgScrollHeight += AsstList.Aileron.GetAsst(this).BShow ? 168 : 29; 1061 | hdgScrollHeight += AsstList.Rudder.GetAsst(this).BShow ? 168 : 27; 1062 | } 1063 | } 1064 | if (bShowVert && dragID != 2) 1065 | { 1066 | vertScrollHeight = 0; 1067 | if (CurrentVertMode == VertMode.Altitude || CurrentVertMode == VertMode.RadarAltitude) 1068 | { 1069 | vertScrollHeight += AsstList.Altitude.GetAsst(this).BShow ? 168 : 27; 1070 | } 1071 | 1072 | if (CurrentVertMode != VertMode.Pitch) 1073 | { 1074 | vertScrollHeight += AsstList.VertSpeed.GetAsst(this).BShow ? 168 : 29; 1075 | } 1076 | 1077 | if (showControlSurfaces) 1078 | { 1079 | vertScrollHeight += AsstList.Elevator.GetAsst(this).BShow ? 168 : 29; 1080 | } 1081 | } 1082 | if (bShowThrottle && dragID != 3) 1083 | { 1084 | thrtScrollHeight = 0; 1085 | if (CurrentThrottleMode != ThrottleMode.Direct) 1086 | { 1087 | if (CurrentThrottleMode == ThrottleMode.Speed) 1088 | { 1089 | thrtScrollHeight += AsstList.Speed.GetAsst(this).BShow ? 168 : 27; 1090 | } 1091 | 1092 | thrtScrollHeight += AsstList.Acceleration.GetAsst(this).BShow ? 168 : 29; 1093 | } 1094 | } 1095 | #endregion 1096 | 1097 | window = GUILayout.Window(34244, window, DisplayWindow, string.Empty, GeneralUI.UISkin.box, GUILayout.Height(0), GUILayout.Width(width)); 1098 | 1099 | // tooltip window. Label skin is transparent so it's only drawing what's inside it 1100 | if (tooltip != string.Empty && PilotAssistantFlightCore.showTooltips) 1101 | { 1102 | GUILayout.Window(34246, new Rect(window.x + window.width, Screen.height - Input.mousePosition.y, 300, 0), TooltipWindow, string.Empty, GeneralUI.UISkin.label); 1103 | } 1104 | 1105 | if (showPresets) 1106 | { 1107 | // move the preset window to sit to the right of the main window, with the tops level 1108 | presetWindow.x = window.x + window.width; 1109 | presetWindow.y = window.y; 1110 | 1111 | presetWindow = GUILayout.Window(34245, presetWindow, DisplayPresetWindow, string.Empty, GeneralUI.UISkin.box, GUILayout.Width(200)); 1112 | } 1113 | 1114 | if (speedSelectWindowVisible) 1115 | { 1116 | speedSelectWindow = GUILayout.Window(34257, speedSelectWindow, DrawSpeedSelectWindow, string.Empty, GeneralUI.UISkin.box); 1117 | } 1118 | } 1119 | 1120 | private bool ControllerVisible(Asst_PID_Controller controller) 1121 | { 1122 | if (!controller.BShow) 1123 | { 1124 | return false; 1125 | } 1126 | 1127 | switch (controller.CtrlID) 1128 | { 1129 | case AsstList.HdgBank: 1130 | case AsstList.BankToYaw: 1131 | return bShowHdg && CurrentHrztMode != HrztMode.Bank; 1132 | case AsstList.Aileron: 1133 | case AsstList.Rudder: 1134 | return bShowHdg && showControlSurfaces; 1135 | case AsstList.Altitude: 1136 | return bShowVert && (CurrentVertMode == VertMode.Altitude || CurrentVertMode == VertMode.RadarAltitude); 1137 | case AsstList.VertSpeed: 1138 | return bShowVert; 1139 | case AsstList.Elevator: 1140 | return bShowVert && showControlSurfaces; 1141 | case AsstList.Speed: 1142 | return bShowThrottle && CurrentThrottleMode == ThrottleMode.Speed; 1143 | case AsstList.Acceleration: 1144 | return bShowThrottle && CurrentThrottleMode != ThrottleMode.Direct; 1145 | default: 1146 | return true; 1147 | } 1148 | } 1149 | 1150 | private void DisplayWindow(int id) 1151 | { 1152 | GUILayout.BeginHorizontal(); 1153 | 1154 | bLockInput = GUILayout.Toggle(bLockInput, new GUIContent("L", "Lock Keyboard Input"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1155 | showPIDLimits = GUILayout.Toggle(showPIDLimits, new GUIContent("L", "Show/Hide PID Limits"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1156 | showControlSurfaces = GUILayout.Toggle(showControlSurfaces, new GUIContent("C", "Show/Hide Control Surfaces"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1157 | doublesided = GUILayout.Toggle(doublesided, new GUIContent("S", "Separate Min and Max limits"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1158 | PilotAssistantFlightCore.showTooltips = GUILayout.Toggle(PilotAssistantFlightCore.showTooltips, new GUIContent("T", "Show/Hide Tooltips"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1159 | GUILayout.FlexibleSpace(); 1160 | showPresets = GUILayout.Toggle(showPresets, new GUIContent("P", "Show/Hide Presets"), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle]); 1161 | if (GUILayout.Button("X", GeneralUI.UISkin.customStyles[(int)MyStyles.redButtonText])) 1162 | { 1163 | PilotAssistantFlightCore.bDisplayAssistant = false; 1164 | Toolbar.AppLauncherFlight.SetBtnState(false); 1165 | } 1166 | GUILayout.EndHorizontal(); 1167 | 1168 | if (bPause) 1169 | { 1170 | GUILayout.Box("CONTROL PAUSED", GeneralUI.UISkin.customStyles[(int)MyStyles.labelAlert]); 1171 | } 1172 | 1173 | #region Hdg GUI 1174 | 1175 | GUILayout.BeginHorizontal(); 1176 | 1177 | GUI.backgroundColor = GeneralUI.HeaderButtonBackground; 1178 | bShowHdg = GUILayout.Toggle(bShowHdg, bShowHdg ? "-" : "+", GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(20)); 1179 | 1180 | if (HrztActive) 1181 | { 1182 | GUI.backgroundColor = GeneralUI.ActiveBackground; 1183 | } 1184 | else 1185 | { 1186 | GUI.backgroundColor = GeneralUI.InActiveBackground; 1187 | } 1188 | 1189 | if (GUILayout.Button("Roll and Yaw Control", GUILayout.Width(186))) 1190 | { 1191 | HdgModeChanged(CurrentHrztMode, !HrztActive); 1192 | } 1193 | 1194 | // reset colour 1195 | GUI.backgroundColor = GeneralUI.stockBackgroundGUIColor; 1196 | GUILayout.EndHorizontal(); 1197 | 1198 | if (bShowHdg) 1199 | { 1200 | if (!bMinimiseHdg) 1201 | { 1202 | var tempMode = (HrztMode)GUILayout.SelectionGrid((int)CurrentHrztMode, hrztLabels, 3, GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(200)); 1203 | if (CurrentHrztMode != tempMode) 1204 | { 1205 | HdgModeChanged(tempMode, HrztActive); 1206 | } 1207 | } 1208 | if (CurrentHrztMode == HrztMode.Heading || CurrentHrztMode == HrztMode.HeadingNum) 1209 | { 1210 | GUILayout.BeginHorizontal(); 1211 | if (GUILayout.Button("Target Hdg: ", GUILayout.Width(90))) 1212 | { 1213 | if (double.TryParse(targetHeading, out double newHdg)) 1214 | { 1215 | SetHrzt(true, true, CurrentHrztMode, newHdg); 1216 | 1217 | GUI.FocusControl("Target Hdg: "); 1218 | GUI.UnfocusWindow(); 1219 | } 1220 | } 1221 | 1222 | double displayTargetDelta = 0; // active setpoint or absolute value to change (yaw L/R input) 1223 | string displayTarget = "0.00"; // target setpoint or setpoint to commit as target setpoint 1224 | if (CurrentHrztMode == HrztMode.Heading) 1225 | { 1226 | if (!hdgShiftIsRunning) 1227 | { 1228 | displayTargetDelta = AsstList.HdgBank.GetAsst(this).Target_setpoint - vesModule.vesselData.heading; 1229 | } 1230 | else 1231 | { 1232 | displayTargetDelta = Utils.CalculateTargetHeading(newTarget, vesModule) - vesModule.vesselData.heading; 1233 | } 1234 | 1235 | displayTargetDelta = displayTargetDelta.HeadingClamp(180); 1236 | 1237 | if (headingEdit) 1238 | { 1239 | displayTarget = targetHeading; 1240 | } 1241 | else 1242 | { 1243 | displayTarget = Utils.CalculateTargetHeading(newTarget, vesModule).ToString("0.00"); 1244 | } 1245 | } 1246 | else 1247 | { 1248 | displayTargetDelta = AsstList.HdgBank.GetAsst(this).Target_setpoint - vesModule.vesselData.heading; 1249 | displayTargetDelta = displayTargetDelta.HeadingClamp(180); 1250 | 1251 | if (headingEdit) 1252 | { 1253 | displayTarget = targetHeading; 1254 | } 1255 | else 1256 | { 1257 | displayTarget = AsstList.HdgBank.GetAsst(this).Target_setpoint.ToString("0.00"); 1258 | } 1259 | } 1260 | 1261 | 1262 | 1263 | targetHeading = GUILayout.TextField(displayTarget, GUILayout.Width(51)); 1264 | if (targetHeading != displayTarget) 1265 | { 1266 | headingEdit = true; 1267 | } 1268 | 1269 | GUILayout.Label(displayTargetDelta.ToString("0.00"), GeneralUI.UISkin.customStyles[(int)MyStyles.greenTextBox], GUILayout.Width(51)); 1270 | GUILayout.EndHorizontal(); 1271 | } 1272 | else 1273 | { 1274 | GUILayout.BeginHorizontal(); 1275 | if (GUILayout.Button("Target Bank: ", GUILayout.Width(90))) 1276 | { 1277 | if (double.TryParse(targetHeading, out double newBank)) 1278 | { 1279 | SetHrzt(true, true, CurrentHrztMode, newBank); 1280 | GUI.FocusControl("Target Bank: "); 1281 | GUI.UnfocusWindow(); 1282 | } 1283 | } 1284 | string displayTarget = headingEdit ? targetHeading : (AsstList.Aileron.GetAsst(this).Target_setpoint).ToString("0.00"); 1285 | targetHeading = GUILayout.TextField(displayTarget, GUILayout.Width(51)); 1286 | if (targetHeading != displayTarget) 1287 | { 1288 | headingEdit = true; 1289 | } 1290 | 1291 | if (GUILayout.Button("Level", GUILayout.Width(51))) 1292 | { 1293 | AsstList.Aileron.GetAsst(this).UpdateSetpoint(0, true, vesModule.vesselData.bank); 1294 | SetHrzt(true, true, CurrentHrztMode, 0); 1295 | } 1296 | GUILayout.EndHorizontal(); 1297 | } 1298 | 1299 | if (!bMinimiseHdg) 1300 | { 1301 | HdgScrollbar = GUILayout.BeginScrollView(HdgScrollbar, GUIStyle.none, GeneralUI.UISkin.verticalScrollbar, GUILayout.Height(Math.Min(hdgScrollHeight, maxHdgScrollbarHeight))); 1302 | if (CurrentHrztMode != HrztMode.Bank) 1303 | { 1304 | DrawPIDvalues(AsstList.HdgBank, "Heading", "\u00B0", vesModule.vesselData.heading, 2, "Bank", "\u00B0"); 1305 | DrawPIDvalues(AsstList.BankToYaw, "Yaw", "\u00B0", vesModule.vesselData.yaw, 2, "Yaw", "\u00B0", false); 1306 | } 1307 | if (showControlSurfaces) 1308 | { 1309 | DrawPIDvalues(AsstList.Aileron, "Bank", "\u00B0", vesModule.vesselData.bank, 3, "Deflection", "\u00B0"); 1310 | DrawPIDvalues(AsstList.Rudder, "Yaw", "\u00B0", vesModule.vesselData.yaw, 3, "Deflection", "\u00B0"); 1311 | } 1312 | GUILayout.EndScrollView(); 1313 | } 1314 | if (GUILayout.RepeatButton(string.Empty, GUILayout.Height(8))) 1315 | {// drag resizing code from Dmagics Contracts window + used as a template 1316 | if (dragID == 0 && Event.current.button == 0) 1317 | { 1318 | dragID = 1; 1319 | dragStart = Input.mousePosition.y; 1320 | maxHdgScrollbarHeight = hdgScrollHeight = Math.Min(maxHdgScrollbarHeight, hdgScrollHeight); 1321 | } 1322 | } 1323 | if (dragID == 1) 1324 | { 1325 | if (Input.GetMouseButtonUp(0)) 1326 | { 1327 | dragID = 0; 1328 | } 1329 | else 1330 | { 1331 | float height = Math.Max(Input.mousePosition.y, 0); 1332 | maxHdgScrollbarHeight += dragStart - height; 1333 | hdgScrollHeight = maxHdgScrollbarHeight = Mathf.Clamp(maxHdgScrollbarHeight, 10, 500); 1334 | if (maxHdgScrollbarHeight > 10) 1335 | { 1336 | dragStart = height; 1337 | } 1338 | } 1339 | } 1340 | 1341 | AsstList.Aileron.GetAsst(this).OutMin = Math.Min(Math.Max(AsstList.Aileron.GetAsst(this).OutMin, -1), 1); 1342 | AsstList.Aileron.GetAsst(this).OutMax = Math.Min(Math.Max(AsstList.Aileron.GetAsst(this).OutMax, -1), 1); 1343 | 1344 | AsstList.Rudder.GetAsst(this).OutMin = Math.Min(Math.Max(AsstList.Rudder.GetAsst(this).OutMin, -1), 1); 1345 | AsstList.Rudder.GetAsst(this).OutMax = Math.Min(Math.Max(AsstList.Rudder.GetAsst(this).OutMax, -1), 1); 1346 | } 1347 | #endregion 1348 | #region Pitch GUI 1349 | 1350 | GUILayout.BeginHorizontal(); 1351 | 1352 | GUI.backgroundColor = GeneralUI.HeaderButtonBackground; 1353 | bShowVert = GUILayout.Toggle(bShowVert, bShowVert ? "-" : "+", GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(20)); 1354 | 1355 | if (VertActive) 1356 | { 1357 | GUI.backgroundColor = GeneralUI.ActiveBackground; 1358 | } 1359 | else 1360 | { 1361 | GUI.backgroundColor = GeneralUI.InActiveBackground; 1362 | } 1363 | 1364 | if (GUILayout.Button("Vertical Control", GUILayout.Width(186))) 1365 | { 1366 | VertModeChanged(CurrentVertMode, !VertActive); 1367 | } 1368 | 1369 | // reset colour 1370 | GUI.backgroundColor = GeneralUI.stockBackgroundGUIColor; 1371 | GUILayout.EndHorizontal(); 1372 | if (bShowVert) 1373 | { 1374 | if (!bMinimiseVert) 1375 | { 1376 | var tempMode = (VertMode)GUILayout.SelectionGrid((int)CurrentVertMode, vertLabels, vertLabels.Length, GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(200)); 1377 | if (tempMode != CurrentVertMode) 1378 | { 1379 | VertModeChanged(tempMode, VertActive); 1380 | } 1381 | } 1382 | GUILayout.BeginHorizontal(); 1383 | string buttonString; 1384 | switch (CurrentVertMode) 1385 | { 1386 | case VertMode.RadarAltitude: 1387 | buttonString = "Target RadarAlt"; 1388 | break; 1389 | case VertMode.Altitude: 1390 | buttonString = "Target Altitude"; 1391 | break; 1392 | case VertMode.VSpeed: 1393 | buttonString = "Target Speed"; 1394 | break; 1395 | case VertMode.Pitch: 1396 | default: 1397 | buttonString = "Target Pitch"; 1398 | break; 1399 | } 1400 | 1401 | if (GUILayout.Button(buttonString, GUILayout.Width(118))) 1402 | { 1403 | ScreenMessages.PostScreenMessage(buttonString + " updated"); 1404 | 1405 | if (double.TryParse(targetVert, out double newVal)) 1406 | { 1407 | SetVert(true, true, CurrentVertMode, newVal); 1408 | } 1409 | 1410 | GUI.FocusControl("Target Hdg: "); 1411 | GUI.UnfocusWindow(); 1412 | } 1413 | targetVert = GUILayout.TextField(targetVert, GUILayout.Width(78)); 1414 | GUILayout.EndHorizontal(); 1415 | 1416 | if (!bMinimiseVert) 1417 | { 1418 | VertScrollbar = GUILayout.BeginScrollView(VertScrollbar, GUIStyle.none, GeneralUI.UISkin.verticalScrollbar, GUILayout.Height(Math.Min(vertScrollHeight, maxVertScrollbarHeight))); 1419 | if (CurrentVertMode == VertMode.RadarAltitude) 1420 | { 1421 | DrawPIDvalues(AsstList.Altitude, "RAltitude", "m", vesModule.vesselData.radarAlt, 2, "Speed ", "m/s"); 1422 | } 1423 | 1424 | if (CurrentVertMode == VertMode.Altitude) 1425 | { 1426 | DrawPIDvalues(AsstList.Altitude, "Altitude", "m", Vessel.altitude, 2, "Speed ", "m/s"); 1427 | } 1428 | 1429 | if (CurrentVertMode != VertMode.Pitch) 1430 | { 1431 | DrawPIDvalues(AsstList.VertSpeed, "Vertical Speed", "m/s", vesModule.vesselData.vertSpeed, 2, "AoA", "\u00B0"); 1432 | } 1433 | 1434 | if (showControlSurfaces) 1435 | { 1436 | DrawPIDvalues(AsstList.Elevator, CurrentVertMode != VertMode.Pitch ? "Angle of Attack" : "Pitch", "\u00B0", CurrentVertMode == VertMode.Pitch ? vesModule.vesselData.pitch : vesModule.vesselData.AoA, 3, "Deflection", "\u00B0", true); 1437 | } 1438 | 1439 | AsstList.Elevator.GetAsst(this).OutMin = Utils.Clamp(AsstList.Elevator.GetAsst(this).OutMin, -1, 1); 1440 | AsstList.Elevator.GetAsst(this).OutMax = Utils.Clamp(AsstList.Elevator.GetAsst(this).OutMax, -1, 1); 1441 | 1442 | GUILayout.EndScrollView(); 1443 | } 1444 | 1445 | if (GUILayout.RepeatButton(string.Empty, GUILayout.Height(8))) 1446 | {// drag resizing code from Dmagics Contracts window + used as a template 1447 | if (dragID == 0 && Event.current.button == 0) 1448 | { 1449 | dragID = 2; 1450 | dragStart = Input.mousePosition.y; 1451 | maxVertScrollbarHeight = vertScrollHeight = Math.Min(maxVertScrollbarHeight, vertScrollHeight); 1452 | } 1453 | } 1454 | if (dragID == 2) 1455 | { 1456 | if (Input.GetMouseButtonUp(0)) 1457 | { 1458 | dragID = 0; 1459 | } 1460 | else 1461 | { 1462 | float height = Math.Max(Input.mousePosition.y, 0); 1463 | maxVertScrollbarHeight += dragStart - height; 1464 | vertScrollHeight = maxVertScrollbarHeight = Mathf.Clamp(maxVertScrollbarHeight, 10, 500); 1465 | if (maxVertScrollbarHeight > 10) 1466 | { 1467 | dragStart = height; 1468 | } 1469 | } 1470 | } 1471 | } 1472 | #endregion 1473 | #region Throttle GUI 1474 | 1475 | GUILayout.BeginHorizontal(); 1476 | // button background 1477 | GUI.backgroundColor = GeneralUI.HeaderButtonBackground; 1478 | bShowThrottle = GUILayout.Toggle(bShowThrottle, bShowThrottle ? "-" : "+", GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(20)); 1479 | if (ThrtActive) 1480 | { 1481 | GUI.backgroundColor = GeneralUI.ActiveBackground; 1482 | } 1483 | else 1484 | { 1485 | GUI.backgroundColor = GeneralUI.InActiveBackground; 1486 | } 1487 | 1488 | if (GUILayout.Button("Throttle Control", GUILayout.Width(186))) 1489 | { 1490 | ThrottleModeChanged(CurrentThrottleMode, !ThrtActive); 1491 | } 1492 | // reset colour 1493 | GUI.backgroundColor = GeneralUI.stockBackgroundGUIColor; 1494 | GUILayout.EndHorizontal(); 1495 | 1496 | if (bShowThrottle) 1497 | { 1498 | if (!bMinimiseThrt) 1499 | { 1500 | var tempMode = (ThrottleMode)GUILayout.SelectionGrid((int)CurrentThrottleMode, throttleLabels, 3, GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(200)); 1501 | if (tempMode != CurrentThrottleMode) 1502 | { 1503 | ThrottleModeChanged(tempMode, ThrtActive); 1504 | } 1505 | } 1506 | GUILayout.BeginHorizontal(); 1507 | 1508 | string tempSpeed = string.Empty; 1509 | switch (CurrentThrottleMode) 1510 | { 1511 | case ThrottleMode.Direct: 1512 | tempSpeed = "Throttle %"; 1513 | break; 1514 | case ThrottleMode.Acceleration: 1515 | tempSpeed = "Target Accel"; 1516 | break; 1517 | case ThrottleMode.Speed: 1518 | tempSpeed = "Target Speed"; 1519 | break; 1520 | } 1521 | if (GUILayout.Button(tempSpeed, GUILayout.Width(108))) 1522 | { 1523 | GeneralUI.PostMessage("Target updated"); 1524 | 1525 | if (double.TryParse(targetSpeed, out double newVal)) 1526 | { 1527 | SetThrottle(true, true, CurrentThrottleMode, newVal); 1528 | } 1529 | 1530 | GUI.FocusControl("Target Hdg: "); 1531 | GUI.UnfocusWindow(); 1532 | } 1533 | targetSpeed = GUILayout.TextField(targetSpeed, GUILayout.Width(68)); 1534 | bool tempToggle = GUILayout.Toggle(speedSelectWindowVisible, ">", GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(18)); 1535 | if (tempToggle != speedSelectWindowVisible) 1536 | { 1537 | speedSelectWindowVisible = tempToggle; 1538 | speedSelectWindow.x = Input.mousePosition.x + 30; 1539 | speedSelectWindow.y = Screen.height - (Input.mousePosition.y + 20); 1540 | } 1541 | 1542 | GUILayout.EndHorizontal(); 1543 | 1544 | if (!bMinimiseThrt) 1545 | { 1546 | ThrtScrollbar = GUILayout.BeginScrollView(ThrtScrollbar, GUIStyle.none, GeneralUI.UISkin.verticalScrollbar, GUILayout.Height(Math.Min(thrtScrollHeight, maxThrtScrollbarHeight))); 1547 | if (CurrentThrottleMode == ThrottleMode.Speed) 1548 | { 1549 | DrawPIDvalues(AsstList.Speed, "Speed", Utils.UnitString(units), adjustedSpeed * Utils.SpeedUnitTransform(units, Vessel.speedOfSound), 2, "Accel ", Utils.UnitString(units) + "/s"); 1550 | } 1551 | 1552 | if (CurrentThrottleMode != ThrottleMode.Direct) 1553 | { 1554 | DrawPIDvalues(AsstList.Acceleration, "Acceleration", Utils.UnitString(units) + "/s", adjustedAcceleration * Utils.SpeedUnitTransform(units, Vessel.speedOfSound), 2, "Throttle ", "%"); 1555 | } 1556 | // can't have people bugging things out now can we... 1557 | AsstList.Acceleration.GetAsst(this).OutMax = AsstList.Speed.GetAsst(this).OutMax.Clamp(0, 1); 1558 | AsstList.Acceleration.GetAsst(this).OutMin = AsstList.Speed.GetAsst(this).OutMin.Clamp(0, 1); 1559 | 1560 | GUILayout.EndScrollView(); 1561 | } 1562 | 1563 | if (GUILayout.RepeatButton(string.Empty, GUILayout.Height(8))) 1564 | {// drag resizing code from Dmagics Contracts window + used as a template 1565 | if (dragID == 0 && Event.current.button == 0) 1566 | { 1567 | dragID = 3; 1568 | dragStart = Input.mousePosition.y; 1569 | maxThrtScrollbarHeight = thrtScrollHeight = Math.Min(maxThrtScrollbarHeight, thrtScrollHeight); 1570 | } 1571 | } 1572 | if (dragID == 3) 1573 | { 1574 | if (Input.GetMouseButtonUp(0)) 1575 | { 1576 | dragID = 0; 1577 | } 1578 | else 1579 | { 1580 | float height = Math.Max(Input.mousePosition.y, 0); 1581 | maxThrtScrollbarHeight += dragStart - height; 1582 | thrtScrollHeight = maxThrtScrollbarHeight = Mathf.Clamp(maxThrtScrollbarHeight, 10, 500); 1583 | if (maxThrtScrollbarHeight > 10) 1584 | { 1585 | dragStart = height; 1586 | } 1587 | } 1588 | } 1589 | } 1590 | #endregion 1591 | 1592 | GUI.DragWindow(); 1593 | if (Event.current.type == EventType.Repaint) 1594 | { 1595 | tooltip = GUI.tooltip; 1596 | } 1597 | } 1598 | 1599 | 1600 | const string OutMaxTooltip = "The absolute maximum value the controller can output"; 1601 | const string OutMinTooltip = "The absolute minimum value the controller can output"; 1602 | 1603 | string tooltip = string.Empty; 1604 | private void TooltipWindow(int id) 1605 | { 1606 | GUILayout.Label(tooltip, GeneralUI.UISkin.textArea); 1607 | } 1608 | 1609 | private void DrawPIDvalues(AsstList controllerid, string inputName, string inputUnits, double inputValue, int displayPrecision, string outputName, string outputUnits, bool showTarget = true) 1610 | { 1611 | Asst_PID_Controller controller = controllerid.GetAsst(this); 1612 | controller.BShow = GUILayout.Toggle(controller.BShow, string.Format("{0}: {1}{2}", inputName, inputValue.ToString("N" + displayPrecision.ToString()), inputUnits), GeneralUI.UISkin.customStyles[(int)MyStyles.btnToggle], GUILayout.Width(200)); 1613 | 1614 | if (controller.BShow) 1615 | { 1616 | if (showTarget) 1617 | { 1618 | switch (controllerid) 1619 | { 1620 | case AsstList.Speed: 1621 | case AsstList.Acceleration: 1622 | GUILayout.Label("Target: " + (controller.Target_setpoint * Utils.SpeedUnitTransform(units, Vessel.speedOfSound)).ToString("N" + displayPrecision.ToString()) + inputUnits, GUILayout.Width(200)); 1623 | break; 1624 | default: 1625 | GUILayout.Label("Target: " + controller.Target_setpoint.ToString("N" + displayPrecision.ToString()) + inputUnits, GUILayout.Width(200)); 1626 | break; 1627 | } 1628 | } 1629 | 1630 | GUILayout.BeginHorizontal(); 1631 | GUILayout.BeginVertical(); 1632 | 1633 | controller.K_proportional = GeneralUI.LabPlusNumBox(GeneralUI.KpLabel, controller.K_proportional.ToString("G3"), 45); 1634 | controller.K_integral = GeneralUI.LabPlusNumBox(GeneralUI.KiLabel, controller.K_integral.ToString("G3"), 45); 1635 | controller.K_derivative = GeneralUI.LabPlusNumBox(GeneralUI.KdLabel, controller.K_derivative.ToString("G3"), 45); 1636 | controller.Scalar = GeneralUI.LabPlusNumBox(GeneralUI.ScalarLabel, controller.Scalar.ToString("G3"), 45); 1637 | 1638 | if (showPIDLimits) 1639 | { 1640 | GUILayout.EndVertical(); 1641 | GUILayout.BeginVertical(); 1642 | 1643 | controller.OutMax = GeneralUI.LabPlusNumBox(new GUIContent(string.Format("Max {0}{1}:", outputName, outputUnits), OutMaxTooltip), controller.OutMax.ToString("G3")); 1644 | if (doublesided) 1645 | { 1646 | controller.OutMin = GeneralUI.LabPlusNumBox(new GUIContent(string.Format("Min {0}{1}:", outputName, outputUnits), OutMinTooltip), controller.OutMin.ToString("G3")); 1647 | controller.IntegralClampLower = GeneralUI.LabPlusNumBox(GeneralUI.IMinLabel, controller.IntegralClampLower.ToString("G3")); 1648 | } 1649 | else 1650 | { 1651 | controller.OutMin = -controller.OutMax; 1652 | controller.IntegralClampLower = -controller.IntegralClampUpper; 1653 | } 1654 | controller.IntegralClampUpper = GeneralUI.LabPlusNumBox(GeneralUI.IMaxLabel, controller.IntegralClampUpper.ToString("G3")); 1655 | controller.Easing = GeneralUI.LabPlusNumBox(GeneralUI.EasingLabel, controller.Easing.ToString("G3")); 1656 | } 1657 | GUILayout.EndVertical(); 1658 | GUILayout.EndHorizontal(); 1659 | } 1660 | } 1661 | 1662 | private void DisplayPresetWindow(int id) 1663 | { 1664 | if (GUI.Button(new Rect(presetWindow.width - 16, 2, 14, 14), string.Empty)) 1665 | { 1666 | showPresets = false; 1667 | } 1668 | 1669 | if (!ReferenceEquals(activePreset, null)) // preset will be null after deleting an active preset 1670 | { 1671 | GUILayout.Label(string.Format("Active Preset: {0}", activePreset.name)); 1672 | if (activePreset.name != "default") 1673 | { 1674 | if (GUILayout.Button("Update Preset")) 1675 | { 1676 | UpdateAsstPreset(); 1677 | } 1678 | } 1679 | GUILayout.Box(string.Empty, GUILayout.Height(10), GUILayout.Width(180)); 1680 | } 1681 | 1682 | GUILayout.BeginHorizontal(); 1683 | newPresetName = GUILayout.TextField(newPresetName); 1684 | if (GUILayout.Button("+", GUILayout.Width(25))) 1685 | { 1686 | if (PresetManager.NewAsstPreset(newPresetName, controllers, Vessel)) 1687 | { 1688 | newPresetName = string.Empty; 1689 | } 1690 | } 1691 | GUILayout.EndHorizontal(); 1692 | 1693 | GUILayout.Box(string.Empty, GUILayout.Height(10)); 1694 | 1695 | if (GUILayout.Button("Reset to Defaults")) 1696 | { 1697 | PresetManager.LoadAsstPreset(PresetManager.Instance.craftPresetDict[PresetManager.craftDefaultName], this); 1698 | } 1699 | 1700 | GUILayout.Box(string.Empty, GUILayout.Height(10)); 1701 | 1702 | AsstPreset presetToDelete = null; 1703 | foreach (AsstPreset p in PresetManager.Instance.AsstPresetList) 1704 | { 1705 | if (p.name == PresetManager.asstDefaultName) 1706 | { 1707 | continue; 1708 | } 1709 | 1710 | GUILayout.BeginHorizontal(); 1711 | if (GUILayout.Button(p.name)) 1712 | { 1713 | PresetManager.LoadAsstPreset(p, this); 1714 | } 1715 | else if (GUILayout.Button("x", GUILayout.Width(25))) 1716 | { 1717 | presetToDelete = p; 1718 | } 1719 | 1720 | GUILayout.EndHorizontal(); 1721 | } 1722 | if (!ReferenceEquals(presetToDelete, null)) 1723 | { 1724 | PresetManager.Instance.DeleteAsstPreset(presetToDelete); 1725 | presetWindow.height = 0; 1726 | } 1727 | } 1728 | 1729 | private void DrawSpeedSelectWindow(int id) 1730 | { 1731 | var tempRef = (SpeedRef)GUILayout.SelectionGrid((int)speedRef, speedRefLabels, 3); 1732 | if (tempRef != speedRef) 1733 | { 1734 | ChangeSpeedRef(tempRef); 1735 | } 1736 | 1737 | var tempUnits = (SpeedUnits)GUILayout.SelectionGrid((int)units, speedUnitLabels, 5); 1738 | if (tempUnits != units) 1739 | { 1740 | ChangeSpeedUnit(tempUnits); 1741 | } 1742 | } 1743 | 1744 | #endregion 1745 | 1746 | /// 1747 | /// Update an active preset with the current values 1748 | /// 1749 | /// 1750 | public void UpdateAsstPreset() 1751 | { 1752 | activePreset.Update(controllers); 1753 | PresetManager.SaveToFile(); 1754 | } 1755 | } 1756 | } --------------------------------------------------------------------------------