Implement command completion suggestions.

This commit is contained in:
BruceChen 2022-12-06 15:50:17 +08:00
parent 5d2589b10f
commit 84cf749344
115 changed files with 4684 additions and 2695 deletions

View file

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class AccountNickArgumentType : ArgumentType<string>
{
public override string Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadString();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
var accountList = Settings.Config.Main.Advanced.AccountList;
foreach (var account in accountList)
builder.Suggest(account.Key);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class AutoCraftRecipeNameArgumentType : ArgumentType<string>
{
public override string Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadString();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
var recipeList = Settings.Config.ChatBot.AutoCraft.Recipes;
foreach (var recipe in recipeList)
builder.Suggest(recipe.Name);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class BotNameArgumentType : ArgumentType<string>
{
public override string Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadString();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
McClient? client = CmdResult.client;
if (client != null)
{
var botList = client.GetLoadedChatBots();
foreach (var bot in botList)
builder.Suggest(bot.GetType().Name);
}
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Exceptions;
using Brigadier.NET.Suggestion;
using MinecraftClient.Mapping;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class EntityTypeArgumentType : ArgumentType<EntityType>
{
public override EntityType Parse(IStringReader reader)
{
reader.SkipWhitespace();
string entity = reader.ReadString();
if (Enum.TryParse(entity, true, out EntityType entityType))
return entityType;
else
throw CommandSyntaxException.BuiltInExceptions.LiteralIncorrect().CreateWithContext(reader, entity);
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
foreach (var result in Enum.GetNames(typeof(EntityType)))
builder.Suggest(result);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Exceptions;
using Brigadier.NET.Suggestion;
using static MinecraftClient.ChatBots.Farmer;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class FarmerCropTypeArgumentType : ArgumentType<CropType>
{
public override CropType Parse(IStringReader reader)
{
reader.SkipWhitespace();
string inputStr = reader.ReadString();
if (Enum.TryParse(inputStr, true, out CropType cropType))
return cropType;
else
throw CommandSyntaxException.BuiltInExceptions.LiteralIncorrect().CreateWithContext(reader, inputStr);
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
foreach (var result in Enum.GetNames(typeof(CropType)))
builder.Suggest(result);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,41 @@
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Exceptions;
using Brigadier.NET.Suggestion;
using MinecraftClient.Inventory;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class InventoryActionArgumentType : ArgumentType<WindowActionType>
{
private WindowActionType[] SupportActions = new WindowActionType[]
{
WindowActionType.LeftClick,
WindowActionType.RightClick,
WindowActionType.MiddleClick,
WindowActionType.ShiftClick,
};
public override WindowActionType Parse(IStringReader reader)
{
reader.SkipWhitespace();
string inputStr = reader.ReadString();
foreach (var action in SupportActions)
{
string actionStr = action.ToString();
if (string.Compare(inputStr, actionStr, true) == 0)
return action;
}
throw CommandSyntaxException.BuiltInExceptions.LiteralIncorrect().CreateWithContext(reader, inputStr);
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
foreach (var action in SupportActions)
builder.Suggest(action.ToString());
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class InventoryIdArgumentType : ArgumentType<int>
{
public override int Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadInt();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
McClient? client = CmdResult.client;
if (client != null)
{
var invList = client.GetInventories();
foreach (var inv in invList)
{
string invName = inv.Key.ToString();
if (invName.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase))
builder.Suggest(invName);
}
}
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Exceptions;
using Brigadier.NET.Suggestion;
using MinecraftClient.Inventory;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class ItemTypeArgumentType : ArgumentType<ItemType>
{
public override ItemType Parse(IStringReader reader)
{
reader.SkipWhitespace();
string entity = reader.ReadString();
if (Enum.TryParse(entity, true, out ItemType itemType))
return itemType;
else
throw CommandSyntaxException.BuiltInExceptions.LiteralIncorrect().CreateWithContext(reader, entity);
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
foreach (var result in Enum.GetNames(typeof(ItemType)))
builder.Suggest(result);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
using MinecraftClient.Mapping;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class LocationArgumentType : ArgumentType<Location>
{
public override Location Parse(IStringReader reader)
{
int[] status = new int[3];
double[] coords = new double[3];
for (int i = 0; i < 3; ++i)
{
reader.SkipWhitespace();
if (reader.Peek() == '~' || reader.Peek() == '')
{
status[i] = 1;
reader.Next();
if (reader.CanRead())
{
char next = reader.Peek();
if (char.IsDigit(next) || next == '.' || next == '-')
coords[i] = reader.ReadDouble();
else if (next == '+')
{
reader.Next();
coords[i] = reader.ReadDouble();
}
else coords[i] = 0;
}
else coords[i] = 0;
}
else
{
status[i] = 0;
coords[i] = reader.ReadDouble();
}
}
return new Location(coords[0], coords[1], coords[2], (byte)(status[0] | status[1] << 1 | status[2] << 2));
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
McClient? client = CmdResult.client;
string[] args = builder.Remaining.Split(' ', StringSplitOptions.TrimEntries);
if (args.Length == 0 || (args.Length == 1 && string.IsNullOrWhiteSpace(args[0])))
{
if (client != null)
{
Location current = client.GetCurrentLocation();
builder.Suggest(string.Format("{0:0.00}", current.X));
builder.Suggest(string.Format("{0:0.00} {1:0.00}", current.X, current.Y));
builder.Suggest(string.Format("{0:0.00} {1:0.00} {2:0.00}", current.X, current.Y, current.Z));
}
else
{
builder.Suggest("~");
builder.Suggest("~ ~");
builder.Suggest("~ ~ ~");
}
}
else if (args.Length == 1 || (args.Length == 2 && string.IsNullOrWhiteSpace(args[1])))
{
string add = args.Length == 1 ? " " : string.Empty;
if (client != null)
{
Location current = client.GetCurrentLocation();
builder.Suggest(string.Format("{0}{2}{1:0.00}", builder.Remaining, current.Y, add));
builder.Suggest(string.Format("{0}{3}{1:0.00} {2:0.00}", builder.Remaining, current.Y, current.Z, add));
}
else
{
builder.Suggest(builder.Remaining + add + "~");
builder.Suggest(builder.Remaining + add + "~ ~");
}
}
else if (args.Length == 2 || (args.Length == 3 && string.IsNullOrWhiteSpace(args[2])))
{
string add = args.Length == 2 ? " " : string.Empty;
if (client != null)
{
Location current = client.GetCurrentLocation();
builder.Suggest(string.Format("{0}{2}{1:0.00}", builder.Remaining, current.Z, add));
}
else
{
builder.Suggest(builder.Remaining + add + "~");
}
}
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
using MinecraftClient.ChatBots;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class MapBotMapIdArgumentType : ArgumentType<int>
{
public override int Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadInt();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
McClient? client = CmdResult.client;
if (client != null)
{
var bot = (Map?)client.GetLoadedChatBots().Find(bot => bot.GetType().Name == "Map");
if (bot != null)
{
var mapList = bot.cachedMaps;
foreach (var map in mapList)
{
string mapName = map.Key.ToString();
if (mapName.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase))
builder.Suggest(mapName);
}
}
}
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,36 @@
using System.Linq;
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
using MinecraftClient.Mapping;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class PlayerNameArgumentType : ArgumentType<string>
{
public override string Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadString();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
McClient? client = CmdResult.client;
if (client != null)
{
var entityList = client.GetEntities().Values.ToList();
foreach (var entity in entityList)
{
if (entity.Type != EntityType.Player || string.IsNullOrWhiteSpace(entity.Name))
continue;
builder.Suggest(entity.Name);
}
builder.Suggest(client.GetUsername());
}
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
using Brigadier.NET.Context;
using Brigadier.NET.Suggestion;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class ServerNickArgumentType : ArgumentType<string>
{
public override string Parse(IStringReader reader)
{
reader.SkipWhitespace();
return reader.ReadString();
}
public override Task<Suggestions> ListSuggestions<TSource>(CommandContext<TSource> context, SuggestionsBuilder builder)
{
var serverList = Settings.Config.Main.Advanced.ServerList;
foreach (var server in serverList)
builder.Suggest(server.Key);
return builder.BuildFuture();
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using Brigadier.NET;
using Brigadier.NET.ArgumentTypes;
namespace MinecraftClient.CommandHandler.ArgumentType
{
public class TupleArgumentType : ArgumentType<Tuple<int, int>>
{
public override Tuple<int, int> Parse(IStringReader reader)
{
reader.SkipWhitespace();
int int1 = reader.ReadInt();
reader.SkipWhitespace();
int int2 = reader.ReadInt();
return new(int1, int2);
}
}
}

View file

@ -0,0 +1,96 @@
using System;
namespace MinecraftClient.CommandHandler
{
public class CmdResult
{
public static readonly CmdResult Empty = new();
internal static McClient? client;
public enum Status
{
NotRun = int.MinValue,
FailChunkNotLoad = -4,
FailNeedEntity = -3,
FailNeedInventory = -2,
FailNeedTerrain = -1,
Fail = 0,
Done = 1,
}
public CmdResult()
{
this.status = Status.NotRun;
this.result = null;
}
public Status status;
public string? result;
public int SetAndReturn(Status status)
{
this.status = status;
this.result = status switch
{
#pragma warning disable format // @formatter:off
Status.NotRun => null,
Status.FailChunkNotLoad => null,
Status.FailNeedEntity => Translations.extra_entity_required,
Status.FailNeedInventory => Translations.extra_inventory_required,
Status.FailNeedTerrain => Translations.extra_terrainandmovement_required,
Status.Fail => Translations.general_fail,
Status.Done => null,
_ => null,
#pragma warning restore format // @formatter:on
};
return Convert.ToInt32(this.status);
}
public int SetAndReturn(Status status, string? result)
{
this.status = status;
this.result = result;
return Convert.ToInt32(this.status);
}
public int SetAndReturn(int code, string? result)
{
this.status = (Status)Enum.ToObject(typeof(Status), code);
if (!Enum.IsDefined(typeof(Status), status))
throw new InvalidOperationException($"{code} is not a legal return value.");
this.result = result;
return code;
}
public int SetAndReturn(bool result)
{
this.status = result ? Status.Done : Status.Fail;
this.result = result ? Translations.general_done : Translations.general_fail;
return Convert.ToInt32(this.status);
}
public int SetAndReturn(string? result)
{
this.status = Status.Done;
this.result = result;
return Convert.ToInt32(this.status);
}
public int SetAndReturn(string? resultstr, bool result)
{
this.status = result ? Status.Done : Status.Fail;
this.result = resultstr;
return Convert.ToInt32(this.status);
}
public override string ToString()
{
if (result != null)
return result;
else
return status.ToString();
}
}
}

View file

@ -0,0 +1,104 @@
using System;
using Brigadier.NET.Context;
using MinecraftClient.CommandHandler.ArgumentType;
namespace MinecraftClient.CommandHandler
{
public static class MccArguments
{
public static LocationArgumentType Location()
{
return new LocationArgumentType();
}
public static Mapping.Location GetLocation<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<Mapping.Location>(name);
}
public static TupleArgumentType Tuple()
{
return new TupleArgumentType();
}
public static Tuple<int, int> GetTuple<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<Tuple<int, int>>(name);
}
public static EntityTypeArgumentType EntityType()
{
return new EntityTypeArgumentType();
}
public static Mapping.EntityType GetEntityType<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<Mapping.EntityType>(name);
}
public static ItemTypeArgumentType ItemType()
{
return new ItemTypeArgumentType();
}
public static Inventory.ItemType GetItemType<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<Inventory.ItemType>(name);
}
public static BotNameArgumentType BotName()
{
return new BotNameArgumentType();
}
public static ServerNickArgumentType ServerNick()
{
return new ServerNickArgumentType();
}
public static AccountNickArgumentType AccountNick()
{
return new AccountNickArgumentType();
}
public static InventoryIdArgumentType InventoryId()
{
return new InventoryIdArgumentType();
}
public static InventoryActionArgumentType InventoryAction()
{
return new InventoryActionArgumentType();
}
public static Inventory.WindowActionType GetInventoryAction<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<Inventory.WindowActionType>(name);
}
public static AutoCraftRecipeNameArgumentType AutoCraftRecipeName()
{
return new AutoCraftRecipeNameArgumentType();
}
public static FarmerCropTypeArgumentType FarmerCropType()
{
return new FarmerCropTypeArgumentType();
}
public static ChatBots.Farmer.CropType GetFarmerCropType<TSource>(CommandContext<TSource> context, string name)
{
return context.GetArgument<ChatBots.Farmer.CropType>(name);
}
public static PlayerNameArgumentType PlayerName()
{
return new PlayerNameArgumentType();
}
public static MapBotMapIdArgumentType MapBotMapId()
{
return new MapBotMapIdArgumentType();
}
}
}

View file

@ -0,0 +1,36 @@
using System.Text;
using Brigadier.NET;
namespace MinecraftClient.CommandHandler.Patch
{
public static class CommandDispatcherExtensions
{
/**
* This method unregisteres a previously declared command
*
* @param The name of the command to remove
*/
public static void Unregister(this CommandDispatcher<CmdResult> commandDispatcher, string commandname)
{
commandDispatcher.GetRoot().RemoveChild(commandname);
}
public static string GetAllUsageString(this CommandDispatcher<CmdResult> commandDispatcher, string commandName, bool restricted)
{
char cmdChar = Settings.Config.Main.Advanced.InternalCmdChar.ToChar();
string[] usages = commandDispatcher.GetAllUsage(commandDispatcher.GetRoot().GetChild(commandName), new(), restricted);
StringBuilder sb = new();
sb.AppendLine("All Usages:");
foreach (var usage in usages)
{
sb.Append(cmdChar).Append(commandName).Append(' ');
if (usage.Length > 0 && usage[0] == '_')
sb.AppendLine(usage.Replace("_help -> ", $"_help -> {cmdChar}help "));
else
sb.AppendLine(usage);
}
sb.Remove(sb.Length - 1, 1);
return sb.ToString();
}
}
}

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Reflection;
using Brigadier.NET.Tree;
namespace MinecraftClient.CommandHandler.Patch
{
public static class CommandNodeExtensions
{
public static void RemoveChild(this CommandNode<CmdResult> commandNode, string name)
{
var children = (IDictionary<string, CommandNode<CmdResult>>)
typeof(CommandNode<CmdResult>)
.GetField("_children", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!
.GetValue(commandNode)!;
var literals = (IDictionary<string, LiteralCommandNode<CmdResult>>)
typeof(CommandNode<CmdResult>)
.GetField("_literals", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!
.GetValue(commandNode)!;
var arguments = (IDictionary<string, ArgumentCommandNode<CmdResult>>)
typeof(CommandNode<CmdResult>)
.GetField("_arguments", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!
.GetValue(commandNode)!;
children.Remove(name);
literals.Remove(name);
}
}
}