Compare commits

...

64 commits

Author SHA1 Message Date
f034ae4220
4.5! 2022-09-19 18:08:43 +02:00
d97499778c
docs: clarified when StreamAttack updates information
fixes #155
2022-09-15 15:17:39 +02:00
d78cf17b6b
docs: added reloading the profile / restarting VA to installation instructions
fixes #154
2022-09-15 15:15:41 +02:00
0d44828422
docs: clarified that neutron router plugin is optional
fixes #153
2022-09-15 15:13:20 +02:00
022c3ac25c
fixed VERSION 2022-09-15 15:08:54 +02:00
fd0bbaa3a9
added StyleCop.ruleset to make that one shut up 2022-09-15 15:07:15 +02:00
cc2694fc49
RatAttack: support for new Horizons 3.8 / 4.0 / Odyssey RATSIGNALs
fixes #159
2022-09-15 15:06:49 +02:00
9db6785ef1
RatAttack: fixed RATSIGNAL regex
Apparently `^` can be part of both CMDR names and IRC nicks.
2022-09-10 03:43:19 +02:00
1135efd761
EliteAttack: fixed potential race condition in the discovery scan event command queue 2022-09-06 00:29:51 +02:00
7c361e9bf7
EliteAttack: added option to exclude settlements from the outdated stations list
fixes #119
2022-09-05 19:21:12 +02:00
54cd1e6fa2
EliteAttack: target nearest […] commands now log the result 2022-08-31 10:26:12 +02:00
3775d3f911
docs: added troubleshooting section about Geforce Now 2022-08-23 22:26:11 +02:00
alterNERDtive
1357bde807
Merge pull request #151 from alterNERDtive/feature/auto-pull-request 2022-07-13 22:26:07 +02:00
db85c2b793
auto-pull-request workflow: fixed branch name 2022-07-13 22:25:13 +02:00
4f9e4799bf
workflows: open pr on branch push 2022-07-13 22:23:45 +02:00
5af0aff95c
workflows: fixed release author 2022-07-13 21:15:35 +02:00
e2789623cd
cleaned up dependency mess 2022-07-12 00:40:55 +02:00
c615af5324
updated CHANGELOG 2022-07-11 19:52:39 +02:00
Ant
4e9a9b4517
Black out the grey censory block 2022-07-11 19:45:29 +02:00
Ant
38ea143ea7
SpanshAttack.md: fix column width 2022-07-11 19:45:25 +02:00
Ant
0ee7701590
EliteAttack: hyperlink path fix - DOH 2022-07-11 19:45:21 +02:00
Ant
05a56d5d85
index.md: correction to hyperlink markdown syntax 2022-07-11 19:45:05 +02:00
Ant
2ec9c2afbe
EliteAttack.md: correction to hyperlink markdown syntax 2022-07-11 19:44:49 +02:00
Ant
3cbfe62ae1
docs markdown fixes & author privacy on screenshot 2022-07-11 19:44:19 +02:00
dependabot[bot]
9cc9965d7c Bump Newtonsoft.Json from 6.0.4 to 13.0.1 in /plugins/VoiceAttack-base
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.4 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.4...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-08 17:52:13 +02:00
acf43b42a0 added .github as solution items 2022-07-08 17:52:09 +02:00
55fa3579cf fixed gh-pages workflow 2022-07-08 17:45:39 +02:00
d1af3ebdcc Merge branch 'release' into devel 2022-07-04 01:32:38 +02:00
b10e2bb2c4 added ko-fi / sponsors 2022-07-04 01:32:29 +02:00
91c47efbef fixed create-release workflow
fixes #148
2022-07-04 00:33:28 +02:00
1cdd9c98bd EliteAttack: fixed Docked event for Oddity settlements
fixes #145
2022-06-16 12:10:49 +02:00
35c26e930f mics fixes 2022-06-16 12:06:28 +02:00
bb8d1067b5 RatAttack: made case list thread safe
My specific setup leads to lots of incoming cases at once when starting VA after boot, when the IRC backlog is parsed.

Probably not relevant in the wild, but still a huge 🤦.
2022-06-03 16:45:12 +02:00
6ec8d7c35e stylecop compliance, round 3 of >3 2022-06-02 17:41:49 +02:00
55f10a1117 all plugins: fixed possible race condition introduced in 4.4 2022-06-02 15:28:17 +02:00
35057b3f35 dependabot 2022-06-01 09:12:01 +02:00
a2f6cc864c auto-deploy gh-pages 2022-06-01 09:11:57 +02:00
9beb6dfa41 Merge branch 'devel' into release 2022-05-31 19:31:28 +02:00
2dae2abc4c workflows: fixed(?) release glob 2022-05-31 19:29:08 +02:00
99305cfb5d 4.4! 2022-05-31 19:23:00 +02:00
bc5addd22a base: removed delays.keyPressDuration option since that isn’t actually used rn 2022-05-30 23:38:21 +02:00
64a096dae7 StyleCop compliance, round 2 of 3 … 2022-05-30 23:22:48 +02:00
33a4fb8e3d base: made config GUI buttons wider 2022-05-30 21:59:52 +02:00
3cbca1e542 base: config GUI now .Activate()s immediately 2022-05-30 21:58:46 +02:00
55a031686c auto draft a release on tag push 2022-05-30 21:28:09 +02:00
02a401a047 housekeeping 2022-05-30 21:25:08 +02:00
e36358be9f stylecop compliance, round 1 2022-05-29 22:16:17 +02:00
999d2b6883 alterNERDtive-base: configuration GUI now has an “Apply” button 2022-05-29 21:32:43 +02:00
e73b8c04f9 bump to c# 10 because why not 2022-05-29 21:14:54 +02:00
931ee4c4e7 RatAttack: added warning when running VA as Admin
fixes #138
2022-05-29 11:13:49 +02:00
751f80461f RatAttack-cli: added error message for running VA as Admin
see #138

Writing to other users’ pipes will cause an exception. You should not run VA elevated.

Needs a warning in VA too.
2022-05-29 10:51:51 +02:00
c9d39f6cc7 docs: typos 2022-05-28 15:09:00 +02:00
f8dd1ff464 docs: added upgrading EDDI event handlers to upgrading.md 2022-05-28 15:05:08 +02:00
f7328e31e2 bumped VERSION
I feel like I should do that whenever I bump it in base.cs …
2022-05-28 14:27:44 +02:00
2b0d2245e2 RatAttack: fixed superfluous “ly” output in distance to commands
fixes #140
2022-05-28 14:19:35 +02:00
dc42912cbe EliteAttack: fixed auto station services option
fixes #142
2022-05-28 14:19:08 +02:00
b3ad1ae799 switched for make to msbuild
Build file still private, since it relies on my local folder structure.
2022-05-27 20:20:41 +02:00
5421478d6e SpanshAttack: fixed getting current jump range from EDDI
Not waiting for the plugin context to finish running caused it to pretty consistently fail on the first attempt, and return the last value for attempts after that.
2022-05-22 12:49:06 +02:00
d771d0b403 docs: fixed weird character in general.md 2022-05-22 12:46:50 +02:00
b371523910 EliteAttack: added options from #133 i forgot
* auto retract landing gear
* auto disable SRV lights

fixes #133
2022-05-19 13:16:00 +02:00
8572d0ec4c CHANGELOG: typo 2022-05-19 12:54:22 +02:00
c00c1d9bbe Merge branch 'devel' into release 2022-05-19 12:49:30 +02:00
3c46b20a23 Merge branch 'devel' into release 2021-05-23 20:36:24 +02:00
1d6ee3953e updated .editorconfig 2021-05-23 20:26:54 +02:00
56 changed files with 3041 additions and 1720 deletions

View file

@ -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

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: alterNERDtive
ko_fi: alterNERDtive

12
.github/dependabot.yaml vendored Normal file
View file

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "develop"
schedule:
interval: "daily"

View file

@ -0,0 +1,20 @@
name: Pull Request on Branch Push
on:
push:
branches-ignore:
- devel
- release
jobs:
auto-pull-request:
name: Open pull request
runs-on: ubuntu-latest
steps:
- name: pull-request-action
uses: vsoch/pull-request-action@1.0.19
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BRANCH: "devel"
PULL_REQUEST_DRAFT: true
PASS_IF_EXISTS: true

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

@ -0,0 +1,23 @@
name: Create release on tag push
on:
push:
tags:
- 'releases/*.*'
- 'releases/*.*.*'
jobs:
build:
name: Create draft release
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Draft release
uses: ncipollo/release-action@v1
with:
bodyFile: "CHANGELOG.md"
draft: true
token: ${{ secrets.RELEASE_TOKEN }}

22
.github/workflows/gh-pages.yaml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Deploy github pages on tag push
on:
push:
tags:
- 'release/*.*'
- 'release/*.*.*'
jobs:
build:
name: Deploy documentation
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@nomaterial
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REQUIREMENTS: requirements.txt

1
.gitignore vendored
View file

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

View file

@ -1,4 +1,94 @@
# 4.3 (2022-05-19)
# 4.5 (2022-09-19)
This might very well be the last release of this. With how the “Horizons 4.0”
launch went, Frontiers communication around it, and just _how_ horrible 4.0 is,
I currently do not see me being motivated to actually port stuff to 4.0. And,
lets face it, 3.8 will go EoL eventually.
Anyway, I still have some programming pet projects around this stuff that I
might continue with and adapt this for, so I might sneak some fixes / updates
in, and Im not going to say never because who knows how Ill feel about this in
the future. Obviously theres also still the option to _pay_ me to do stuff =p
### Fixed
* Race condition in all plugins that might lead to commands using command-scoped
variables (`~<name>`) not working as intended. This was introduced in
refactoring work that was done for 4.4.
* Documentation proof read and fixed by @ACyprus. Thanks!
### Changed
* Some behind the scenes things regarding how builds work. This will make it
possible to build this entirely on Github (= less potential for human error)
once I have dealt with #62 (see #143 as well).
## EliteAttack 8.5
### Added
* `target nearest […]` commands now log the result (with log level “INFO”).
* `include outdated settlements` option: Include Odyssey settlements in the
outdated stations list. Default: true.
### Fixed
* `Docked` event now handles Odyssey settlements properly (they have no hangar).
(#145)
* Fixed potential race condition with the discovery scan event command queue.
Might have an impact on #64.
## RatAttack 6.4
### Fixed
* Support for new Horizons 3 / Horizons 4 / Odyssey RATSIGNALs. (#159)
* Made case list thread safe. Probably only ever impacted my own specific setup,
but still a huge 🤦.
* Apparently `^` can be part of both CMDR names and IRC nicks (fixed RATSIGNAL
regex).
-----
# 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 +142,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 clients system to
the clipboard when you open a rat case. Default: true.
@ -1045,8 +1135,7 @@ up to date in EDDN.
* Added `open [rat;] dispatch board` command. Opens the web dispatch board in
your default browser.
* Added proper handling for multiple ratsignals hitting at once. Thats mainly
an IRC client config thing,
[see the docs](docs/RatAttack.md#getting-case-data-from-irc).
an IRC client config thing, [see the docs](docs/configuration/RatAttack.md).
* Renamed `RatAttack.getInfoFromRatsignal` to
`RatAttack.announceCaseFromRatsignal`. Removed the “open case?” voice input
prompt.

25
Directory.build.props Normal file
View file

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

View file

@ -37,3 +37,6 @@ issue](https://github.com/alterNERDtive/VoiceAttack-profiles/issues). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/kXtXm54) if that is your
thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

2
StyleCop.ruleset Normal file
View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="StyleCop.Analyzers rules with default action" Description="StyleCop.Analyzers with default action. Rules with IsEnabledByDefault = false are disabled." ToolsVersion="17.0" />

View file

@ -1 +1 @@
4.3
4.5

View file

@ -35,7 +35,7 @@ the edit window ready to send. They will _not_ hit Enter on their own.
## Navigation
There are so many navigation-focused commands now, they deserve there own
There are so many navigation-focused commands now, they deserve their own
category. Basically anything that helps you plot anywhere. A lot of those are
powered by awesome EDDI so I dont have to do the work myself!
@ -72,10 +72,9 @@ Basically anything that is related to directly doing something with your ship.
* `[close;deploy;extend;open;retract;] [cargo scoop;hard points; landing gear] [up;down;]`:
Overly complicated command to handle everything related to Cargo Scoop, Hard
Points, Landing Gear. You get the gist, I guess. Works in SRV too.
* `[dis;]engage silent running`: Handles silent running.
* `[disco;discovery scan]`: Executes a discovery scan. Expects the Discovery
Scanner in your first fire group, secondary fire. [You can change
that](/configuration/EliteAttack/#settings).
that](configuration/EliteAttack.md#settings).
* `[dis;]engage silent running`: Turns silent running on and off.
* `[head;spot;] lights [on;off]`: Turns your lights on and off. Works in SRV
too, kinda; turning lights off there relies on the state updating fast enough,
@ -96,7 +95,7 @@ Basically anything that is related to directly doing something with your ship.
given “when ready” will wait for mass lock to clear and your FSD to cool down
first.
## SRV controls
## SRV Controls
Things relevant to your SRV, but not your ship.
@ -107,13 +106,12 @@ Things relevant to your SRV, but not your ship.
## Targeting
Well … targeting stuff, I guess. Not really sure why I made that its own
Well … targeting stuff, I guess. Not really sure why I made that its own
category, but oh well :)
* `target the [drive;drives;power plant;frame shift drive;f s d;shield
generator]`:*
Targets the given submodule on your current target, or your next target if you
dont have one currently. Does not persist between targets.
generator]`: Targets the given submodule on your current target, or your next
target if you dont have one currently. Does not persist between targets.
* `clear sub [module;system] target`: Clears the current submodule target.
* `target next system`: Selects the next system on your route.
* `target wing man [1;2;3]`: Targets your wingmen.
@ -272,7 +270,7 @@ the (rough) range you still have on the fumes left in your tank.
### Material Threshold
Warns you when a monitored material falls below its minimum stock level and
Warns you when a monitored material falls below its minimum stock level and
tells you when you reach your desired level or fill up.
You will have to set minimum and desired amounts in EDDIs material monitor
@ -308,7 +306,7 @@ Reports on the synthesis type and quality.
### System Scan Complete
Lists you all bodies EDDI considers worth mapping in the current system.
Lists all bodies EDDI considers worth mapping in the current system.
### Undocked

View file

@ -66,7 +66,7 @@ client or use the “General IRC Integration”, see below.
login to the game or have to take off from your current
station/port/outpost/planet.
* `call jumps [left;]`: Calls jumps for the currently open case based on a
neutron trip (requires Spanshattack) or a plotted ingame route.
neutron trip (requires SpanshAttack) or a plotted ingame route.
* `call friend [positive;negative] [in pg;in private group;in solo;in main menu;sysconf;system confirmed;]`:
Friend request confirmations, with all the
things you might want to / should call with it.

View file

@ -1,10 +1,10 @@
# SpanshAttack
This profile uses the
[ED-NeutronRouter](https://github.com/sc-pulgan/ED-NeutronRouter) plugin to plot
neutron jumps using [spansh](https://spansh.co.uk/plotter). It fully does
everything you need from within the game and VoiceAttack, you wont have to
visit the site at any point.
[ED-NeutronRouter](https://github.com/sc-pulgan/ED-NeutronRouter) plugin to
plot neutron jumps using [Spansh](https://spansh.co.uk/plotter). It does
everything you need fully from within the game and VoiceAttack, you wont have
to visit the site at any point.
## Plotting a Route

View file

@ -1,4 +1,4 @@
# StreamAttack
# StreamAttack
This profile uses the [EDDI](https://github.com/EDCD/EDDI) plugin to write
a bunch of information about your commander, your current location and your ship
@ -23,6 +23,10 @@ Default folder is `%appdata%\StreamAttack\`.
### Elite
Please do note that information in the output files is only updated when a
journal event that contains the information is detected. E.g. the distance to
your jump target is not constantly calculated, but only updated after a jump.
#### Commander
* `Elite\cmdr\name`: The current commanders name.
@ -45,4 +49,4 @@ Default folder is `%appdata%\StreamAttack\`.
* `Elite\ship\build`: Your current ships loadout (link to coriolis).
* `Elite\ship\full`: `“<name>” | <model> | <build>`.
* `Elite\ship\model`: Your current ships model.
* `Elite\ship\name`: Your current ships name.
* `Elite\ship\name`: Your current ships name.

View file

@ -14,10 +14,14 @@ Toggles:
true.
* `auto restock`: Automatically restock after docking at a station. Default:
true.
* `auto move to hangar`: Automatically move the ship to the hanger after docking
* `auto move to hangar`: Automatically move the ship to the hangar after docking
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
@ -33,6 +37,8 @@ Toggles:
Ammonia Worlds that have not been mapped yet.) Default: true.
* `outdated stations`: Announce stations with outdated data in the online
databases. Default: true.
* `include outdated settlements` option: Include Odyssey settlements in the
outdated stations list. Default: true.
* `repair reports`: Report on AFMU repairs. Default: true.
* `road to riches`: Announce bodies worth scanning if you are looking for some
starting cash on the Road to Riches. Default: false.

View file

@ -13,7 +13,7 @@ For any ships that you regularly use for neutron jumping, e.g. long range Fuel
Rat ships, I recommend telling SpanshAttack about the range they are supposed to
have with full fuel and your preferred amount of cargo/limpets.
In oder to do that, copy the `SpanshAttack.getShipRange` command from
In order to do that, copy the `SpanshAttack.getShipRange` command from
SpanshAttack or the example profile to your custom profile and add your ships.
Any ship listed in there will automatically have its jump range used instead of
EDDIs reported laden range or VoiceAttack prompting you to manually supply it.

View file

@ -7,8 +7,8 @@ configuration is stored in a bunch of VoiceAttack variables which in turn are
stored in your custom profile. You could even have different custom profiles
with their own distinct settings.
The easiest way to change setings is to say `customize settings`. That will
bring up a rudminteary settings UI.
The easiest way to change settings is to say `customize settings`. That will
bring up a rudimentary settings UI.
You change also change the configuration directly via voice commands:

View file

@ -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
@ -10,7 +10,7 @@ See [the configuration section](../configuration/general#settings).
* `paste text`: Pastes the contents of your current clipboard. Note that this
command is supposed to be used for pasting _into Elite_ and hence uses the
configured paste key. If youre using a non-standard layout that means that
you can _not_ use this command to paste text into other applications.
you _cannot_ use this command to paste text into other applications.
## Updating

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -8,9 +8,9 @@ Rats](https://fuelrats.com) and [Hull Seals](https://hullseals.space) work.
**NOTE**: Further development is on hold and Odyssey compatibility will not be
worked on for the time being. See [the corresponding issue on
Github](https://github.com/alterNERDtive/VoiceAttack-profiles/issues/113). This
might or might not change after the Horizons/Odyssey merge when console release
is upon us. Feel free to file issues for anything that is broken on Odyssey and
it will be worked on when it is worked on.
might or might not change after the Horizons/Odyssey merge. Feel free to file
issues for anything that is broken on Odyssey and it will be worked on when it
is worked on.
## Available Profiles
@ -31,10 +31,13 @@ Well, you are in the right place. You can find comprehensive documentation right
here.
If you run into any errors, please make sure you are running the latest version
of the profiles and all requirements.
of the profiles and all [requirements](requirements.md).
If your problem persists, please [file an
issue](https://github.com/alterNERDtive/VoiceAttack-profiles/issues). Thanks! :)
You can also [say “Hi” on Discord](https://discord.gg/kXtXm54) if that is your
thing.
[![GitHub Sponsors](https://img.shields.io/github/sponsors/alterNERDtive?style=for-the-badge)](https://github.com/sponsors/alterNERDtive)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S1DLYBS)

View file

@ -7,9 +7,7 @@ version](https://voiceattack.com/#download-1) off the official site and install
it.
If you are using the standalone version you should probably download the
executable installer. If you are using the Steam version of VoiceAttack, you
will have to download the zipped folder and replace your installed version with
its contents.
executable installer.
![[VoiceAttack-download.png]]
@ -55,12 +53,12 @@ hit `Enter`) and do
```
If you have installed the non-Steam version of VoiceAttack to the default folder
within “ProgramFiles” you will have to run the command prompt as admin (Windows
within “Program Files” you will have to run the command prompt as admin (Windows
key + `R`, enter “cmd”, hit `Control` + `Shift` + `Enter`).
## Install ED-NeutronRouter
[Grab the latest release release from
Grab [the latest release release from
Github](https://github.com/sc-pulgan/ED-NeutronRouter/releases/latest) (The
`EDNeutronRouter.vX.YZ.zip` under “Assets”). You will have to extract the
contents of the release .zip file to your VoiceAttack Apps folder:
@ -99,7 +97,7 @@ the profiles that you want to change and add voice triggers or hotkeys.
You can either use an existing profile, create a new one or use the provided
profile example as a basis. Regardless of which way you choose, make sure to
read the [[#Include Profiles]] section and follow the instructions there!
read the [Include Profiles](#include-profiles) section and follow the instructions there!
### Option 1: Create a New Profile Or Use an Existing One
@ -123,7 +121,7 @@ will now have to create the startup command. Hit the “New Command” button.
![[VoiceAttack-edit-new-command.png]]
You can name it anything you want but I recommend calling it “startup” or
similar, and to deactivate the “when i say”checkbox in the command options to
similar, and to deactivate the “when I say” checkbox in the command options to
make sure you do not accidentally run it via voice.
![[VoiceAttack-edit-startup.png]]
@ -148,7 +146,7 @@ action. You do not have to set any configuration options, this can be done way
more elegantly! More on this [later on](general.md#settings).
After adding the startup command you will have to right click VoiceAttacks
title bar and choose “Reload Active Profile” or restart VoiceAttack to it
title bar and choose “Reload Active Profile” or restart VoiceAttack to see it
executed for the first time.
### Option 2: Use the Profile Example
@ -185,8 +183,8 @@ button.
![[VoiceAttack-profile-options-include.png]]
Add all my profiles (“alterNERDtive-base”, “EliteDangerous”, “RatAttack”,
“SpanshAttack”,“StreamAttack”).
Add all my profiles (“alterNERDtive-base”, “EliteAttack”, “RatAttack”,
“SpanshAttack”, “StreamAttack”).
![[VoiceAttack-profile-options-includelist.png]]
@ -202,6 +200,14 @@ earlier.
![[VoiceAttack-profile-options-startup.png]]
## Reload the Profile
To make sure everything is loaded correctly, you now need to either reload the
profile by right clicking on VoiceAttacks title bar → “Reset Active Profile” or
by simply restarting VoiceAttack.
You should see a bunch of initialization messages pop up in the VoiceAttack log.
## Set Elite Keyboard Binds
You need to have keyboard binds setup at least as secondary bindings in Elites

View file

@ -1,68 +1,69 @@
# Requirements
## VoiceAttack
Obviously you will need to install [VoiceAttack](https://voiceattack.com). There
is a free trial version available, but that one is limited to a single profile
and a few commands. This is 5 profiles and … a lot of commands. You will need the
full version, available for $10 (official site) or €11.99 (Steam, IIRC $14.99
for our US-based friends).
I recommend buying on the site. Why? Because on Steam, Valve gets a 30% cut.
Unlike many other developers Gary (the developer of VoiceAttack) remedies that
by having a price on Steam that ends up paying $10 to him. So basically, you are
paying Valve out of your own pocket. Many other developers do not do that, and
by buying from them directly instead of on Steam you are literally giving them
extra money. Please do keep that in mind in the future!
You also will generally need to opt into the beta version. I am usually at the
forefront of bug reports and feature requests, and I do rely on the
fixes/additions in beta versions quite often.
## EDDI
[EDDI](https://github.com/EDCD/EDDI) is a companion application for Elite:
Dangerous, providing responses to events that occur in-game using data from the
game as well as various third-party tools. In this case, you will need to run it
as a VoiceAttack plugin.
EDDI also regularly publishes beta versions. Unless a profiles release
explicitly states it you will _not_ have to run EDDI beta.
Do note that the profiles put EDDI into quiet mode by default, disabling the
built-in speech responders. This can be changed
[via the `EDDI quiet mode` setting](configuration/general.md#general-settings-for-all-profiles).
## bindED
[bindED](https://alterNERDtive.github.io/bindED) reads your Elite Dangerous
binding files and makes them available to VoiceAttack as variables. That way
commands can be portable and you do not have to manually go through them and
change any actions that you happen to not have the standard binds for.
This plugin is _included_ in the release package. You do _not_ have to download
and install it manually, but you _can_ independently update it if a newer
version is available.
## Elite Scripts
I have written a [collection of Python
scripts](https://github.com/alterNERDtive/elite-scripts) to interface with
various 3ʳᵈ party services like EDSM or Spansh. Those are called by the profiles
for various tasks, like checking a systems body count.
In the future they will be replaced by VoiceAttack plugin code.
The scripts are _included_ in the release package. You do _not_ have to download
and install them manually, but you _can_ independently update them if a newer
version is available.
## ED-NeutronRouter
(required for SpanshAttack)
[ED-NeutronRouter](https://github.com/sc-pulgan/ED-NeutronRouter) interfaces
with [Spanshs neutron plotter](https://spansh.uk/plotter) and makes the result
# Requirements
## VoiceAttack
Obviously you will need to install [VoiceAttack](https://voiceattack.com). There
is a free trial version available, but that one is limited to a single profile
and a few commands. This is 5 profiles and … a lot of commands. You will need the
full version, available for $10 (official site) or €11.99 (Steam, IIRC $14.99
for our US-based friends).
I recommend buying on the site. Why? Because on Steam, Valve gets a 30% cut.
Unlike many other developers Gary (the developer of VoiceAttack) remedies that
by having a price on Steam that ends up paying $10 to him. So basically, you are
paying Valve out of your own pocket. Many other developers do not do that, and
by buying from them directly instead of on Steam you are literally giving them
extra money. Please do keep that in mind in the future!
You also will generally need to opt into the beta version. I am usually at the
forefront of bug reports and feature requests, and I do rely on the
fixes/additions in beta versions quite often.
## EDDI
[EDDI](https://github.com/EDCD/EDDI) is a companion application for Elite:
Dangerous, providing responses to events that occur in-game using data from the
game as well as various third-party tools. In this case, you will need to run it
as a VoiceAttack plugin.
EDDI also regularly publishes beta versions. Unless a profiles release
explicitly states it you will _not_ have to run EDDI beta.
Do note that the profiles put EDDI into quiet mode by default, disabling the
built-in speech responders. This can be changed
[via the `EDDI quiet mode` setting](configuration/general.md#general-settings-for-all-profiles).
## bindED
[bindED](https://alterNERDtive.github.io/bindED) reads your Elite Dangerous
binding files and makes them available to VoiceAttack as variables. That way
commands can be portable and you do not have to manually go through them and
change any actions that you happen to not have the standard binds for.
This plugin is _included_ in the release package. You do _not_ have to download
and install it manually, but you _can_ independently update it if a newer
version is available.
## Elite Scripts
I have written a [collection of Python
scripts](https://github.com/alterNERDtive/elite-scripts) to interface with
various 3ʳᵈ party services like EDSM or Spansh. Those are called by the profiles
for various tasks, like checking a systems body count.
In the future they will be replaced by VoiceAttack plugin code.
The scripts are _included_ in the release package. You do _not_ have to download
and install them manually, but you _can_ independently update them if a newer
version is available.
## ED-NeutronRouter
[ED-NeutronRouter](https://github.com/sc-pulgan/ED-NeutronRouter) interfaces
with [Spanshs neutron plotter](https://spansh.uk/plotter) and makes the result
available to VoiceAttack.
This will also eventually be replaced by my own plugins.
This plugin is _only_ required if you intend to use the SpanshAttack profile for
neutron routing. Otherwise you do not have to install it.
This will also eventually be replaced by my own plugins.

View file

@ -1,91 +1,100 @@
# Troubleshooting
This will fill up gradually with Troubleshooting tips as people run into common
ones.
## VoiceAttack does not understand me / mishears me / fires random commands
There is [a thread on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=2667.msg12197#msg12197)
on how to set up your microphone and the speech recognition engine to work best.
If your microphone is bad and you still get erroneous recognitions when you are
not speaking it is probably going to recognize the same command every time. You
can remedy that by blocking the voice trigger. One-syllable triggers are
especially prone to misrecognition.
1. Create a new command in your custom profile.
1. Set the “when I say” field to the trigger that gets misrecognized.
Adding the “Other” → “VoiceAttack Action” → “Ignore an Unrecognized Word or
Phrase” action will also hide it from the VoiceAttack log when it is (wrongly)
recognized. You might or might not want that.
Example for the “cruise” voice trigger of the Supercruise command:
![[troubleshooting-remove-trigger.png]]
Alternatively you can raise the minimum confidence level and call the underlying
command to make misfires less likely:
![[troubleshooting-raise-min-confidence.png]]
There are a few examples in the [Custom Profile
Example](../installing#use-the-profile-example).
## VoiceAttack recognizes a command, but doesnt do anything in game
Make sure you have a keyboard bind for whatever the command is supposed to do as
outlined in [[Installing#Set Elite Keyboard Binds]].
## VoiceAttack talks over the COVAS voice
There is no way to know for sure when the ingame COVAS is talking to you, so
there is no way to always prevent the two from speaking over each other.
You can however either disable TTS responses for events that you know will clash
(or [file a feature
request](https://github.com/alterNERDtive/VoiceAttack-profiles/issues/) if one
is not optional yet). Alternatively, if you prefer the info given by VoiceAttack
over the ingame COVAS, you can deactivate its response to these events in the
ingame Audo settings:
![[Elite-COVAS.png]]
While youre in there you might as well get rid of the spoken FSD countdown that
# Troubleshooting
This will fill up gradually with Troubleshooting tips as people run into common
ones.
## VoiceAttack does not understand me / mishears me / fires random commands
There is [a thread on the VoiceAttack
forums](https://forum.voiceattack.com/smf/index.php?topic=2667.msg12197#msg12197)
on how to set up your microphone and the speech recognition engine to work best.
If your microphone is bad and you still get erroneous recognitions when you are
not speaking it is probably going to recognize the same command every time. You
can remedy that by blocking the voice trigger. One-syllable triggers are
especially prone to misrecognition.
1. Create a new command in your custom profile.
1. Set the “when I say” field to the trigger that gets misrecognized.
Adding the “Other” → “VoiceAttack Action” → “Ignore an Unrecognized Word or
Phrase” action will also hide it from the VoiceAttack log when it is (wrongly)
recognized. You might or might not want that.
Example for the “cruise” voice trigger of the Supercruise command:
![[troubleshooting-remove-trigger.png]]
Alternatively you can raise the minimum confidence level and call the underlying
command to make misfires less likely:
![[troubleshooting-raise-min-confidence.png]]
There are a few examples in the [Custom Profile
Example](../installing#use-the-profile-example).
## VoiceAttack recognizes a command, but doesnt do anything in game
Make sure you have a keyboard bind for whatever the command is supposed to do as
outlined in [[Installing#Set Elite Keyboard Binds]].
## VoiceAttack talks over the COVAS voice
There is no way to know for sure when the ingame COVAS is talking to you, so
there is no way to always prevent the two from speaking over each other.
You can however either disable TTS responses for events that you know will clash
(or [file a feature
request](https://github.com/alterNERDtive/VoiceAttack-profiles/issues/) if one
is not optional yet). Alternatively, if you prefer the info given by VoiceAttack
over the ingame COVAS, you can deactivate its response to these events in the
ingame Audo settings:
![[Elite-COVAS.png]]
While youre in there you might as well get rid of the spoken FSD countdown that
is off by one second …
## This doesnt work (well) with my HCS pack
My profiles are designed from the ground up to work with whatever else you are
doing with VoiceAttack; that is the reason for importing them into your own
My profiles are designed from the ground up to work with whatever else you are
doing with VoiceAttack; that is the reason for importing them into your own
custom profile instead of selecting e.g. `EliteAttack` as your active profile.
HCS on the contrary explicitly expects you do exclusively use HCS with
VoiceAttack. There is the rudimentary way of including simple profiles into
theirs, but the mechanism falls flat in many places. For example you cannot tell
HCS on the contrary explicitly expects you do exclusively use HCS with
VoiceAttack. There is the rudimentary way of including simple profiles into
theirs, but the mechanism falls flat in many places. For example you cannot tell
HCS to run an included profiles startup command.
So, in order to mostly make stuff work, you need to treat the HCS profile as
So, in order to mostly make stuff work, you need to treat the HCS profile as
your “custom” profile as per this documentation.
1. Include `alterNERDtive-base` and all profiles you want to use into the active
1. Include `alterNERDtive-base` and all profiles you want to use into the active
HCS profile.
1. Include a custom profile that has a startup command with a voice trigger of
1. Include a custom profile that has a startup command with a voice trigger of
your choosing, e.g. “load included profiles”.
1. Set up said startup command as you would normally.
1. Every time you start VA or change profiles, you will have to manually say
1. Every time you start VA or change profiles, you will have to manually say
“load included profiles”.
That will make most things work. Conflicts may arise if HCS happens to have
voice triggers that are the same as mine, in which case their command will take
That will make most things work. Conflicts may arise if HCS happens to have
voice triggers that are the same as mine, in which case their command will take
priority.
**Note on TTS**: EDDIs TTS (used by my profiles) and HCS TTS / recorded voice
lines act 100% independently. That means they will frequently “speak over each
**Note on TTS**: EDDIs TTS (used by my profiles) and HCS TTS / recorded voice
lines act 100% independently. That means they will frequently “speak over each
other”. There is no way to alleviate this.
EDDI does have a mechanism to detect if it is currently speaking it sets a
corresponding VoiceAttack variable. HCS neither does anything similar nor checks
if EDDI is speaking to prevent conflicts. Refer to them if you want that
EDDI does have a mechanism to detect if it is currently speaking it sets a
corresponding VoiceAttack variable. HCS neither does anything similar nor checks
if EDDI is speaking to prevent conflicts. Refer to them if you want that
changed.
## This does not work with Geforce Now
Nope. Just wont. Geforce Now obviously has Elites files, journals and keybinds
stored on some random PC in the cloud. Your local VoiceAttack has no way of
accessing those.
If you can get VoiceAttack to run _on Geforce Now_ in parallel to Elite, I dont
see why it wouldnt work.

View file

@ -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

View file

@ -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

View file

@ -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
@ -45,6 +49,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "configuration", "configurat
docs\configuration\StreamAttack.md = docs\configuration\StreamAttack.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A68BA76B-47FA-4D25-805E-66EBDD8C5223}"
ProjectSection(SolutionItems) = preProject
.github\dependabot.yaml = .github\dependabot.yaml
.github\FUNDING.yml = .github\FUNDING.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{0147AF7E-BB7F-4D5F-96EC-8734393DFF56}"
ProjectSection(SolutionItems) = preProject
.github\workflows\create-release.yaml = .github\workflows\create-release.yaml
.github\workflows\gh-pages.yaml = .github\workflows\gh-pages.yaml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -78,6 +94,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{5401ADF7-CB6D-448B-A4AC-D8A17B2D841F} = {C2B4D94B-8D73-431A-880B-B1E7ADF064B2}
{1AFD9AE6-7D22-4EF4-B0DE-51C9E91370FB} = {5401ADF7-CB6D-448B-A4AC-D8A17B2D841F}
{A68BA76B-47FA-4D25-805E-66EBDD8C5223} = {C2B4D94B-8D73-431A-880B-B1E7ADF064B2}
{0147AF7E-BB7F-4D5F-96EC-8734393DFF56} = {A68BA76B-47FA-4D25-805E-66EBDD8C5223}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {339E6747-C7BF-43C3-99C6-9249C9849A84}

View file

@ -1,113 +1,175 @@
#nullable enable
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; }
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 void Context_Log(dynamic vaProxy)
{
string message = vaProxy.GetText("~message");
string level = vaProxy.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(dynamic vaProxy)
{
Log.Notice("Starting up …");
VA = vaProxy;
Log.Notice("Finished startup.");
}
/*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
static readonly Version VERSION = new Version("8.3");
public static Guid VA_Id()
=> new Guid("{5B46321D-2935-4550-BEEA-36C2145547B8}");
public static string VA_DisplayName()
=> $"EliteAttack {VERSION}";
public static string VA_DisplayInfo()
=> "EliteAttack: a plugin for doing Elite-y things.";
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("EliteAttack.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;
// 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}'");
}
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() { }
}
}
// <copyright file="EliteAttack.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using alterNERDtive.util;
namespace EliteAttack
{
/// <summary>
/// VoiceAttack plugin for the EliteAttack profile.
/// </summary>
public class EliteAttack
{
private static readonly Version VERSION = new ("8.5");
private static VoiceAttackLog? log;
private static VoiceAttackCommands? commands;
private static dynamic? VA { get; set; }
private static VoiceAttackLog Log => log ??= new (VA, "EliteAttack");
private static VoiceAttackCommands Commands => commands ??= new (VA, Log);
/*========================================\
| required VoiceAttack plugin shenanigans |
\========================================*/
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id()
=> new ("{5B46321D-2935-4550-BEEA-36C2145547B8}");
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"EliteAttack {VERSION}";
/// <summary>
/// The plugins 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;
Log.Notice("Initializing …");
VA.SetText("EliteAttack.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)
{
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup(vaProxy);
break;
case "log.log":
// log
Context_Log(vaProxy);
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()
{
}
/*================\
| plugin contexts |
\================*/
#pragma warning disable IDE0060 // Remove unused parameter
private static void Context_Log(dynamic vaProxy)
{
string message = vaProxy.GetText("~message");
string level = vaProxy.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(dynamic vaProxy)
{
Log.Notice("Starting up …");
Log.Notice("Finished startup.");
}
#pragma warning restore IDE0060 // Remove unused parameter
}
}

View file

@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\EliteAttack.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\EliteAttack.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -1,4 +1,23 @@
using System.Reflection;
// <copyright file="AssemblyInfo.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View file

@ -1,4 +1,23 @@
using System.Reflection;
// <copyright file="AssemblyInfo.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View file

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

View file

@ -25,7 +25,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\RatAttack-cli.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -35,7 +35,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\RatAttack-cli.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -50,8 +50,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">

View file

@ -0,0 +1,68 @@
// <copyright file="RatAttack_cli.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.IO;
using System.IO.Pipes;
using System.Text.RegularExpressions;
namespace RatAttack
{
/// <summary>
/// CLI helper tool for the RatAttack VoiceAttack plugin. Accepts RATSIGNALs
/// e.g. from an IRC client and passes them to the plugin via named pipe.
/// </summary>
public class RatAttack_cli
{
/// <summary>
/// Main entry point.
/// </summary>
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args)
{
RatAttack.Ratsignal ratsignal = new (StripIrcCodes(args[0]), args.Length > 1 && args[1].ToLower() == "true");
using (NamedPipeClientStream pipeClient = new (".", "RatAttack", PipeDirection.Out))
{
try
{
// try connecting for up to 2minutes; then well assume VoiceAttack just isnt up and wont come up
pipeClient.Connect(120000);
using StreamWriter writer = new (pipeClient);
writer.WriteLine(ratsignal);
}
catch (TimeoutException)
{
Console.Error.WriteLine("Connection to RatAttack pipe has timed out.");
}
catch (UnauthorizedAccessException)
{
Console.Error.WriteLine("Cannot connect to RatAttack pipe. Are you running VoiceAttack as Admin?");
}
}
}
private static string StripIrcCodes(string message)
{
return Regex.Replace(message, @"[\x02\x11\x0F\x1D\x1E\x1F\x16]|\x03(\d\d?(,\d\d?)?)?", string.Empty);
}
}
}

View file

@ -1,4 +1,23 @@
using System.Reflection;
// <copyright file="AssemblyInfo.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View file

@ -1,332 +1,445 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using alterNERDtive.util;
namespace RatAttack
{
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 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 VoiceAttackLog Log
=> log ??= new VoiceAttackLog(VA, "RatAttack");
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;
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);
public string ShortInfo
{
get => $"#{Number}, {Platform}{(Odyssey ? " (Odyssey)" : "")}{(CodeRed ? ", code red" : "")}, {System ?? "None"}{(SystemInfo != null ? $" ({SystemInfo}{(PermitLocked ? ", permit required" : "")})" : "")}";
}
public override string ToString()
=> ShortInfo;
}
public class Ratsignal : IPipable
{
public string Signal { get; set; }
public bool Announce { get; set; }
private readonly char separator = '\x1F';
public Ratsignal()
=> (Signal, Announce) = ("", false);
public Ratsignal(string signal, bool announce)
=> (Signal, Announce) = (signal, announce);
public void ParseString(string serialization)
{
try
{
string[] parts = serialization.Split(separator);
Signal = parts[0];
Announce = Boolean.Parse(parts[1]);
}
catch (Exception e)
{
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e);
}
}
public override string ToString()
=> $"{Signal}{separator}{Announce}";
}
private static int ParseRatsignal(string ratsignal)
{
if (!RatsignalRegex.IsMatch(ratsignal))
throw new ArgumentException($"Invalid RATSIGNAL format: '{ratsignal}'.", "ratsignal");
Match match = RatsignalRegex.Match(ratsignal);
string cmdr = match.Groups["cmdr"].Value;
string? language = match.Groups["language"].Value;
string? system = match.Groups["system"].Value;
string? systemInfo = match.Groups["systemInfo"].Value;
bool permitLocked = match.Groups["permit"].Success;
string? permitName = match.Groups["permitName"].Value;
string platform = match.Groups["platform"].Value;
bool codeRed = match.Groups["oxygen"].Success;
bool odyssey = match.Groups["odyssey"].Success;
int number = int.Parse(match.Groups["number"].Value);
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}).");
CaseList[number] = new RatCase(cmdr, language, system, systemInfo, permitLocked, permitName, platform, odyssey, codeRed, number);
return number;
}
private static void On_Ratsignal(Ratsignal ratsignal)
{
try
{
int number = ParseRatsignal(ratsignal.Signal);
Log.Notice($"New rat case: {CaseList[number]}.");
Commands.TriggerEvent("RatAttack.incomingCase", parameters: new dynamic[] { new int[] { number }, new bool[] { ratsignal.Announce } });
}
catch (ArgumentException e)
{
Log.Error(e.Message);
Commands.TriggerEvent("RatAttack.invalidRatsignal", parameters: new dynamic[] { new string[] { ratsignal.Signal } });
}
catch (Exception e)
{
Log.Error($"Unhandled exception while parsing RATSIGNAL: '{e.Message}'.");
}
}
private static void On_ProfileChanged(Guid? from, Guid? to, string fromName, string toName)
=> VA_Exit1(VA);
/*================\
| plugin contexts |
\================*/
private static void Context_EDSM_GetNearestCMDR(dynamic vaProxy)
{
int caseNo = vaProxy.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
string cmdrList = vaProxy.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs");
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 arguments = $@"--short --text --system ""{system}"" ""{string.Join(@""" """, cmdrs)}""";
Process p = PythonProxy.SetupPythonScript(path, arguments);
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd();
p.WaitForExit();
string message = stdout;
string? errorMessage = null;
bool error = true;
switch (p.ExitCode)
{
case 0:
error = false;
Log.Info(message);
break;
case 1: // CMDR not found, Server Error, Api Exception (jeez, what a mess did I make there?)
error = true;
Log.Error(message);
break;
case 2: // System not found
error = true;
Log.Warn(message);
break;
default:
error = true;
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);
}
private static void Context_GetCaseData(dynamic vaProxy)
{
int cn = vaProxy.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);
}
else
{
Log.Warn($"Case #{cn} not found in the case list");
}
}
private static void Context_Log(dynamic vaProxy)
{
string message = vaProxy.GetText("~message");
string level = vaProxy.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(dynamic vaProxy)
{
Log.Notice("Starting up …");
VA = vaProxy;
_ = RatsignalPipe.Run();
Log.Notice("Finished startup.");
}
private static void Context_ParseRatsignal(dynamic vaProxy)
{
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")));
}
/*========================================\
| 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)
{
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 static void VA_Invoke1(dynamic vaProxy)
{
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}'");
}
catch (Exception e)
{
Log.Error($"Unhandled exception while executing plugin context '{context}'. ({e.Message})");
}
}
public static void VA_Exit1(dynamic vaProxy)
{
Log.Debug("Starting teardown …");
Log.Debug("Closing RATSIGNAL pipe …");
RatsignalPipe.Stop();
Log.Debug("Teardown finished.");
}
public static void VA_StopCommand() { }
}
}
// <copyright file="RatAttack.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.Collections.Concurrent;
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 readonly Version VERSION = new ("6.4");
private static readonly Regex RatsignalRegex = new (
@"^RATSIGNAL Case #(?<number>\d+) (?<platform>(PC|Xbox|Playstation))( )?(?<mode>H3.8|H4.0|ODY)?(?<oxygen> \(Code Red\))? 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_\[\]\-\^]+))? \((H3|H4|ODY|XB|PS)_SIGNAL\)\v*$");
private static PipeServer<Ratsignal>? ratsignalPipe;
private static VoiceAttackLog? log;
private static VoiceAttackCommands? commands;
private static ConcurrentDictionary<int, RatCase> CaseList { get; } = new ();
private static dynamic? VA { get; set; }
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 plugins 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 plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"RatAttack {VERSION}";
/// <summary>
/// The plugins 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)
{
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.");
}
/// <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)
{
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "getcasedata":
// plugin methods
Context_GetCaseData(vaProxy);
break;
case "parseratsignal":
Context_ParseRatsignal(vaProxy);
break;
case "startup":
Context_Startup(vaProxy);
break;
case "edsm.getnearestcmdr":
// EDSM
Context_EDSM_GetNearestCMDR(vaProxy);
break;
case "log.log":
// log
Context_Log(vaProxy);
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)
{
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);
string cmdr = match.Groups["cmdr"].Value;
string? language = match.Groups["language"].Value;
string? system = match.Groups["system"].Value;
string? systemInfo = match.Groups["systemInfo"].Value;
bool permitLocked = match.Groups["permit"].Success;
string? permitName = match.Groups["permitName"].Value;
string platform = match.Groups["platform"].Value;
bool codeRed = match.Groups["oxygen"].Success;
string? mode = match.Groups["mode"].Value;
int number = int.Parse(match.Groups["number"].Value);
if (string.IsNullOrEmpty(system))
{
system = "None";
}
Log.Debug($"New rat case: CMDR “{cmdr}” in “{system}”{(!string.IsNullOrEmpty(systemInfo) ? $" ({systemInfo})" : string.Empty)} on {platform}{(!string.IsNullOrEmpty(mode) ? $" ({mode})" : string.Empty)}, permit locked: {permitLocked}{(permitLocked && !string.IsNullOrEmpty(permitName) ? $" (permit name: {permitName})" : string.Empty)}, code red: {codeRed} (#{number}).");
CaseList[number] = new RatCase(cmdr, language, system, systemInfo, permitLocked, permitName, platform, mode, codeRed, number);
return number;
}
private static void On_Ratsignal(Ratsignal ratsignal)
{
try
{
int number = ParseRatsignal(ratsignal.Signal);
Log.Notice($"New rat case: {CaseList[number]}.");
Commands.TriggerEvent("RatAttack.incomingCase", parameters: new dynamic[] { new int[] { number }, new bool[] { ratsignal.Announce } });
}
catch (ArgumentException e)
{
Log.Error(e.Message);
Commands.TriggerEvent("RatAttack.invalidRatsignal", parameters: new dynamic[] { new string[] { ratsignal.Signal } });
}
catch (Exception e)
{
Log.Error($"Unhandled exception while parsing RATSIGNAL: '{e.Message}'.");
}
}
private static void On_ProfileChanged(Guid? from, Guid? to, string fromName, string toName)
=> VA_Exit1(VA);
/*================\
| plugin contexts |
\================*/
#pragma warning disable IDE0060 // Remove unused parameter
private static void Context_EDSM_GetNearestCMDR(dynamic vaProxy)
{
int caseNo = vaProxy.GetInt("~caseNo") ?? throw new ArgumentNullException("~caseNo");
string cmdrList = vaProxy.GetText("~cmdrs") ?? throw new ArgumentNullException("~cmdrs");
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 arguments = $@"--short --text --system ""{system}"" ""{string.Join(@""" """, cmdrs)}""";
Process p = PythonProxy.SetupPythonScript(path, arguments);
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd();
p.WaitForExit();
string message = stdout;
string? errorMessage = null;
bool error;
switch (p.ExitCode)
{
case 0:
error = false;
Log.Info(message);
break;
case 1: // CMDR not found, Server Error, Api Exception (jeez, what a mess did I make there?)
error = true;
Log.Error(message);
break;
case 2: // System not found
error = true;
Log.Warn(message);
break;
default:
error = true;
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);
}
private static void Context_GetCaseData(dynamic vaProxy)
{
int cn = vaProxy.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.SetText("~~mode", rc?.Mode);
vaProxy.SetBoolean("~~codeRed", rc?.CodeRed);
}
else
{
Log.Warn($"Case #{cn} not found in the case list");
}
}
private static void Context_Log(dynamic vaProxy)
{
string message = vaProxy.GetText("~message");
string level = vaProxy.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(dynamic vaProxy)
{
Log.Notice("Starting up …");
_ = RatsignalPipe.Run();
Log.Notice("Finished startup.");
}
private static void Context_ParseRatsignal(dynamic vaProxy)
{
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(vaProxy.GetText("~ratsignal"), vaProxy.GetBoolean("~announceRatsignal") ?? false));
}
#pragma warning restore IDE0060 // Remove unused parameter
/// <summary>
/// Encapsulates a RATSIGNAL for sending between the CLI helper tool and
/// the plugin via named pipe.
/// </summary>
public class Ratsignal : IPipable
{
private readonly char separator = '\x1F';
/// <summary>
/// Initializes a new instance of the <see cref="Ratsignal"/> class.
/// </summary>
public Ratsignal()
=> (this.Signal, this.Announce) = (string.Empty, false);
/// <summary>
/// Initializes a new instance of the <see cref="Ratsignal"/> class.
/// </summary>
/// <param name="signal">The RATSIGNAL.</param>
/// <param name="announce">Whether or not to announce the new case.</param>
public Ratsignal(string signal, bool announce)
=> (this.Signal, this.Announce) = (signal, announce);
/// <summary>
/// Gets or sets the RATSIGNAL.
/// </summary>
public string Signal { get; set; }
/// <summary>
/// Gets or Sets a value indicating whether to announce the incoming
/// case.
/// </summary>
public bool Announce { get; set; }
/// <summary>
/// Initializes the <see cref="Ratsignal"/> instance from a
/// serialized representation.
/// FIXXME: should probably make this a static factory method.
/// </summary>
/// <param name="serialization">The serialized <see cref="Ratsignal"/>.</param>
/// <exception cref="ArgumentException">Thrown on receiving an invalid signal.</exception>
public void ParseString(string serialization)
{
try
{
string[] parts = serialization.Split(this.separator);
this.Signal = parts[0];
this.Announce = bool.Parse(parts[1]);
}
catch (Exception e)
{
throw new ArgumentException($"Invalid serialized RATSIGNAL: '{serialization}'", e);
}
}
/// <inheritdoc/>
public override string ToString()
=> $"{this.Signal}{this.separator}{this.Announce}";
}
private class RatCase
{
public RatCase(string cmdr, string? language, string? system, string? systemInfo, bool permitLocked, string? permitName, string platform, string mode, bool codeRed, int number)
=> (this.Cmdr, this.Language, this.System, this.SystemInfo, this.PermitLocked, this.PermitName, this.Platform, this.Mode, this.CodeRed, this.Number)
= (cmdr, language, system, systemInfo, permitLocked, permitName, platform, mode, 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 string? Mode { get; }
public bool CodeRed { get; }
public int Number { get; }
public string ShortInfo
{
get => $"#{this.Number}, {this.Platform}{(!string.IsNullOrEmpty(this.Mode) ? $" ({this.Mode})" : string.Empty)}{(this.CodeRed ? ", code red" : string.Empty)}, {this.System ?? "None"}{(!string.IsNullOrEmpty(this.SystemInfo) ? $" ({this.SystemInfo}{(this.PermitLocked ? ", permit required" : string.Empty)})" : string.Empty)}";
}
public override string ToString()
=> this.ShortInfo;
}
}
}

View file

@ -25,7 +25,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\RatAttack.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@ -34,7 +34,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\RatAttack.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -1,4 +1,23 @@
using System.Reflection;
// <copyright file="AssemblyInfo.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View file

@ -1,29 +1,158 @@
#nullable enable
// <copyright file="SpanshAttack.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using alterNERDtive.util;
using alterNERDtive.edts;
using System;
using System.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 plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id()
=> new ("{e722b29d-898e-47dd-a843-a409c87e0bd8}");
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"SpanshAttack {VERSION}";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo()
=> "SpanshAttack: a plugin for doing routing with spansh.co.uk for Elite: Dangerous.";
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("SpanshAttack.version", VERSION.ToString());
Log.Notice("Init successful.");
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup(vaProxy);
break;
case "edts.getcoordinates":
// EDTS
Context_EDTS_GetCoordinates(vaProxy);
break;
case "log.log":
// log
Context_Log(vaProxy);
break;
case "spansh.systemexists":
// Spansh
Context_Spansh_SytemExists(vaProxy);
break;
case "spansh.nearestsystem":
Context_Spansh_Nearestsystem(vaProxy);
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()
{
}
/*================\
| plugin contexts |
\================*/
#pragma warning disable IDE0060 // Remove unused parameter
private static void Context_EDTS_GetCoordinates(dynamic vaProxy)
{
string name = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system");
@ -51,11 +180,13 @@ namespace SpanshAttack
vaProxy.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;
@ -85,7 +216,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}'.");
@ -104,11 +238,11 @@ namespace SpanshAttack
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();
@ -159,7 +293,7 @@ namespace SpanshAttack
bool exists = true;
bool error = false;
string errorMessage = "";
string errorMessage = string.Empty;
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
@ -195,75 +329,8 @@ namespace SpanshAttack
private static void Context_Startup(dynamic vaProxy)
{
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() { }
#pragma warning restore IDE0060 // Remove unused parameter
}
}

View file

@ -21,7 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\SpanshAttack.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,7 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\SpanshAttack.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -0,0 +1,28 @@
// <copyright file="GlobalSuppressions.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Diagnostics.CodeAnalysis;
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive.edts")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "just cause", Scope = "namespace", Target = "~N:alterNERDtive.util")]

View file

@ -1,4 +1,23 @@
using System.Reflection;
// <copyright file="AssemblyInfo.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -10,7 +29,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("VoiceAttack-base")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCopyright("Copyright © 20202022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View file

@ -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>

View file

@ -1,4 +1,25 @@
using System;
// <copyright file="SettingsDialog.xaml.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
@ -6,44 +27,38 @@ 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")
{
profile = "alterNERDtive-base";
}
tab.IsEnabled = BasePlugin.IsProfileActive(profile);
StackPanel panel = new StackPanel();
util.Configuration.OptDict<string, util.Configuration.Option> options = config.GetOptions(profile);
util.Configuration.OptDict<string, util.Configuration.Option> options = util.Configuration.GetOptions(profile);
foreach (dynamic option in options.Values)
{
@ -57,7 +72,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 +91,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,19 +101,13 @@ 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;
dynamic? state = null;
try
{
@ -129,21 +138,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; }
}
}
}

View file

@ -24,7 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\base.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@ -33,46 +33,50 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>8.0</LangVersion>
<DocumentationFile>..\build\alterNERDtive\base.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</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>
</Compile>
<Compile Include="util.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Page Include="SettingsDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client">
<Version>5.2.9</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.1</Version>
</PackageReference>
<PackageReference Include="System.Net.Http">
<Version>4.3.4</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -1,6 +1,24 @@
#nullable enable
// <copyright file="base.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using alterNERDtive.util;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -9,31 +27,190 @@ 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.5");
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 plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public static Guid VA_Id()
=> new ("{F7F59CFD-1AE2-4A7E-8F62-C62372418BAC}");
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public static string VA_DisplayName()
=> $"alterNERDtive-base {VERSION}";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public static string VA_DisplayInfo()
=> "The alterNERDtive plugin to manage all the alterNERDtive profiles!";
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Init1(dynamic vaProxy)
{
VA = vaProxy;
Log.Notice("Initializing …");
VA.SetText("alterNERDtive-base.version", VERSION.ToString());
vaProxy.BooleanVariableChanged += new Action<string, bool?, bool?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DateVariableChanged += new Action<string, DateTime?, DateTime?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.DecimalVariableChanged += new Action<string, decimal?, decimal?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.IntegerVariableChanged += new Action<string, int?, int?, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
vaProxy.TextVariableChanged += new Action<string, string, string, Guid?>((name, from, to, id) => { ConfigurationChanged(name, from, to, id); });
VA.SetBoolean("alterNERDtive-base.initialized", true);
Commands.TriggerEvent("alterNERDtive-base.initialized", wait: false, logMissing: false);
Log.Notice("Init successful.");
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public static void VA_Invoke1(dynamic vaProxy)
{
string context = vaProxy.Context.ToLower();
Log.Debug($"Running context '{context}' …");
try
{
switch (context)
{
case "startup":
Context_Startup(vaProxy);
break;
case "config.dialog":
// config
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;
case "edsm.bodycount":
// EDSM
Context_EDSM_BodyCount(vaProxy);
break;
case "edsm.distancebetween":
Context_EDSM_DistanceBetween(vaProxy);
break;
case "eddi.event":
// EDDI
Context_Eddi_Event(vaProxy);
break;
case "spansh.outdatedstations":
// Spansh
Context_Spansh_OutdatedStations(vaProxy);
break;
case "log.log":
// log
Context_Log(vaProxy);
break;
case "update.check":
// update
Context_Update_Check(vaProxy);
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 +220,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
@ -68,7 +246,8 @@ namespace alterNERDtive
string name = match.Groups["name"].Value;
Log.Debug($"Configuration has changed, '{id}.{name}': '{from}' → '{to}'");
dynamic o = Config.GetOption(id, name);
dynamic o = Configuration.GetOption(id, name);
// When loaded from profile but not explicitly set, will be null.
// Then load default.
// Same applies to resetting a saved option (= saving null to the profile).
@ -105,7 +284,8 @@ namespace alterNERDtive
}
}
if (option == "alterNERDtive-base.eddi.quietMode#" && VA!.GetText("EDDI version") != null) // if null, EDDI isnt up yet
// if null, EDDI isnt up yet
if (option == "alterNERDtive-base.eddi.quietMode#" && VA!.GetText("EDDI version") != null)
{
Log.Debug($"Resetting speech responder ({(to ?? false ? "off" : "on")}) …");
Commands.Run("alterNERDtive-base.setEDDISpeechResponder");
@ -137,7 +317,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,18 +326,21 @@ namespace alterNERDtive
| plugin contexts |
\================*/
#pragma warning disable IDE0060 // Remove unused parameter
private static void Context_Config_Dialog(dynamic vaProxy)
{
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;
@ -181,6 +365,7 @@ namespace alterNERDtive
{
Config.SetVoiceTriggers(type);
}
Config.LoadFromProfile();
Log.Debug("Finished loading configuration.");
}
@ -194,6 +379,25 @@ namespace alterNERDtive
private static void Context_Config_VersionMigration(dynamic vaProxy)
{
// =============
// === 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 = vaProxy.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 ===
// ===========
@ -217,7 +421,7 @@ namespace alterNERDtive
string name = $"{prefix}.{option}";
string oldName = $"{oldPrefix}.{option}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{oldName}", "boolean" } });
bool? value = VA!.GetBoolean(oldName);
bool? value = vaProxy.GetBoolean(oldName);
if (value != null)
{
Log.Info($"Migrating option {oldName} …");
@ -232,7 +436,7 @@ namespace alterNERDtive
{
string name = $"{prefix}.{option}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
bool? value = VA!.GetBoolean(name);
bool? value = vaProxy.GetBoolean(name);
if (value != null)
{
Log.Info($"Migrating option {name} …");
@ -240,11 +444,12 @@ 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}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "text" } });
string value = VA!.GetText(name);
string value = vaProxy.GetText(name);
if (!string.IsNullOrEmpty(value))
{
Log.Info($"Migrating option {name} …");
@ -259,7 +464,7 @@ namespace alterNERDtive
{
string name = $"{prefix}.{option}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "boolean" } });
bool? value = VA!.GetBoolean(name);
bool? value = vaProxy.GetBoolean(name);
if (value != null)
{
Log.Info($"Migrating option {name} …");
@ -267,11 +472,12 @@ 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}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "text" } });
string value = VA!.GetText(name);
string value = vaProxy.GetText(name);
if (!string.IsNullOrEmpty(value))
{
Log.Info($"Migrating option {name} …");
@ -286,7 +492,7 @@ namespace alterNERDtive
{
string name = $"{prefix}.{option}";
Commands.Run("alterNERDtive-base.loadVariableFromProfile", wait: true, parameters: new dynamic[] { new string[] { $"{name}", "text" } });
string value = VA!.GetText(name);
string value = vaProxy.GetText(name);
if (!string.IsNullOrEmpty(value))
{
Log.Info($"Migrating option {name} …");
@ -315,7 +521,7 @@ namespace alterNERDtive
int bodyCount = 0;
bool error = false;
string errorMessage = "";
string errorMessage = string.Empty;
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
@ -363,7 +569,7 @@ namespace alterNERDtive
decimal distance = 0;
bool error = false;
string errorMessage = "";
string errorMessage = string.Empty;
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
@ -386,7 +592,6 @@ namespace alterNERDtive
Log.Error(stderr);
errorMessage = "Unrecoverable error in plugin.";
break;
}
vaProxy.SetDecimal("~distance", distance);
@ -410,7 +615,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}'.");
@ -422,13 +630,13 @@ namespace alterNERDtive
{
string system = vaProxy.GetText("~system") ?? throw new ArgumentNullException("~system");
int minage = vaProxy.GetInt("~minage") ?? throw new ArgumentNullException("~minage");
bool includeSettlements = vaProxy.GetBoolean("~includeSettlements") ?? throw new ArgumentNullException("~includeSettlements");
string path = $@"{vaProxy.SessionState["VA_SOUNDS"]}\Scripts\spansh.exe";
string arguments = $@"oldstations --system ""{system}"" --minage {minage}";
string arguments = $@"oldstations --system ""{system}"" --minage {minage}{(includeSettlements ? string.Empty : " --nofeet")}";
Process p = PythonProxy.SetupPythonScript(path, arguments);
p.Start();
string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd();
@ -457,7 +665,6 @@ namespace alterNERDtive
Log.Error(stderr);
errorMessage = "Unrecoverable error in plugin.";
break;
}
vaProxy.SetText("~message", message);
@ -469,7 +676,7 @@ namespace alterNERDtive
private static void Context_Startup(dynamic vaProxy)
{
Log.Notice("Starting up …");
CheckProfiles(VA);
CheckProfiles(vaProxy);
Log.Notice($"Active profiles: {string.Join(", ", ActiveProfiles)}");
Commands.TriggerEventAll(ActiveProfiles, "startup", logMissing: false);
Log.Notice("Finished startup.");
@ -479,108 +686,6 @@ namespace alterNERDtive
{
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() { }
#pragma warning restore IDE0060 // Remove unused parameter
}
}

View file

@ -1,10 +1,31 @@
#nullable enable
// <auto-generated/>
// Not really, but this file will not bow to StyleCop tyranny.
// Why? Because it will be obsolete mid term anyway ;)
// <copyright file="edts.cs" company="alterNERDtive">
// Copyright 20192022 alterNERDtive.
//
// This file is part of alterNERDtive VoiceAttack profiles for Elite Dangerous.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// alterNERDtive VoiceAttack profiles for Elite Dangerous is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with alterNERDtive VoiceAttack profiles for Elite Dangerous. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace alterNERDtive.edts
{
@ -57,4 +78,4 @@ namespace alterNERDtive.edts
return new StarSystem { Name=name, Coords=new Position { X=x, Y=y, Z=z, Precision=uncertainty } };
}
}
}
}

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.7" targetFramework="net48" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net48" />
</packages>

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
mkdocs-roamlinks-plugin

16
stylecop.json Normal file
View file

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