Merge branch 'devel' into release
This commit is contained in:
commit
9beb6dfa41
32 changed files with 2140 additions and 1559 deletions
|
@ -1,4 +1,4 @@
|
|||
[*]
|
||||
[*]
|
||||
guidelines = 80
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
@ -6,3 +6,9 @@ charset = utf-8-bom
|
|||
|
||||
[*.cs]
|
||||
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
19
.github/workflows/create-release.yaml
vendored
Normal 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
1
.gitignore
vendored
|
@ -358,3 +358,4 @@ MigrationBackup/
|
|||
/Makefile
|
||||
/site
|
||||
/src
|
||||
/build.csproj
|
||||
|
|
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -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
|
||||
worked on for the time being. See [the corresponding issue on
|
||||
|
@ -52,7 +90,7 @@ the job.
|
|||
|
||||
### Added
|
||||
|
||||
* Now gives feedback after asking for call confirmation: “Call aborted.”
|
||||
* Now gives feedback after asking for call confirmation: “Call aborted.” /
|
||||
“Calling <…>.”.
|
||||
* `auto copy rat case system` setting: Automatically copy the client’s system to
|
||||
the clipboard when you open a rat case. Default: true.
|
||||
|
|
25
Directory.build.props
Normal file
25
Directory.build.props
Normal 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>
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
4.3
|
||||
4.4
|
||||
|
|
|
@ -18,6 +18,10 @@ Toggles:
|
|||
at a station. Default: true.
|
||||
* `auto enter station services`: Automatically enter the Station Services menu
|
||||
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
|
||||
against your discovery scan. Default: true.
|
||||
* `discovery scan on primary fire`: Use primary fire for honking instead of
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## 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).
|
||||
|
||||
## Chat
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Upgrading
|
||||
# Upgrading
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
If you have done anything non-standard with bindED before, it might break. The
|
||||
|
|
|
@ -4,7 +4,7 @@ repo_url: https://github.com/alterNERDtive/VoiceAttack-profiles
|
|||
edit_uri: "edit/devel/docs/"
|
||||
site_description: "alterNERDtive VoiceAttack profiles for Elite: Dangerous"
|
||||
site_author: "alterNERDtive"
|
||||
remote_name: "ssh-origin"
|
||||
remote_name: "origin"
|
||||
|
||||
theme:
|
||||
name: readthedocs
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30413.136
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32519.111
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceAttack-base", "plugins\VoiceAttack-base\VoiceAttack-base.csproj", "{1C05DB3F-3449-4664-B363-A379892995E5}"
|
||||
EndProject
|
||||
|
@ -16,8 +16,12 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2B4D94B-8D73-431A-880B-B1E7ADF064B2}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.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
|
||||
README.md = README.md
|
||||
stylecop.json = stylecop.json
|
||||
VERSION = VERSION
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
|
|
@ -1,72 +1,76 @@
|
|||
#nullable enable
|
||||
// <copyright file="EliteAttack.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
|
||||
using alterNERDtive.util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EliteAttack
|
||||
{
|
||||
public class EliteAttack {
|
||||
private static dynamic? VA { get; set; }
|
||||
/// <summary>
|
||||
/// VoiceAttack plugin for the EliteAttack profile.
|
||||
/// </summary>
|
||||
public class EliteAttack
|
||||
{
|
||||
private static readonly Version VERSION = new ("8.4");
|
||||
|
||||
private static VoiceAttackLog Log
|
||||
=> log ??= new VoiceAttackLog(VA, "EliteAttack");
|
||||
private static VoiceAttackLog? log;
|
||||
|
||||
private static VoiceAttackCommands Commands
|
||||
=> commands ??= new VoiceAttackCommands(VA, Log);
|
||||
private static VoiceAttackCommands? commands;
|
||||
|
||||
/*================\
|
||||
| plugin contexts |
|
||||
\================*/
|
||||
private static dynamic? VA { get; set; }
|
||||
|
||||
private static void Context_Log(dynamic vaProxy)
|
||||
{
|
||||
string message = vaProxy.GetText("~message");
|
||||
string level = vaProxy.GetText("~level");
|
||||
private static VoiceAttackLog Log => log ??= new (VA, "EliteAttack");
|
||||
|
||||
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(dynamic vaProxy)
|
||||
{
|
||||
Log.Notice("Starting up …");
|
||||
VA = vaProxy;
|
||||
Log.Notice("Finished startup.");
|
||||
}
|
||||
private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
|
||||
|
||||
/*========================================\
|
||||
| required VoiceAttack plugin shenanigans |
|
||||
\========================================*/
|
||||
|
||||
static readonly Version VERSION = new Version("8.3");
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s GUID, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The GUID.</returns>
|
||||
public static Guid VA_Id()
|
||||
=> new Guid("{5B46321D-2935-4550-BEEA-36C2145547B8}");
|
||||
=> new ("{5B46321D-2935-4550-BEEA-36C2145547B8}");
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s display name, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The display name.</returns>
|
||||
public static string VA_DisplayName()
|
||||
=> $"EliteAttack {VERSION}";
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s description, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The description.</returns>
|
||||
public static string VA_DisplayInfo()
|
||||
=> "EliteAttack: a plugin for doing Elite-y things.";
|
||||
|
||||
/// <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;
|
||||
|
@ -75,8 +79,15 @@ namespace EliteAttack
|
|||
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
|
||||
|
@ -84,14 +95,14 @@ namespace EliteAttack
|
|||
switch (context)
|
||||
{
|
||||
case "startup":
|
||||
Context_Startup(vaProxy);
|
||||
Context_Startup();
|
||||
break;
|
||||
// log
|
||||
case "log.log":
|
||||
Context_Log(vaProxy);
|
||||
// log
|
||||
Context_Log();
|
||||
break;
|
||||
// invalid
|
||||
default:
|
||||
// invalid
|
||||
Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
break;
|
||||
}
|
||||
|
@ -106,8 +117,59 @@ namespace EliteAttack
|
|||
}
|
||||
}
|
||||
|
||||
public static void VA_Exit1(dynamic vaProxy) { }
|
||||
/// <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)
|
||||
{
|
||||
}
|
||||
|
||||
public static void VA_StopCommand() { }
|
||||
/// <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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
|
@ -30,7 +29,6 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
|
|
|
@ -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 2 minutes; then we’ll assume VoiceAttack just isn’t up and won’t 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,6 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
@ -35,7 +34,6 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
|
@ -50,8 +48,8 @@
|
|||
<Reference Include="System.Xml.Serialization" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="RatAttack-cli.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RatAttack_cli.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RatAttack\RatAttack.csproj">
|
||||
|
|
68
plugins/RatAttack-cli/RatAttack_cli.cs
Normal file
68
plugins/RatAttack-cli/RatAttack_cli.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
// <copyright file="RatAttack_cli.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </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 2 minutes; then we’ll assume VoiceAttack just isn’t up and won’t 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +1,184 @@
|
|||
#nullable enable
|
||||
// <copyright file="RatAttack.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using alterNERDtive.util;
|
||||
|
||||
namespace RatAttack
|
||||
{
|
||||
/// <summary>
|
||||
/// VoiceAttack plugin for the RatAttack profile.
|
||||
/// </summary>
|
||||
public class RatAttack
|
||||
{
|
||||
private static Dictionary<int,RatCase> CaseList { get; } = new Dictionary<int, RatCase>();
|
||||
private static dynamic? VA { get; set; }
|
||||
private static alterNERDtive.util.PipeServer<Ratsignal> RatsignalPipe
|
||||
=> ratsignalPipe ??= new alterNERDtive.util.PipeServer<Ratsignal>(Log, "RatAttack",
|
||||
new alterNERDtive.util.PipeServer<Ratsignal>.SignalHandler(On_Ratsignal));
|
||||
private static alterNERDtive.util.PipeServer<Ratsignal>? ratsignalPipe;
|
||||
private static readonly Version VERSION = new ("6.3.1");
|
||||
|
||||
private static readonly Regex RatsignalRegex = new Regex(
|
||||
@"^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*$"
|
||||
);
|
||||
private static readonly Regex RatsignalRegex = new (
|
||||
@"^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*$");
|
||||
|
||||
private static VoiceAttackLog Log
|
||||
=> log ??= new VoiceAttackLog(VA, "RatAttack");
|
||||
private static PipeServer<Ratsignal>? ratsignalPipe;
|
||||
private static VoiceAttackLog? log;
|
||||
|
||||
private static VoiceAttackCommands Commands
|
||||
=> commands ??= new VoiceAttackCommands(VA, Log);
|
||||
private static VoiceAttackCommands? commands;
|
||||
|
||||
private class RatCase
|
||||
{
|
||||
public string Cmdr;
|
||||
public string? Language;
|
||||
public string? System;
|
||||
public string? SystemInfo;
|
||||
public bool PermitLocked;
|
||||
public string? PermitName;
|
||||
public string Platform;
|
||||
public bool Odyssey;
|
||||
public bool CodeRed;
|
||||
public int Number;
|
||||
private static Dictionary<int, RatCase> CaseList { get; } = new ();
|
||||
|
||||
public RatCase(string cmdr, string? language, string? system, string? systemInfo, bool permitLocked, string? permitName, string platform, bool odyssey, bool codeRed, int number)
|
||||
=> (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
|
||||
=> ratsignalPipe ??= new (
|
||||
Log,
|
||||
"RatAttack",
|
||||
new PipeServer<Ratsignal>.SignalHandler(On_Ratsignal));
|
||||
|
||||
private static VoiceAttackLog Log => log ??= new (VA, "RatAttack");
|
||||
|
||||
private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
|
||||
|
||||
/*========================================\
|
||||
| required VoiceAttack plugin shenanigans |
|
||||
\========================================*/
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s GUID, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The GUID.</returns>
|
||||
public static Guid VA_Id()
|
||||
=> new ("{F2ADF0AE-4837-4E4A-9C87-8A7E2FA63E5F}");
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s display name, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The display name.</returns>
|
||||
public static string VA_DisplayName()
|
||||
=> $"RatAttack {VERSION}";
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s description, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The description.</returns>
|
||||
public static string VA_DisplayInfo()
|
||||
=> "RatAttack: a plugin to handle FuelRats cases.";
|
||||
|
||||
/// <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)
|
||||
{
|
||||
get => $"#{Number}, {Platform}{(Odyssey ? " (Odyssey)" : "")}{(CodeRed ? ", code red" : "")}, {System ?? "None"}{(SystemInfo != null ? $" ({SystemInfo}{(PermitLocked ? ", permit required" : "")})" : "")}";
|
||||
VA = vaProxy;
|
||||
Log.Notice("Initializing …");
|
||||
VA.SetText("RatAttack.version", VERSION.ToString());
|
||||
vaProxy.ProfileChanged += new Action<Guid?, Guid?, string, string>(On_ProfileChanged);
|
||||
Log.Notice("Init successful.");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> ShortInfo;
|
||||
}
|
||||
|
||||
public class Ratsignal : IPipable
|
||||
/// <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)
|
||||
{
|
||||
public string Signal { get; set; }
|
||||
public bool Announce { get; set; }
|
||||
private readonly char separator = '\x1F';
|
||||
VA = vaProxy;
|
||||
|
||||
public Ratsignal()
|
||||
=> (Signal, Announce) = ("", false);
|
||||
|
||||
public Ratsignal(string signal, bool announce)
|
||||
=> (Signal, Announce) = (signal, announce);
|
||||
|
||||
public void ParseString(string serialization)
|
||||
{
|
||||
string context = vaProxy.Context.ToLower();
|
||||
Log.Debug($"Running context '{context}' …");
|
||||
try
|
||||
{
|
||||
string[] parts = serialization.Split(separator);
|
||||
Signal = parts[0];
|
||||
Announce = Boolean.Parse(parts[1]);
|
||||
switch (context)
|
||||
{
|
||||
case "getcasedata":
|
||||
// plugin methods
|
||||
Context_GetCaseData();
|
||||
break;
|
||||
case "parseratsignal":
|
||||
Context_ParseRatsignal();
|
||||
break;
|
||||
case "startup":
|
||||
Context_Startup();
|
||||
break;
|
||||
case "edsm.getnearestcmdr":
|
||||
// EDSM
|
||||
Context_EDSM_GetNearestCMDR();
|
||||
break;
|
||||
case "log.log":
|
||||
// log
|
||||
Context_Log();
|
||||
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)
|
||||
{
|
||||
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e);
|
||||
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Signal}{separator}{Announce}";
|
||||
/// <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)
|
||||
{
|
||||
Log.Debug("Starting teardown …");
|
||||
Log.Debug("Closing RATSIGNAL pipe …");
|
||||
RatsignalPipe.Stop();
|
||||
Log.Debug("Teardown finished.");
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Parses a RATSIGNAL and extracts case data for storage.
|
||||
/// </summary>
|
||||
/// <param name="ratsignal">The incoming RATSIGNAL.</param>
|
||||
/// <returns>The case number.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown on invalid RATSIGNAL.</exception>
|
||||
private static int ParseRatsignal(string ratsignal)
|
||||
{
|
||||
if (!RatsignalRegex.IsMatch(ratsignal))
|
||||
{
|
||||
throw new ArgumentException($"Invalid RATSIGNAL format: '{ratsignal}'.", "ratsignal");
|
||||
}
|
||||
|
||||
Match match = RatsignalRegex.Match(ratsignal);
|
||||
|
||||
|
@ -103,12 +194,12 @@ namespace RatAttack
|
|||
|
||||
int number = int.Parse(match.Groups["number"].Value);
|
||||
|
||||
if (String.IsNullOrEmpty(system))
|
||||
if (string.IsNullOrEmpty(system))
|
||||
{
|
||||
system = "None";
|
||||
}
|
||||
|
||||
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}).");
|
||||
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}).");
|
||||
|
||||
CaseList[number] = new RatCase(cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number);
|
||||
|
||||
|
@ -141,18 +232,19 @@ namespace RatAttack
|
|||
| plugin contexts |
|
||||
\================*/
|
||||
|
||||
private static void Context_EDSM_GetNearestCMDR(dynamic vaProxy)
|
||||
private static void Context_EDSM_GetNearestCMDR()
|
||||
{
|
||||
int caseNo = vaProxy.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
|
||||
string cmdrList = vaProxy.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs");
|
||||
int caseNo = VA!.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
|
||||
string cmdrList = VA!.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs");
|
||||
string[] cmdrs = cmdrList.Split(';');
|
||||
if (cmdrs.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException("~cmdrs");
|
||||
}
|
||||
|
||||
string system = CaseList[caseNo]?.System ?? throw new ArgumentException($"Case #{caseNo} has no system information", "~caseNo");
|
||||
|
||||
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\edsm-getnearest.exe";
|
||||
string path = $@"{VA!.SessionState["VA_SOUNDS"]}\Scripts\edsm-getnearest.exe";
|
||||
string arguments = $@"--short --text --system ""{system}"" ""{string.Join(@""" """, cmdrs)}""";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
@ -164,7 +256,7 @@ namespace RatAttack
|
|||
|
||||
string message = stdout;
|
||||
string? errorMessage = null;
|
||||
bool error = true;
|
||||
bool error;
|
||||
|
||||
switch (p.ExitCode)
|
||||
{
|
||||
|
@ -185,32 +277,31 @@ namespace RatAttack
|
|||
Log.Error(stderr);
|
||||
errorMessage = "Unrecoverable error in plugin.";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
vaProxy.SetText("~message", message);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetText("~message", message);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
VA!.SetInt("~exitCode", p.ExitCode);
|
||||
}
|
||||
|
||||
private static void Context_GetCaseData(dynamic vaProxy)
|
||||
private static void Context_GetCaseData()
|
||||
{
|
||||
int cn = vaProxy.GetInt("~caseNumber");
|
||||
int cn = VA!.GetInt("~caseNumber");
|
||||
|
||||
if (CaseList.ContainsKey(cn))
|
||||
{
|
||||
RatCase rc = CaseList[cn];
|
||||
|
||||
vaProxy.SetInt("~~caseNumber", rc.Number);
|
||||
vaProxy.SetText("~~cmdr", rc.Cmdr);
|
||||
vaProxy.SetText("~~system", rc?.System?.ToLower());
|
||||
vaProxy.SetText("~~systemInfo", rc?.SystemInfo);
|
||||
vaProxy.SetBoolean("~~permitLocked", rc?.PermitLocked);
|
||||
vaProxy.SetText("~~permitName", rc?.PermitName);
|
||||
vaProxy.SetText("~~platform", rc?.Platform);
|
||||
vaProxy.SetBoolean("~~odyssey", rc?.Odyssey);
|
||||
vaProxy.SetBoolean("~~codeRed", rc?.CodeRed);
|
||||
VA!.SetInt("~~caseNumber", rc.Number);
|
||||
VA!.SetText("~~cmdr", rc.Cmdr);
|
||||
VA!.SetText("~~system", rc?.System?.ToLower());
|
||||
VA!.SetText("~~systemInfo", rc?.SystemInfo);
|
||||
VA!.SetBoolean("~~permitLocked", rc?.PermitLocked);
|
||||
VA!.SetText("~~permitName", rc?.PermitName);
|
||||
VA!.SetText("~~platform", rc?.Platform);
|
||||
VA!.SetBoolean("~~odyssey", rc?.Odyssey);
|
||||
VA!.SetBoolean("~~codeRed", rc?.CodeRed);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -218,10 +309,10 @@ namespace RatAttack
|
|||
}
|
||||
}
|
||||
|
||||
private static void Context_Log(dynamic vaProxy)
|
||||
private static void Context_Log()
|
||||
{
|
||||
string message = vaProxy.GetText("~message");
|
||||
string level = vaProxy.GetText("~level");
|
||||
string message = VA!.GetText("~message");
|
||||
string level = VA!.GetText("~level");
|
||||
|
||||
if (level == null)
|
||||
{
|
||||
|
@ -233,7 +324,10 @@ namespace RatAttack
|
|||
{
|
||||
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
|
||||
}
|
||||
catch (ArgumentNullException) { throw; }
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Log.Error($"Invalid log level '{level}'.");
|
||||
|
@ -241,92 +335,111 @@ namespace RatAttack
|
|||
}
|
||||
}
|
||||
|
||||
private static void Context_Startup(dynamic vaProxy)
|
||||
private static void Context_Startup()
|
||||
{
|
||||
Log.Notice("Starting up …");
|
||||
VA = vaProxy;
|
||||
_ = RatsignalPipe.Run();
|
||||
Log.Notice("Finished startup.");
|
||||
}
|
||||
|
||||
private static void Context_ParseRatsignal(dynamic vaProxy)
|
||||
private static void Context_ParseRatsignal()
|
||||
{
|
||||
Log.Warn("Passing a RATSIGNAL from VoiceAttack (through the clipboard or a file) is DEPRECATED and will no longer be supported in the future.");
|
||||
On_Ratsignal(new Ratsignal(vaProxy.GetText("~ratsignal"), vaProxy.GetBoolean("~announceRatsignal")));
|
||||
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")));
|
||||
}
|
||||
|
||||
/*========================================\
|
||||
| required VoiceAttack plugin shenanigans |
|
||||
\========================================*/
|
||||
|
||||
static readonly Version VERSION = new Version("6.3");
|
||||
|
||||
public static Guid VA_Id()
|
||||
=> new Guid("{F2ADF0AE-4837-4E4A-9C87-8A7E2FA63E5F}");
|
||||
public static string VA_DisplayName()
|
||||
=> $"RatAttack {VERSION}";
|
||||
public static string VA_DisplayInfo()
|
||||
=> "RatAttack: a plugin to handle FuelRats cases.";
|
||||
|
||||
public static void VA_Init1(dynamic vaProxy)
|
||||
/// <summary>
|
||||
/// Encapsulates a RATSIGNAL for sending between the CLI helper tool and
|
||||
/// the plugin via named pipe.
|
||||
/// </summary>
|
||||
public class Ratsignal : IPipable
|
||||
{
|
||||
VA = vaProxy;
|
||||
Log.Notice("Initializing …");
|
||||
VA.SetText("RatAttack.version", VERSION.ToString());
|
||||
vaProxy.ProfileChanged += new Action<Guid?, Guid?, String, String>(On_ProfileChanged);
|
||||
Log.Notice("Init successful.");
|
||||
}
|
||||
private readonly char separator = '\x1F';
|
||||
|
||||
public static void VA_Invoke1(dynamic vaProxy)
|
||||
/// <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)
|
||||
{
|
||||
string context = vaProxy.Context.ToLower();
|
||||
Log.Debug($"Running context '{context}' …");
|
||||
try
|
||||
{
|
||||
switch (context)
|
||||
{
|
||||
// plugin methods
|
||||
case "getcasedata":
|
||||
Context_GetCaseData(vaProxy);
|
||||
break;
|
||||
case "parseratsignal":
|
||||
Context_ParseRatsignal(vaProxy);
|
||||
break;
|
||||
case "startup":
|
||||
Context_Startup(vaProxy);
|
||||
break;
|
||||
// EDSM
|
||||
case "edsm.getnearestcmdr":
|
||||
Context_EDSM_GetNearestCMDR(vaProxy);
|
||||
break;
|
||||
// log
|
||||
case "log.log":
|
||||
Context_Log(vaProxy);
|
||||
break;
|
||||
// invalid
|
||||
default:
|
||||
Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException e)
|
||||
{
|
||||
Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
|
||||
string[] parts = serialization.Split(this.separator);
|
||||
this.Signal = parts[0];
|
||||
this.Announce = bool.Parse(parts[1]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
|
||||
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void VA_Exit1(dynamic vaProxy)
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
=> $"{this.Signal}{this.separator}{this.Announce}";
|
||||
}
|
||||
|
||||
private class RatCase
|
||||
{
|
||||
Log.Debug("Starting teardown …");
|
||||
Log.Debug("Closing RATSIGNAL pipe …");
|
||||
RatsignalPipe.Stop();
|
||||
Log.Debug("Teardown finished.");
|
||||
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 static void VA_StopCommand() { }
|
||||
public override string ToString()
|
||||
=> this.ShortInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
|
@ -34,7 +33,6 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
|
|
|
@ -1,32 +1,162 @@
|
|||
#nullable enable
|
||||
// <copyright file="SpanshAttack.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using alterNERDtive.util;
|
||||
using alterNERDtive.edts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
using alterNERDtive.edts;
|
||||
using alterNERDtive.util;
|
||||
|
||||
namespace SpanshAttack
|
||||
{
|
||||
/// <summary>
|
||||
/// VoiceAttack plugin for the SpanshAttack profile.
|
||||
/// </summary>
|
||||
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 VoiceAttackLog Log
|
||||
=> log ??= new VoiceAttackLog(VA, "SpanshAttack");
|
||||
private static VoiceAttackLog? log;
|
||||
private static VoiceAttackLog Log => log ??= new (VA, "SpanshAttack");
|
||||
|
||||
private static VoiceAttackCommands Commands
|
||||
=> commands ??= new VoiceAttackCommands(VA, Log);
|
||||
private static VoiceAttackCommands? commands;
|
||||
private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
|
||||
|
||||
/*========================================\
|
||||
| required VoiceAttack plugin shenanigans |
|
||||
\========================================*/
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s 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 plugin’s display name, as required by the VoiceAttack plugin API.
|
||||
/// </summary>
|
||||
/// <returns>The display name.</returns>
|
||||
public static string VA_DisplayName()
|
||||
=> $"SpanshAttack {VERSION}";
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s 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 |
|
||||
\================*/
|
||||
|
||||
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;
|
||||
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");
|
||||
}
|
||||
|
||||
vaProxy.SetInt("~x", system.Coords.X);
|
||||
vaProxy.SetInt("~y", system.Coords.Y);
|
||||
vaProxy.SetInt("~z", system.Coords.Z);
|
||||
vaProxy.SetInt("~precision", system.Coords.Precision);
|
||||
VA!.SetInt("~x", system.Coords.X);
|
||||
VA!.SetInt("~y", system.Coords.Y);
|
||||
VA!.SetInt("~z", system.Coords.Z);
|
||||
VA!.SetInt("~precision", system.Coords.Precision);
|
||||
|
||||
success = true;
|
||||
} catch (ArgumentException e)
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
errorType = "invalid name";
|
||||
errorMessage = e.Message;
|
||||
} catch (Exception e)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errorType = "connection error";
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
|
||||
vaProxy.SetBoolean("~success", success);
|
||||
VA!.SetBoolean("~success", success);
|
||||
if (!string.IsNullOrWhiteSpace(errorType))
|
||||
{
|
||||
Log.Error(errorMessage!);
|
||||
vaProxy.SetText("~errorType", errorType);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
VA!.SetText("~errorType", errorType);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Context_Log(dynamic vaProxy)
|
||||
private static void Context_Log()
|
||||
{
|
||||
string message = vaProxy.GetText("~message");
|
||||
string level = vaProxy.GetText("~level");
|
||||
string message = VA!.GetText("~message");
|
||||
string level = VA!.GetText("~level");
|
||||
|
||||
if (level == null)
|
||||
{
|
||||
|
@ -85,7 +217,10 @@ namespace SpanshAttack
|
|||
{
|
||||
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
|
||||
}
|
||||
catch (ArgumentNullException) { throw; }
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
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 y = vaProxy.GetInt("~y") ?? throw new ArgumentNullException("~y");
|
||||
int z = vaProxy.GetInt("~z") ?? throw new ArgumentNullException("~z");
|
||||
int x = VA!.GetInt("~x") ?? throw new ArgumentNullException("~x");
|
||||
int y = VA!.GetInt("~y") ?? throw new ArgumentNullException("~y");
|
||||
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}";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
||||
Dictionary<char, decimal> coords = new Dictionary<char, decimal> { { 'x', 0 }, { 'y', 0 }, { 'z', 0 } };
|
||||
string system = "";
|
||||
Dictionary<char, decimal> coords = new () { { 'x', 0 }, { 'y', 0 }, { 'z', 0 } };
|
||||
string system = string.Empty;
|
||||
decimal distance = 0;
|
||||
bool error = false;
|
||||
string errorMessage = "";
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
p.Start();
|
||||
string stdout = p.StandardOutput.ReadToEnd();
|
||||
|
@ -138,28 +273,28 @@ namespace SpanshAttack
|
|||
break;
|
||||
}
|
||||
|
||||
vaProxy.SetText("~system", system);
|
||||
vaProxy.SetDecimal("~x", coords['x']);
|
||||
vaProxy.SetDecimal("~y", coords['y']);
|
||||
vaProxy.SetDecimal("~z", coords['z']);
|
||||
vaProxy.SetDecimal("~distance", distance);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetText("~system", system);
|
||||
VA!.SetDecimal("~x", coords['x']);
|
||||
VA!.SetDecimal("~y", coords['y']);
|
||||
VA!.SetDecimal("~z", coords['z']);
|
||||
VA!.SetDecimal("~distance", distance);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
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}""";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
||||
bool exists = true;
|
||||
bool error = false;
|
||||
string errorMessage = "";
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
p.Start();
|
||||
string stdout = p.StandardOutput.ReadToEnd();
|
||||
|
@ -186,84 +321,16 @@ namespace SpanshAttack
|
|||
break;
|
||||
}
|
||||
|
||||
vaProxy.SetBoolean("~systemExists", exists);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetBoolean("~systemExists", exists);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
VA!.SetInt("~exitCode", p.ExitCode);
|
||||
}
|
||||
|
||||
private static void Context_Startup(dynamic vaProxy)
|
||||
private static void Context_Startup()
|
||||
{
|
||||
Log.Notice("Starting up …");
|
||||
VA = vaProxy;
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -30,7 +29,6 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
|
|
10
plugins/VoiceAttack-base/GlobalSuppressions.cs
Normal file
10
plugins/VoiceAttack-base/GlobalSuppressions.cs
Normal 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")]
|
|
@ -16,8 +16,9 @@
|
|||
<TabItem Name="StreamAttack" Header="StreamAttack"></TabItem>
|
||||
</TabControl>
|
||||
<WrapPanel VerticalAlignment="Bottom" HorizontalAlignment="Right">
|
||||
<Button Name="cancelButton" Click="cancelButton_Click" Padding="5" Margin="5">Cancel</Button>
|
||||
<Button Name="okButton" Click="okButton_Click" Padding="5" Margin="5">OK</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" Width="100">Done</Button>
|
||||
<Button Name="cancelButton" Click="CancelButton_Click" Padding="5" Margin="5" Width="100">Cancel</Button>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
using System;
|
||||
// <copyright file="SettingsDialog.xaml.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
@ -6,33 +25,27 @@ using System.Windows.Controls;
|
|||
namespace alterNERDtive
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for SettingsDialog.xaml
|
||||
/// Interaction logic for SettingsDialog.xaml.
|
||||
/// </summary>
|
||||
public partial class SettingsDialog : UserControl
|
||||
{
|
||||
private struct 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 readonly List<Setting> values = new List<Setting>();
|
||||
private util.Configuration config;
|
||||
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)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.InitializeComponent();
|
||||
|
||||
this.config = config;
|
||||
this.log = log;
|
||||
|
||||
foreach (TabItem tab in tabs.Items)
|
||||
foreach (TabItem tab in this.tabs.Items)
|
||||
{
|
||||
string profile = tab.Name;
|
||||
if (profile == "general")
|
||||
|
@ -57,7 +70,7 @@ namespace alterNERDtive
|
|||
checkBox.IsChecked = value;
|
||||
checkBox.VerticalAlignment = VerticalAlignment.Center;
|
||||
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.Content = option.Description;
|
||||
|
@ -76,7 +89,7 @@ namespace alterNERDtive
|
|||
TextBox input = new TextBox();
|
||||
input.Text = value.ToString();
|
||||
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);
|
||||
}
|
||||
|
@ -86,17 +99,11 @@ namespace alterNERDtive
|
|||
}
|
||||
}
|
||||
|
||||
private void cancelButton_Click(object sender, RoutedEventArgs e)
|
||||
private bool ApplySettings()
|
||||
{
|
||||
Window.GetWindow(this).Close();
|
||||
log.Log("Settings dialog cancelled.", util.LogLevel.DEBUG);
|
||||
}
|
||||
bool success = true;
|
||||
|
||||
private void okButton_Click(object sender, RoutedEventArgs reargs)
|
||||
{
|
||||
bool error = false;
|
||||
|
||||
foreach (Setting setting in values)
|
||||
foreach (Setting setting in this.values)
|
||||
{
|
||||
dynamic state = null;
|
||||
|
||||
|
@ -129,21 +136,51 @@ namespace alterNERDtive
|
|||
|
||||
if (state != setting.Value)
|
||||
{
|
||||
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.log.Log($@"Configuration changed via settings dialog: ""{setting.Profile}.{setting.Option.Name}"" → ""{state}""", util.LogLevel.DEBUG);
|
||||
this.config.SetConfig(setting.Profile, setting.Option.Name, state);
|
||||
}
|
||||
}
|
||||
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);
|
||||
error = true;
|
||||
this.log.Log($@"Invalid value for ""{setting.Profile}.{setting.Option.Name}"": ""{((TextBox)setting.UiElement).Text}""", util.LogLevel.ERROR);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
|
@ -33,7 +32,6 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
|
@ -58,7 +56,10 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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="SettingsDialog.xaml.cs">
|
||||
<DependentUpon>SettingsDialog.xaml</DependentUpon>
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
#nullable enable
|
||||
// <copyright file="base.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using alterNERDtive.util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -9,31 +27,192 @@ using System.Net;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using alterNERDtive.util;
|
||||
|
||||
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
|
||||
{
|
||||
private static dynamic? VA { get; set; }
|
||||
private static readonly Dictionary<Guid, string> Profiles = new Dictionary<Guid, string> {
|
||||
private static readonly Version VERSION = new ("4.4");
|
||||
|
||||
private static readonly Dictionary<Guid, string> Profiles = new ()
|
||||
{
|
||||
{ new Guid("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}"), "alterNERDtive-base" },
|
||||
{ new Guid("{f31b575b-6ce4-44eb-91fc-7459e55013cf}"), "EliteAttack" },
|
||||
{ new Guid("{87276668-2a6e-4d80-af77-80651daa58b7}"), "RatAttack" },
|
||||
{ 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 VoiceAttackCommands Commands => commands ??= new VoiceAttackCommands(VA, Log);
|
||||
private static VoiceAttackCommands? commands;
|
||||
private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
|
||||
|
||||
private static Configuration Config => config ??= new Configuration(VA, Log, Commands, "alterNERDtive-base");
|
||||
private static Configuration? config;
|
||||
private static Configuration Config => config ??= new (VA, Log, Commands, "alterNERDtive-base");
|
||||
|
||||
private static VoiceAttackLog Log => log ??= new (VA, "alterNERDtive-base");
|
||||
|
||||
private static dynamic? VA { get; set; }
|
||||
|
||||
/*========================================\
|
||||
| required VoiceAttack plugin shenanigans |
|
||||
\========================================*/
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s 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 plugin’s 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 plugin’s 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)
|
||||
{
|
||||
|
@ -43,20 +222,21 @@ namespace alterNERDtive
|
|||
foreach (KeyValuePair<Guid, string> profile in Profiles)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (vaProxy.Profile.Exists(profile.Key))
|
||||
{
|
||||
InstalledProfiles.Add(profile.Value);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
@ -69,6 +249,7 @@ namespace alterNERDtive
|
|||
Log.Debug($"Configuration has changed, '{id}.{name}': '{from}' → '{to}'");
|
||||
|
||||
dynamic o = Config.GetOption(id, name);
|
||||
|
||||
// When loaded from profile but not explicitly set, will be null.
|
||||
// Then load default.
|
||||
// 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 isn’t up yet
|
||||
// if null, EDDI isn’t up yet
|
||||
if (option == "alterNERDtive-base.eddi.quietMode#" && VA!.GetText("EDDI version") != null)
|
||||
{
|
||||
Log.Debug($"Resetting speech responder ({(to ?? false ? "off" : "on")}) …");
|
||||
Commands.Run("alterNERDtive-base.setEDDISpeechResponder");
|
||||
|
@ -137,7 +319,8 @@ namespace alterNERDtive
|
|||
|
||||
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 } });
|
||||
}
|
||||
|
||||
|
@ -145,35 +328,37 @@ namespace alterNERDtive
|
|||
| plugin contexts |
|
||||
\================*/
|
||||
|
||||
private static void Context_Config_Dialog(dynamic vaProxy)
|
||||
private static void Context_Config_Dialog()
|
||||
{
|
||||
Thread dialogThread = new Thread(new ThreadStart(() =>
|
||||
{
|
||||
_ = new System.Windows.Window
|
||||
System.Windows.Window options = new ()
|
||||
{
|
||||
Title = "alterNERDtive Profile Options",
|
||||
Content = new SettingsDialog(Config, Log),
|
||||
SizeToContent = System.Windows.SizeToContent.WidthAndHeight,
|
||||
ResizeMode = System.Windows.ResizeMode.NoResize,
|
||||
WindowStyle = System.Windows.WindowStyle.ToolWindow
|
||||
}.ShowDialog();
|
||||
WindowStyle = System.Windows.WindowStyle.ToolWindow,
|
||||
};
|
||||
options.ShowDialog();
|
||||
options.Activate();
|
||||
}));
|
||||
dialogThread.SetApartmentState(ApartmentState.STA);
|
||||
dialogThread.IsBackground = true;
|
||||
dialogThread.Start();
|
||||
}
|
||||
|
||||
private static void Context_Config_Dump(dynamic vaProxy)
|
||||
private static void Context_Config_Dump()
|
||||
{
|
||||
Config.DumpConfig();
|
||||
}
|
||||
|
||||
private static void Context_Config_List(dynamic vaProxy)
|
||||
private static void Context_Config_List()
|
||||
{
|
||||
Config.ListConfig();
|
||||
}
|
||||
|
||||
private static void Context_Config_Setup(dynamic vaProxy)
|
||||
private static void Context_Config_Setup()
|
||||
{
|
||||
Log.Debug("Loading default configuration …");
|
||||
Config.ApplyAllDefaults();
|
||||
|
@ -181,25 +366,45 @@ namespace alterNERDtive
|
|||
{
|
||||
Config.SetVoiceTriggers(type);
|
||||
}
|
||||
|
||||
Config.LoadFromProfile();
|
||||
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}' …");
|
||||
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 ===
|
||||
// ===========
|
||||
|
||||
// SpanshAttack
|
||||
string edtsPath = $@"{vaProxy.SessionState["VA_SOUNDS"]}\scripts\edts.exe";
|
||||
string edtsPath = $@"{VA!.SessionState["VA_SOUNDS"]}\scripts\edts.exe";
|
||||
if (File.Exists(edtsPath))
|
||||
{
|
||||
File.Delete(edtsPath);
|
||||
|
@ -240,6 +445,7 @@ namespace alterNERDtive
|
|||
Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string option in new string[] { "CMDRs", "platforms" })
|
||||
{
|
||||
string name = $"{prefix}.{option}";
|
||||
|
@ -267,6 +473,7 @@ namespace alterNERDtive
|
|||
Commands.Run("alterNERDtive-base.unsetVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string option in new string[] { "announceJumpsLeft" })
|
||||
{
|
||||
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);
|
||||
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>"
|
||||
}
|
||||
|
||||
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}""";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
||||
int bodyCount = 0;
|
||||
bool error = false;
|
||||
string errorMessage = "";
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
p.Start();
|
||||
string stdout = p.StandardOutput.ReadToEnd();
|
||||
|
@ -344,26 +551,26 @@ namespace alterNERDtive
|
|||
break;
|
||||
}
|
||||
|
||||
vaProxy.SetInt("~bodyCount", bodyCount);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetInt("~bodyCount", bodyCount);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
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 toSystem = vaProxy.GetText("~toSystem") ?? throw new ArgumentNullException("~toSystem");
|
||||
int roundTo = vaProxy.GetInt("~roundTo") ?? 2;
|
||||
string fromSystem = VA!.GetText("~fromSystem") ?? throw new ArgumentNullException("~fromSystem");
|
||||
string toSystem = VA!.GetText("~toSystem") ?? throw new ArgumentNullException("~toSystem");
|
||||
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}""";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
||||
decimal distance = 0;
|
||||
bool error = false;
|
||||
string errorMessage = "";
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
p.Start();
|
||||
string stdout = p.StandardOutput.ReadToEnd();
|
||||
|
@ -386,19 +593,18 @@ namespace alterNERDtive
|
|||
Log.Error(stderr);
|
||||
errorMessage = "Unrecoverable error in plugin.";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
vaProxy.SetDecimal("~distance", distance);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetDecimal("~distance", distance);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
VA!.SetInt("~exitCode", p.ExitCode);
|
||||
}
|
||||
|
||||
private static void Context_Log(dynamic vaProxy)
|
||||
private static void Context_Log()
|
||||
{
|
||||
string message = vaProxy.GetText("~message");
|
||||
string level = vaProxy.GetText("~level");
|
||||
string message = VA!.GetText("~message");
|
||||
string level = VA!.GetText("~level");
|
||||
|
||||
if (level == null)
|
||||
{
|
||||
|
@ -410,7 +616,10 @@ namespace alterNERDtive
|
|||
{
|
||||
Log.Log(message, (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()));
|
||||
}
|
||||
catch (ArgumentNullException) { throw; }
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
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");
|
||||
int minage = vaProxy.GetInt("~minage") ?? throw new ArgumentNullException("~minage");
|
||||
string system = VA!.GetText("~system") ?? throw new ArgumentNullException("~system");
|
||||
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}";
|
||||
|
||||
Process p = PythonProxy.SetupPythonScript(path, arguments);
|
||||
|
||||
|
||||
p.Start();
|
||||
string stdout = p.StandardOutput.ReadToEnd();
|
||||
string stderr = p.StandardError.ReadToEnd();
|
||||
|
@ -457,16 +665,15 @@ namespace alterNERDtive
|
|||
Log.Error(stderr);
|
||||
errorMessage = "Unrecoverable error in plugin.";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
vaProxy.SetText("~message", message);
|
||||
vaProxy.SetBoolean("~error", error);
|
||||
vaProxy.SetText("~errorMessage", errorMessage);
|
||||
vaProxy.SetInt("~exitCode", p.ExitCode);
|
||||
VA!.SetText("~message", message);
|
||||
VA!.SetBoolean("~error", error);
|
||||
VA!.SetText("~errorMessage", errorMessage);
|
||||
VA!.SetInt("~exitCode", p.ExitCode);
|
||||
}
|
||||
|
||||
private static void Context_Startup(dynamic vaProxy)
|
||||
private static void Context_Startup()
|
||||
{
|
||||
Log.Notice("Starting up …");
|
||||
CheckProfiles(VA);
|
||||
|
@ -475,112 +682,9 @@ namespace alterNERDtive
|
|||
Log.Notice("Finished startup.");
|
||||
}
|
||||
|
||||
private static void Context_Update_Check(dynamic vaProxy)
|
||||
private static void Context_Update_Check()
|
||||
{
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace alterNERDtive.edts
|
||||
{
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
#nullable enable
|
||||
// <copyright file="util.cs" company="alterNERDtive">
|
||||
// Copyright 2019–2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -9,6 +28,7 @@ using System.Linq;
|
|||
|
||||
namespace alterNERDtive.util
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "file contains collection of utility classes")]
|
||||
public class Configuration
|
||||
{
|
||||
private readonly dynamic VA;
|
||||
|
@ -20,8 +40,8 @@ namespace alterNERDtive.util
|
|||
{
|
||||
"alterNERDtive-base",
|
||||
new OptDict<string, Option>{
|
||||
{ new Option<decimal>("delays.keyPressDuration", (decimal)0.01, voiceTrigger: "key press duration",
|
||||
description: "The time keys will be held down for.") },
|
||||
/*{ new Option<decimal>("delays.keyPressDuration", (decimal)0.01, voiceTrigger: "key press duration",
|
||||
description: "The time keys will be held down for.") },*/
|
||||
{ new Option<decimal>("delays.quitToDesktop", (decimal)10.0, voiceTrigger: "quit to desktop delay",
|
||||
description: "The delay before restarting the game after hitting “Exit to Desktop”, in seconds.\nDefault: 10.0. (Used by the `restart from desktop` command)") },
|
||||
{ new Option<bool>("eddi.quietMode", true, voiceTrigger: "eddi quiet mode",
|
||||
|
@ -69,8 +89,12 @@ namespace alterNERDtive.util
|
|||
description: "Automatically restock after docking at a station.") },
|
||||
{ new Option<bool>("autoHangar", true, voiceTrigger: "auto move to hangar",
|
||||
description: "Automatically move the ship to the hangar after docking at a station.") },
|
||||
{ new Option<bool>("autoStationService", true, voiceTrigger: "auto enter station services",
|
||||
{ new Option<bool>("autoStationServices", true, voiceTrigger: "auto enter station services",
|
||||
description: "Automatically enter the Station Services menu after docking at a station.") },
|
||||
{ new Option<bool>("autoRetractLandingGear", true, voiceTrigger: "auto retract landing gear",
|
||||
description: "Automatically retract landing gear when lifting off a planet / undocking from a station.") },
|
||||
{ new Option<bool>("autoDisableSrvLights", true, voiceTrigger: "auto disable s r v lights",
|
||||
description: "Automatically turn SRV lights off when deploying one.") },
|
||||
{ new Option<bool>("flightAssistOff", false, voiceTrigger: "flight assist off",
|
||||
description: "Permanent Flight Assist off mode. You should really do that, it’s great.") },
|
||||
{ new Option<bool>("hyperspaceDethrottle", true, voiceTrigger: "hyper space dethrottle",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
16
stylecop.json
Normal file
16
stylecop.json
Normal 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": "2019–2022"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue