documentation
plus some refactoring / added code on the way …
This commit is contained in:
parent
0e477058cc
commit
5b6db94bce
21 changed files with 971 additions and 92 deletions
|
@ -19,9 +19,10 @@
|
|||
|
||||
using System;
|
||||
|
||||
using alterNERDtive.Yavapf;
|
||||
using VoiceAttack;
|
||||
|
||||
namespace alterNERDtive.Yavapf.Example
|
||||
namespace alterNERDtive.Example
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an example for a VoiceAttack plugin using YAVAPF.
|
||||
|
@ -163,7 +164,7 @@ namespace alterNERDtive.Yavapf.Example
|
|||
/// An example handler for VA_StopCommand. If your plugin needs to
|
||||
/// execute anything when all commands are stopped this is the place.
|
||||
/// </summary>
|
||||
[Stop]
|
||||
[StopCommand]
|
||||
public static void Stop()
|
||||
{
|
||||
Plugin.Log.Notice("This is the example Stop handler method.");
|
||||
|
@ -197,8 +198,8 @@ namespace alterNERDtive.Yavapf.Example
|
|||
/// contexts that begin with “foo” or contain “bar”.
|
||||
/// </summary>
|
||||
/// <param name="vaProxy">The current VoiceAttack proxy object.</param>
|
||||
[Context("^foo.*")]
|
||||
[Context("^.*bar.*")]
|
||||
[Context(@"^foo.*")]
|
||||
[Context(@"^.*bar.*")]
|
||||
public static void RegexContext(VoiceAttackInvokeProxyClass vaProxy)
|
||||
{
|
||||
Plugin.Log.Notice(
|
||||
|
@ -212,9 +213,8 @@ namespace alterNERDtive.Yavapf.Example
|
|||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="from">The old value of the variable.</param>
|
||||
/// <param name="to">The new value of the variable.</param>
|
||||
/// <param name="internalID">The GUID of the variable.</param>
|
||||
[Bool("isDay#")]
|
||||
public static void DayChanged(string name, bool? from, bool? to, Guid? internalID)
|
||||
public static void DayChanged(string name, bool? from, bool? to)
|
||||
{
|
||||
Plugin.Log.Notice($"This is the example handler for changed bool variables. It is now {(to ?? false ? "day" : "night")}.");
|
||||
}
|
||||
|
@ -226,11 +226,14 @@ namespace alterNERDtive.Yavapf.Example
|
|||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="from">The old value of the variable.</param>
|
||||
/// <param name="to">The new value of the variable.</param>
|
||||
/// <param name="internalID">The GUID of the variable.</param>
|
||||
[String]
|
||||
public static void StringChanged(string name, string? from, string? to, Guid? internalID)
|
||||
public static void StringChanged(string name, string? from, string? to)
|
||||
{
|
||||
Plugin.Log.Notice($"This is the example handler for changed string variables. '{name}' changed from '{from ?? "Not Set"}' to '{to ?? "Not Set"}'.");
|
||||
// exclude log level changes
|
||||
if (name != $"{Plugin.Name}.loglevel#")
|
||||
{
|
||||
Plugin.Log.Notice($"This is the example handler for changed string variables. '{name}' changed from '{from ?? "Not Set"}' to '{to ?? "Not Set"}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<RootNamespace>alterNERDtive.Yavapf.Example</RootNamespace>
|
||||
<RootNamespace>alterNERDtive.Example</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
@ -20,6 +20,7 @@
|
|||
<Reference Include="VoiceAttack">
|
||||
<HintPath>C:\Program Files\VoiceAttack\VoiceAttack.exe</HintPath>
|
||||
<Private>False</Private>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -23,4 +23,4 @@
|
|||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "It’s my name, you prick :)", Scope = "namespace", Target = "~N:alterNERDtive.Yavapf.Example")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "It’s my name, you prick :)", Scope = "namespace", Target = "~N:alterNERDtive.Example")]
|
||||
|
|
125
ExamplePlugin/MinimumViablePlugin.cs
Normal file
125
ExamplePlugin/MinimumViablePlugin.cs
Normal file
|
@ -0,0 +1,125 @@
|
|||
// <copyright file="MinimumViablePlugin.cs" company="alterNERDtive">
|
||||
// Copyright 2022 alterNERDtive.
|
||||
//
|
||||
// This file is part of YAVAPF.
|
||||
//
|
||||
// YAVAPF 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.
|
||||
//
|
||||
// YAVAPF 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 YAVAPF. If not, see <https://www.gnu.org/licenses/>.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
using alterNERDtive.Yavapf;
|
||||
|
||||
namespace alterNERDtive.Example
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an example for a VoiceAttack plugin using YAVAPF.
|
||||
///
|
||||
/// You can use this class and this project as base for your own implementation.
|
||||
/// </summary>
|
||||
public class MinimumViablePlugin : VoiceAttackPlugin
|
||||
{
|
||||
private static readonly MinimumViablePlugin Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="MinimumViablePlugin"/> class.
|
||||
///
|
||||
/// Since VoiceAttack’s plugin API requires a bunch of static methods
|
||||
/// instead of instantiating a plugin class, the “Constructor” here also
|
||||
/// needs to be static. It is executed right before a static method is
|
||||
/// used for the first time, which would usually be when VoiceAttack
|
||||
/// calls the <see cref="VA_Init1(dynamic)"/> method.
|
||||
/// </summary>
|
||||
static MinimumViablePlugin()
|
||||
{
|
||||
// You can generate a GUID in Visual Studio under “Tools” → “Create
|
||||
// GUID”. Choose “Registry Format”.
|
||||
Plugin = new ()
|
||||
{
|
||||
Name = "Minimum Viable Plugin",
|
||||
Version = "0.0.1",
|
||||
Info = "This is a description",
|
||||
Guid = "{2E5CDD74-0E05-4745-A791-76E8C5AABBC3}",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s display name, as required by the VoiceAttack plugin
|
||||
/// API.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <returns>The display name.</returns>
|
||||
public static string VA_DisplayName() => Plugin.VaDisplayName();
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s description, as required by the VoiceAttack plugin API.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <returns>The description.</returns>
|
||||
public static string VA_DisplayInfo() => Plugin.VaDisplayInfo();
|
||||
|
||||
/// <summary>
|
||||
/// The plugin’s GUID, as required by the VoiceAttack plugin API.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <returns>The GUID.</returns>
|
||||
public static Guid VA_Id() => Plugin.VaId();
|
||||
|
||||
/// <summary>
|
||||
/// The Init method, as required by the VoiceAttack plugin API.
|
||||
/// Runs when the plugin is initially loaded.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
|
||||
public static void VA_Init1(dynamic vaProxy) => Plugin.VaInit1(vaProxy);
|
||||
|
||||
/// <summary>
|
||||
/// The Invoke method, as required by the VoiceAttack plugin API.
|
||||
/// Runs whenever a plugin context is invoked.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
|
||||
public static void VA_Invoke1(dynamic vaProxy) => Plugin.VaInvoke1(vaProxy);
|
||||
|
||||
/// <summary>
|
||||
/// The Exit method, as required by the VoiceAttack plugin API.
|
||||
/// Runs when VoiceAttack is shut down.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
|
||||
public static void VA_Exit1(dynamic vaProxy) => Plugin.VaExit1(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.
|
||||
///
|
||||
/// Since it is required to be static, it must be defined in your plugin
|
||||
/// class for VoiceAttack to pick it up as a plugin.
|
||||
/// </summary>
|
||||
public static void VA_StopCommand() => Plugin.VaStopCommand();
|
||||
}
|
||||
}
|
2
ExamplePlugin/Properties/Settings.Designer.cs
generated
2
ExamplePlugin/Properties/Settings.Designer.cs
generated
|
@ -8,7 +8,7 @@
|
|||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace alterNERDtive.Yavapf.Example.Properties {
|
||||
namespace alterNERDtive.Example.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
|
|
|
@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
LICENSE = LICENSE
|
||||
mkdocs.yml = mkdocs.yml
|
||||
README.md = README.md
|
||||
requirements.txt = requirements.txt
|
||||
stylecop.json = stylecop.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
@ -42,13 +43,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{3FE46F37-0
|
|||
docs\commands.md = docs\commands.md
|
||||
docs\contexts.md = docs\contexts.md
|
||||
docs\events.md = docs\events.md
|
||||
docs\extra.css = docs\extra.css
|
||||
docs\gettingstarted.md = docs\gettingstarted.md
|
||||
docs\faq.md = docs\faq.md
|
||||
docs\index.md = docs\index.md
|
||||
docs\troubleshooting.md = docs\troubleshooting.md
|
||||
docs\logging.md = docs\logging.md
|
||||
docs\variables.md = docs\variables.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceAttack-Framework-Test", "VoiceAttack-Framework-Test\VoiceAttack-Framework-Test.csproj", "{D49B2D9A-1014-4554-977A-E57E9972D196}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceAttack-Framework-Test", "VoiceAttack-Framework-Test\VoiceAttack-Framework-Test.csproj", "{D49B2D9A-1014-4554-977A-E57E9972D196}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFrameworks>net48</TargetFrameworks>
|
||||
<RootNamespace>alterNERDtive.Yavapf</RootNamespace>
|
||||
<PackageId>alterNERDtive.YAVAPF</PackageId>
|
||||
<Version>0.0.1</Version>
|
||||
<Version>0.1.0</Version>
|
||||
<Company>alterNERDtive</Company>
|
||||
<Authors>alterNERDtive</Authors>
|
||||
<Description>YAVAPF is yet another VoiceAttack plugin framework.</Description>
|
||||
|
@ -15,7 +15,7 @@
|
|||
<PackageIconUrl />
|
||||
<RepositoryUrl>https://github.com/alterNERDtive/YAVAPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>VoiceAttack</PackageTags>
|
||||
<PackageTags>VoiceAttack;plugin;framework</PackageTags>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
|
@ -44,6 +44,7 @@
|
|||
<Reference Include="VoiceAttack">
|
||||
<HintPath>C:\Program Files\VoiceAttack\VoiceAttack.exe</HintPath>
|
||||
<Private>False</Private>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -54,8 +54,12 @@ namespace alterNERDtive.Yavapf
|
|||
get => logLevel ?? Yavapf.LogLevel.NOTICE;
|
||||
set
|
||||
{
|
||||
logLevel = value;
|
||||
this.Notice($"Log level set to {value ?? Yavapf.LogLevel.NOTICE}.");
|
||||
if (value != logLevel)
|
||||
{
|
||||
logLevel = value;
|
||||
this.vaProxy.SetText($"{this.id}.loglevel#", value.ToString().ToLower());
|
||||
this.Notice($"Log level set to {value ?? Yavapf.LogLevel.NOTICE}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +69,9 @@ namespace alterNERDtive.Yavapf
|
|||
/// Valid values are ERROR, WARN, NOTICE, INFO and DEBUG.
|
||||
/// </summary>
|
||||
/// <param name="level">The new log level.</param>
|
||||
public void SetLogLevel(string level)
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref
|
||||
/// name="level"/>is not a valid log level.</exception>
|
||||
public void SetLogLevel(string? level)
|
||||
{
|
||||
if (level == null)
|
||||
{
|
||||
|
@ -77,25 +83,6 @@ namespace alterNERDtive.Yavapf
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a given message with the specified log level.
|
||||
///
|
||||
/// If the current log level is higher than the message’s log level it
|
||||
/// will not be logged.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be logged.</param>
|
||||
/// <param name="level">The desired log level.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the message is null.</exception>
|
||||
public void Log(string message, LogLevel level = Yavapf.LogLevel.INFO)
|
||||
{
|
||||
_ = message ?? throw new ArgumentNullException("message");
|
||||
|
||||
if (level <= this.LogLevel)
|
||||
{
|
||||
this.vaProxy.WriteToLog($"{level} | {this.id}: {message}", LogColour[(int)level]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a given message with the ERROR log level.
|
||||
/// </summary>
|
||||
|
@ -135,6 +122,25 @@ namespace alterNERDtive.Yavapf
|
|||
/// </summary>
|
||||
/// <param name="message">The message to be logged.</param>
|
||||
public void Debug(string message) => this.Log(message, Yavapf.LogLevel.DEBUG);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a given message with the specified log level.
|
||||
///
|
||||
/// If the current log level is higher than the message’s log level it
|
||||
/// will not be logged.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be logged.</param>
|
||||
/// <param name="level">The desired log level.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the message is null.</exception>
|
||||
private void Log(string message, LogLevel level = Yavapf.LogLevel.INFO)
|
||||
{
|
||||
_ = message ?? throw new ArgumentNullException("message");
|
||||
|
||||
if (level <= this.LogLevel)
|
||||
{
|
||||
this.vaProxy.WriteToLog($"{level} | {this.id}: {message}", LogColour[(int)level]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -62,31 +62,31 @@ namespace alterNERDtive.Yavapf
|
|||
/// Gets or sets the Actions to be run when a <see cref="bool"/>
|
||||
/// variable changed.
|
||||
/// </summary>
|
||||
protected HandlerList<Action<string, bool?, bool?, Guid?>> BoolChangedHandlers { get; set; } = new ();
|
||||
protected HandlerList<Action<string, bool?, bool?>> BoolChangedHandlers { get; set; } = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Actions to be run when a <see cref="DateTime"/>
|
||||
/// variable changed.
|
||||
/// </summary>
|
||||
protected HandlerList<Action<string, DateTime?, DateTime?, Guid?>> DateTimeChangedHandlers { get; set; } = new ();
|
||||
protected HandlerList<Action<string, DateTime?, DateTime?>> DateTimeChangedHandlers { get; set; } = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Actions to be run when a <see cref="decimal"/>
|
||||
/// variable changed.
|
||||
/// </summary>
|
||||
protected HandlerList<Action<string, decimal?, decimal?, Guid?>> DecimalChangedHandlers { get; set; } = new ();
|
||||
protected HandlerList<Action<string, decimal?, decimal?>> DecimalChangedHandlers { get; set; } = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Actions to be run when a <see cref="int"/>
|
||||
/// variable changed.
|
||||
/// </summary>
|
||||
protected HandlerList<Action<string, int?, int?, Guid?>> IntChangedHandlers { get; set; } = new ();
|
||||
protected HandlerList<Action<string, int?, int?>> IntChangedHandlers { get; set; } = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Actions to be run when a <see cref="string"/>
|
||||
/// variable changed.
|
||||
/// </summary>
|
||||
protected HandlerList<Action<string, string?, string?, Guid?>> StringChangedHandlers { get; set; } = new ();
|
||||
protected HandlerList<Action<string, string?, string?>> StringChangedHandlers { get; set; } = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently stored VoiceAttackInitProxyClass object which is
|
||||
|
@ -271,26 +271,26 @@ namespace alterNERDtive.Yavapf
|
|||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<ExitAttribute>().Any()).ToList().ForEach(
|
||||
m => this.ExitActions += (Action<VoiceAttackProxyClass>)m.CreateDelegate(typeof(Action<VoiceAttackProxyClass>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<StopAttribute>().Any()).ToList().ForEach(
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<StopCommandAttribute>().Any()).ToList().ForEach(
|
||||
m => this.StopActions += (Action)m.CreateDelegate(typeof(Action)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<ContextAttribute>().Any()).ToList().ForEach(
|
||||
m => this.Contexts += (Action<VoiceAttackInvokeProxyClass>)m.CreateDelegate(typeof(Action<VoiceAttackInvokeProxyClass>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<BoolAttribute>().Any()).ToList().ForEach(
|
||||
m => this.BoolChangedHandlers += (Action<string, bool?, bool?, Guid?>)m.CreateDelegate(typeof(Action<string, bool?, bool?, Guid?>)));
|
||||
m => this.BoolChangedHandlers += (Action<string, bool?, bool?>)m.CreateDelegate(typeof(Action<string, bool?, bool?>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<DateTimeAttribute>().Any()).ToList().ForEach(
|
||||
m => this.DateTimeChangedHandlers += (Action<string, DateTime?, DateTime?, Guid?>)m.CreateDelegate(typeof(Action<string, DateTime?, DateTime?, Guid?>)));
|
||||
m => this.DateTimeChangedHandlers += (Action<string, DateTime?, DateTime?>)m.CreateDelegate(typeof(Action<string, DateTime?, DateTime?>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<DecimalAttribute>().Any()).ToList().ForEach(
|
||||
m => this.DecimalChangedHandlers += (Action<string, decimal?, decimal?, Guid?>)m.CreateDelegate(typeof(Action<string, decimal?, decimal?, Guid?>)));
|
||||
m => this.DecimalChangedHandlers += (Action<string, decimal?, decimal?>)m.CreateDelegate(typeof(Action<string, decimal?, decimal?>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<IntAttribute>().Any()).ToList().ForEach(
|
||||
m => this.IntChangedHandlers += (Action<string, int?, int?, Guid?>)m.CreateDelegate(typeof(Action<string, int?, int?, Guid?>)));
|
||||
m => this.IntChangedHandlers += (Action<string, int?, int?>)m.CreateDelegate(typeof(Action<string, int?, int?>)));
|
||||
|
||||
this.GetType().GetMethods().Where(m => m.GetCustomAttributes<StringAttribute>().Any()).ToList().ForEach(
|
||||
m => this.StringChangedHandlers += (Action<string, string?, string?, Guid?>)m.CreateDelegate(typeof(Action<string, string?, string?, Guid?>)));
|
||||
m => this.StringChangedHandlers += (Action<string, string?, string?>)m.CreateDelegate(typeof(Action<string, string?, string?>)));
|
||||
|
||||
this.Log.Debug("Running Init handlers …");
|
||||
this.InitActions?.Invoke(vaProxy);
|
||||
|
@ -311,33 +311,79 @@ namespace alterNERDtive.Yavapf
|
|||
|
||||
string context = vaProxy.Context.ToLower();
|
||||
|
||||
List<Action<VoiceAttackInvokeProxyClass>> actions = this.Contexts.Where(
|
||||
action => action.Method.GetCustomAttributes<ContextAttribute>().Where(
|
||||
attr => attr.Name == context ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(context, attr.Name).Success))
|
||||
.Any()).ToList();
|
||||
|
||||
if (actions.Any())
|
||||
if (context.StartsWith("log."))
|
||||
{
|
||||
foreach (Action<VoiceAttackInvokeProxyClass> action in actions)
|
||||
try
|
||||
{
|
||||
try
|
||||
string message = this.Get<string>("~message") ?? throw new ArgumentNullException("~message");
|
||||
switch (context)
|
||||
{
|
||||
action.Invoke(vaProxy);
|
||||
}
|
||||
catch (ArgumentNullException e)
|
||||
{
|
||||
this.Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Log.Error($"Unhandled exception while executing plugin context '{context}': {e.Message}");
|
||||
case "log.error":
|
||||
this.Log.Error(message);
|
||||
break;
|
||||
case "log.warn":
|
||||
this.Log.Warn(message);
|
||||
break;
|
||||
case "log.notice":
|
||||
this.Log.Notice(message);
|
||||
break;
|
||||
case "log.info":
|
||||
this.Log.Info(message);
|
||||
break;
|
||||
case "log.debug":
|
||||
this.Log.Debug(message);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("invalid context", "context");
|
||||
}
|
||||
}
|
||||
catch (ArgumentNullException e)
|
||||
{
|
||||
this.Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
|
||||
}
|
||||
catch (ArgumentException e) when (e.ParamName == "context")
|
||||
{
|
||||
this.Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Log.Error($"Unhandled exception while executing plugin context '{context}': {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
List<Action<VoiceAttackInvokeProxyClass>> actions = this.Contexts.Where(
|
||||
action => action.Method.GetCustomAttributes<ContextAttribute>().Where(
|
||||
attr => attr.Name == context ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(context, attr.Name).Success))
|
||||
.Any()).ToList();
|
||||
|
||||
if (actions.Any())
|
||||
{
|
||||
foreach (Action<VoiceAttackInvokeProxyClass> action in actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(vaProxy);
|
||||
}
|
||||
catch (ArgumentNullException e)
|
||||
{
|
||||
this.Log.Error($"Missing parameter '{e.ParamName}' for context '{context}'");
|
||||
}
|
||||
catch (ArgumentException e) when (e.ParamName == "context")
|
||||
{
|
||||
this.Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Log.Error($"Unhandled exception while executing plugin context '{context}': {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Log.Error($"Invalid plugin context '{vaProxy.Context}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,7 +416,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="internalID">The internal GUID of the variable.</param>
|
||||
private void BooleanVariableChanged(string name, bool? from, bool? to, Guid? internalID = null)
|
||||
{
|
||||
foreach (Action<string, bool?, bool?, Guid?> action in this.BoolChangedHandlers.Where(
|
||||
foreach (Action<string, bool?, bool?> action in this.BoolChangedHandlers.Where(
|
||||
action => action.Method.GetCustomAttributes<BoolAttribute>().Where(
|
||||
attr => attr.Name == name ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(name, attr.Name).Success))
|
||||
|
@ -378,7 +424,7 @@ namespace alterNERDtive.Yavapf
|
|||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(name, from, to, internalID);
|
||||
action.Invoke(name, from, to);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -396,7 +442,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="internalID">The internal GUID of the variable.</param>
|
||||
private void DateVariableChanged(string name, DateTime? from, DateTime? to, Guid? internalID = null)
|
||||
{
|
||||
foreach (Action<string, DateTime?, DateTime?, Guid?> action in this.DateTimeChangedHandlers.Where(
|
||||
foreach (Action<string, DateTime?, DateTime?> action in this.DateTimeChangedHandlers.Where(
|
||||
action => action.Method.GetCustomAttributes<DateTimeAttribute>().Where(
|
||||
attr => attr.Name == name ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(name, attr.Name).Success))
|
||||
|
@ -404,7 +450,7 @@ namespace alterNERDtive.Yavapf
|
|||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(name, from, to, internalID);
|
||||
action.Invoke(name, from, to);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -422,7 +468,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="internalID">The internal GUID of the variable.</param>
|
||||
private void DecimalVariableChanged(string name, decimal? from, decimal? to, Guid? internalID = null)
|
||||
{
|
||||
foreach (Action<string, decimal?, decimal?, Guid?> action in this.DecimalChangedHandlers.Where(
|
||||
foreach (Action<string, decimal?, decimal?> action in this.DecimalChangedHandlers.Where(
|
||||
action => action.Method.GetCustomAttributes<DecimalAttribute>().Where(
|
||||
attr => attr.Name == name ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(name, attr.Name).Success))
|
||||
|
@ -430,7 +476,7 @@ namespace alterNERDtive.Yavapf
|
|||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(name, from, to, internalID);
|
||||
action.Invoke(name, from, to);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -448,7 +494,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="internalID">The internal GUID of the variable.</param>
|
||||
private void IntegerVariableChanged(string name, int? from, int? to, Guid? internalID = null)
|
||||
{
|
||||
foreach (Action<string, int?, int?, Guid?> action in this.IntChangedHandlers.Where(
|
||||
foreach (Action<string, int?, int?> action in this.IntChangedHandlers.Where(
|
||||
action => action.Method.GetCustomAttributes<IntAttribute>().Where(
|
||||
attr => attr.Name == name ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(name, attr.Name).Success))
|
||||
|
@ -456,7 +502,7 @@ namespace alterNERDtive.Yavapf
|
|||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(name, from, to, internalID);
|
||||
action.Invoke(name, from, to);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -474,7 +520,19 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="internalID">The internal GUID of the variable.</param>
|
||||
private void TextVariableChanged(string name, string? from, string? to, Guid? internalID = null)
|
||||
{
|
||||
foreach (Action<string, string?, string?, Guid?> action in this.StringChangedHandlers.Where(
|
||||
if (name == $"{this.Name}.loglevel#")
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Log.SetLogLevel(to);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
this.Log.Error($"Error setting log level: '{to!}' is not a valid log level.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Action<string, string?, string?> action in this.StringChangedHandlers.Where(
|
||||
action => action.Method.GetCustomAttributes<StringAttribute>().Where(
|
||||
attr => attr.Name == name ||
|
||||
(attr.Name.StartsWith("^") && Regex.Match(name, attr.Name).Success))
|
||||
|
@ -482,7 +540,7 @@ namespace alterNERDtive.Yavapf
|
|||
{
|
||||
try
|
||||
{
|
||||
action.Invoke(name, from, to, internalID);
|
||||
action.Invoke(name, from, to);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -545,7 +603,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// Denotes a handler for <see cref="VaStopCommand()"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
protected class StopAttribute : Attribute
|
||||
protected class StopCommandAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -562,7 +620,7 @@ namespace alterNERDtive.Yavapf
|
|||
/// <param name="name">The name of or regex for the context.</param>
|
||||
public ContextAttribute(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Name = name.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Executing VoiceAttack Commands
|
||||
|
||||
Not implemented yet.
|
171
docs/contexts.md
171
docs/contexts.md
|
@ -0,0 +1,171 @@
|
|||
# Defining Plugin Contexts
|
||||
|
||||
Plugin contexts are defined similarly to [event handlers](events.md).
|
||||
|
||||
They are `public static` methods of your plugin class that must have a
|
||||
`ContextAttribute` and must accept a `VoiceAttackInvokeProxyClass` parameter.
|
||||
|
||||
`ContextAttribute` has a single property `Name`. `Name` can either be the name
|
||||
of a plugin context or a regular expression defining all plugin contexts it
|
||||
should be associated with. `Name` is set through an optional parameter of the
|
||||
attribute constructor; if it is omitted, the method will be executed for any
|
||||
plugin context.
|
||||
|
||||
A method can have multiple `ContextAttribute`s. It will be executed if any of
|
||||
them matches the context of a plugin invocation. That also means that you can
|
||||
have several methods that handle the same plugin context; as with [event
|
||||
handlers](events.md), a specific order of execution cannot be guaranteed.
|
||||
|
||||
If your method handles multiple contexts, the context it was invoked with can be
|
||||
found in the `Context` property of its `VoiceAttackInvokeProxyClass` parameter.
|
||||
|
||||
**Note**: The `log.*` context is reserved [for
|
||||
logging](logging.md#from-a-voiceattack-command) and cannot be used for your
|
||||
plugin.
|
||||
|
||||
## Named Plugin Contexts
|
||||
|
||||
For singular context names, add a `ContextAttribute` for each name. Context
|
||||
names are to be lower case by convention.
|
||||
|
||||
This should be the default way to handle plugin contexts, and multiple contexts
|
||||
handled by the same method should be alternate names for the same functionality.
|
||||
Separate functionality, separate handler method(s).
|
||||
|
||||
```csharp
|
||||
[Context("test")]
|
||||
[Context("test context")]
|
||||
[Context("alternate context name")]
|
||||
public static void TestContext(VoiceAttackInvokeProxyClass vaProxy) {
|
||||
[…]
|
||||
}
|
||||
```
|
||||
|
||||
## Regular Expression Plugin Contexts
|
||||
|
||||
For contexts defined by regular expressions, the `Name` property must start with
|
||||
a `^` to be recognized as a regular expression. Incidentally that means you have
|
||||
to define your regular expression to match from the beginning of the context
|
||||
string.
|
||||
|
||||
The main use case for regular expression contexts is grouping contexts that
|
||||
logically belong together or behave in very similar ways. For example you could
|
||||
have a single `^edsm\..*` context in an Elite Dangerous related plugin that
|
||||
handles anything related to querying [EDSM](https://edsm.net).
|
||||
|
||||
As with [catchall contexts](#catchall-plugin-contexts), you should probably have
|
||||
some kind of way to differentiate between contexts. For any contexts that match
|
||||
the regular expression(s) but are not valid contexts for your plugin, `throw` an
|
||||
`ArgumentException` with “context” as the parameter name. The exception message
|
||||
can be anything, it will not be used.
|
||||
|
||||
Oh, and of course you can combine named and regular expression contexts. This
|
||||
example features some different regular expressions and corresponding
|
||||
conditionals:
|
||||
|
||||
```csharp
|
||||
[Context(@"^foo.*")]
|
||||
[Context(@"^.*bar.*")]
|
||||
[Context(@"^.*baz")]
|
||||
[Context("some name")]
|
||||
public static void RegexContext(VoiceAttackInvokeProxyClass vaProxy) {
|
||||
string context = vaProxy.Context;
|
||||
if (context.StartsWith("foo")) {
|
||||
[…]
|
||||
}
|
||||
else if (context.Contains("bar")) {
|
||||
[…]
|
||||
}
|
||||
else if (context.EndsWith("baz")) {
|
||||
[…]
|
||||
}
|
||||
else if (context == "some name")) {
|
||||
[…]
|
||||
}
|
||||
else {
|
||||
throw new ArgumentException("", "context");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example is more focused and closer to how regular expression contexts are
|
||||
intended to be used in practice:
|
||||
|
||||
```csharp
|
||||
[Context(@"^edsm\..*")]
|
||||
public static void EdsmContext(VoiceAttackInvokeProxyClass vaProxy) {
|
||||
switch(vaProxy.Context)
|
||||
{
|
||||
case "edsm.findsystem":
|
||||
[…]
|
||||
break;
|
||||
case "edsm.findcommander":
|
||||
[…]
|
||||
break;
|
||||
case "edsm.trafficreport":
|
||||
[…]
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("", "context");;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## “Catchall” Plugin Contexts
|
||||
|
||||
To have a method invoked on any plugin invocation regardless of context, add a
|
||||
`ContextAttribute` and omit the `Name`.
|
||||
|
||||
**This is not recommended** and has similar issues to using the bare VoiceAttack
|
||||
plugin API. It is mostly provided for backwards compatibility; you can easily
|
||||
convert your old `VA_Invoke1(dynamic)` method to a catchall plugin context and
|
||||
then modify from there.
|
||||
|
||||
As with [regular expression contexts](#regular-expression-plugin-contexts), you
|
||||
should probably have some kind of way to differentiate between contexts. For any
|
||||
contexts that are not valid contexts for your plugin, `throw` an
|
||||
`ArgumentException` with “context” as the parameter name. The exception message,
|
||||
again, doesn’t matter.
|
||||
|
||||
```csharp
|
||||
[Context]
|
||||
public static void CatchallContext(VoiceAttackInvokeProxyClass vaProxy) {
|
||||
switch (vaProxy.Context)
|
||||
{
|
||||
case "some context":
|
||||
[…]
|
||||
break;
|
||||
case "some other context":
|
||||
[…]
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("", "context");;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Context Parameters
|
||||
|
||||
VoiceAttack plugin contexts by design do not have any parameters. If you need
|
||||
data passed from a VoiceAttack command to the plugin when a context is invoked,
|
||||
you will have to set a variable in your VoiceAttack command and then retrieve
|
||||
said variable from the context handler method.
|
||||
|
||||
In general it is recommended to provide context parameters as command scoped
|
||||
variables (`~` prefix) in order not to interfere with other commands / plugin
|
||||
invocations and their data.
|
||||
|
||||
This example accesses the `~test` text variable from plugin code:
|
||||
|
||||
```csharp
|
||||
string? testParameter = Plugin.Get<string>("~test");
|
||||
```
|
||||
|
||||
In case a parameter is missing that is _required_ for your context `throw` an
|
||||
`ArgumentNullException` with the variable name as the parameter name:
|
||||
|
||||
```csharp
|
||||
string testParameter = Plugin.Get<string>("~test") ?? throw new ArgumentNullException("~test");
|
||||
```
|
||||
|
||||
[More about variables](variables.md).
|
|
@ -0,0 +1,61 @@
|
|||
# Handling VoiceAttack Events
|
||||
|
||||
In order to handle VoiceAttack’s `Init`, `Exit` and `StopCommand` events, you
|
||||
will have to define corresponding event handlers. [The `Invoke` event is handled
|
||||
separately](contexts.md).
|
||||
|
||||
Generally speaking, event handlers in YAVAPF are `public static` methods of your
|
||||
plugin class that must have certain attributes associated to them and must have
|
||||
the correct method signature.
|
||||
|
||||
An event can have as many handlers as you require. Do note that a specific order
|
||||
of execution cannot be guaranteed.
|
||||
|
||||
## Init
|
||||
|
||||
`Init` handlers are invoked when VoiceAttack inintializes your plugin. That
|
||||
happens exactly once at application startup. Use these to setup your plugin for
|
||||
use.
|
||||
|
||||
`Init` handlers must accept a single `VoiceAttackInitProxyClass` parameter and
|
||||
must have an `InitAttribute`. `InitAttribute` does not have any properties.
|
||||
|
||||
```csharp
|
||||
[Init]
|
||||
public static void MyInitHandler(VoiceAttackInitProxyClass vaProxy) {
|
||||
[…]
|
||||
}
|
||||
```
|
||||
|
||||
## Exit
|
||||
|
||||
`Exit` handlers are invoked when VoiceAttack closes. That happens exactly once
|
||||
at application shutdown. Use these to gracefully tear down anything your plugin
|
||||
has to tear down.
|
||||
|
||||
`Exit` handlers must accept a single `VoiceAttackProxyClass` parameter and must
|
||||
have an `ExitAttribute`. `ExitAttribute` does not have any properties.
|
||||
|
||||
```csharp
|
||||
[Exit]
|
||||
public static void MyExitHandler(VoiceAttackProxyClass vaProxy) {
|
||||
[…]
|
||||
}
|
||||
```
|
||||
|
||||
## StopCommand
|
||||
|
||||
`StopCommand` handlers are invoked whenever VoiceAttack stops all commands. That
|
||||
happens e.g. when a “Stop all commands” command action is executed or when the
|
||||
“Stop Commands” button on the main interface is pressed. If your plugin has to
|
||||
respond to that, use these.
|
||||
|
||||
`StopCommand` handlers must have no parameters and must have a
|
||||
`StopCommandAttribute`. `StopCommandAttribute` does not have any properties.
|
||||
|
||||
```csharp
|
||||
[StopCommand]
|
||||
public static void MyStopCommandHandler() {
|
||||
[…]
|
||||
}
|
||||
```
|
5
docs/extra.css
Normal file
5
docs/extra.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
div.section > p,
|
||||
div.section > ol,
|
||||
div.section > ul {
|
||||
text-align: justify
|
||||
}
|
3
docs/faq.md
Normal file
3
docs/faq.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
There doesn’t seem to be anything here. Maybe you should ask one :)
|
|
@ -0,0 +1,193 @@
|
|||
# Getting Started
|
||||
|
||||
First off, you can see [the Example plugin
|
||||
project](https://github.com/alterNERDtive/YAVAPF/tree/release/ExamplePlugin) on
|
||||
Github for reference.
|
||||
|
||||
Second off, this documentation assumes that you have at least cross read the
|
||||
section about plugins [in the VoiceAttack
|
||||
manual](https://voiceattack.com/VoiceAttackHelp.pdf). If any terminology is new
|
||||
to you, it is probably introduced there. Unlike said manual though this will
|
||||
provide step by step instructions to get your plugin set up.
|
||||
|
||||
## Creating a Visual Studio Project
|
||||
|
||||
I am going to assume for this part of the documentation that you are using
|
||||
Visual Studio 2022 or later (_not_ Visual Studio Code!) as your development
|
||||
environment. [The Community Edition is free for unlimited time personal
|
||||
use](https://visualstudio.microsoft.com/vs/compare/).
|
||||
|
||||
VoiceAttack is a .Net Framework 4.8 application. Plugins targeting .Net 5+ or
|
||||
.Net Core will not work. I still recommend creating a .Net project instead of a
|
||||
.Net Framework project, then changing the “Target Framework” to .Net Framework
|
||||
4.8. This allows you to use the full `dotnet` tool chain, which makes e.g. using
|
||||
Github Actions to build / release your project much less painful. Trust me, I’ve
|
||||
done it both ways.
|
||||
|
||||
So, create a new “Class Library” project, then use a text editor to change the
|
||||
“TargetFrameworks” property to `net48`. While you’re there you might also want
|
||||
to change the “LanguageVerison” to `10`. Most new features are backwards
|
||||
compatible with .Net Framework. The compiler will assist you with errors for
|
||||
those that are not.
|
||||
|
||||
## Adding YAVAPF as a Dependency
|
||||
|
||||
This one is the simple part, just install `alterNERDtive.YAVAPF` through NuGet.
|
||||
Done.
|
||||
|
||||
Alternatively you can add it manually by cloning
|
||||
`github.com/alterNERDtive/YAVAPF.git` as a git submodule and referencing
|
||||
`VoiceAttack-Framework\VoiceAttack-Framework.csproj` as a project reference.
|
||||
|
||||
But seriously, use NuGet. I haven’t taught myself how to release NuGet packages
|
||||
just for you to ignore it!
|
||||
|
||||
## Adding VoiceAttack as a Dependency
|
||||
|
||||
This is a little more involved. In order to use the actual proxy classes from
|
||||
VoiceAttack instead of the “official” crutch of `dynamic` types, you will need
|
||||
to add an assembly reference to `VoiceAttack.exe`.
|
||||
|
||||
Right click → “Add” → “Assembly Reference…” → “Browse” → browse to the
|
||||
VoiceAttack installation folder → select `VoiceAttack.exe` → hit “Add” → make
|
||||
sure it is ticked in the list → hit “OK”.
|
||||
|
||||
Now, we want to _reference_ `VoiceAttack.exe`, but we don’t want to _include_ it
|
||||
when compiling the plugin. So select “VoiceAttack” in “Dependencies” →
|
||||
“Assemblies” and make sure that both “Copy Local” and “Embed Interop Types” are
|
||||
set to “No”.
|
||||
|
||||
Distributing `VoiceAttack.exe` with your plugin would technically be a copyright
|
||||
violation. _Do_ make sure to take the steps outlined in the last paragraph to
|
||||
prevent accidentally doing that! Using it as a reference assembly is generally
|
||||
OK and I have received confirmation from Gary, the author of VoiceAttack.
|
||||
|
||||
## Setting Up Debugging Through VoiceAttack
|
||||
|
||||
In order to be able to run VoiceAttack when debugging and actually debug your
|
||||
plugin, you will need to open “Debug” → “<your project\> Debug Properties”.
|
||||
|
||||
There you will need to “Create a new profile” → “Executable”. Set the path to
|
||||
your VoiceAttack executable and any command line options you might prefer.
|
||||
Personally I like to set a custom `-datadir` in order to not mess with my
|
||||
regular profile database accidentally.
|
||||
|
||||
The example plugin project has a `Properties\launchSettings.sample.json` file
|
||||
that you can copy to `Properties\launchSettings.json` and edit accordingly to
|
||||
accomplish the same thing.
|
||||
|
||||
The last thing you’ll need to do is make your plugin available to VoiceAttack
|
||||
in a place where it can find it. I have requested an equivalent `-appdir`
|
||||
parameter, but as long as that is not available you will need to have your
|
||||
plugin present inside the regular `Apps` folder of VoiceAttack. I recommend
|
||||
creating a directory junction (`mklink /j`, or `New-Item -ItemType Junction` in
|
||||
PowerShell) between an `Apps` subfolder and your project’s debug output
|
||||
directory (usually `<project name>\bin\Debug\net48` inside your solution
|
||||
folder).
|
||||
|
||||
## Building Through Github Actions
|
||||
|
||||
If you, like me, want to automate building/testing/releasing through [Github
|
||||
Actions](https://docs.github.com/en/actions), you’ll need to have VoiceAttack
|
||||
available while building on the worker. Obviously that will only work on a
|
||||
Windows worker.
|
||||
|
||||
I have created the
|
||||
[`alterNERDtive/setup-voiceattack-action`](https://github.com/alterNERDtive/setup-voiceattack-action)
|
||||
to facilitate that. Usage example:
|
||||
|
||||
```yaml
|
||||
- name: Install VoiceAttack
|
||||
uses: alterNERDtive/setup-voiceattack-action
|
||||
with:
|
||||
version: "1.10"
|
||||
```
|
||||
|
||||
Make sure that the path to VoiceAttack on your machine (which is the path
|
||||
referenced in the project file) matches the path where you install VoiceAttack
|
||||
on the worker! Alternatively, if you have installed VoiceAttack in a custom
|
||||
folder locally, you can create a symlink (`mklink`, or
|
||||
`New-Item -ItemType SymbolicLink` in PowerShell) to your `VoiceAttack.exe`
|
||||
location at `C:\Program Files\VoiceAttack\VoiceAttack.exe` and include that as
|
||||
the assembly reference.
|
||||
|
||||
## Creating a Minimum Viable Plugin
|
||||
|
||||
A valid VoiceAttack plugin must implement a selection of public, static methods:
|
||||
|
||||
* `VA_DisplayName()`: Must return the name of the plugin.
|
||||
* `VA_DisplayInfo()`: Must return the description of the plugin.
|
||||
* `VA_Id()`: Must return the GUID of the plugin.
|
||||
* `VA_Init1(dynamic)`: Is executed when the plugin is loaded into VoiceAttack.
|
||||
* `VA_Invoke1(dynamic)`: Is executed whenever a plugin context is run from a
|
||||
command.
|
||||
* `VA_Exit1(dynamic)`: Is executed when VoiceAttack shuts down.
|
||||
* `VA_StopCommand()`: Is executed when VoiceAttack stops all commands, e.g.
|
||||
through the command action or main interface button.
|
||||
|
||||
When using YAVAPF these methods are to be passed straight to the corresponding
|
||||
methods of a `VoiceAttackPlugin` object that handles most things for you. It has
|
||||
a few required properties:
|
||||
|
||||
* `Name`: The name of the plugin.
|
||||
* `Version`: The version of the plugin.
|
||||
* `Info`: The description of the plugin.
|
||||
* `Guid`: The GUID of the plugin.
|
||||
|
||||
All of those are `string`s for ease of use, though the `Guid` obviously has to
|
||||
be a valid string representation of a GUID. You can generate one using “Tools” →
|
||||
“Create GUID”. Make sure to select “Registry Format”.
|
||||
|
||||
For a YAVAPF plugin you will have to derive your plugin class from
|
||||
`alterNERDtive.Yavapf.VoiceAttackPlugin`. Since VoiceAttack’s plugin API relies
|
||||
entirely on static methods, you’ll need to instantiate your plugin object in its
|
||||
static constructor and hold it in a static variable for future reference (no pun
|
||||
intended).
|
||||
|
||||
So a minimum viable plugin using YAVAPF looks kind of like this:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
using alterNERDtive.Yavapf;
|
||||
|
||||
namespace YourNamespace
|
||||
{
|
||||
public class YourPlugin : VoiceAttackPlugin
|
||||
{
|
||||
private static readonly YourPlugin Plugin;
|
||||
|
||||
static YourPlugin()
|
||||
{
|
||||
Plugin = new ()
|
||||
{
|
||||
Name = "Your Plugin",
|
||||
Version = "0.0.1",
|
||||
Info = "This is a description",
|
||||
Guid = "{5E93F293-B2CB-4B3F-AFC5-AE500A7EEBA9}",
|
||||
};
|
||||
}
|
||||
|
||||
public static string VA_DisplayName() => Plugin.VaDisplayName();
|
||||
|
||||
public static string VA_DisplayInfo() => Plugin.VaDisplayInfo();
|
||||
|
||||
public static Guid VA_Id() => Plugin.VaId();
|
||||
|
||||
public static void VA_Init1(dynamic vaProxy) => Plugin.VaInit1(vaProxy);
|
||||
|
||||
public static void VA_Invoke1(dynamic vaProxy) => Plugin.VaInvoke1(vaProxy);
|
||||
|
||||
public static void VA_Exit1(dynamic vaProxy) => Plugin.VaExit1(vaProxy);
|
||||
|
||||
public static void VA_StopCommand() => Plugin.VaStopCommand();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That’s it! Technically you’re done. Hit the debug button, and VoiceAttack should
|
||||
find your plugin on startup, report loading it in the event log, and list it
|
||||
under “Options” → “General” → “Plugin Manager”.
|
||||
|
||||
Of course you are only just getting started if you want your plugin to actually
|
||||
_do_ something!
|
|
@ -13,13 +13,23 @@ The goal is to get you up & running with as little code and as little knowledge
|
|||
the inner workings of VoiceAttack as possible.
|
||||
|
||||
You can find an [example plugin on
|
||||
Github](https://github.com/alterNERDtive/YAVAPF/tree/develop/ExamplePlugin).
|
||||
Github](https://github.com/alterNERDtive/YAVAPF/tree/release/ExamplePlugin).
|
||||
|
||||
## Current Implementation Status
|
||||
|
||||
* [x] VoiceAttack plugin API
|
||||
* [x] Handlers for Init/Invoke/Exit/StopCommand
|
||||
* [x] Plugin contexts
|
||||
* [x] Handlers for variable changed events
|
||||
* [x] Logging to the VoiceAttack event log
|
||||
* [ ] Logging to a log file
|
||||
* [ ] Wrapper for executing commands
|
||||
* [ ] Miscellaneous VoiceAttack proxy functionality
|
||||
* [ ] Full unit test coverage 😬
|
||||
|
||||
## Need Help / Want to Contribute?
|
||||
|
||||
Have a look at [the troubleshooting
|
||||
guide](https://alterNERDtive.github.io/YAVAPF/troubleshooting). If your problem
|
||||
persists, please [file an
|
||||
Have a look at [the FAQ](faq.md). If your problem persists, please [file an
|
||||
issue](https://github.com/alterNERDtive/YAVAPF/issues/new). Thanks! :)
|
||||
|
||||
You can also [say “Hi” on Discord](https://discord.gg/3pWdJwfJc5) if that is
|
||||
|
|
73
docs/logging.md
Normal file
73
docs/logging.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
# Logging
|
||||
|
||||
YAVAPF allows logging to the VoiceAttack event log.
|
||||
|
||||
Logging to a log file is planned, but not implemented yet.
|
||||
|
||||
## Write a Log Line
|
||||
|
||||
To write a log message from plugin code, use the methods provided by the
|
||||
`VoiceAttackLog` object availabe in the `Log` property of your plugin object.
|
||||
There is one per log level.
|
||||
|
||||
```csharp
|
||||
Plugin.Log.Error("Example error message.");
|
||||
Plugin.Log.Debug("Just sent an error message.");
|
||||
```
|
||||
|
||||
You can also log messages from a VoiceAttack command. Unlike a regular “Write to
|
||||
Log” command action going through the plugin will enforce the correct format and
|
||||
log level.
|
||||
|
||||
Your plugin will automatically provide the reserved plugin contexts
|
||||
`log.<log level>` for each of the 5 log levels. Simply set a `~message` string
|
||||
and call the appropriate plugin context. This should be equivalent to the code
|
||||
above:
|
||||
|
||||
```
|
||||
Set text [~message] to 'Example error message.'
|
||||
Execute external plugin, 'Your Plugin v0.0.1' using context 'log.error' and wait for return
|
||||
Set text [~message] to 'Just sent an error message.'
|
||||
Execute external plugin, 'Your Plugin v0.0.1' using context 'log.debug' and wait for return
|
||||
```
|
||||
|
||||
## Log Level
|
||||
|
||||
A message will be colour coded with the corresponding colour. If the log level
|
||||
assigned to a message is below the current log level, it will not be displayed.
|
||||
|
||||
E.g. an `INFO` message will not be displayed by default since the default log
|
||||
level is `NOTICE`. A `DEBUG` message will only ever be displayed if the current
|
||||
log level is `DEBUG`.
|
||||
|
||||
| Log Level | Log Colour | Recommended Use
|
||||
|-----------|---------------|----------------------------
|
||||
| ERROR | 🟥 red | unrecoverable error
|
||||
| WARN | 🟨 yellow | recoverable error, warning
|
||||
| NOTICE | 🟩 green | noteworthy information
|
||||
| INFO | 🟦 blue | miscellaneous information
|
||||
| DEBUG | ⬜ gray | debugging
|
||||
|
||||
## Setting the Current Log Level
|
||||
|
||||
You can set the current log level by either setting the `LogLevel` property or
|
||||
by invoking `SetLogLevel(string)` of your plugin’s `Log` property.
|
||||
|
||||
The latter is mostly useful when dealing with input from a VoiceAttack command.
|
||||
Its parameter is not case sensitive.
|
||||
|
||||
```csharp
|
||||
Plugin.Log.LogLevel = LogLevel.WARN;
|
||||
Plugin.Log.SetLogLevel = "info";
|
||||
```
|
||||
|
||||
You can also set the log level from a VoiceAttack command directly by simply
|
||||
setting `<plugin name>.loglevel#` to the desired log level.
|
||||
|
||||
```
|
||||
Set text [Your Plugin.loglevel#] to 'debug'
|
||||
```
|
||||
|
||||
The variable changed event for this specific variable name will be handled by
|
||||
YAVAPF internally. You can of course still define your own handlers in addition
|
||||
to it.
|
|
@ -0,0 +1,157 @@
|
|||
# Using VoiceAttack Variables
|
||||
|
||||
VoiceAttack allows you to set a plethora of variables. Each variable has
|
||||
|
||||
* a name
|
||||
* a type
|
||||
* a scope
|
||||
|
||||
Variable names are unique to each variable type. E.g. you can have _both_ a text
|
||||
variable `test` _and_ a boolean variable `test`.
|
||||
|
||||
In addition to that variable names are also unique to each _scope_; or,
|
||||
technically speaking, the scope is part of the variable name. E.g. a text
|
||||
variable `~test` is different from a text variable `>test`, and both are
|
||||
different from a text variable `test`.
|
||||
|
||||
## Variable Types
|
||||
|
||||
| VoiceAttack type | .Net type | Note
|
||||
|-----------------------|-----------|-------------------------------------
|
||||
| Text | string |
|
||||
| True/False (Boolean) | bool |
|
||||
| Integer | int |
|
||||
| Decimal | decimal |
|
||||
| Date/Time | DateTime |
|
||||
| Small Integer | short | deprecated; not supported by YAVAPF
|
||||
|
||||
Technical note: VoiceAttack internally holds a `Dictionary<string, T>` for each
|
||||
variable type. That is why, unlike regular variables in a .Net scope, the names
|
||||
are unique to their type.
|
||||
|
||||
## Variable Scopes
|
||||
|
||||
| Prefix | Scope | Accessibility
|
||||
|-----------|-----------------------|-------------------------------------------------
|
||||
| none | global | everywhere
|
||||
| `>>` | profile, persistent | same profile, preserved across profile switches
|
||||
| `>` | profile | same profile, reset on profile switch
|
||||
| `~~` | command + subcommands | this command invocation and its subcommands
|
||||
| `~` | command only | this command invocation
|
||||
|
||||
There is no scope that retains variable values across VoiceAttack restarts. You
|
||||
can save variable values to / load them from the current profile using a “Set a
|
||||
<Type\> Value” command action and ticking the corresponding box, but there is
|
||||
currently no way to do it from a plugin. Feature request pending.
|
||||
|
||||
Restricting variable scope as far as possible is recommended. For communication
|
||||
between commands and their plugin invocations scope should almost always be
|
||||
command only (`~`).
|
||||
|
||||
For global commands used by your plugin having some unique prefix is a sensible
|
||||
idea. For example, YAVAPF automatically sets the text variable
|
||||
`<plugin name>.version` to the current version of your plugin.
|
||||
|
||||
## Default Variables
|
||||
|
||||
By default, YAVAPF automatically sets the following variables for your plugin:
|
||||
|
||||
| Variable | Type | Description
|
||||
|-------------------------------|-----------|--------------------------------------------
|
||||
| `<plugin name>.version` | string | The current version of your plugin.
|
||||
| `<plugin name>.initialized` | bool | The plugin has been initialized correctly.
|
||||
|
||||
## Getting Variable Values
|
||||
|
||||
To get the value of a variable, invoke the `Get<T>(string name)` method of your
|
||||
plugin where `T` is the type of the variable and `name` is its name including
|
||||
its scope:
|
||||
|
||||
```csharp
|
||||
string? foo = Plugin.Get<string>("foo");
|
||||
bool bar = Plugin.Get<bool>("~bar") ?? false;
|
||||
```
|
||||
|
||||
Remember that variable values will be returned as `null` (“Not Set” in
|
||||
VoiceAttack terminology) if they are currently not holding a value.
|
||||
|
||||
## Setting Variable Values
|
||||
|
||||
To set the value of a variable, invoke the `Set<T>(string name, T value)` method
|
||||
of your plugin where `T` is the type of the variable, `name` is its name
|
||||
including its scope and `value` is the desired new value:
|
||||
|
||||
```csharp
|
||||
Plugin.Set<DateTime>("current", DateTime.Now);
|
||||
Plugin.Set<int>(">>deaths", (Plugin.Get<int>(">>deaths") ?? 0) + 1);
|
||||
```
|
||||
|
||||
## Clearing Variable Values
|
||||
|
||||
To clear a variable, invoke the `UnSet<T>(string name)` method of your plugin
|
||||
where `T` is the type of the variable and `name` is its name including its
|
||||
scope:
|
||||
|
||||
```csharp
|
||||
Plugin.UnSet<decimal>("π");
|
||||
```
|
||||
|
||||
Or `Set<T>(string, T)` it to `null`:
|
||||
|
||||
```csharp
|
||||
Plugin.Set<string>(">fizzbang", null);
|
||||
```
|
||||
|
||||
## Subscribing to “Variable Changed” Events
|
||||
|
||||
VoiceAttack allows triggering plugins when a variable value changes. Handlers
|
||||
must have an Attribute corresponding to the variable type (`BoolAttribute`,
|
||||
`DateTimeAttribute`, `DecimalAttribute`, `IntAttribute`, `StringAttribute`) and
|
||||
accept the following parameters:
|
||||
|
||||
* `string name`: the name of the variable that has changed
|
||||
* `T? from`: the old value of the variable
|
||||
* `T? to`: the new value of the variable
|
||||
|
||||
where `T` is the type of the variable. Remember that at any point either `to`
|
||||
or `from` might be `null`.
|
||||
|
||||
**Note**: In order for a variable to trigger variable changed events, the
|
||||
variable name **must** end with a number sign (`#`)! This is a limitation
|
||||
enforced by VoiceAttack. The purpose of this constraint is to not constantly
|
||||
invoke plugins whenever any variable changes.
|
||||
|
||||
```csharp
|
||||
[Bool("isDay#")]
|
||||
public static void DayChanged(string name, bool? from, bool? to)
|
||||
{
|
||||
Plugin.Log.Notice($"It is now {(to ?? false ? "day" : "night")}.");
|
||||
}
|
||||
```
|
||||
|
||||
This constraint also applies to catchall handlers. Even though the following
|
||||
method accepts any variable name, it will still only be invoked if the name of a
|
||||
changed variable ends with a `#`. E.g. changing the text variable `foo#` will
|
||||
invoke it while changing the text variable `foo` will not.
|
||||
|
||||
```csharp
|
||||
[String]
|
||||
public static void StringChanged(string name, string? from, string? to)
|
||||
{
|
||||
Plugin.Log.Notice($"Text variable '{name}' changed from '{from ?? "Not Set"}' to '{to ?? "Not Set"}'.");
|
||||
}
|
||||
```
|
||||
|
||||
[More on logging](logging.md).
|
||||
|
||||
Do be aware that changing the value of a variable from within its handler will
|
||||
trigger another variable changed event and run the handler again. It is very
|
||||
much possible to create an infinite loop.
|
||||
|
||||
Attribute names work in the same way as [context names](contexts.md). You can
|
||||
have handlers for singular variable names, regular expressions, and catchall
|
||||
handlers.
|
||||
|
||||
The text variable `<plugin name>.loglevel#` is handled by YAVAPF internally to
|
||||
set the current log level. You can still add additional handlers for this
|
||||
variable.
|
19
mkdocs.yml
19
mkdocs.yml
|
@ -1,4 +1,4 @@
|
|||
site_name: "bindED VoiceAttack plugin"
|
||||
site_name: "YAVAPF – Yet Another VoiceAttack Plugin Framework"
|
||||
site_url: https://alterNERDtive.github.io/YAVAPF
|
||||
repo_url: https://github.com/alterNERDtive/YAVAPF
|
||||
edit_uri: "edit/devel/docs/"
|
||||
|
@ -6,6 +6,8 @@ site_description: "YAVAPF is yet another VoiceAttack plugin framework."
|
|||
site_author: "alterNERDtive"
|
||||
remote_name: "origin"
|
||||
|
||||
extra_css: [extra.css]
|
||||
|
||||
theme:
|
||||
name: readthedocs
|
||||
prev_next_buttons_location: both
|
||||
|
@ -18,15 +20,18 @@ markdown_extensions:
|
|||
- toc:
|
||||
permalink: True
|
||||
- sane_lists
|
||||
- pymdownx.tasklist
|
||||
|
||||
nav:
|
||||
- 'Home': 'index.md'
|
||||
- 'Usage':
|
||||
- 'Getting Started': 'gettingstarted.md'
|
||||
- 'Event Handlers': 'events.md'
|
||||
- 'Plugin Contexts': 'contexts.md'
|
||||
- 'Working with Variables': 'variables.md'
|
||||
- 'Running Commands': 'commands.md'
|
||||
- 'troubleshooting.md'
|
||||
- 'gettingstarted.md'
|
||||
- 'events.md'
|
||||
- 'contexts.md'
|
||||
- 'variables.md'
|
||||
- 'commands.md'
|
||||
- 'logging.md'
|
||||
- Troubleshooting:
|
||||
- 'faq.md'
|
||||
- '⎋ Changelog': 'https://github.com/alterNERDtive/YAVAPF/blob/release/CHANGELOG.md'
|
||||
- '⎋ Report a Bug': 'https://github.com/alterNERDtive/YAVAPF/issues/'
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
mkdocs-roamlinks-plugin
|
||||
mkdocs-roamlinks-plugin
|
||||
pymdown-extensions
|
||||
|
|
Loading…
Reference in a new issue