diff --git a/ExamplePlugin/ExamplePlugin.cs b/ExamplePlugin/ExamplePlugin.cs index 2118df5..c9e6095 100644 --- a/ExamplePlugin/ExamplePlugin.cs +++ b/ExamplePlugin/ExamplePlugin.cs @@ -19,6 +19,8 @@ using System; +using VoiceAttack; + namespace alterNERDtive.Yavapf.Example { public class ExamplePlugin @@ -27,13 +29,17 @@ namespace alterNERDtive.Yavapf.Example static ExamplePlugin() { - Plugin = new () - { - Name = "Example Plugin", - Version = "0.0.1", - Info = "This is a description", - Guid = "{76FE674F-F729-45FD-A1DD-E53E9E66B360}", - }; + Plugin = new ( + name: "Example Plugin", + version: "0.0.1", + info: "This is a description", + guid: "{76FE674F-F729-45FD-A1DD-E53E9E66B360}"); + + Plugin.Init += Init; + Plugin.Exit += Exit; + Plugin.Stop += Stop; + + Plugin.Contexts += Test; } public static string VA_DisplayName() => Plugin.VA_DisplayName(); @@ -49,5 +55,24 @@ namespace alterNERDtive.Yavapf.Example public static void VA_Exit1(dynamic vaProxy) => Plugin.VA_Exit1(vaProxy); public static void VA_StopCommand() => Plugin.VA_StopCommand(); + + private static void Init(VoiceAttackInitProxyClass vaProxy) + { + } + + private static void Exit(VoiceAttackProxyClass vaProxy) + { + } + + private static void Stop() + { + } + + [Context("test")] + [Context("other name")] + private static void Test(VoiceAttackInvokeProxyClass vaProxy) + { + Plugin.Log.Notice($"Plugin context '{vaProxy.Context}' invoked."); + } } } diff --git a/ExamplePlugin/ExamplePlugin.csproj b/ExamplePlugin/ExamplePlugin.csproj index 37855b7..c9ca55f 100644 --- a/ExamplePlugin/ExamplePlugin.csproj +++ b/ExamplePlugin/ExamplePlugin.csproj @@ -6,10 +6,21 @@ enable + + + + + + + C:\Program Files\VoiceAttack\VoiceAttack.exe + False + + + True diff --git a/VoiceAttack-Framework/VoiceAttack-Framework.csproj b/VoiceAttack-Framework/VoiceAttack-Framework.csproj index a963370..afb663c 100644 --- a/VoiceAttack-Framework/VoiceAttack-Framework.csproj +++ b/VoiceAttack-Framework/VoiceAttack-Framework.csproj @@ -1,8 +1,8 @@ - + net48 - alterNERDtive.YAVAPF + alterNERDtive.Yavapf alterNERDtive.YAVAPF 0.0.1 alterNERDtive @@ -38,7 +38,11 @@ - + + + C:\Program Files\VoiceAttack\VoiceAttack.exe + False + diff --git a/VoiceAttack-Framework/VoiceAttackLog.cs b/VoiceAttack-Framework/VoiceAttackLog.cs new file mode 100644 index 0000000..33491c8 --- /dev/null +++ b/VoiceAttack-Framework/VoiceAttackLog.cs @@ -0,0 +1,117 @@ +// +// 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/>. +// + +using System; + +using VoiceAttack; + +namespace alterNERDtive.Yavapf +{ + public class VoiceAttackLog + { + private static readonly string[] LogColour = { "red", "yellow", "green", "blue", "gray" }; + + private static LogLevel? logLevel; + + private readonly VoiceAttackInitProxyClass vaProxy; + private readonly string id; + + public VoiceAttackLog(VoiceAttackInitProxyClass vaProxy, string id) + { + this.vaProxy = vaProxy; + this.id = id; + } + + public LogLevel? LogLevel + { + get => logLevel ?? Yavapf.LogLevel.NOTICE; + set + { + logLevel = value; + this.Notice($"Log level set to {value ?? Yavapf.LogLevel.NOTICE}."); + } + } + + public void SetLogLevel(string level) + { + if (level == null) + { + this.LogLevel = null; + } + else + { + this.LogLevel = (LogLevel)Enum.Parse(typeof(LogLevel), level.ToUpper()); + } + } + + 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]); + } + } + + public void Error(string message) => this.Log(message, Yavapf.LogLevel.ERROR); + + public void Warn(string message) => this.Log(message, Yavapf.LogLevel.WARN); + + public void Notice(string message) => this.Log(message, Yavapf.LogLevel.NOTICE); + + public void Info(string message) => this.Log(message, Yavapf.LogLevel.INFO); + + public void Debug(string message) => this.Log(message, Yavapf.LogLevel.DEBUG); + } + + /// + /// Log levels that can be used when writing to the VoiceAttack log. + /// + public enum LogLevel + { + /// + /// Log level for error messages. Errors cause execution to abort. + /// + ERROR, + + /// + /// Log level for warning messages. Warnings should not cause execution + /// to abort. + /// + WARN, + + /// + /// Log level for notices. Notices should be noteworthy. + /// + NOTICE, + + /// + /// Log level for informational messages. These should not be + /// noteworthy. + /// + INFO, + + /// + /// Log level for debug messages. They should be useful only for + /// debugging. + /// + DEBUG, + } +} diff --git a/VoiceAttack-Framework/VoiceAttackPlugin.cs b/VoiceAttack-Framework/VoiceAttackPlugin.cs index 08e02ba..e2fe78f 100644 --- a/VoiceAttack-Framework/VoiceAttackPlugin.cs +++ b/VoiceAttack-Framework/VoiceAttackPlugin.cs @@ -18,64 +18,228 @@ // using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using VoiceAttack; namespace alterNERDtive.Yavapf { public class VoiceAttackPlugin { - public string Name { get; set; } + public VoiceAttackPlugin(string name, string version, string info, string guid) + => (this.Name, this.Version, this.Info, this.Guid) + = (name, version, info, new (guid)); - public string Version { get; set; } + // this just hides the default constructor to make sure Properties are set. + private VoiceAttackPlugin() + { + } - public string Info { get; set; } + public event Action? Init; - public string Guid { get; set; } + public event Action? Exit; - private dynamic? vaProxy; + public event Action? Stop; + + public HandlerList> Contexts { get; set; } = new (); + + public VoiceAttackInitProxyClass Proxy + { + get => this.vaProxy; + } + + public string Name { get; } + + public string Version { get; } + + public string Info { get; } + + public Guid Guid { get; } + + public VoiceAttackLog Log { + get => this.log ??= new VoiceAttackLog(this.vaProxy, this.Name); + } + + private VoiceAttackLog log; + + protected VoiceAttackInitProxyClass vaProxy; + + public void Set(string name, T? value) + { + switch (value) + { + case bool b: + this.vaProxy.SetBoolean(name, b); + break; + case DateTime d: + this.vaProxy.SetDate(name, d); + break; + case decimal d: + this.vaProxy.SetDecimal(name, d); + break; + case int i: + this.vaProxy.SetInt(name, i); + break; + case short s: + this.vaProxy.SetSmallInt(name, s); + break; + case string s: + this.vaProxy.SetText(name, s); + break; + default: + throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'."); + } + } + + public T? Get(string name) + { + dynamic? ret = null; + + switch (default(T)) + { + case bool _: + ret = this.vaProxy.GetBoolean(name); + break; + case DateTime _: + ret = this.vaProxy.GetDate(name); + break; + case decimal _: + ret = this.vaProxy.GetDecimal(name); + break; + case int _: + ret = this.vaProxy.GetInt(name); + break; + case short _: + ret = this.vaProxy.GetSmallInt(name); + break; + case string _: + ret = this.vaProxy.GetText(name); + break; + default: + throw new InvalidDataException($"Cannot get variable '{name}': invalid type '{typeof(T).Name}'."); + } + + return ret; + } public string VA_DisplayName() => $"{this.Name} v{this.Version}"; public string VA_DisplayInfo() => this.Info; - public Guid VA_Id() => new (this.Guid); + public Guid VA_Id() => this.Guid; - public void VA_Init1(dynamic vaProxy) + public void VA_Init1(VoiceAttackInitProxyClass vaProxy) { this.vaProxy = vaProxy; - this.vaProxy.TextVariableChanged += new Action(this.TextVariableChanged); - this.vaProxy.IntegerVariableChanged += new Action(this.IntegerVariableChanged); - this.vaProxy.DecimalVariableChanged += new Action(this.DecimalVariableChanged); - this.vaProxy.BooleanVariableChanged += new Action(this.BooleanVariableChanged); - this.vaProxy.DateVariableChanged += new Action(this.DateVariableChanged); + + this.Set($"{this.Name}.version", this.Version); + this.Log.Debug($"Initializing v{this.Version} …"); + + this.vaProxy.TextVariableChanged += this.TextVariableChanged; + this.vaProxy.IntegerVariableChanged += this.IntegerVariableChanged; + this.vaProxy.DecimalVariableChanged += this.DecimalVariableChanged; + this.vaProxy.BooleanVariableChanged += this.BooleanVariableChanged; + this.vaProxy.DateVariableChanged += this.DateVariableChanged; + + this.Log.Debug("Running Init handlers …"); + this.Init?.Invoke(vaProxy); + this.Log.Debug("Finished running Init handlers."); + + this.Set($"{this.Name}.initialized", true); + this.Log.Debug("Initialized."); } - public void VA_Invoke1(dynamic vaProxy) + public void VA_Invoke1(VoiceAttackInvokeProxyClass vaProxy) { this.vaProxy = vaProxy; + + string context = vaProxy.Context.ToLower(); + + try + { + bool exists = false; + foreach (Action action in Contexts) + { + foreach (ContextAttribute attr in action.Method.GetCustomAttributes()) + { + if (attr.Name == context) + { + exists = true; + action.Invoke(vaProxy); + } + } + } + + if (!exists) + { + this.Log.Error($"Invalid plugin context '{vaProxy.Context}'."); + } + } + 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})"); + } } - public void VA_Exit1(dynamic vaProxy) + public void VA_Exit1(VoiceAttackProxyClass vaProxy) { - this.vaProxy = vaProxy; + this.Exit?.Invoke(vaProxy); } public void VA_StopCommand() { + this.Stop?.Invoke(); } private void TextVariableChanged(string name, string from, string to, Guid? internalID = null) - { } + { + } private void IntegerVariableChanged(string name, int? from, int? to, Guid? internalID = null) - { } + { + } - private void DecimalVariableChanged(string name, decimal? from, decimal? to, Guid? internalID = null) - { } + protected void DecimalVariableChanged(string name, decimal? from, decimal? to, Guid? internalID = null) + { + } - private void BooleanVariableChanged(string name, bool? from, bool? to, Guid? internalID = null) - { } + protected void BooleanVariableChanged(string name, bool? from, bool? to, Guid? internalID = null) + { + } - private void DateVariableChanged(string name, DateTime? from, DateTime? to, Guid? internalID = null) - { } + protected void DateVariableChanged(string name, DateTime? from, DateTime? to, Guid? internalID = null) + { + } + + public class HandlerList : List + { + public static HandlerList operator +(HandlerList handlers, T item) + { + handlers.Add(item); + return handlers; + } + + public static HandlerList operator -(HandlerList handlers, T item) + { + handlers.Remove(item); + return handlers; + } + } + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class ContextAttribute : Attribute + { + public string Name { get; } + + public ContextAttribute(string context) + { + this.Name = context; + } } }