diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index a9fccdba..104ad218 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1025,6 +1025,7 @@ namespace MinecraftClient } b.OnUnload(); + b.UnregisterChatBotCommands(); bots.RemoveAll(item => ReferenceEquals(item, b)); diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 8b612ab8..586a6fa8 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -6,7 +6,9 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using Brigadier.NET; +using Brigadier.NET.Builder; using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; using MinecraftClient.Mapping; using static MinecraftClient.Settings; @@ -43,6 +45,7 @@ namespace MinecraftClient.Scripting private McClient? _handler = null; private ChatBot? master = null; private readonly List registeredPluginChannels = new(); + private readonly List registeredChatBotCommands = new(); private readonly Lock delayTasksLock = new(); private readonly List delayedTasks = new(); protected McClient Handler @@ -1710,6 +1713,34 @@ namespace MinecraftClient.Scripting /// Command result to display to the user public delegate string CommandRunner(string command, string[] args); + /// + /// Register a simple ChatBot command with the Brigadier dispatcher. + /// The command is automatically unregistered when the bot is unloaded. + /// + /// Name of the command (used as the literal command name) + /// Description of the command + /// Usage string for the command + /// Method to handle the command execution + protected void RegisterChatBotCommand(string cmdName, string cmdDesc, string cmdUsage, CommandRunner callback) + { + var command = new ChatBotCommand(cmdName, cmdDesc, cmdUsage, callback); + command.RegisterCommand(McClient.dispatcher); + registeredChatBotCommands.Add(cmdName); + } + + /// + /// Unregisters all commands that were registered via RegisterChatBotCommand. + /// Called automatically by McClient.BotUnLoad() during bot unload - do not call manually. + /// + internal void UnregisterChatBotCommands() + { + foreach (var cmdName in registeredChatBotCommands) + { + McClient.dispatcher.Unregister(cmdName); + } + registeredChatBotCommands.Clear(); + } + /// /// Command class with constructor for creating command for ChatBots. /// @@ -1727,7 +1758,22 @@ namespace MinecraftClient.Scripting public override void RegisterCommand(CommandDispatcher dispatcher) { - + dispatcher.Register(l => l.Literal(_cmdName) + .Then(l => l.Argument("args", Arguments.GreedyString()) + .Executes(r => + { + string fullArgs = Arguments.GetString(r, "args"); + string result = Runner( + $"{_cmdName} {fullArgs}", + fullArgs.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); + return r.Source.SetAndReturn(CmdResult.Status.Done, result); + })) + .Executes(r => + { + string result = Runner(_cmdName, Array.Empty()); + return r.Source.SetAndReturn(CmdResult.Status.Done, result); + }) + ); } /// diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs index 9cc1df13..2207bad8 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs @@ -49,7 +49,7 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder private static CSharpCompilation GenerateCode(string sourceCode, string fileName, List additionalAssemblies) { var codeString = SourceText.From(sourceCode); - var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9); + var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); @@ -129,9 +129,22 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder var assemblyrefs = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList()!; assemblyrefs.Add(new("MinecraftClient")); assemblyrefs.Add(new("System.Private.CoreLib")); + // Facade assemblies needed for compiling scripts that reference netstandard libraries (e.g. Brigadier.NET) + assemblyrefs.Add(new("netstandard")); + assemblyrefs.Add(new("System.Runtime")); foreach (var refs in assemblyrefs) { - var loadedAssembly = Assembly.Load(refs); + Assembly? loadedAssembly; + try + { + loadedAssembly = Assembly.Load(refs); + } + catch (FileNotFoundException) + { + // Facade assemblies like netstandard may not be loadable in all environments + continue; + } + if (string.IsNullOrEmpty(loadedAssembly.Location)) { // Check if we can access the file from the executable. var reference = files.FirstOrDefault(x => @@ -145,8 +158,8 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder } if (reference is null) { - throw new InvalidOperationException( - "[Script Error] The executable does not contain a referenced assembly. Assembly name: " + refs.Name); + // Facade assemblies may not be in the bundle - skip them silently + continue; } assemblyStream = reference.AsStream(); @@ -163,6 +176,16 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder references.Add(MetadataReference.CreateFromFile(SystemConsole)); references.Add(MetadataReference.CreateFromFile(MinecraftClientDll)); Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList().ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location))); + + // Add facade assemblies needed for Roslyn compilation when referencing + // libraries that target netstandard (e.g. Brigadier.NET). + var runtimeDir = Path.GetDirectoryName(SystemPrivateCoreLib)!; + foreach (var facadeName in new[] { "netstandard.dll", "System.Runtime.dll" }) + { + var facadePath = Path.Combine(runtimeDir, facadeName); + if (File.Exists(facadePath)) + references.Add(MetadataReference.CreateFromFile(facadePath)); + } } #pragma warning restore IL3000 diff --git a/MinecraftClient/config/ChatBots/AutoTree.cs b/MinecraftClient/config/ChatBots/AutoTree.cs index 5488d8ad..21d731b7 100644 --- a/MinecraftClient/config/ChatBots/AutoTree.cs +++ b/MinecraftClient/config/ChatBots/AutoTree.cs @@ -75,7 +75,7 @@ public class AutoTree : ChatBot } } - public override void Initialize(CommandDispatcher dispatcher) + public override void Initialize() { if (!GetTerrainEnabled()) { @@ -89,7 +89,7 @@ public class AutoTree : ChatBot } else { - dispatcher.Register(l => l.Literal("help") + McClient.dispatcher.Register(l => l.Literal("help") .Then(l => l.Literal(CommandName) .Executes(r => OnCommandHelp(r.Source, string.Empty)) .Then(l => l.Literal("set") @@ -99,7 +99,7 @@ public class AutoTree : ChatBot ) ); - dispatcher.Register(l => l.Literal(CommandName) + McClient.dispatcher.Register(l => l.Literal(CommandName) .Then(l => l.Literal("toggle") .Executes(r => { return r.Source.SetAndReturn(CmdResult.Status.Done, Toggle() ? "Now is running" : "Now is stopping"); })) .Then(l => l.Literal("set") @@ -109,17 +109,17 @@ public class AutoTree : ChatBot .Then(l => l.Argument("TreeType", Arguments.String()) .Executes(r => OnCommandType(r.Source, Arguments.GetString(r, "TreeType"))))) .Then(l => l.Literal("_help") - .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) ); LogToConsole("Loaded."); } } - public override void OnUnload(CommandDispatcher dispatcher) + public override void OnUnload() { - dispatcher.Unregister(CommandName); - dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); } private int OnCommandHelp(CmdResult r, string? cmd) diff --git a/MinecraftClient/config/ChatBots/DiscordWebhook.cs b/MinecraftClient/config/ChatBots/DiscordWebhook.cs index 892f087d..ce0e6d22 100644 --- a/MinecraftClient/config/ChatBots/DiscordWebhook.cs +++ b/MinecraftClient/config/ChatBots/DiscordWebhook.cs @@ -306,7 +306,7 @@ class DiscordWebhook : ChatBot LogToConsole("Made by Daenges.\nSpecial thanks to Crafatar for providing the beautiful avatars!"); LogToConsole("Please set a Webhook with '/dw changeurl [URL]'. For further information type '/discordwebhook help'."); - Handler.dispatcher.Register(l => l.Literal(CommandName) + McClient.dispatcher.Register(l => l.Literal(CommandName) .Then(l => l.Argument("Commands", Arguments.GreedyString()) .Executes(r => { CommandHandler(Arguments.GetString(r, "Commands").Split(' ', StringSplitOptions.TrimEntries)); @@ -317,7 +317,7 @@ class DiscordWebhook : ChatBot public override void OnUnload() { - Handler.dispatcher.Unregister(CommandName); + McClient.dispatcher.Unregister(CommandName); } public override void Update() diff --git a/MinecraftClient/config/ChatBots/MineCube.cs b/MinecraftClient/config/ChatBots/MineCube.cs index 71d760a6..1f64f0da 100644 --- a/MinecraftClient/config/ChatBots/MineCube.cs +++ b/MinecraftClient/config/ChatBots/MineCube.cs @@ -46,7 +46,7 @@ class MineCube : ChatBot LogToConsole("Mining bot created by Daenges."); - Handler.dispatcher.Register(l => l.Literal(CommandName) + McClient.dispatcher.Register(l => l.Literal(CommandName) .Then(l => l.Argument("Commands", Arguments.GreedyString()) .Executes(r => { EvaluateMineCommand(CommandName + ' ' + Arguments.GetString(r, "Commands"), Arguments.GetString(r, "Commands").Split(' ', StringSplitOptions.TrimEntries)); @@ -57,7 +57,7 @@ class MineCube : ChatBot public override void OnUnload() { - Handler.dispatcher.Unregister(CommandName); + McClient.dispatcher.Unregister(CommandName); } /// diff --git a/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs index 68979e4b..9b670b48 100644 --- a/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs +++ b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs @@ -151,7 +151,7 @@ class SugarCaneFarmer : SugarCaneFarmerBase { LogToConsole("Sugar Cane farming bot created by Daenges."); - Handler.dispatcher.Register(l => l.Literal(CommandName) + McClient.dispatcher.Register(l => l.Literal(CommandName) .Then(l => l.Argument("Commands", Arguments.GreedyString()) .Executes(r => { CommandHandler(Arguments.GetString(r, "Commands").Split(' ', StringSplitOptions.TrimEntries)); @@ -162,7 +162,7 @@ class SugarCaneFarmer : SugarCaneFarmerBase public override void OnUnload() { - Handler.dispatcher.Unregister(CommandName); + McClient.dispatcher.Unregister(CommandName); } ///