diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ba39d..98cb402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1093,8 +1093,7 @@ up to date in EDDN. * Added `open [rat;] dispatch board` command. Opens the web dispatch board in your default browser. * Added proper handling for multiple ratsignals hitting at once. That’s mainly - an IRC client config thing, - [see the docs](docs/RatAttack.md#getting-case-data-from-irc). + an IRC client config thing, [see the docs](docs/configuration/RatAttack.md). * Renamed `RatAttack.getInfoFromRatsignal` to `RatAttack.announceCaseFromRatsignal`. Removed the “open case?” voice input prompt. diff --git a/plugins/EliteAttack/EliteAttack.csproj b/plugins/EliteAttack/EliteAttack.csproj index 9fe30f5..839a74f 100644 --- a/plugins/EliteAttack/EliteAttack.csproj +++ b/plugins/EliteAttack/EliteAttack.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + ..\build\alterNERDtive\EliteAttack.xml none @@ -29,6 +30,7 @@ TRACE prompt 4 + ..\build\alterNERDtive\EliteAttack.xml @@ -51,4 +53,4 @@ - + \ No newline at end of file diff --git a/plugins/EliteAttack/Properties/AssemblyInfo.cs b/plugins/EliteAttack/Properties/AssemblyInfo.cs index 11fe575..cedd4c4 100644 --- a/plugins/EliteAttack/Properties/AssemblyInfo.cs +++ b/plugins/EliteAttack/Properties/AssemblyInfo.cs @@ -1,4 +1,23 @@ -using System.Reflection; +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/plugins/RatAttack-cli/Properties/AssemblyInfo.cs b/plugins/RatAttack-cli/Properties/AssemblyInfo.cs index cb8a8ea..3bd89ba 100644 --- a/plugins/RatAttack-cli/Properties/AssemblyInfo.cs +++ b/plugins/RatAttack-cli/Properties/AssemblyInfo.cs @@ -1,4 +1,23 @@ -using System.Reflection; +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/plugins/RatAttack-cli/RatAttack-cli.csproj b/plugins/RatAttack-cli/RatAttack-cli.csproj index 0b495d3..2960a73 100644 --- a/plugins/RatAttack-cli/RatAttack-cli.csproj +++ b/plugins/RatAttack-cli/RatAttack-cli.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + ..\build\alterNERDtive\RatAttack-cli.xml AnyCPU @@ -34,6 +35,7 @@ TRACE prompt 4 + ..\build\alterNERDtive\RatAttack-cli.xml diff --git a/plugins/RatAttack/Properties/AssemblyInfo.cs b/plugins/RatAttack/Properties/AssemblyInfo.cs index 6079baf..1fd4faf 100644 --- a/plugins/RatAttack/Properties/AssemblyInfo.cs +++ b/plugins/RatAttack/Properties/AssemblyInfo.cs @@ -1,4 +1,23 @@ -using System.Reflection; +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/plugins/RatAttack/RatAttack.csproj b/plugins/RatAttack/RatAttack.csproj index 1475205..3a3e126 100644 --- a/plugins/RatAttack/RatAttack.csproj +++ b/plugins/RatAttack/RatAttack.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + ..\build\alterNERDtive\RatAttack.xml none @@ -33,6 +34,7 @@ TRACE prompt 4 + ..\build\alterNERDtive\RatAttack.xml @@ -57,4 +59,4 @@ - + \ No newline at end of file diff --git a/plugins/SpanshAttack/Properties/AssemblyInfo.cs b/plugins/SpanshAttack/Properties/AssemblyInfo.cs index 311a704..7fe5579 100644 --- a/plugins/SpanshAttack/Properties/AssemblyInfo.cs +++ b/plugins/SpanshAttack/Properties/AssemblyInfo.cs @@ -1,4 +1,23 @@ -using System.Reflection; +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/plugins/SpanshAttack/SpanshAttack.csproj b/plugins/SpanshAttack/SpanshAttack.csproj index a279b46..4c736e5 100644 --- a/plugins/SpanshAttack/SpanshAttack.csproj +++ b/plugins/SpanshAttack/SpanshAttack.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + ..\build\alterNERDtive\SpanshAttack.xml pdbonly @@ -29,6 +30,7 @@ TRACE prompt 4 + ..\build\alterNERDtive\SpanshAttack.xml @@ -51,4 +53,4 @@ - + \ No newline at end of file diff --git a/plugins/VoiceAttack-base/GlobalSuppressions.cs b/plugins/VoiceAttack-base/GlobalSuppressions.cs index 9a06d8e..32cfcb3 100644 --- a/plugins/VoiceAttack-base/GlobalSuppressions.cs +++ b/plugins/VoiceAttack-base/GlobalSuppressions.cs @@ -1,10 +1,28 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// using System.Diagnostics.CodeAnalysis; +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive.edts")] [assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive.util")] diff --git a/plugins/VoiceAttack-base/Properties/AssemblyInfo.cs b/plugins/VoiceAttack-base/Properties/AssemblyInfo.cs index aabd5aa..42518aa 100644 --- a/plugins/VoiceAttack-base/Properties/AssemblyInfo.cs +++ b/plugins/VoiceAttack-base/Properties/AssemblyInfo.cs @@ -1,4 +1,23 @@ -using System.Reflection; +// +// Copyright 2019–2022 alterNERDtive. +// +// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>. +// + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,7 +29,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("VoiceAttack-base")] -[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyCopyright("Copyright © 2020–2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/plugins/VoiceAttack-base/SettingsDialog.xaml.cs b/plugins/VoiceAttack-base/SettingsDialog.xaml.cs index 3134a3b..5e1ebd8 100644 --- a/plugins/VoiceAttack-base/SettingsDialog.xaml.cs +++ b/plugins/VoiceAttack-base/SettingsDialog.xaml.cs @@ -56,7 +56,7 @@ namespace alterNERDtive tab.IsEnabled = BasePlugin.IsProfileActive(profile); StackPanel panel = new StackPanel(); - util.Configuration.OptDict options = config.GetOptions(profile); + util.Configuration.OptDict options = util.Configuration.GetOptions(profile); foreach (dynamic option in options.Values) { diff --git a/plugins/VoiceAttack-base/VoiceAttack-base.csproj b/plugins/VoiceAttack-base/VoiceAttack-base.csproj index b478461..df39c70 100644 --- a/plugins/VoiceAttack-base/VoiceAttack-base.csproj +++ b/plugins/VoiceAttack-base/VoiceAttack-base.csproj @@ -24,6 +24,7 @@ DEBUG;TRACE prompt 4 + ..\build\alterNERDtive\base.xml none @@ -32,6 +33,7 @@ TRACE prompt 4 + ..\build\alterNERDtive\base.xml @@ -76,4 +78,4 @@ - + \ No newline at end of file diff --git a/plugins/VoiceAttack-base/base.cs b/plugins/VoiceAttack-base/base.cs index 73d7b08..d308260 100644 --- a/plugins/VoiceAttack-base/base.cs +++ b/plugins/VoiceAttack-base/base.cs @@ -119,8 +119,6 @@ namespace alterNERDtive /// The VoiceAttack proxy object. public static void VA_Invoke1(dynamic vaProxy) { - VA = vaProxy; - string context = vaProxy.Context.ToLower(); Log.Debug($"Running context '{context}' …"); try @@ -128,49 +126,49 @@ namespace alterNERDtive switch (context) { case "startup": - Context_Startup(); + Context_Startup(vaProxy); break; case "config.dialog": // config - Context_Config_Dialog(); + Context_Config_Dialog(vaProxy); break; case "config.dump": - Context_Config_Dump(); + Context_Config_Dump(vaProxy); break; case "config.getvariables": - Context_Config_SetVariables(); + Context_Config_SetVariables(vaProxy); break; case "config.list": - Context_Config_List(); + Context_Config_List(vaProxy); break; case "config.setup": - Context_Config_Setup(); + Context_Config_Setup(vaProxy); break; case "config.versionmigration": - Context_Config_VersionMigration(); + Context_Config_VersionMigration(vaProxy); break; case "edsm.bodycount": // EDSM - Context_EDSM_BodyCount(); + Context_EDSM_BodyCount(vaProxy); break; case "edsm.distancebetween": - Context_EDSM_DistanceBetween(); + Context_EDSM_DistanceBetween(vaProxy); break; case "eddi.event": // EDDI - Context_Eddi_Event(); + Context_Eddi_Event(vaProxy); break; case "spansh.outdatedstations": // Spansh - Context_Spansh_OutdatedStations(); + Context_Spansh_OutdatedStations(vaProxy); break; case "log.log": // log - Context_Log(); + Context_Log(vaProxy); break; case "update.check": // update - Context_Update_Check(); + Context_Update_Check(vaProxy); break; default: // invalid @@ -248,7 +246,7 @@ namespace alterNERDtive string name = match.Groups["name"].Value; Log.Debug($"Configuration has changed, '{id}.{name}': '{from}' → '{to}'"); - dynamic o = Config.GetOption(id, name); + dynamic o = Configuration.GetOption(id, name); // When loaded from profile but not explicitly set, will be null. // Then load default. diff --git a/plugins/VoiceAttack-base/util.cs b/plugins/VoiceAttack-base/util.cs index a2366e4..1a98ba9 100644 --- a/plugins/VoiceAttack-base/util.cs +++ b/plugins/VoiceAttack-base/util.cs @@ -28,241 +28,510 @@ using System.Linq; namespace alterNERDtive.util { + /// + /// Log levels that can be used when writing to the VoiceAttack log. + /// + public enum LogLevel + { + /// + /// Log level for error messages. Errors cause execution to abort. + /// + ERROR, + + /// + /// Log level for warning messages. Warnings should not cause execution + /// to abort. + /// + WARN, + + /// + /// Log level for notices. Notices should be noteworthy. + /// + NOTICE, + + /// + /// Log level for informational messages. These should not be + /// noteworthy. + /// + INFO, + + /// + /// Log level for debug messages. They should be useful only for + /// debugging. + /// + DEBUG, + } + + /// + /// Interface describing an object that can be send through a Pipe. Needs + /// serializing to and deserializing from . + /// [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "file contains collection of utility classes")] + public interface IPipable + { + /// + /// Parses object data from a serialized string. + /// + /// The serialized string. + public void ParseString(string serialization); + + /// + /// Serializes the object to a string. + /// + /// The serialized string. + public string ToString(); + } + + /// + /// Contains the configuration for a plugin/profile and handles + /// configuration-related VoiceAttack interactions. + /// public class Configuration { - private readonly dynamic VA; - private readonly string ID; - private readonly VoiceAttackLog Log; - private readonly VoiceAttackCommands Commands; - private static readonly Dictionary> Defaults = new Dictionary> + private static readonly Dictionary> Defaults = new () { { "alterNERDtive-base", - new OptDict{ + new OptDict + { /*{ new Option("delays.keyPressDuration", (decimal)0.01, voiceTrigger: "key press duration", description: "The time keys will be held down for.") },*/ - { new Option("delays.quitToDesktop", (decimal)10.0, voiceTrigger: "quit to desktop delay", - description: "The delay before restarting the game after hitting “Exit to Desktop”, in seconds.\nDefault: 10.0. (Used by the `restart from desktop` command)") }, - { new Option("eddi.quietMode", true, voiceTrigger: "eddi quiet mode", - description: "Make EDDI shut up. Disables all built-in speech responders.") }, - { new Option("elite.pasteKey", "v", voiceTrigger: "elite paste key", - description: "The key used to paste in conjunction with CTRL. The physical key in your layout that would be 'V' on QWERTY.") }, - { new Option("enableAutoUpdateCheck", true, voiceTrigger: "auto update check", - description: "Automatically check Github for profiles updates when the profile loads.") }, - { new Option("log.logLevel", "NOTICE", voiceTrigger: "log level", validValues: new List{ "ERROR", "WARN", "NOTICE", "INFO", "DEBUG" }, - description: "The level of detail for logging to the VoiceAttack log.\nValid levels are \"ERROR\", \"WARN\", \"NOTICE\", \"INFO\" and \"DEBUG\".\nDefault: \"NOTICE\".") }, + { + new Option( + name: "delays.quitToDesktop", + defaultValue: 10.0M, + voiceTrigger: "quit to desktop delay", + description: "The delay before restarting the game after hitting “Exit to Desktop”, in seconds.\nDefault: 10.0. (Used by the `restart from desktop` command)") + }, + { + new Option( + name: "eddi.quietMode", + defaultValue: true, + voiceTrigger: "eddi quiet mode", + description: "Make EDDI shut up. Disables all built-in speech responders.") + }, + { + new Option( + name: "elite.pasteKey", + defaultValue: "v", + voiceTrigger: "elite paste key", + description: "The key used to paste in conjunction with CTRL. The physical key in your layout that would be 'V' on QWERTY.") + }, + { + new Option( + name: "enableAutoUpdateCheck", + defaultValue: true, + voiceTrigger: "auto update check", + description: "Automatically check Github for profiles updates when the profile loads.") + }, + { + new Option( + name: "log.logLevel", + defaultValue: "NOTICE", + voiceTrigger: "log level", + validValues: new List { "ERROR", "WARN", "NOTICE", "INFO", "DEBUG" }, + description: "The level of detail for logging to the VoiceAttack log.\nValid levels are \"ERROR\", \"WARN\", \"NOTICE\", \"INFO\" and \"DEBUG\".\nDefault: \"NOTICE\".") + }, } }, { "EliteAttack", - new OptDict{ - { new Option("announceEdsmSystemStatus", true, voiceTrigger: "edsm system status", - description: "Pull system data from EDSM and compare it against your discovery scan.") }, - { new Option("announceJumpsInRoute", true, voiceTrigger: "route jump count", - description: "Give a jump count on plotting a route.") }, - { new Option("announceMappingCandidates", true, voiceTrigger: "mapping candidates", - description: "Announce bodies worth mapping when you have finished scanning a system.\n(Terraformables, Water Worlds, Earth-Like Worlds and Ammonia Worlds that have not been mapped yet.)") }, - { new Option("announceOutdatedStationData", true, voiceTrigger: "outdated stations", - description: "Announce stations with outdated data in the online databases.") }, - { new Option("outdatedStationThreshold", 365, voiceTrigger: "outdated station threshold", - description: "The threshold for station data to count as “outdated”, in days.\nDefault: 365.") }, - { new Option("announceR2RMappingCandidates", false, voiceTrigger: "road to riches", - description: "Announce bodies worth scanning if you are looking for some starting cash on the Road to Riches.") }, - { new Option("announceRepairs", true, voiceTrigger: "repair reports", - description: "Report on AFMU repairs.") }, - { new Option("announceSynthesis", true, voiceTrigger: "synthesis reports", - description: "Report on synthesis.") }, - { new Option("autoHonkNewSystems", true, voiceTrigger: "auto honk new systems", - description: "Automatically honk upon entering a system if it is your first visit.") }, - { new Option("autoHonkAllSystems", false, voiceTrigger: "auto honk all systems", - description: "Automatically honk upon entering a system, each jump, without constraints.") }, - { new Option("scannerFireGroup", 0, voiceTrigger: "scanner fire group", - description: "The fire group your discovery scanner is assigned to.\nDefault: 0 (the first one).") }, - { new Option("usePrimaryFireForDiscoveryScan", false, voiceTrigger: "discovery scan on primary fire", - description: "Use primary fire for honking instead of secondary.") }, - { new Option("autoRefuel", true, voiceTrigger: "auto refuel", - description: "Automatically refuel after docking at a station.") }, - { new Option("autoRepair", true, voiceTrigger: "auto repair", - description: "Automatically repair after docking at a station.") }, - { new Option("autoRestock", true, voiceTrigger: "auto restock", - description: "Automatically restock after docking at a station.") }, - { new Option("autoHangar", true, voiceTrigger: "auto move to hangar", - description: "Automatically move the ship to the hangar after docking at a station.") }, - { new Option("autoStationServices", true, voiceTrigger: "auto enter station services", - description: "Automatically enter the Station Services menu after docking at a station.") }, - { new Option("autoRetractLandingGear", true, voiceTrigger: "auto retract landing gear", - description: "Automatically retract landing gear when lifting off a planet / undocking from a station.") }, - { new Option("autoDisableSrvLights", true, voiceTrigger: "auto disable s r v lights", - description: "Automatically turn SRV lights off when deploying one.") }, - { new Option("flightAssistOff", false, voiceTrigger: "flight assist off", - description: "Permanent Flight Assist off mode. You should really do that, it’s great.") }, - { new Option("hyperspaceDethrottle", true, voiceTrigger: "hyper space dethrottle", - description: "Throttle down after a jump and when dropping from SC. Like the SC Assist module does.") }, - { new Option("limpetCheck", true, voiceTrigger: "limpet check", - description: "Do a limpet check when undocking, reminding you if you forgot to buy some.") }, + new OptDict + { + { + new Option( + name: "announceEdsmSystemStatus", + defaultValue: true, + voiceTrigger: "edsm system status", + description: "Pull system data from EDSM and compare it against your discovery scan.") + }, + { + new Option( + name: "announceJumpsInRoute", + defaultValue: true, + voiceTrigger: "route jump count", + description: "Give a jump count on plotting a route.") + }, + { + new Option( + name: "announceMappingCandidates", + defaultValue: true, + voiceTrigger: "mapping candidates", + description: "Announce bodies worth mapping when you have finished scanning a system.\n(Terraformables, Water Worlds, Earth-Like Worlds and Ammonia Worlds that have not been mapped yet.)") + }, + { + new Option( + name: "announceOutdatedStationData", + defaultValue: true, + voiceTrigger: "outdated stations", + description: "Announce stations with outdated data in the online databases.") + }, + { + new Option( + name: "outdatedStationThreshold", + defaultValue: 365, + voiceTrigger: "outdated station threshold", + description: "The threshold for station data to count as “outdated”, in days.\nDefault: 365.") + }, + { + new Option( + name: "announceR2RMappingCandidates", + defaultValue: false, + voiceTrigger: "road to riches", + description: "Announce bodies worth scanning if you are looking for some starting cash on the Road to Riches.") + }, + { + new Option( + name: "announceRepairs", + defaultValue: true, + voiceTrigger: "repair reports", + description: "Report on AFMU repairs.") + }, + { + new Option( + name: "announceSynthesis", + defaultValue: true, + voiceTrigger: "synthesis reports", + description: "Report on synthesis.") + }, + { + new Option( + name: "autoHonkNewSystems", + defaultValue: true, + voiceTrigger: "auto honk new systems", + description: "Automatically honk upon entering a system if it is your first visit.") + }, + { + new Option( + name: "autoHonkAllSystems", + defaultValue: false, + voiceTrigger: "auto honk all systems", + description: "Automatically honk upon entering a system, each jump, without constraints.") + }, + { + new Option( + name: "scannerFireGroup", + defaultValue: 0, + voiceTrigger: "scanner fire group", + description: "The fire group your discovery scanner is assigned to.\nDefault: 0 (the first one).") + }, + { + new Option( + name: "usePrimaryFireForDiscoveryScan", + defaultValue: false, + voiceTrigger: "discovery scan on primary fire", + description: "Use primary fire for honking instead of secondary.") + }, + { + new Option( + name: "autoRefuel", + defaultValue: true, + voiceTrigger: "auto refuel", + description: "Automatically refuel after docking at a station.") + }, + { + new Option( + name: "autoRepair", + defaultValue: true, + voiceTrigger: "auto repair", + description: "Automatically repair after docking at a station.") + }, + { + new Option( + name: "autoRestock", + defaultValue: true, + voiceTrigger: "auto restock", + description: "Automatically restock after docking at a station.") + }, + { + new Option( + name: "autoHangar", + defaultValue: true, + voiceTrigger: "auto move to hangar", + description: "Automatically move the ship to the hangar after docking at a station.") + }, + { + new Option( + name: "autoStationServices", + defaultValue: true, + voiceTrigger: "auto enter station services", + description: "Automatically enter the Station Services menu after docking at a station.") + }, + { + new Option( + name: "autoRetractLandingGear", + defaultValue: true, + voiceTrigger: "auto retract landing gear", + description: "Automatically retract landing gear when lifting off a planet / undocking from a station.") + }, + { + new Option( + name: "autoDisableSrvLights", + defaultValue: true, + voiceTrigger: "auto disable s r v lights", + description: "Automatically turn SRV lights off when deploying one.") + }, + { + new Option( + name: "flightAssistOff", + defaultValue: false, + voiceTrigger: "flight assist off", + description: "Permanent Flight Assist off mode. You should really do that, it’s great.") + }, + { + new Option( + name: "hyperspaceDethrottle", + defaultValue: true, + voiceTrigger: "hyper space dethrottle", + description: "Throttle down after a jump and when dropping from SC. Like the SC Assist module does.") + }, + { + new Option( + name: "limpetCheck", + defaultValue: true, + voiceTrigger: "limpet check", + description: "Do a limpet check when undocking, reminding you if you forgot to buy some.") + }, } }, { "RatAttack", - new OptDict{ - { new Option("autoCloseCase", false, voiceTrigger: "auto close fuel rat case", - description: "Automatically close a rat case when sending “fuel+” via voice command or ingame chat.") }, - { new Option("autoCopySystem", true, voiceTrigger: "auto copy rat case system", - description: "Automatically copy the client’s system to the clipboard when you open a rat case.") }, - { new Option("announceNearestCMDR", false, voiceTrigger: "nearest commander to fuel rat case", - description: "Announce the nearest commander to incoming rat cases.") }, - { new Option("CMDRs", "", voiceTrigger: "fuel rat commanders", - description: "All your CMDRs that are ready to take rat cases.\nUse ‘;’ as separator, e.g. “Bud Spencer;Terrence Hill”.") }, - { new Option("announcePlatform", false, voiceTrigger: "platform for fuel rat case", - description: "Announce the platform for incoming rat cases.") }, - { new Option("platforms", "PC", voiceTrigger: "fuel rat platforms", validValues: new List{ "PC", "Xbox", "Playstation" }, - description: "The platform(s) you want to get case announcements for (PC, Xbox, Playstation).\nUse ‘;’ as separator, e.g. “PC;Xbox”.") }, - { new Option("announceSystemInfo", true, voiceTrigger: "system information for fuel rat case", - description: "System information provided by Mecha.")}, - { new Option("confirmCalls", true, voiceTrigger: "fuel rat call confirmation", - description: "Only make calls in #fuelrats after vocal confirmation to prevent mistakes.") }, - { new Option("onDuty", true, voiceTrigger: "fuel rat duty", - description: "On duty, receiving case announcements via TTS.") }, + new OptDict + { + { + new Option( + name: "autoCloseCase", + defaultValue: false, + voiceTrigger: "auto close fuel rat case", + description: "Automatically close a rat case when sending “fuel+” via voice command or ingame chat.") + }, + { + new Option( + "autoCopySystem", + defaultValue: true, + voiceTrigger: "auto copy rat case system", + description: "Automatically copy the client’s system to the clipboard when you open a rat case.") + }, + { + new Option( + name: "announceNearestCMDR", + defaultValue: false, + voiceTrigger: "nearest commander to fuel rat case", + description: "Announce the nearest commander to incoming rat cases.") + }, + { + new Option( + name: "CMDRs", + defaultValue: string.Empty, + voiceTrigger: "fuel rat commanders", + description: "All your CMDRs that are ready to take rat cases.\nUse ‘;’ as separator, e.g. “Bud Spencer;Terrence Hill”.") + }, + { + new Option( + name: "announcePlatform", + defaultValue: false, + voiceTrigger: "platform for fuel rat case", + description: "Announce the platform for incoming rat cases.") + }, + { + new Option( + name: "platforms", + defaultValue: "PC", + voiceTrigger: "fuel rat platforms", + validValues: new List { "PC", "Xbox", "Playstation" }, + description: "The platform(s) you want to get case announcements for (PC, Xbox, Playstation).\nUse ‘;’ as separator, e.g. “PC;Xbox”.") + }, + { + new Option( + name: "announceSystemInfo", + defaultValue: true, + voiceTrigger: "system information for fuel rat case", + description: "System information provided by Mecha.") + }, + { + new Option( + name: "confirmCalls", + defaultValue: true, + voiceTrigger: "fuel rat call confirmation", + description: "Only make calls in #fuelrats after vocal confirmation to prevent mistakes.") + }, + { + new Option( + name: "onDuty", + defaultValue: true, + voiceTrigger: "fuel rat duty", + description: "On duty, receiving case announcements via TTS.") + }, } }, { "SpanshAttack", - new OptDict{ - { new Option("announceJumpsLeft", ";1;3;5;10;15;20;30;50;75;100;", voiceTrigger: "announce jumps left", - description: "Estimated jumps left to announce when reached.\nNEEDS to have leading and trailing “;”.") }, - { new Option("announceWaypoints", true, voiceTrigger: "waypoint announcements", - description: "Announce each waypoint by name.") }, - { new Option("autoJumpAfterScooping", true, voiceTrigger: "auto jump after scooping", - description: "Automatically jump out when fuel scooping is complete.") }, - { new Option("autoPlot", true, voiceTrigger: "auto plot", - description: "Automatically plot to the next waypoint after supercharging.") }, - { new Option("clearOnShutdown", true, voiceTrigger: "clear neutron route on shutdown", - description: "Clear an active neutron route when the game is shut down.") }, - { new Option("copyWaypointToClipboard", false, voiceTrigger: "copy neutron waypoints to clipboard", - description: "Copy each neutron waypoint into the Windows clipboard.") }, - { new Option("defaultToLadenRange", false, voiceTrigger: "default to laden range", - description: "Default to the current ship’s laden range as reported by EDDI instead of prompting for input.") }, - { new Option("timeTrip", false, voiceTrigger: "time neutron route", - description: "Keep track of how long a neutron route takes you to complete.") }, + new OptDict + { + { + new Option( + name: "announceJumpsLeft", + defaultValue: ";1;3;5;10;15;20;30;50;75;100;", + voiceTrigger: "announce jumps left", + description: "Estimated jumps left to announce when reached.\nNEEDS to have leading and trailing “;”.") + }, + { + new Option( + name: "announceWaypoints", + defaultValue: true, + voiceTrigger: "waypoint announcements", + description: "Announce each waypoint by name.") + }, + { + new Option( + name: "autoJumpAfterScooping", + defaultValue: true, + voiceTrigger: "auto jump after scooping", + description: "Automatically jump out when fuel scooping is complete.") + }, + { + new Option( + name: "autoPlot", + defaultValue: true, + voiceTrigger: "auto plot", + description: "Automatically plot to the next waypoint after supercharging.") + }, + { + new Option( + name: "clearOnShutdown", + defaultValue: true, + voiceTrigger: "clear neutron route on shutdown", + description: "Clear an active neutron route when the game is shut down.") + }, + { + new Option( + name: "copyWaypointToClipboard", + defaultValue: false, + voiceTrigger: "copy neutron waypoints to clipboard", + description: "Copy each neutron waypoint into the Windows clipboard.") + }, + { + new Option( + "defaultToLadenRange", + defaultValue: false, + voiceTrigger: "default to laden range", + description: "Default to the current ship’s laden range as reported by EDDI instead of prompting for input.") + }, + { + new Option( + name: "timeTrip", + defaultValue: false, + voiceTrigger: "time neutron route", + description: "Keep track of how long a neutron route takes you to complete.") + }, } }, { "StreamAttack", - new OptDict{ - { new Option("outputDir", @"%appdata%\StreamAttack\", voiceTrigger: "StreamAttack output directory", - description: "The directory the status files are written to.") } + new OptDict + { + { + new Option( + name: "outputDir", + defaultValue: @"%appdata%\StreamAttack\", + voiceTrigger: "StreamAttack output directory", + description: "The directory the status files are written to.") + }, } - } + }, }; - public abstract class Option - { -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public string Name { get; } - public dynamic DefaultValue { get; } - public List? ValidValues { get; } - public string VoiceTrigger { get; } - public string TtsDescription { get; } - public string Description { get; } - public Type Type { get; } +#pragma warning disable SA1306 // Field names should begin with lower-case letter + private readonly dynamic VA; + private readonly string ID; +#pragma warning restore SA1306 // Field names should begin with lower-case letter + private readonly VoiceAttackLog log; + private readonly VoiceAttackCommands commands; - public string? TypeString { get; } - public override string ToString() => DefaultValue!.ToString(); -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - } - public class Option : Option - { - public new string Name { get; } - public new T DefaultValue { get; } - public new List? ValidValues { get; } - public new string VoiceTrigger { get; } - public new string TtsDescription { get => ttsDescription ?? VoiceTrigger; } - private readonly string? ttsDescription; - public new string Description { get => description ?? "No description available."; } - private readonly string? description; + /// + /// Initializes a new instance of the class. + /// + /// The VoiceAttack proxy object. + /// The VoiceAttack log. + /// The VoiceAttack commands object. + /// The profile ID. + public Configuration(dynamic vaProxy, VoiceAttackLog log, VoiceAttackCommands commands, string id) + => (this.VA, this.log, this.commands, this.ID) + = (vaProxy, log, commands, id); - public new Type Type { get => typeof(T); } - public new string? TypeString - { - get - { - string? type = null; - if (typeof(T) == typeof(bool)) - { - type = "boolean"; - } - else if (typeof(T) == typeof(DateTime)) - { - type = "date"; - } - else if (typeof(T) == typeof(decimal)) - { - type = "decimal"; - } - else if (typeof(T) == typeof(int)) - { - type = "int"; - } - else if (typeof(T) == typeof(short)) - { - type = "smallint"; - } - else if (typeof(T) == typeof(string)) - { - type = "text"; - } - return type; - } - } - - public Option(string name, T defaultValue, string voiceTrigger, List? validValues = null, string? ttsDescription = null, string? description = null) - => (Name, DefaultValue, VoiceTrigger, ValidValues, this.ttsDescription, this.description) = (name, defaultValue, voiceTrigger, validValues, ttsDescription, description); - - - public static implicit operator (string, Option)(Option o) => (o.Name, o); - public static explicit operator T(Option o) => o.DefaultValue; - } - public class OptDict : Dictionary - { - public OptDict() : base() { } - public OptDict(int capacity) : base(capacity) { } - - public void Add((TKey,TValue) tuple) - { - base.Add(tuple.Item1, tuple.Item2); - } - } - - public Configuration(dynamic vaProxy, VoiceAttackLog log, VoiceAttackCommands commands, string id) => (VA, Log, Commands, ID) = (vaProxy, log, commands, id); - - public dynamic GetDefault(string name) - { - return GetDefault(ID, name); - } + /// + /// Gets the default value of an option. + /// + /// The profile ID. + /// The name of the option. + /// The default value of the option. public static dynamic GetDefault(string id, string name) { - return Defaults[id][name]; + return ((Option)Defaults[id][name]).DefaultValue; } - public Option GetOption(string name) - { - return GetOption(ID, name); - } - public Option GetOption(string id, string name) + /// + /// Gets an Option. + /// + /// The profile ID. + /// The name of the Option. + /// The Option. + public static Option GetOption(string id, string name) { return Defaults[id][name]; } - public OptDict GetOptions(string id) + /// + /// Gets all Options for a give profile. + /// + /// The profile ID. + /// The + public static OptDict GetOptions(string id) { return Defaults[id]; } - public void SetVoiceTriggers(System.Type type) + /// + /// Checks if a given option has a default value. + /// + /// The profile ID. + /// The name of the option. + /// Wether the option has a default value. + public static bool HasDefault(string id, string name) { - List triggers = new List(); - foreach (Dictionary options in Defaults.Values) + return Defaults[id].ContainsKey(name); + } + + /// + /// Gets the default value of an option by name. + /// + /// The name of the option. + /// The value of the option. + public dynamic GetDefault(string name) + { + return GetDefault(this.ID, name); + } + + /// + /// Gets an Option by name. + /// + /// The name of the Option. + /// The Option. + public Option GetOption(string name) + { + return GetOption(this.ID, name); + } + + /// + /// Sets the voice triggers for the voice commands that set options from + /// VoiceAttack. + /// + /// The data type to set voice triggers for. + /// Thrown when duplicate triggers are found. + public void SetVoiceTriggers(Type type) + { + List triggers = new (); + foreach (Dictionary options in Defaults.Values) { foreach (dynamic option in options.Values) { @@ -272,30 +541,39 @@ namespace alterNERDtive.util { throw new ArgumentException($"Voice trigger '{option.VoiceTrigger}' is not unique, aborting …"); } + triggers.Add(option.VoiceTrigger); } } } + if (triggers.Count > 0) { string triggerString = string.Join(";", triggers); - VA.SetText($"alterNERDtive-base.triggers.{type.Name}", triggerString); - Log.Debug($"Voice triggers for {type.Name}: '{triggerString}'"); + this.VA.SetText($"alterNERDtive-base.triggers.{type.Name}", triggerString); + this.log.Debug($"Voice triggers for {type.Name}: '{triggerString}'"); } else { // make sure we don’t accidentally have weird things happening with empty config voice triggers string triggerString = $"tenuiadafesslejüsljlejutlesuivle{type.Name}"; - VA.SetText($"alterNERDtive-base.triggers.{type.Name}", triggerString); - Log.Debug($"No voice triggers found for {type.Name}"); + this.VA.SetText($"alterNERDtive-base.triggers.{type.Name}", triggerString); + this.log.Debug($"No voice triggers found for {type.Name}"); } } + /// + /// Sets VoiceAttack variables required for handling the reporting and + /// setting of a configuration option. + /// + /// The command’s VoiceAttack proxy object. + /// The voice trigger for the option in question. + /// Thrown when the voice trigger is missing/null. public void SetVariablesForTrigger(dynamic vaProxy, string trigger) { _ = trigger ?? throw new ArgumentNullException("trigger"); - foreach (KeyValuePair> options in Defaults) + foreach (KeyValuePair> options in Defaults) { try { @@ -305,34 +583,49 @@ namespace alterNERDtive.util vaProxy.SetText("~description", option.Description); break; } - catch (InvalidOperationException) { } + catch (InvalidOperationException) + { + // trigger doesn’t exist in this profile, skip + } } } + /// + /// Checks if a given option has a default value. + /// + /// The name of the option. + /// Whether the option has a default value. public bool HasDefault(string name) { - return HasDefault(ID, name); - } - public static bool HasDefault(string id, string name) - { - return Defaults[id].ContainsKey(name); + return HasDefault(this.ID, name); } + /// + /// Loads stored value of all options from the currently active + /// VoiceAttack profile. + /// public void LoadFromProfile() { foreach (KeyValuePair> options in Defaults) { - LoadFromProfile(options.Key); + this.LoadFromProfile(options.Key); } } + + /// + /// Loads stored options for a given profile ID from the currently + /// active VoiceAttack profile. + /// + /// The profile ID. + /// Thrown when encountering an incompatible data type. public void LoadFromProfile(string id) { foreach (dynamic option in Defaults[id].Values) { string name = $"{id}.{option.Name}"; string type = option.TypeString ?? throw new InvalidDataException($"Invalid data type for option '{name}': '{option}'"); - Log.Debug($"Loading value for option '{name}' from profile …"); - Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}#", type } }); + this.log.Debug($"Loading value for option '{name}' from profile …"); + this.commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}#", type } }); } } @@ -340,17 +633,18 @@ namespace alterNERDtive.util { return ApplyDefault(ID, name); } + public dynamic? ApplyDefault(string id, string name) { if (!HasDefault(id, name)) { - Log.Warn($"No default configuration value found for '{id}.{name}'"); + log.Warn($"No default configuration value found for '{id}.{name}'"); return null; } dynamic option = Defaults[id][name]; dynamic value = option.DefaultValue; - Log.Debug($"Loading default configuration value, '{id}.{name}': '{value}' …"); + log.Debug($"Loading default configuration value, '{id}.{name}': '{value}' …"); string variable = $"{id}.{name}#"; if (value is bool) { @@ -410,7 +704,7 @@ namespace alterNERDtive.util } public void DumpConfig(string id) { - Log.Notice($"===== {id} configuration: ====="); + log.Notice($"===== {id} configuration: ====="); foreach (string name in Defaults[id].Keys) { DumpConfig(id, name); @@ -420,7 +714,7 @@ namespace alterNERDtive.util { dynamic defaultValue = ((dynamic)Defaults[id][name]).DefaultValue; dynamic value = GetConfig(id, name); - Log.Notice($"{id}.{name}# = {value}{(value == defaultValue ? " (default)" : "")}"); + log.Notice($"{id}.{name}# = {value}{(value == defaultValue ? " (default)" : "")}"); } public dynamic GetConfig(string id, string name) { @@ -462,27 +756,27 @@ namespace alterNERDtive.util string variable = $"{id}.{name}#"; if (value is bool) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new bool[] { value } }); ; + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new bool[] { value } }); ; } else if (value is DateTime) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new DateTime[] { value } }); + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new DateTime[] { value } }); } else if (value is decimal) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new decimal[] { value } }); + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new decimal[] { value } }); } else if (value is int) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new int[] { value } }); + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new int[] { value } }); } else if (value is short) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new short[] { value } }); + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}" }, new short[] { value } }); } else if (value is string) { - Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}", value } }); + commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{variable}", value } }); } else { @@ -499,7 +793,7 @@ namespace alterNERDtive.util } public void ListConfig(string id) { - Log.Notice($"===== {id} settings: ====="); + log.Notice($"===== {id} settings: ====="); foreach (string name in Defaults[id].Keys) { ListConfig(id, name); @@ -508,33 +802,178 @@ namespace alterNERDtive.util public void ListConfig(string id, string name) { dynamic option = Defaults[id][name]; - Log.Notice($"“{option.VoiceTrigger}”: {option.Description}"); + log.Notice($"“{option.VoiceTrigger}”: {option.Description}"); } - } - public class PythonProxy - { - public static Process SetupPythonScript(string path, string arguments) + /// + /// A plugin/profile option. Abstract base class for typed Options. + /// + public abstract class Option { - Process p = new Process(); - p.StartInfo.FileName = path; - p.StartInfo.Arguments = arguments; - p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.RedirectStandardError = true; - p.StartInfo.UseShellExecute = false; - p.StartInfo.CreateNoWindow = true; - return p; - } - } + /// + /// Gets the name of the Option. + /// + public abstract string Name { get; } - public enum LogLevel - { - ERROR, - WARN, - NOTICE, - INFO, - DEBUG + /// + /// Gets the voice trigger phrase for the Option. + /// + public abstract string VoiceTrigger { get; } + + /// + /// Gets the (optional) text to speech description for the Option. + /// Usually the voice trigger is used in confirmation text to + /// speech, this can be used to override it. + /// + public abstract string TtsDescription { get; } + + /// + /// Gets the description of the Option. + /// + public abstract string Description { get; } + + /// + /// Gets the type string for the option as used by VoiceAttack. + /// + public abstract string? TypeString { get; } + } + + /// + /// A typed plugin/profile option. + /// + /// The data type of the option. + public class Option : Option + { + private readonly string? ttsDescription; + private readonly string? description; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the option. + /// The default value for the option. + /// The voice trigger for the option. + /// The (optional) list of valid values for the otpion. + /// The (optional) TTS description of the option. + /// The descrption of the option. + public Option(string name, T defaultValue, string voiceTrigger, List? validValues = null, string? ttsDescription = null, string? description = null) + => (this.Name, this.DefaultValue, this.VoiceTrigger, this.ValidValues, this.ttsDescription, this.description) + = (name, defaultValue, voiceTrigger, validValues, ttsDescription, description); + + /// + public override string Name { get; } + + /// + /// Gets the default value for the Option. + /// + public T DefaultValue { get; } + + /// + /// Gets the (optional) list of valid values for the Option. + /// + public List? ValidValues { get; } + + /// + public override string VoiceTrigger { get; } + + /// + public override string TtsDescription { get => this.ttsDescription ?? this.VoiceTrigger; } + + /// + public override string Description { get => this.description ?? "No description available."; } + + /// + /// Gets the data type of the Option. + /// + public Type Type { get => typeof(T); } + + /// + public override string? TypeString + { + get + { + string? type = null; + if (typeof(T) == typeof(bool)) + { + type = "boolean"; + } + else if (typeof(T) == typeof(DateTime)) + { + type = "date"; + } + else if (typeof(T) == typeof(decimal)) + { + type = "decimal"; + } + else if (typeof(T) == typeof(int)) + { + type = "int"; + } + else if (typeof(T) == typeof(short)) + { + type = "smallint"; + } + else if (typeof(T) == typeof(string)) + { + type = "text"; + } + return type; + } + } + + /// + /// Converts an to a of and . + /// + /// The Option to convert. + public static implicit operator (string, Option)(Option o) => (o.Name, o); + + /// + /// Converts an to the contained default value. + /// + /// The option to convert. + public static explicit operator T(Option o) => o.DefaultValue; + + /// + public override string ToString() => this.DefaultValue!.ToString(); + } + + /// + /// A Dictionary containing s. Used in + /// conjunction with ’s implicit conversion to a + /// tuple to make adding Options less painful. + /// + /// The key type. + /// The value type. + public class OptDict : Dictionary + { + /// + /// Initializes a new instance of the class. + /// + public OptDict() + : base() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial capacity. + public OptDict(int capacity) + : base(capacity) + { + } + + /// + /// Adds a to the list. + /// + /// The Tuple to be added. + public void Add((TKey, TValue) tuple) + { + this.Add(tuple.Item1, tuple.Item2); + } + } } public class VoiceAttackCommands @@ -710,46 +1149,59 @@ namespace alterNERDtive.util public void Debug(string message) => Log(message, LogLevel.DEBUG); } - public interface IPipable + public class PythonProxy { - public void ParseString(string serialization); - public string ToString(); + public static Process SetupPythonScript(string path, string arguments) + { + Process p = new Process(); + p.StartInfo.FileName = path; + p.StartInfo.Arguments = arguments; + p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + return p; + } } - public class PipeServer where Thing : IPipable, new() + public class PipeServer + where Thing : IPipable, new() { - private readonly string PipeName; - private readonly SignalHandler Handler; - private readonly VoiceAttackLog Log; + private readonly string pipeName; + private readonly SignalHandler handler; + private readonly VoiceAttackLog log; - private bool Running = false; + private bool running = false; - private NamedPipeServerStream? Server; + private NamedPipeServerStream? server; public PipeServer(VoiceAttackLog log, string name, SignalHandler handler) - => (Log, PipeName, Handler) = (log, name, handler); + => (this.log, this.pipeName, this.handler) = (log, name, handler); public delegate void SignalHandler(Thing thing); public PipeServer Run() { - Log.Debug($"Starting '{PipeName}' pipe …"); - if (!Running) + this.log.Debug($"Starting '{this.pipeName}' pipe …"); + if (!this.running) { - Running = true; - WaitForConnection(); + this.running = true; + this.WaitForConnection(); } + return this; } public PipeServer Stop() { - Log.Debug($"Stopping '{PipeName}' pipe …"); - if (Running) + this.log.Debug($"Stopping '{this.pipeName}' pipe …"); + if (this.running) { - Running = false; - Server!.Close(); + this.running = false; + this.server!.Close(); } + return this; } @@ -757,12 +1209,17 @@ namespace alterNERDtive.util { try { - Server = new NamedPipeServerStream(PipeName, PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Message, PipeOptions.Asynchronous); - Server.BeginWaitForConnection(OnConnect, Server); + this.server = new NamedPipeServerStream( + this.pipeName, + PipeDirection.In, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Message, + PipeOptions.Asynchronous); + this.server.BeginWaitForConnection(this.OnConnect, this.server); } catch (Exception e) { - Log.Error($"Error setting up pipe: {e.Message}"); + this.log.Error($"Error setting up pipe: {e.Message}"); } } @@ -772,19 +1229,19 @@ namespace alterNERDtive.util try { server.EndWaitForConnection(ar); - WaitForConnection(); - using StreamReader reader = new StreamReader(server); - Thing thing = new Thing(); + this.WaitForConnection(); + using StreamReader reader = new (server); + Thing thing = new (); thing.ParseString(reader.ReadToEnd()); - Handler(thing); + this.handler(thing); } catch (ObjectDisposedException) { - Log.Debug($"'{PipeName}' pipe has been closed."); + this.log.Debug($"'{this.pipeName}' pipe has been closed."); } catch (Exception e) { - Log.Error($"Error reading pipe: {e.Message}"); + this.log.Error($"Error reading pipe: {e.Message}"); } } }