Merge branch 'devel' into release

This commit is contained in:
alterNERDtive 2022-05-31 19:31:28 +02:00
commit 9beb6dfa41
32 changed files with 2140 additions and 1559 deletions

View file

@ -1,4 +1,4 @@
[*] [*]
guidelines = 80 guidelines = 80
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
@ -6,3 +6,9 @@ charset = utf-8-bom
[*.cs] [*.cs]
guidelines = 80, 120 guidelines = 80, 120
# IDE0021: Use block body for constructors
csharp_style_expression_bodied_constructors = when_on_single_line
# IDE0024: Use block body for operators
csharp_style_expression_bodied_operators = when_on_single_line

19
.github/workflows/create-release.yaml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Create release on tag push
on:
push:
tags:
- 'releases/*.*'
- 'releases/*.*.*'
jobs:
release:
name: Create draft release
runs-on: ubuntu-latest
steps:
- name: Draft release
uses: ncipollo/release-action@v1
with:
bodyFile: "CHANGELOG.md"
draft: true

1
.gitignore vendored
View file

@ -358,3 +358,4 @@ MigrationBackup/
/Makefile /Makefile
/site /site
/src /src
/build.csproj

View file

@ -1,4 +1,42 @@
# 4.3 (2022-05-19) # 4.4 (2022-05-31)
### Added
* The Configuration GUI now has an “Apply” button.
### Fixed
* Configuration GUI now `.Activate()`s immediately to prevent it from hiding
behind other windows.
## EliteAttack 8.4
### Added
* `auto retract landing gear` setting: Automatically retract landing gear when
lifting off a planet / undocking from a station. Default: true. (#133)
* `auto disable s r v lights` setting: Automatically turn SRV lights off when
deploying one. Default: true. (#133)
### Fixed
* `auto enter station services` option. (#142)
## RatAttack 6.3.1
* Added error message to the CLI tool for running VoiceAttack with elevated
privileges which will cause an `UnAuthorizedAccessException` trying to
communicate with the plugin. (#138)
* Added warning to VoiceAttack when running it with elevated privileges. (#138)
## SpashAttack 7.2.2
* Fixed getting current jump range from EDDI; no longer fails on the first try,
no longer sometimes reports the last requested range instead of current.
-----
# 4.3 (2022-05-19)
**NOTE**: Further development is on hold and Odyssey compatibility will not be **NOTE**: Further development is on hold and Odyssey compatibility will not be
worked on for the time being. See [the corresponding issue on worked on for the time being. See [the corresponding issue on
@ -52,7 +90,7 @@ the job.
### Added ### Added
* Now gives feedback after asking for call confirmation: “Call aborted.” * Now gives feedback after asking for call confirmation: “Call aborted.” /
“Calling <…>.”. “Calling <…>.”.
* `auto copy rat case system` setting: Automatically copy the clients system to * `auto copy rat case system` setting: Automatically copy the clients system to
the clipboard when you open a rat case. Default: true. the clipboard when you open a rat case. Default: true.

25
Directory.build.props Normal file
View file

@ -0,0 +1,25 @@
<Project>
<PropertyGroup>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- StyleCop Analyzers configuration -->
<PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)StyleCop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers.Error" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.CSharp.Async.Rules" Version="6.1.41">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="$(SolutionDir)stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

View file

@ -1 +1 @@
4.3 4.4

View file

@ -18,6 +18,10 @@ Toggles:
at a station. Default: true. at a station. Default: true.
* `auto enter station services`: Automatically enter the Station Services menu * `auto enter station services`: Automatically enter the Station Services menu
after docking at a station. Default: true. after docking at a station. Default: true.
* `auto retract landing gear`: Automatically retract landing gear when lifting
off a planet / undocking from a station. Default: true. (#133)
* `auto disable s r v lights`: Automatically turn SRV lights off when deploying
one. Default: true. (#133)
* `edsm system status`: Pull system data from EDSM and compare it * `edsm system status`: Pull system data from EDSM and compare it
against your discovery scan. Default: true. against your discovery scan. Default: true.
* `discovery scan on primary fire`: Use primary fire for honking instead of * `discovery scan on primary fire`: Use primary fire for honking instead of

View file

@ -2,7 +2,7 @@
## Configuration ## Configuration
The base profile provides voice commands for changing the profiles’ settings. The base profile provides voice commands for changing the profiles settings.
See [the configuration section](../configuration/general#settings). See [the configuration section](../configuration/general#settings).
## Chat ## Chat

View file

@ -1,4 +1,4 @@
# Upgrading # Upgrading
To upgrade to the latest version, follow these simple steps: To upgrade to the latest version, follow these simple steps:
@ -29,6 +29,13 @@ Please do not fiddle with the configuration variables from your startup command
entirely unnecessary since configuration will be saved to and loaded from the entirely unnecessary since configuration will be saved to and loaded from the
profile anyway. profile anyway.
### EDDI Events
The process for adding your own handlers for EDDI events has changed. You no
longer have to check which of my profiles handle them and add the commands for
those manually; instead you need to [run the `eddi.event` context of the
`alterNERDtive-base` plugin](../configuration/general#eddi-events).
### bindED ### bindED
If you have done anything non-standard with bindED before, it might break. The If you have done anything non-standard with bindED before, it might break. The

View file

@ -4,7 +4,7 @@ repo_url: https://github.com/alterNERDtive/VoiceAttack-profiles
edit_uri: "edit/devel/docs/" edit_uri: "edit/devel/docs/"
site_description: "alterNERDtive VoiceAttack profiles for Elite: Dangerous" site_description: "alterNERDtive VoiceAttack profiles for Elite: Dangerous"
site_author: "alterNERDtive" site_author: "alterNERDtive"
remote_name: "ssh-origin" remote_name: "origin"
theme: theme:
name: readthedocs name: readthedocs

View file

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.30413.136 VisualStudioVersion = 17.3.32519.111
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceAttack-base", "plugins\VoiceAttack-base\VoiceAttack-base.csproj", "{1C05DB3F-3449-4664-B363-A379892995E5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceAttack-base", "plugins\VoiceAttack-base\VoiceAttack-base.csproj", "{1C05DB3F-3449-4664-B363-A379892995E5}"
EndProject EndProject
@ -16,8 +16,12 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2B4D94B-8D73-431A-880B-B1E7ADF064B2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2B4D94B-8D73-431A-880B-B1E7ADF064B2}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
CHANGELOG.md = CHANGELOG.md
.github\workflows\create-release.yaml = .github\workflows\create-release.yaml
Directory.build.props = Directory.build.props
mkdocs.yml = mkdocs.yml mkdocs.yml = mkdocs.yml
README.md = README.md README.md = README.md
stylecop.json = stylecop.json
VERSION = VERSION VERSION = VERSION
EndProjectSection EndProjectSection
EndProject EndProject

View file

@ -1,113 +1,175 @@
#nullable enable // <copyright file="EliteAttack.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
using alterNERDtive.util; //
using System; // This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
using System.Collections.Generic; //
using System.Linq; // alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
using System.Text; // it under the terms of the GNU General Public License as published by
using System.Threading.Tasks; // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
namespace EliteAttack //
{ // alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
public class EliteAttack { // but WITHOUT ANY WARRANTY; without even the implied warranty of
private static dynamic? VA { get; set; } // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
private static VoiceAttackLog Log //
=> log ??= new VoiceAttackLog(VA, "EliteAttack"); // You should have received a copy of the GNU General Public License
private static VoiceAttackLog? log; // along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
private static VoiceAttackCommands Commands
=> commands ??= new VoiceAttackCommands(VA, Log); #nullable enable
private static VoiceAttackCommands? commands;
using System;
/*================\
| plugin contexts | using alterNERDtive.util;
\================*/
namespace EliteAttack
private static void Context_Log(dynamic vaProxy) {
{ /// <summary>
string message = vaProxy.GetText("~message"); /// VoiceAttack plugin for the EliteAttack profile.
string level = vaProxy.GetText("~level"); /// </summary>
public class EliteAttack
if (level == null) {
{ private static readonly Version VERSION = new ("8.4");
Log.Log(message);
} private static VoiceAttackLog? log;
else private static VoiceAttackCommands? commands;
{
try private static dynamic? VA { get; set; }
{
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper())); private static VoiceAttackLog Log => log ??= new (VA, "EliteAttack");
}
catch (ArgumentNullException) { throw; } private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
catch (ArgumentException)
{ /*========================================\
Log.Error($"Invalid log level '{level}'."); | required VoiceAttack plugin shenanigans |
} \========================================*/
}
} /// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
private static void Context_Startup(dynamic vaProxy) /// </summary>
{ /// <returns>The GUID.</returns>
Log.Notice("Starting up …"); public static Guid VA_Id()
VA = vaProxy; => new ("{5B46321D-2935-4550-BEEA-36C2145547B8}");
Log.Notice("Finished startup.");
} /// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/*========================================\ /// </summary>
| required VoiceAttack plugin shenanigans | /// <returns>The display name.</returns>
\========================================*/ public static string VA_DisplayName()
=> $"EliteAttack {VERSION}";
static readonly Version VERSION = new Version("8.3");
/// <summary>
public static Guid VA_Id() /// The plugins description, as required by the VoiceAttack plugin API.
=> new Guid("{5B46321D-2935-4550-BEEA-36C2145547B8}"); /// </summary>
public static string VA_DisplayName() /// <returns>The description.</returns>
=> $"EliteAttack {VERSION}"; public static string VA_DisplayInfo()
public static string VA_DisplayInfo() => "EliteAttack: a plugin for doing Elite-y things.";
=> "EliteAttack: a plugin for doing Elite-y things.";
/// <summary>
public static void VA_Init1(dynamic vaProxy) /// The Init method, as required by the VoiceAttack plugin API.
{ /// Runs when the plugin is initially loaded.
VA = vaProxy; /// </summary>
Log.Notice("Initializing …"); /// <param name="vaProxy">The VoiceAttack proxy object.</param>
VA.SetText("EliteAttack.version", VERSION.ToString()); public static void VA_Init1(dynamic vaProxy)
Log.Notice("Init successful."); {
} VA = vaProxy;
Log.Notice("Initializing …");
public static void VA_Invoke1(dynamic vaProxy) VA.SetText("EliteAttack.version", VERSION.ToString());
{ Log.Notice("Init successful.");
string context = vaProxy.Context.ToLower(); }
Log.Debug($"Running context '{context}' …");
try /// <summary>
{ /// The Invoke method, as required by the VoiceAttack plugin API.
switch (context) /// Runs whenever a plugin context is invoked.
{ /// </summary>
case "startup": /// <param name="vaProxy">The VoiceAttack proxy object.</param>
Context_Startup(vaProxy); public static void VA_Invoke1(dynamic vaProxy)
break; {
// log VA = vaProxy;
case "log.log":
Context_Log(vaProxy); string context = vaProxy.Context.ToLower();
break; Log.Debug($"Running context '{context}' …");
// invalid try
default: {
Log.Error($"Invalid plugin context '{vaProxy.Context}'."); switch (context)
break; {
} case "startup":
} Context_Startup();
catch (ArgumentNullException e) break;
{ case "log.log":
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'"); // log
} Context_Log();
catch (Exception e) break;
{ default:
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})"); // invalid
} Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
} break;
}
public static void VA_Exit1(dynamic vaProxy) { } }
catch (ArgumentNullException e)
public static void VA_StopCommand() { } {
} Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
} }
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
public static void VA_Exit1(dynamic vaProxy)
{
}
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
public static void VA_StopCommand()
{
}
/*================\
| plugin contexts |
\================*/
private static void Context_Log()
{
string message = VA!.GetText("~message");
string level = VA!.GetText("~level");
if (level == null)
{
Log.Log(message);
}
else
{
try
{
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
}
catch (ArgumentNullException)
{
throw;
}
catch (ArgumentException)
{
Log.Error($"Invalid log level '{level}'.");
}
}
}
private static void Context_Startup()
{
Log.Notice("Starting up …");
Log.Notice("Finished startup.");
}
}
}

View file

@ -21,7 +21,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -30,7 +29,6 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -53,4 +51,4 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -1,39 +0,0 @@
#nullable enable
using System;
using System.IO;
using System.IO.Pipes;
using System.Text.RegularExpressions;
namespace RatAttack
{
class RatAttack_cli
{
static string stripIrcCodes(string message)
{
return Regex.Replace(message, @"[\x02\x11\x0F\x1D\x1E\x1F\x16]|\x03(\d\d?(,\d\d?)?)?", String.Empty);
}
static void Main(string[] args)
{
RatAttack.Ratsignal ratsignal = new RatAttack.Ratsignal(stripIrcCodes(args[0]), args.Length > 1 && args[1].ToLower() == "true");
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "RatAttack", PipeDirection.Out))
{
try
{
// try connecting for up to 2minutes; then well assume VoiceAttack just isnt up and wont come up
pipeClient.Connect(120000);
using (StreamWriter writer = new StreamWriter(pipeClient))
{
writer.WriteLine(ratsignal);
}
}
catch (TimeoutException)
{
Console.Error.WriteLine("Connection to RatAttack pipe has timed out.");
}
}
}
}
}

View file

@ -25,7 +25,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -35,7 +34,6 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -50,8 +48,8 @@
<Reference Include="System.Xml.Serialization" /> <Reference Include="System.Xml.Serialization" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="RatAttack-cli.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RatAttack_cli.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RatAttack\RatAttack.csproj"> <ProjectReference Include="..\RatAttack\RatAttack.csproj">

View file

@ -0,0 +1,68 @@
// <copyright file="RatAttack_cli.cs" company="alterNERDtive">
// Copyright 20192022 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 &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.IO;
using System.IO.Pipes;
using System.Text.RegularExpressions;
namespace RatAttack
{
/// <summary>
/// CLI helper tool for the RatAttack VoiceAttack plugin. Accepts RATSIGNALs
/// e.g. from an IRC client and passes them to the plugin via named pipe.
/// </summary>
public class RatAttack_cli
{
/// <summary>
/// Main entry point.
/// </summary>
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args)
{
RatAttack.Ratsignal ratsignal = new (StripIrcCodes(args[0]), args.Length > 1 && args[1].ToLower() == "true");
using (NamedPipeClientStream pipeClient = new (".", "RatAttack", PipeDirection.Out))
{
try
{
// try connecting for up to 2minutes; then well assume VoiceAttack just isnt up and wont come up
pipeClient.Connect(120000);
using StreamWriter writer = new (pipeClient);
writer.WriteLine(ratsignal);
}
catch (TimeoutException)
{
Console.Error.WriteLine("Connection to RatAttack pipe has timed out.");
}
catch (UnauthorizedAccessException)
{
Console.Error.WriteLine("Cannot connect to RatAttack pipe. Are you running VoiceAttack as Admin?");
}
}
}
private static string StripIrcCodes(string message)
{
return Regex.Replace(message, @"[\x02\x11\x0F\x1D\x1E\x1F\x16]|\x03(\d\d?(,\d\d?)?)?", string.Empty);
}
}
}

View file

@ -1,332 +1,445 @@
#nullable enable // <copyright file="RatAttack.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
using System; //
using System.Collections.Generic; // This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
using System.Diagnostics; //
using System.Text.RegularExpressions; // alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
using alterNERDtive.util; // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
namespace RatAttack // (at your option) any later version.
{ //
public class RatAttack // 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
private static Dictionary<int,RatCase> CaseList { get; } = new Dictionary<int, RatCase>(); // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
private static dynamic? VA { get; set; } // GNU General Public License for more details.
private static alterNERDtive.util.PipeServer<Ratsignal> RatsignalPipe //
=> ratsignalPipe ??= new alterNERDtive.util.PipeServer<Ratsignal>(Log, "RatAttack", // You should have received a copy of the GNU General Public License
new alterNERDtive.util.PipeServer<Ratsignal>.SignalHandler(On_Ratsignal)); // along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
private static alterNERDtive.util.PipeServer<Ratsignal>? ratsignalPipe; // </copyright>
private static readonly Regex RatsignalRegex = new Regex( #nullable enable
@"^RATSIGNAL Case #(?<number>\d+) (?<platform>(PC|Xbox|Playstation))(?<oxygen> \(Code Red\))?(?<odyssey> \(Odyssey\))? CMDR (?<cmdr>.+) System: (None|u\u200bnknown system|""(?<system>.+)"" \((?<systemInfo>([a-zA-Z0-9\s\(\)\-/]*(~?[0-9,\.]+ LY (""[a-zA-Z\-]+"" of|from) [a-zA-Z0-9\s\*\-]+)?( \([a-zA-Z\s]+\))?|Not found in galaxy database|Invalid system name))\)(?<permit> \(((?<permitName>.*) )?Permit Required\))?) Language: (?<language>[a-zA-z0-9\x7f-\xff\-\(\)&,\s\.]+)( Nick: (?<nick>[a-zA-Z0-9_\[\]\-]+))? \((PC|XB|PS)_SIGNAL\)\v*$"
); using System;
using System.Collections.Generic;
private static VoiceAttackLog Log using System.Diagnostics;
=> log ??= new VoiceAttackLog(VA, "RatAttack"); using System.Text.RegularExpressions;
private static VoiceAttackLog? log;
using alterNERDtive.util;
private static VoiceAttackCommands Commands
=> commands ??= new VoiceAttackCommands(VA, Log); namespace RatAttack
private static VoiceAttackCommands? commands; {
/// <summary>
private class RatCase /// VoiceAttack plugin for the RatAttack profile.
{ /// </summary>
public string Cmdr; public class RatAttack
public string? Language; {
public string? System; private static readonly Version VERSION = new ("6.3.1");
public string? SystemInfo;
public bool PermitLocked; private static readonly Regex RatsignalRegex = new (
public string? PermitName; @"^RATSIGNAL Case #(?<number>\d+) (?<platform>(PC|Xbox|Playstation))(?<oxygen> \(Code Red\))?(?<odyssey> \(Odyssey\))? CMDR (?<cmdr>.+) System: (None|u\u200bnknown system|""(?<system>.+)"" \((?<systemInfo>([a-zA-Z0-9\s\(\)\-/]*(~?[0-9,\.]+ LY (""[a-zA-Z\-]+"" of|from) [a-zA-Z0-9\s\*\-]+)?( \([a-zA-Z\s]+\))?|Not found in galaxy database|Invalid system name))\)(?<permit> \(((?<permitName>.*) )?Permit Required\))?) Language: (?<language>[a-zA-z0-9\x7f-\xff\-\(\)&,\s\.]+)( Nick: (?<nick>[a-zA-Z0-9_\[\]\-]+))? \((PC|XB|PS)_SIGNAL\)\v*$");
public string Platform;
public bool Odyssey; private static PipeServer<Ratsignal>? ratsignalPipe;
public bool CodeRed; private static VoiceAttackLog? log;
public int Number; private static VoiceAttackCommands? commands;
public RatCase(string cmdr, string? language, string? system, string? systemInfo, bool permitLocked, string? permitName, string platform, bool odyssey, bool codeRed, int number) private static Dictionary<int, RatCase> CaseList { get; } = new ();
=> (Cmdr, Language, System, SystemInfo, PermitLocked, PermitName, Platform, Odyssey, CodeRed, Number) = (cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number);
private static dynamic? VA { get; set; }
public string ShortInfo
{ private static PipeServer<Ratsignal> RatsignalPipe
get => $"#{Number}, {Platform}{(Odyssey ? " (Odyssey)" : "")}{(CodeRed ? ", code red" : "")}, {System ?? "None"}{(SystemInfo != null ? $" ({SystemInfo}{(PermitLocked ? ", permit required" : "")})" : "")}"; => ratsignalPipe ??= new (
} Log,
"RatAttack",
public override string ToString() new PipeServer<Ratsignal>.SignalHandler(On_Ratsignal));
=> ShortInfo;
} private static VoiceAttackLog Log => log ??= new (VA, "RatAttack");
public class Ratsignal : IPipable private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
{
public string Signal { get; set; } /*========================================\
public bool Announce { get; set; } | required VoiceAttack plugin shenanigans |
private readonly char separator = '\x1F'; \========================================*/
public Ratsignal() /// <summary>
=> (Signal, Announce) = ("", false); /// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
public Ratsignal(string signal, bool announce) /// <returns>The GUID.</returns>
=> (Signal, Announce) = (signal, announce); public static Guid VA_Id()
=> new ("{F2ADF0AE-4837-4E4A-9C87-8A7E2FA63E5F}");
public void ParseString(string serialization)
{ /// <summary>
try /// The plugins display name, as required by the VoiceAttack plugin API.
{ /// </summary>
string[] parts = serialization.Split(separator); /// <returns>The display name.</returns>
Signal = parts[0]; public static string VA_DisplayName()
Announce = Boolean.Parse(parts[1]); => $"RatAttack {VERSION}";
}
catch (Exception e) /// <summary>
{ /// The plugins description, as required by the VoiceAttack plugin API.
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e); /// </summary>
} /// <returns>The description.</returns>
} public static string VA_DisplayInfo()
=> "RatAttack: a plugin to handle FuelRats cases.";
public override string ToString()
=> $"{Signal}{separator}{Announce}"; /// <summary>
} /// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
private static int ParseRatsignal(string ratsignal) /// </summary>
{ /// <param name="vaProxy">The VoiceAttack proxy object.</param>
if (!RatsignalRegex.IsMatch(ratsignal)) public static void VA_Init1(dynamic vaProxy)
throw new ArgumentException($"Invalid RATSIGNAL format: '{ratsignal}'.", "ratsignal"); {
VA = vaProxy;
Match match = RatsignalRegex.Match(ratsignal); Log.Notice("Initializing …");
VA.SetText("RatAttack.version", VERSION.ToString());
string cmdr = match.Groups["cmdr"].Value; vaProxy.ProfileChanged += new Action<Guid?, Guid?, string, string>(On_ProfileChanged);
string? language = match.Groups["language"].Value; Log.Notice("Init successful.");
string? system = match.Groups["system"].Value; }
string? systemInfo = match.Groups["systemInfo"].Value;
bool permitLocked = match.Groups["permit"].Success; /// <summary>
string? permitName = match.Groups["permitName"].Value; /// The Invoke method, as required by the VoiceAttack plugin API.
string platform = match.Groups["platform"].Value; /// Runs whenever a plugin context is invoked.
bool codeRed = match.Groups["oxygen"].Success; /// </summary>
bool odyssey = match.Groups["odyssey"].Success; /// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
int number = int.Parse(match.Groups["number"].Value); {
VA = vaProxy;
if (String.IsNullOrEmpty(system))
{ string context = vaProxy.Context.ToLower();
system = "None"; Log.Debug($"Running context '{context}' …");
} try
{
Log.Debug($"New rat case: CMDR “{cmdr}” in “{system}”{(systemInfo != null ? $" ({systemInfo})" : "")} on {platform}{(odyssey ? " (Odyssey)" : "")}, permit locked: {permitLocked}{(permitLocked && permitName != null ? $" (permit name: {permitName})" : "")}, code red: {codeRed} (#{number})."); switch (context)
{
CaseList[number] = new RatCase(cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number); case "getcasedata":
// plugin methods
return number; Context_GetCaseData();
} break;
case "parseratsignal":
private static void On_Ratsignal(Ratsignal ratsignal) Context_ParseRatsignal();
{ break;
try case "startup":
{ Context_Startup();
int number = ParseRatsignal(ratsignal.Signal); break;
Log.Notice($"New rat case: {CaseList[number]}."); case "edsm.getnearestcmdr":
Commands.TriggerEvent("RatAttack.incomingCase", parameters: new dynamic[] { new int[] { number }, new bool[] { ratsignal.Announce } }); // EDSM
} Context_EDSM_GetNearestCMDR();
catch (ArgumentException e) break;
{ case "log.log":
Log.Error(e.Message); // log
Commands.TriggerEvent("RatAttack.invalidRatsignal", parameters: new dynamic[] { new string[] { ratsignal.Signal } }); Context_Log();
} break;
catch (Exception e) default:
{ // invalid
Log.Error($"Unhandled exception while parsing RATSIGNAL: '{e.Message}'."); Log.Error($"Invalid plugin context '{VA!.Context}'.");
} break;
} }
}
private static void On_ProfileChanged(Guid? from, Guid? to, string fromName, string toName) catch (ArgumentNullException e)
=> VA_Exit1(VA); {
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
/*================\ }
| plugin contexts | catch (Exception e)
\================*/ {
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
private static void Context_EDSM_GetNearestCMDR(dynamic vaProxy) }
{ }
int caseNo = vaProxy.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
string cmdrList = vaProxy.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs"); /// <summary>
string[] cmdrs = cmdrList.Split(';'); /// The Exit method, as required by the VoiceAttack plugin API.
if (cmdrs.Length == 0) /// Runs when VoiceAttack is shut down.
{ /// </summary>
throw new ArgumentNullException("~cmdrs"); /// <param name="vaProxy">The VoiceAttack proxy object.</param>
} [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
string system = CaseList[caseNo]?.System ?? throw new ArgumentException($"Case #{caseNo} has no system information", "~caseNo"); public static void VA_Exit1(dynamic vaProxy)
{
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\edsm-getnearest.exe"; Log.Debug("Starting teardown …");
string arguments = $@"--short --text --system ""{system}"" ""{string.Join(@""" """, cmdrs)}"""; Log.Debug("Closing RATSIGNAL pipe …");
RatsignalPipe.Stop();
Process p = PythonProxy.SetupPythonScript(path, arguments); Log.Debug("Teardown finished.");
}
p.Start();
string stdout = p.StandardOutput.ReadToEnd(); /// <summary>
string stderr = p.StandardError.ReadToEnd(); /// The StopCommand method, as required by the VoiceAttack plugin API.
p.WaitForExit(); /// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
string message = stdout; /// </summary>
string? errorMessage = null; public static void VA_StopCommand()
bool error = true; {
}
switch (p.ExitCode)
{ /// <summary>
case 0: /// Parses a RATSIGNAL and extracts case data for storage.
error = false; /// </summary>
Log.Info(message); /// <param name="ratsignal">The incoming RATSIGNAL.</param>
break; /// <returns>The case number.</returns>
case 1: // CMDR not found, Server Error, Api Exception (jeez, what a mess did I make there?) /// <exception cref="ArgumentException">Thrown on invalid RATSIGNAL.</exception>
error = true; private static int ParseRatsignal(string ratsignal)
Log.Error(message); {
break; if (!RatsignalRegex.IsMatch(ratsignal))
case 2: // System not found {
error = true; throw new ArgumentException($"Invalid RATSIGNAL format: '{ratsignal}'.", "ratsignal");
Log.Warn(message); }
break;
default: Match match = RatsignalRegex.Match(ratsignal);
error = true;
Log.Error(stderr); string cmdr = match.Groups["cmdr"].Value;
errorMessage = "Unrecoverable error in plugin."; string? language = match.Groups["language"].Value;
break; string? system = match.Groups["system"].Value;
string? systemInfo = match.Groups["systemInfo"].Value;
} bool permitLocked = match.Groups["permit"].Success;
string? permitName = match.Groups["permitName"].Value;
vaProxy.SetText("~message", message); string platform = match.Groups["platform"].Value;
vaProxy.SetBoolean("~error", error); bool codeRed = match.Groups["oxygen"].Success;
vaProxy.SetText("~errorMessage", errorMessage); bool odyssey = match.Groups["odyssey"].Success;
vaProxy.SetInt("~exitCode", p.ExitCode);
} int number = int.Parse(match.Groups["number"].Value);
private static void Context_GetCaseData(dynamic vaProxy) if (string.IsNullOrEmpty(system))
{ {
int cn = vaProxy.GetInt("~caseNumber"); system = "None";
}
if (CaseList.ContainsKey(cn))
{ Log.Debug($"New rat case: CMDR “{cmdr}” in “{system}”{(systemInfo != null ? $" ({systemInfo})" : string.Empty)} on {platform}{(odyssey ? " (Odyssey)" : string.Empty)}, permit locked: {permitLocked}{(permitLocked && permitName != null ? $" (permit name: {permitName})" : string.Empty)}, code red: {codeRed} (#{number}).");
RatCase rc = CaseList[cn];
CaseList[number] = new RatCase(cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number);
vaProxy.SetInt("~~caseNumber", rc.Number);
vaProxy.SetText("~~cmdr", rc.Cmdr); return number;
vaProxy.SetText("~~system", rc?.System?.ToLower()); }
vaProxy.SetText("~~systemInfo", rc?.SystemInfo);
vaProxy.SetBoolean("~~permitLocked", rc?.PermitLocked); private static void On_Ratsignal(Ratsignal ratsignal)
vaProxy.SetText("~~permitName", rc?.PermitName); {
vaProxy.SetText("~~platform", rc?.Platform); try
vaProxy.SetBoolean("~~odyssey", rc?.Odyssey); {
vaProxy.SetBoolean("~~codeRed", rc?.CodeRed); int number = ParseRatsignal(ratsignal.Signal);
} Log.Notice($"New rat case: {CaseList[number]}.");
else Commands.TriggerEvent("RatAttack.incomingCase", parameters: new dynamic[] { new int[] { number }, new bool[] { ratsignal.Announce } });
{ }
Log.Warn($"Case #{cn} not found in the case list"); catch (ArgumentException e)
} {
} Log.Error(e.Message);
Commands.TriggerEvent("RatAttack.invalidRatsignal", parameters: new dynamic[] { new string[] { ratsignal.Signal } });
private static void Context_Log(dynamic vaProxy) }
{ catch (Exception e)
string message = vaProxy.GetText("~message"); {
string level = vaProxy.GetText("~level"); Log.Error($"Unhandled exception while parsing RATSIGNAL: '{e.Message}'.");
}
if (level == null) }
{
Log.Log(message); private static void On_ProfileChanged(Guid? from, Guid? to, string fromName, string toName)
} => VA_Exit1(VA);
else
{ /*================\
try | plugin contexts |
{ \================*/
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
} private static void Context_EDSM_GetNearestCMDR()
catch (ArgumentNullException) { throw; } {
catch (ArgumentException) int caseNo = VA!.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
{ string cmdrList = VA!.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs");
Log.Error($"Invalid log level '{level}'."); string[] cmdrs = cmdrList.Split(';');
} if (cmdrs.Length == 0)
} {
} throw new ArgumentNullException("~cmdrs");
}
private static void Context_Startup(dynamic vaProxy)
{ string system = CaseList[caseNo]?.System ?? throw new ArgumentException($"Case #{caseNo} has no system information", "~caseNo");
Log.Notice("Starting up …");
VA = vaProxy; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\edsm-getnearest.exe";
_ = RatsignalPipe.Run(); string arguments = $@"--short --text --system ""{system}"" ""{string.Join(@""" """, cmdrs)}""";
Log.Notice("Finished startup.");
} Process p = PythonProxy.SetupPythonScript(path, arguments);
private static void Context_ParseRatsignal(dynamic vaProxy) p.Start();
{ string stdout = p.StandardOutput.ReadToEnd();
Log.Warn("Passing a RATSIGNAL from VoiceAttack (through the clipboard or a file) is DEPRECATED and will no longer be supported in the future."); string stderr = p.StandardError.ReadToEnd();
On_Ratsignal(new Ratsignal(vaProxy.GetText("~ratsignal"), vaProxy.GetBoolean("~announceRatsignal"))); p.WaitForExit();
}
string message = stdout;
/*========================================\ string? errorMessage = null;
| required VoiceAttack plugin shenanigans | bool error;
\========================================*/
switch (p.ExitCode)
static readonly Version VERSION = new Version("6.3"); {
case 0:
public static Guid VA_Id() error = false;
=> new Guid("{F2ADF0AE-4837-4E4A-9C87-8A7E2FA63E5F}"); Log.Info(message);
public static string VA_DisplayName() break;
=> $"RatAttack {VERSION}"; case 1: // CMDR not found, Server Error, Api Exception (jeez, what a mess did I make there?)
public static string VA_DisplayInfo() error = true;
=> "RatAttack: a plugin to handle FuelRats cases."; Log.Error(message);
break;
public static void VA_Init1(dynamic vaProxy) case 2: // System not found
{ error = true;
VA = vaProxy; Log.Warn(message);
Log.Notice("Initializing …"); break;
VA.SetText("RatAttack.version", VERSION.ToString()); default:
vaProxy.ProfileChanged += new Action<Guid?, Guid?, String, String>(On_ProfileChanged); error = true;
Log.Notice("Init successful."); Log.Error(stderr);
} errorMessage = "Unrecoverable error in plugin.";
break;
public static void VA_Invoke1(dynamic vaProxy) }
{
string context = vaProxy.Context.ToLower(); VA!.SetText("~message", message);
Log.Debug($"Running context '{context}' …"); VA!.SetBoolean("~error", error);
try VA!.SetText("~errorMessage", errorMessage);
{ VA!.SetInt("~exitCode", p.ExitCode);
switch (context) }
{
// plugin methods private static void Context_GetCaseData()
case "getcasedata": {
Context_GetCaseData(vaProxy); int cn = VA!.GetInt("~caseNumber");
break;
case "parseratsignal": if (CaseList.ContainsKey(cn))
Context_ParseRatsignal(vaProxy); {
break; RatCase rc = CaseList[cn];
case "startup":
Context_Startup(vaProxy); VA!.SetInt("~~caseNumber", rc.Number);
break; VA!.SetText("~~cmdr", rc.Cmdr);
// EDSM VA!.SetText("~~system", rc?.System?.ToLower());
case "edsm.getnearestcmdr": VA!.SetText("~~systemInfo", rc?.SystemInfo);
Context_EDSM_GetNearestCMDR(vaProxy); VA!.SetBoolean("~~permitLocked", rc?.PermitLocked);
break; VA!.SetText("~~permitName", rc?.PermitName);
// log VA!.SetText("~~platform", rc?.Platform);
case "log.log": VA!.SetBoolean("~~odyssey", rc?.Odyssey);
Context_Log(vaProxy); VA!.SetBoolean("~~codeRed", rc?.CodeRed);
break; }
// invalid else
default: {
Log.Error($"Invalid plugin context '{vaProxy.Context}'."); Log.Warn($"Case #{cn} not found in the case list");
break; }
} }
}
catch (ArgumentNullException e) private static void Context_Log()
{ {
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'"); string message = VA!.GetText("~message");
} string level = VA!.GetText("~level");
catch (Exception e)
{ if (level == null)
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})"); {
} Log.Log(message);
} }
else
public static void VA_Exit1(dynamic vaProxy) {
{ try
Log.Debug("Starting teardown …"); {
Log.Debug("Closing RATSIGNAL pipe …"); Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
RatsignalPipe.Stop(); }
Log.Debug("Teardown finished."); catch (ArgumentNullException)
} {
throw;
public static void VA_StopCommand() { } }
} catch (ArgumentException)
} {
Log.Error($"Invalid log level '{level}'.");
}
}
}
private static void Context_Startup()
{
Log.Notice("Starting up …");
_ = RatsignalPipe.Run();
Log.Notice("Finished startup.");
}
private static void Context_ParseRatsignal()
{
Log.Warn("Passing a RATSIGNAL to VoiceAttack through the clipboard or a file is DEPRECATED and will no longer be supported in the future.");
On_Ratsignal(new Ratsignal(VA!.GetText("~ratsignal"), VA!.GetBoolean("~announceRatsignal")));
}
/// <summary>
/// Encapsulates a RATSIGNAL for sending between the CLI helper tool and
/// the plugin via named pipe.
/// </summary>
public class Ratsignal : IPipable
{
private readonly char separator = '\x1F';
/// <summary>
/// Initializes a new instance of the <see cref="Ratsignal"/> class.
/// </summary>
public Ratsignal()
=> (this.Signal, this.Announce) = (string.Empty, false);
/// <summary>
/// Initializes a new instance of the <see cref="Ratsignal"/> class.
/// </summary>
/// <param name="signal">The RATSIGNAL.</param>
/// <param name="announce">Whether or not to announce the new case.</param>
public Ratsignal(string signal, bool announce)
=> (this.Signal, this.Announce) = (signal, announce);
/// <summary>
/// Gets or sets the RATSIGNAL.
/// </summary>
public string Signal { get; set; }
/// <summary>
/// Gets or Sets a value indicating whether to announce the incoming
/// case.
/// </summary>
public bool Announce { get; set; }
/// <summary>
/// Initializes the <see cref="Ratsignal"/> instance from a
/// serialized representation.
/// FIXXME: should probably make this a static factory method.
/// </summary>
/// <param name="serialization">The serialized <see cref="Ratsignal"/>.</param>
/// <exception cref="ArgumentException">Thrown on receiving an invalid signal.</exception>
public void ParseString(string serialization)
{
try
{
string[] parts = serialization.Split(this.separator);
this.Signal = parts[0];
this.Announce = bool.Parse(parts[1]);
}
catch (Exception e)
{
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e);
}
}
/// <inheritdoc/>
public override string ToString()
=> $"{this.Signal}{this.separator}{this.Announce}";
}
private class RatCase
{
public RatCase(string cmdr, string? language, string? system, string? systemInfo, bool permitLocked, string? permitName, string platform, bool odyssey, bool codeRed, int number)
=> (this.Cmdr, this.Language, this.System, this.SystemInfo, this.PermitLocked, this.PermitName, this.Platform, this.Odyssey, this.CodeRed, this.Number)
= (cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number);
public string Cmdr { get; }
public string? Language { get; }
public string? System { get; }
public string? SystemInfo { get; }
public bool PermitLocked { get; }
public string? PermitName { get; }
public string Platform { get; }
public bool Odyssey { get; }
public bool CodeRed { get; }
public int Number { get; }
public string ShortInfo
{
get => $"#{this.Number}, {this.Platform}{(this.Odyssey ? " (Odyssey)" : string.Empty)}{(this.CodeRed ? ", code red" : string.Empty)}, {this.System ?? "None"}{(this.SystemInfo != null ? $" ({this.SystemInfo}{(this.PermitLocked ? ", permit required" : string.Empty)})" : string.Empty)}";
}
public override string ToString()
=> this.ShortInfo;
}
}
}

View file

@ -25,7 +25,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -34,7 +33,6 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -59,4 +57,4 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -1,32 +1,162 @@
#nullable enable // <copyright file="SpanshAttack.cs" company="alterNERDtive">
// Copyright 20192022 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 &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using alterNERDtive.util;
using alterNERDtive.edts;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using alterNERDtive.edts;
using alterNERDtive.util;
namespace SpanshAttack namespace SpanshAttack
{ {
/// <summary>
/// VoiceAttack plugin for the SpanshAttack profile.
/// </summary>
public class SpanshAttack public class SpanshAttack
{ {
private static readonly Version VERSION = new ("7.2.2");
private static VoiceAttackLog? log;
private static VoiceAttackCommands? commands;
private static dynamic? VA { get; set; } private static dynamic? VA { get; set; }
private static VoiceAttackLog Log private static VoiceAttackLog Log => log ??= new (VA, "SpanshAttack");
=> log ??= new VoiceAttackLog(VA, "SpanshAttack");
private static VoiceAttackLog? log;
private static VoiceAttackCommands Commands private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
=> commands ??= new VoiceAttackCommands(VA, Log);
private static VoiceAttackCommands? commands; /*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id()
=> new ("{e722b29d-898e-47dd-a843-a409c87e0bd8}");
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"SpanshAttack {VERSION}";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo()
=> "SpanshAttack: a plugin for doing routing with spansh.co.uk for Elite: Dangerous.";
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("SpanshAttack.version", VERSION.ToString());
Log.Notice("Init successful.");
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
VA = vaProxy;
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup();
break;
case "edts.getcoordinates":
// EDTS
Context_EDTS_GetCoordinates();
break;
case "log.log":
// log
Context_Log();
break;
case "spansh.systemexists":
// Spansh
Context_Spansh_SytemExists();
break;
case "spansh.nearestsystem":
Context_Spansh_Nearestsystem();
break;
default:
// invalid
Log.Error($"Invalid plugin context '{VA!.Context}'.");
break;
}
}
catch (ArgumentNullException e)
{
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
}
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
public static void VA_Exit1(dynamic vaProxy)
{
}
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
public static void VA_StopCommand()
{
}
/*================\ /*================\
| plugin contexts | | plugin contexts |
\================*/ \================*/
private static void Context_EDTS_GetCoordinates(dynamic vaProxy) private static void Context_EDTS_GetCoordinates()
{ {
string name = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system"); string name = VA!.GetText("~system") ?? throw new ArgumentNullException("~system");
bool success = false; bool success = false;
string? errorType = null; string? errorType = null;
@ -45,35 +175,37 @@ namespace SpanshAttack
Log.Warn($@"Coordinates with low precision for ""{name}"": ({system.Coords.X}, {system.Coords.Y}, {system.Coords.Z}), precision: {system.Coords.Precision}ly"); Log.Warn($@"Coordinates with low precision for ""{name}"": ({system.Coords.X}, {system.Coords.Y}, {system.Coords.Z}), precision: {system.Coords.Precision}ly");
} }
vaProxy.SetInt("~x", system.Coords.X); VA!.SetInt("~x", system.Coords.X);
vaProxy.SetInt("~y", system.Coords.Y); VA!.SetInt("~y", system.Coords.Y);
vaProxy.SetInt("~z", system.Coords.Z); VA!.SetInt("~z", system.Coords.Z);
vaProxy.SetInt("~precision", system.Coords.Precision); VA!.SetInt("~precision", system.Coords.Precision);
success = true; success = true;
} catch (ArgumentException e) }
catch (ArgumentException e)
{ {
errorType = "invalid name"; errorType = "invalid name";
errorMessage = e.Message; errorMessage = e.Message;
} catch (Exception e) }
catch (Exception e)
{ {
errorType = "connection error"; errorType = "connection error";
errorMessage = e.Message; errorMessage = e.Message;
} }
vaProxy.SetBoolean("~success", success); VA!.SetBoolean("~success", success);
if (!string.IsNullOrWhiteSpace(errorType)) if (!string.IsNullOrWhiteSpace(errorType))
{ {
Log.Error(errorMessage!); Log.Error(errorMessage!);
vaProxy.SetText("~errorType", errorType); VA!.SetText("~errorType", errorType);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
} }
} }
private static void Context_Log(dynamic vaProxy) private static void Context_Log()
{ {
string message = vaProxy.GetText("~message"); string message = VA!.GetText("~message");
string level = vaProxy.GetText("~level"); string level = VA!.GetText("~level");
if (level == null) if (level == null)
{ {
@ -85,7 +217,10 @@ namespace SpanshAttack
{ {
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper())); Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
} }
catch (ArgumentNullException) { throw; } catch (ArgumentNullException)
{
throw;
}
catch (ArgumentException) catch (ArgumentException)
{ {
Log.Error($"Invalid log level '{level}'."); Log.Error($"Invalid log level '{level}'.");
@ -93,22 +228,22 @@ namespace SpanshAttack
} }
} }
private static void Context_Spansh_Nearestsystem(dynamic vaProxy) private static void Context_Spansh_Nearestsystem()
{ {
int x = vaProxy.GetInt("~x") ?? throw new ArgumentNullException("~x"); int x = VA!.GetInt("~x") ?? throw new ArgumentNullException("~x");
int y = vaProxy.GetInt("~y") ?? throw new ArgumentNullException("~y"); int y = VA!.GetInt("~y") ?? throw new ArgumentNullException("~y");
int z = vaProxy.GetInt("~z") ?? throw new ArgumentNullException("~z"); int z = VA!.GetInt("~z") ?? throw new ArgumentNullException("~z");
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe"; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe";
string arguments = $@"nearestsystem --parsable {x} {y} {z}"; string arguments = $@"nearestsystem --parsable {x} {y} {z}";
Process p = PythonProxy.SetupPythonScript(path, arguments); Process p = PythonProxy.SetupPythonScript(path, arguments);
Dictionary<char, decimal> coords = new Dictionary<char, decimal> { { 'x', 0 }, { 'y', 0 }, { 'z', 0 } }; Dictionary<char, decimal> coords = new () { { 'x', 0 }, { 'y', 0 }, { 'z', 0 } };
string system = ""; string system = string.Empty;
decimal distance = 0; decimal distance = 0;
bool error = false; bool error = false;
string errorMessage = ""; string errorMessage = string.Empty;
p.Start(); p.Start();
string stdout = p.StandardOutput.ReadToEnd(); string stdout = p.StandardOutput.ReadToEnd();
@ -138,28 +273,28 @@ namespace SpanshAttack
break; break;
} }
vaProxy.SetText("~system", system); VA!.SetText("~system", system);
vaProxy.SetDecimal("~x", coords['x']); VA!.SetDecimal("~x", coords['x']);
vaProxy.SetDecimal("~y", coords['y']); VA!.SetDecimal("~y", coords['y']);
vaProxy.SetDecimal("~z", coords['z']); VA!.SetDecimal("~z", coords['z']);
vaProxy.SetDecimal("~distance", distance); VA!.SetDecimal("~distance", distance);
vaProxy.SetBoolean("~error", error); VA!.SetBoolean("~error", error);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
vaProxy.SetInt("~exitCode", p.ExitCode); VA!.SetInt("~exitCode", p.ExitCode);
} }
private static void Context_Spansh_SytemExists(dynamic vaProxy) private static void Context_Spansh_SytemExists()
{ {
string system = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system"); string system = VA!.GetText("~system") ?? throw new ArgumentNullException("~system");
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe"; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe";
string arguments = $@"systemexists ""{system}"""; string arguments = $@"systemexists ""{system}""";
Process p = PythonProxy.SetupPythonScript(path, arguments); Process p = PythonProxy.SetupPythonScript(path, arguments);
bool exists = true; bool exists = true;
bool error = false; bool error = false;
string errorMessage = ""; string errorMessage = string.Empty;
p.Start(); p.Start();
string stdout = p.StandardOutput.ReadToEnd(); string stdout = p.StandardOutput.ReadToEnd();
@ -186,84 +321,16 @@ namespace SpanshAttack
break; break;
} }
vaProxy.SetBoolean("~systemExists", exists); VA!.SetBoolean("~systemExists", exists);
vaProxy.SetBoolean("~error", error); VA!.SetBoolean("~error", error);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
vaProxy.SetInt("~exitCode", p.ExitCode); VA!.SetInt("~exitCode", p.ExitCode);
} }
private static void Context_Startup(dynamic vaProxy) private static void Context_Startup()
{ {
Log.Notice("Starting up …"); Log.Notice("Starting up …");
VA = vaProxy;
Log.Notice("Finished startup."); Log.Notice("Finished startup.");
} }
/*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
static readonly Version VERSION = new Version("7.2.1");
public static Guid VA_Id()
=> new Guid("{e722b29d-898e-47dd-a843-a409c87e0bd8}");
public static string VA_DisplayName()
=> $"SpanshAttack {VERSION}";
public static string VA_DisplayInfo()
=> "SpanshAttack: a plugin for doing routing with spansh.co.uk for Elite: Dangerous.";
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("SpanshAttack.version", VERSION.ToString());
Log.Notice("Init successful.");
}
public static void VA_Invoke1(dynamic vaProxy)
{
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup(vaProxy);
break;
// EDTS
case "edts.getcoordinates":
Context_EDTS_GetCoordinates(vaProxy);
break;
// log
case "log.log":
Context_Log(vaProxy);
break;
// Spansh
case "spansh.systemexists":
Context_Spansh_SytemExists(vaProxy);
break;
case "spansh.nearestsystem":
Context_Spansh_Nearestsystem(vaProxy);
break;
// invalid
default:
Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
break;
}
}
catch (ArgumentNullException e)
{
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
}
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
public static void VA_Exit1(dynamic vaProxy) { }
public static void VA_StopCommand() { }
} }
} }

View file

@ -21,7 +21,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,7 +29,6 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -53,4 +51,4 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -0,0 +1,10 @@
// 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.
using System.Diagnostics.CodeAnalysis;
[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")]

View file

@ -16,8 +16,9 @@
<TabItem Name="StreamAttack" Header="StreamAttack"></TabItem> <TabItem Name="StreamAttack" Header="StreamAttack"></TabItem>
</TabControl> </TabControl>
<WrapPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"> <WrapPanel VerticalAlignment="Bottom" HorizontalAlignment="Right">
<Button Name="cancelButton" Click="cancelButton_Click" Padding="5" Margin="5">Cancel</Button> <Button Name="applyButton" Click="ApplyButton_Click" Padding="5" Margin="5" Width="100">Apply</Button>
<Button Name="okButton" Click="okButton_Click" Padding="5" Margin="5">OK</Button> <Button Name="okButton" Click="OkButton_Click" Padding="5" Margin="5" Width="100">Done</Button>
<Button Name="cancelButton" Click="CancelButton_Click" Padding="5" Margin="5" Width="100">Cancel</Button>
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

View file

@ -1,4 +1,23 @@
using System; // <copyright file="SettingsDialog.xaml.cs" company="alterNERDtive">
// Copyright 20192022 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 &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -6,40 +25,34 @@ using System.Windows.Controls;
namespace alterNERDtive namespace alterNERDtive
{ {
/// <summary> /// <summary>
/// Interaction logic for SettingsDialog.xaml /// Interaction logic for SettingsDialog.xaml.
/// </summary> /// </summary>
public partial class SettingsDialog : UserControl public partial class SettingsDialog : UserControl
{ {
private struct Setting private readonly List<Setting> values = new List<Setting>();
{
public string Profile { get; }
public dynamic Option { get; }
public dynamic Value { get; }
public dynamic UiElement { get; }
public Setting(string profile, dynamic option, dynamic value, dynamic uiElement)
=> (Profile, Option, Value, UiElement) = (profile, option, value, uiElement);
}
private List<Setting> values = new List<Setting>();
private util.Configuration config; private util.Configuration config;
private util.VoiceAttackLog log; private util.VoiceAttackLog log;
/// <summary>
/// Initializes a new instance of the <see cref="SettingsDialog"/> class.
/// </summary>
/// <param name="config">The plugin Configuration.</param>
/// <param name="log">The plugin Log.</param>
public SettingsDialog(util.Configuration config, util.VoiceAttackLog log) public SettingsDialog(util.Configuration config, util.VoiceAttackLog log)
{ {
InitializeComponent(); this.InitializeComponent();
this.config = config; this.config = config;
this.log = log; this.log = log;
foreach (TabItem tab in tabs.Items) foreach (TabItem tab in this.tabs.Items)
{ {
string profile = tab.Name; string profile = tab.Name;
if (profile == "general") if (profile == "general")
{ {
profile = "alterNERDtive-base"; profile = "alterNERDtive-base";
} }
tab.IsEnabled = BasePlugin.IsProfileActive(profile); tab.IsEnabled = BasePlugin.IsProfileActive(profile);
StackPanel panel = new StackPanel(); StackPanel panel = new StackPanel();
@ -57,7 +70,7 @@ namespace alterNERDtive
checkBox.IsChecked = value; checkBox.IsChecked = value;
checkBox.VerticalAlignment = VerticalAlignment.Center; checkBox.VerticalAlignment = VerticalAlignment.Center;
row.Children.Add(checkBox); row.Children.Add(checkBox);
values.Add(new Setting(profile, option, value, checkBox)); this.values.Add(new Setting(profile, option, value, checkBox));
Label label = new Label(); Label label = new Label();
label.Content = option.Description; label.Content = option.Description;
@ -76,7 +89,7 @@ namespace alterNERDtive
TextBox input = new TextBox(); TextBox input = new TextBox();
input.Text = value.ToString(); input.Text = value.ToString();
row.Children.Add(input); row.Children.Add(input);
values.Add(new Setting(profile, option, value, input)); this.values.Add(new Setting(profile, option, value, input));
panel.Children.Add(row); panel.Children.Add(row);
} }
@ -86,17 +99,11 @@ namespace alterNERDtive
} }
} }
private void cancelButton_Click(object sender, RoutedEventArgs e) private bool ApplySettings()
{ {
Window.GetWindow(this).Close(); bool success = true;
log.Log("Settings dialog cancelled.", util.LogLevel.DEBUG);
}
private void okButton_Click(object sender, RoutedEventArgs reargs) foreach (Setting setting in this.values)
{
bool error = false;
foreach (Setting setting in values)
{ {
dynamic state = null; dynamic state = null;
@ -129,21 +136,51 @@ namespace alterNERDtive
if (state != setting.Value) if (state != setting.Value)
{ {
log.Log($@"Configuration changed via settings dialog: ""{setting.Profile}.{setting.Option.Name}"" → ""{state}""", util.LogLevel.DEBUG); this.log.Log($@"Configuration changed via settings dialog: ""{setting.Profile}.{setting.Option.Name}"" → ""{state}""", util.LogLevel.DEBUG);
config.SetConfig(setting.Profile, setting.Option.Name, state); this.config.SetConfig(setting.Profile, setting.Option.Name, state);
} }
} }
catch (Exception e) when (e is ArgumentNullException || e is FormatException || e is OverflowException) catch (Exception e) when (e is ArgumentNullException || e is FormatException || e is OverflowException)
{ {
log.Log($@"Invalid value for ""{setting.Profile}.{setting.Option.Name}"": ""{((TextBox)setting.UiElement).Text}""", util.LogLevel.ERROR); this.log.Log($@"Invalid value for ""{setting.Profile}.{setting.Option.Name}"": ""{((TextBox)setting.UiElement).Text}""", util.LogLevel.ERROR);
error = true; success = false;
} }
} }
if (!error) return success;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
Window.GetWindow(this).Close();
this.log.Log("Settings dialog cancelled.", util.LogLevel.DEBUG);
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
if (this.ApplySettings())
{ {
Window.GetWindow(this).Close(); Window.GetWindow(this).Close();
} }
} }
private void ApplyButton_Click(object sender, RoutedEventArgs reeargs)
{
this.ApplySettings();
}
private struct Setting
{
public Setting(string profile, dynamic option, dynamic value, dynamic uiElement)
=> (this.Profile, this.Option, this.Value, this.UiElement) = (profile, option, value, uiElement);
public string Profile { get; }
public dynamic Option { get; }
public dynamic Value { get; }
public dynamic UiElement { get; }
}
} }
} }

View file

@ -24,7 +24,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -33,7 +32,6 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@ -58,7 +56,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="base.cs" /> <Compile Include="base.cs" />
<Compile Include="edts.cs" /> <Compile Include="edts.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SettingsDialog.xaml.cs"> <Compile Include="SettingsDialog.xaml.cs">
<DependentUpon>SettingsDialog.xaml</DependentUpon> <DependentUpon>SettingsDialog.xaml</DependentUpon>
@ -75,4 +76,4 @@
</Page> </Page>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -1,6 +1,24 @@
#nullable enable // <copyright file="base.cs" company="alterNERDtive">
// Copyright 20192022 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 &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using alterNERDtive.util;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -9,31 +27,192 @@ using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using alterNERDtive.util;
namespace alterNERDtive namespace alterNERDtive
{ {
/// <summary>
/// This is the base plugin orchestrating all the profile-specific plugins
/// to work together properly. It handles things like configuration or
/// subscribing to VoiceAttack-triggered events.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "F off :)")]
public class BasePlugin public class BasePlugin
{ {
private static dynamic? VA { get; set; } private static readonly Version VERSION = new ("4.4");
private static readonly Dictionary<Guid, string> Profiles = new Dictionary<Guid, string> {
private static readonly Dictionary<Guid, string> Profiles = new ()
{
{ new Guid("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}"), "alterNERDtive-base" }, { new Guid("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}"), "alterNERDtive-base" },
{ new Guid("{f31b575b-6ce4-44eb-91fc-7459e55013cf}"), "EliteAttack" }, { new Guid("{f31b575b-6ce4-44eb-91fc-7459e55013cf}"), "EliteAttack" },
{ new Guid("{87276668-2a6e-4d80-af77-80651daa58b7}"), "RatAttack" }, { new Guid("{87276668-2a6e-4d80-af77-80651daa58b7}"), "RatAttack" },
{ new Guid("{e722b29d-898e-47dd-a843-a409c87e0bd8}"), "SpanshAttack" }, { new Guid("{e722b29d-898e-47dd-a843-a409c87e0bd8}"), "SpanshAttack" },
{ new Guid("{05580e6c-442c-46cd-b36f-f5a1f967ec59}"), "StreamAttack" } { new Guid("{05580e6c-442c-46cd-b36f-f5a1f967ec59}"), "StreamAttack" },
}; };
private static readonly List<string> ActiveProfiles = new List<string>();
private static readonly List<string> InstalledProfiles = new List<string>();
private static readonly Regex ConfigurationVariableRegex = new Regex(@$"(?<id>({String.Join("|", Profiles.Values)}))\.(?<name>.+)#"); private static readonly List<string> ActiveProfiles = new ();
private static readonly List<string> InstalledProfiles = new ();
private static VoiceAttackLog Log => log ??= new VoiceAttackLog(VA, "alterNERDtive-base"); private static readonly Regex ConfigurationVariableRegex = new (@$"(?<id>({string.Join("|", Profiles.Values)}))\.(?<name>.+)#");
private static VoiceAttackCommands? commands;
private static Configuration? config;
private static VoiceAttackLog? log; private static VoiceAttackLog? log;
private static VoiceAttackCommands Commands => commands ??= new VoiceAttackCommands(VA, Log); private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
private static VoiceAttackCommands? commands;
private static Configuration Config => config ??= new Configuration(VA, Log, Commands, "alterNERDtive-base"); private static Configuration Config => config ??= new (VA, Log, Commands, "alterNERDtive-base");
private static Configuration? config;
private static VoiceAttackLog Log => log ??= new (VA, "alterNERDtive-base");
private static dynamic? VA { get; set; }
/*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id()
=> new ("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}");
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"alterNERDtive-base {VERSION}";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo()
=> "The alterNERDtive plugin to manage all the alterNERDtive profiles!";
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("alterNERDtive-base.version", VERSION.ToString());
vaProxy.BooleanVariableChanged += new Action<string, bool?, bool?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DateVariableChanged += new Action<string, DateTime?, DateTime?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DecimalVariableChanged += new Action<string, decimal?, decimal?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.IntegerVariableChanged += new Action<string, int?, int?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.TextVariableChanged += new Action<string, string, string, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
VA.SetBoolean("alterNERDtive-base.initialized", true);
Commands.TriggerEvent("alterNERDtive-base.initialized", wait: false, logMissing: false);
Log.Notice("Init successful.");
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
VA = vaProxy;
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup();
break;
case "config.dialog":
// config
Context_Config_Dialog();
break;
case "config.dump":
Context_Config_Dump();
break;
case "config.getvariables":
Context_Config_SetVariables();
break;
case "config.list":
Context_Config_List();
break;
case "config.setup":
Context_Config_Setup();
break;
case "config.versionmigration":
Context_Config_VersionMigration();
break;
case "edsm.bodycount":
// EDSM
Context_EDSM_BodyCount();
break;
case "edsm.distancebetween":
Context_EDSM_DistanceBetween();
break;
case "eddi.event":
// EDDI
Context_Eddi_Event();
break;
case "spansh.outdatedstations":
// Spansh
Context_Spansh_OutdatedStations();
break;
case "log.log":
// log
Context_Log();
break;
case "update.check":
// update
Context_Update_Check();
break;
default:
// invalid
Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
break;
}
}
catch (ArgumentNullException e)
{
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
}
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
public static void VA_Exit1(dynamic vaProxy)
{
}
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
public static void VA_StopCommand()
{
}
/// <summary>
/// Returns whether a given profile is currently active.
/// </summary>
/// <param name="profileName">The name of the profile in question.</param>
/// <returns>The state of the profile in question.</returns>
public static bool IsProfileActive(string profileName) => ActiveProfiles.Contains(profileName);
private static void CheckProfiles(dynamic vaProxy) private static void CheckProfiles(dynamic vaProxy)
{ {
@ -43,20 +222,21 @@ namespace alterNERDtive
foreach (KeyValuePair<Guid, string> profile in Profiles) foreach (KeyValuePair<Guid, string> profile in Profiles)
{ {
if (vaProxy.Command.Exists($"(({profile.Value}.startup))")) if (vaProxy.Command.Exists($"(({profile.Value}.startup))"))
// Sadly there is no way to find _active_ profiles, so we have to check the one command that always is in them.
{ {
// Sadly there is no way to find _active_ profiles, so we have to check the one command that always is in them.
ActiveProfiles.Add(profile.Value); ActiveProfiles.Add(profile.Value);
} }
if (vaProxy.Profile.Exists(profile.Key)) if (vaProxy.Profile.Exists(profile.Key))
{ {
InstalledProfiles.Add(profile.Value); InstalledProfiles.Add(profile.Value);
} }
} }
Log.Debug($"Profiles found: {string.Join<string>(", ", ActiveProfiles)}"); Log.Debug($"Profiles found: {string.Join<string>(", ", ActiveProfiles)}");
} }
public static bool IsProfileActive(string profileName) => ActiveProfiles.Contains(profileName); [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "required by VoiceAttack plugin API")]
private static void ConfigurationChanged(string option, dynamic? from, dynamic? to, Guid? guid = null) private static void ConfigurationChanged(string option, dynamic? from, dynamic? to, Guid? guid = null)
{ {
try try
@ -69,6 +249,7 @@ namespace alterNERDtive
Log.Debug($"Configuration has changed, '{id}.{name}': '{from}' → '{to}'"); Log.Debug($"Configuration has changed, '{id}.{name}': '{from}' → '{to}'");
dynamic o = Config.GetOption(id, name); dynamic o = Config.GetOption(id, name);
// When loaded from profile but not explicitly set, will be null. // When loaded from profile but not explicitly set, will be null.
// Then load default. // Then load default.
// Same applies to resetting a saved option (= saving null to the profile). // Same applies to resetting a saved option (= saving null to the profile).
@ -105,7 +286,8 @@ namespace alterNERDtive
} }
} }
if (option == "alterNERDtive-base.eddi.quietMode#" && VA!.GetText("EDDI version") != null) // if null, EDDI isnt up yet // if null, EDDI isnt up yet
if (option == "alterNERDtive-base.eddi.quietMode#" && VA!.GetText("EDDI version") != null)
{ {
Log.Debug($"Resetting speech responder ({(to ?? false ? "off" : "on")}) …"); Log.Debug($"Resetting speech responder ({(to ?? false ? "off" : "on")}) …");
Commands.Run("alterNERDtive-base.setEDDISpeechResponder"); Commands.Run("alterNERDtive-base.setEDDISpeechResponder");
@ -137,7 +319,8 @@ namespace alterNERDtive
Log.Notice($"Local version: {VERSION}, latest release: {latestVersion}."); Log.Notice($"Local version: {VERSION}, latest release: {latestVersion}.");
Commands.TriggerEvent("alterNERDtive-base.updateCheck", Commands.TriggerEvent(
"alterNERDtive-base.updateCheck",
parameters: new dynamic[] { new string[] { VERSION.ToString(), latestVersion.ToString() }, new bool[] { VERSION.CompareTo(latestVersion) < 0 } }); parameters: new dynamic[] { new string[] { VERSION.ToString(), latestVersion.ToString() }, new bool[] { VERSION.CompareTo(latestVersion) < 0 } });
} }
@ -145,35 +328,37 @@ namespace alterNERDtive
| plugin contexts | | plugin contexts |
\================*/ \================*/
private static void Context_Config_Dialog(dynamic vaProxy) private static void Context_Config_Dialog()
{ {
Thread dialogThread = new Thread(new ThreadStart(() => Thread dialogThread = new Thread(new ThreadStart(() =>
{ {
_ = new System.Windows.Window System.Windows.Window options = new ()
{ {
Title = "alterNERDtive Profile Options", Title = "alterNERDtive Profile Options",
Content = new SettingsDialog(Config, Log), Content = new SettingsDialog(Config, Log),
SizeToContent = System.Windows.SizeToContent.WidthAndHeight, SizeToContent = System.Windows.SizeToContent.WidthAndHeight,
ResizeMode = System.Windows.ResizeMode.NoResize, ResizeMode = System.Windows.ResizeMode.NoResize,
WindowStyle = System.Windows.WindowStyle.ToolWindow WindowStyle = System.Windows.WindowStyle.ToolWindow,
}.ShowDialog(); };
options.ShowDialog();
options.Activate();
})); }));
dialogThread.SetApartmentState(ApartmentState.STA); dialogThread.SetApartmentState(ApartmentState.STA);
dialogThread.IsBackground = true; dialogThread.IsBackground = true;
dialogThread.Start(); dialogThread.Start();
} }
private static void Context_Config_Dump(dynamic vaProxy) private static void Context_Config_Dump()
{ {
Config.DumpConfig(); Config.DumpConfig();
} }
private static void Context_Config_List(dynamic vaProxy) private static void Context_Config_List()
{ {
Config.ListConfig(); Config.ListConfig();
} }
private static void Context_Config_Setup(dynamic vaProxy) private static void Context_Config_Setup()
{ {
Log.Debug("Loading default configuration …"); Log.Debug("Loading default configuration …");
Config.ApplyAllDefaults(); Config.ApplyAllDefaults();
@ -181,25 +366,45 @@ namespace alterNERDtive
{ {
Config.SetVoiceTriggers(type); Config.SetVoiceTriggers(type);
} }
Config.LoadFromProfile(); Config.LoadFromProfile();
Log.Debug("Finished loading configuration."); Log.Debug("Finished loading configuration.");
} }
private static void Context_Config_SetVariables(dynamic vaProxy) private static void Context_Config_SetVariables()
{ {
string trigger = vaProxy.GetText("~trigger") ?? throw new ArgumentNullException("~trigger"); string trigger = VA!.GetText("~trigger") ?? throw new ArgumentNullException("~trigger");
Log.Debug($"Loading variables for trigger '{trigger}' …"); Log.Debug($"Loading variables for trigger '{trigger}' …");
Config.SetVariablesForTrigger(vaProxy, trigger); Config.SetVariablesForTrigger(VA!, trigger);
} }
private static void Context_Config_VersionMigration(dynamic vaProxy) private static void Context_Config_VersionMigration()
{ {
// =============
// === 4.3.1 ===
// =============
// EliteAttack
foreach (string option in new string[] { "autoStationService" })
{
string name = $"EliteAttack.{option}s#";
string oldName = $"EliteAttack.{option}#";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{oldName}", "boolean" } });
bool? value = VA!.GetBoolean(oldName);
if (value != null)
{
Log.Info($"Migrating option {oldName} …");
Commands.Run("alterNERDtive-base.saveVariableToProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}" }, new bool[] { (bool)value } });
Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{oldName}", "boolean" } });
}
}
// =========== // ===========
// === 4.2 === // === 4.2 ===
// =========== // ===========
// SpanshAttack // SpanshAttack
string edtsPath = $@"{vaProxy.SessionState["VA_SOUNDS"]}\scripts\edts.exe"; string edtsPath = $@"{VA!.SessionState["VA_SOUNDS"]}\scripts\edts.exe";
if (File.Exists(edtsPath)) if (File.Exists(edtsPath))
{ {
File.Delete(edtsPath); File.Delete(edtsPath);
@ -240,6 +445,7 @@ namespace alterNERDtive
Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } }); Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
} }
} }
foreach (string option in new string[] { "CMDRs", "platforms" }) foreach (string option in new string[] { "CMDRs", "platforms" })
{ {
string name = $"{prefix}.{option}"; string name = $"{prefix}.{option}";
@ -267,6 +473,7 @@ namespace alterNERDtive
Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } }); Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
} }
} }
foreach (string option in new string[] { "announceJumpsLeft" }) foreach (string option in new string[] { "announceJumpsLeft" })
{ {
string name = $"{prefix}.{option}"; string name = $"{prefix}.{option}";
@ -296,26 +503,26 @@ namespace alterNERDtive
} }
} }
private static void Context_Eddi_Event(dynamic vaProxy) private static void Context_Eddi_Event()
{ {
string eddiEvent = vaProxy.Command.Name(); string eddiEvent = VA!.Command.Name();
string command = eddiEvent.Substring(2, eddiEvent.Length - 4); string command = eddiEvent.Substring(2, eddiEvent.Length - 4);
Log.Debug($"Running EDDI event '{command}' …"); Log.Debug($"Running EDDI event '{command}' …");
Commands.RunAll(ActiveProfiles, command, logMissing: false, subcommand: true); // FIXXME: a) triggerAll or something, b) change all profiles to use "((<name>.<event>))" over "<name>.<event>" Commands.RunAll(ActiveProfiles, command, logMissing: false, subcommand: true); // FIXXME: a) triggerAll or something, b) change all profiles to use "((<name>.<event>))" over "<name>.<event>"
} }
private static void Context_EDSM_BodyCount(dynamic vaProxy) private static void Context_EDSM_BodyCount()
{ {
string system = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system"); string system = VA!.GetText("~system") ?? throw new ArgumentNullException("~system");
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\scripts\explorationtools.exe"; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\scripts\explorationtools.exe";
string arguments = $@"bodycount ""{system}"""; string arguments = $@"bodycount ""{system}""";
Process p = PythonProxy.SetupPythonScript(path, arguments); Process p = PythonProxy.SetupPythonScript(path, arguments);
int bodyCount = 0; int bodyCount = 0;
bool error = false; bool error = false;
string errorMessage = ""; string errorMessage = string.Empty;
p.Start(); p.Start();
string stdout = p.StandardOutput.ReadToEnd(); string stdout = p.StandardOutput.ReadToEnd();
@ -344,26 +551,26 @@ namespace alterNERDtive
break; break;
} }
vaProxy.SetInt("~bodyCount", bodyCount); VA!.SetInt("~bodyCount", bodyCount);
vaProxy.SetBoolean("~error", error); VA!.SetBoolean("~error", error);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
vaProxy.SetInt("~exitCode", p.ExitCode); VA!.SetInt("~exitCode", p.ExitCode);
} }
private static void Context_EDSM_DistanceBetween(dynamic vaProxy) private static void Context_EDSM_DistanceBetween()
{ {
string fromSystem = vaProxy.GetText("~fromSystem") ?? throw new ArgumentNullException("~fromSystem"); string fromSystem = VA!.GetText("~fromSystem") ?? throw new ArgumentNullException("~fromSystem");
string toSystem = vaProxy.GetText("~toSystem") ?? throw new ArgumentNullException("~toSystem"); string toSystem = VA!.GetText("~toSystem") ?? throw new ArgumentNullException("~toSystem");
int roundTo = vaProxy.GetInt("~roundTo") ?? 2; int roundTo = VA!.GetInt("~roundTo") ?? 2;
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\explorationtools.exe"; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\explorationtools.exe";
string arguments = $@"distancebetween --roundto {roundTo} ""{fromSystem}"" ""{toSystem}"""; string arguments = $@"distancebetween --roundto {roundTo} ""{fromSystem}"" ""{toSystem}""";
Process p = PythonProxy.SetupPythonScript(path, arguments); Process p = PythonProxy.SetupPythonScript(path, arguments);
decimal distance = 0; decimal distance = 0;
bool error = false; bool error = false;
string errorMessage = ""; string errorMessage = string.Empty;
p.Start(); p.Start();
string stdout = p.StandardOutput.ReadToEnd(); string stdout = p.StandardOutput.ReadToEnd();
@ -386,19 +593,18 @@ namespace alterNERDtive
Log.Error(stderr); Log.Error(stderr);
errorMessage = "Unrecoverable error in plugin."; errorMessage = "Unrecoverable error in plugin.";
break; break;
} }
vaProxy.SetDecimal("~distance", distance); VA!.SetDecimal("~distance", distance);
vaProxy.SetBoolean("~error", error); VA!.SetBoolean("~error", error);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
vaProxy.SetInt("~exitCode", p.ExitCode); VA!.SetInt("~exitCode", p.ExitCode);
} }
private static void Context_Log(dynamic vaProxy) private static void Context_Log()
{ {
string message = vaProxy.GetText("~message"); string message = VA!.GetText("~message");
string level = vaProxy.GetText("~level"); string level = VA!.GetText("~level");
if (level == null) if (level == null)
{ {
@ -410,7 +616,10 @@ namespace alterNERDtive
{ {
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper())); Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
} }
catch (ArgumentNullException) { throw; } catch (ArgumentNullException)
{
throw;
}
catch (ArgumentException) catch (ArgumentException)
{ {
Log.Error($"Invalid log level '{level}'."); Log.Error($"Invalid log level '{level}'.");
@ -418,17 +627,16 @@ namespace alterNERDtive
} }
} }
private static void Context_Spansh_OutdatedStations(dynamic vaProxy) private static void Context_Spansh_OutdatedStations()
{ {
string system = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system"); string system = VA!.GetText("~system") ?? throw new ArgumentNullException("~system");
int minage = vaProxy.GetInt("~minage") ?? throw new ArgumentNullException("~minage"); int minage = VA!.GetInt("~minage") ?? throw new ArgumentNullException("~minage");
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe"; string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe";
string arguments = $@"oldstations --system ""{system}"" --minage {minage}"; string arguments = $@"oldstations --system ""{system}"" --minage {minage}";
Process p = PythonProxy.SetupPythonScript(path, arguments); Process p = PythonProxy.SetupPythonScript(path, arguments);
p.Start(); p.Start();
string stdout = p.StandardOutput.ReadToEnd(); string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd(); string stderr = p.StandardError.ReadToEnd();
@ -457,16 +665,15 @@ namespace alterNERDtive
Log.Error(stderr); Log.Error(stderr);
errorMessage = "Unrecoverable error in plugin."; errorMessage = "Unrecoverable error in plugin.";
break; break;
} }
vaProxy.SetText("~message", message); VA!.SetText("~message", message);
vaProxy.SetBoolean("~error", error); VA!.SetBoolean("~error", error);
vaProxy.SetText("~errorMessage", errorMessage); VA!.SetText("~errorMessage", errorMessage);
vaProxy.SetInt("~exitCode", p.ExitCode); VA!.SetInt("~exitCode", p.ExitCode);
} }
private static void Context_Startup(dynamic vaProxy) private static void Context_Startup()
{ {
Log.Notice("Starting up …"); Log.Notice("Starting up …");
CheckProfiles(VA); CheckProfiles(VA);
@ -475,112 +682,9 @@ namespace alterNERDtive
Log.Notice("Finished startup."); Log.Notice("Finished startup.");
} }
private static void Context_Update_Check(dynamic vaProxy) private static void Context_Update_Check()
{ {
UpdateCheck(); UpdateCheck();
} }
/*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
static readonly Version VERSION = new Version("4.3");
public static Guid VA_Id()
=> new Guid("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}");
public static string VA_DisplayName()
=> $"alterNERDtive-base {VERSION}";
public static string VA_DisplayInfo()
=> "The alterNERDtive plugin to manage all the alterNERDtive profiles!";
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("alterNERDtive-base.version", VERSION.ToString());
vaProxy.BooleanVariableChanged += new Action<String, Boolean?, Boolean?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DateVariableChanged += new Action<String, DateTime?, DateTime?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DecimalVariableChanged += new Action<String, decimal?, decimal?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.IntegerVariableChanged += new Action<String, int?, int?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.TextVariableChanged += new Action<String, String, String, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
VA.SetBoolean("alterNERDtive-base.initialized", true);
Commands.TriggerEvent("alterNERDtive-base.initialized", wait: false, logMissing: false);
Log.Notice("Init successful.");
}
public static void VA_Invoke1(dynamic vaProxy)
{
VA = vaProxy;
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup(vaProxy);
break;
// config
case "config.dialog":
Context_Config_Dialog(vaProxy);
break;
case "config.dump":
Context_Config_Dump(vaProxy);
break;
case "config.getvariables":
Context_Config_SetVariables(vaProxy);
break;
case "config.list":
Context_Config_List(vaProxy);
break;
case "config.setup":
Context_Config_Setup(vaProxy);
break;
case "config.versionmigration":
Context_Config_VersionMigration(vaProxy);
break;
// EDSM
case "edsm.bodycount":
Context_EDSM_BodyCount(vaProxy);
break;
case "edsm.distancebetween":
Context_EDSM_DistanceBetween(vaProxy);
break;
// EDDI
case "eddi.event":
Context_Eddi_Event(vaProxy);
break;
// Spansh
case "spansh.outdatedstations":
Context_Spansh_OutdatedStations(vaProxy);
break;
// log
case "log.log":
Context_Log(vaProxy);
break;
// update
case "update.check":
Context_Update_Check(vaProxy);
break;
// invalid
default:
Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
break;
}
}
catch (ArgumentNullException e)
{
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
}
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
public static void VA_Exit1(dynamic vaProxy) { }
public static void VA_StopCommand() { }
} }
} }

View file

@ -1,10 +1,31 @@
#nullable enable // <auto-generated/>
// Not really, but this file will not bow to StyleCop tyranny.
// Why? Because it will be obsolete mid term anyway ;)
// <copyright file="edts.cs" company="alterNERDtive">
// Copyright 20192022 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 &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace alterNERDtive.edts namespace alterNERDtive.edts
{ {
@ -57,4 +78,4 @@ namespace alterNERDtive.edts
return new StarSystem { Name=name, Coords=new Position { X=x, Y=y, Z=z, Precision=uncertainty } }; return new StarSystem { Name=name, Coords=new Position { X=x, Y=y, Z=z, Precision=uncertainty } };
} }
} }
} }

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
stylecop.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
},
"documentationRules": {
"companyName": "alterNERDtive",
"copyrightText": "Copyright {year} {companyName}.\n\nThis file is part of {application}.\n\n{application} is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\n{application} is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with {application}. If not, see <https://www.gnu.org/licenses/>.",
"variables": {
"application": "alterNERDtive VoiceAttack profiles for Elite Dangerous",
"year": "20192022"
}
}
}
}