diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 0ab15085..5e74359a 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -8,7 +8,7 @@ on: env: PROJECT: "MinecraftClient" - target-version: "net6.0" + target-version: "net7.0" compile-flags: "--self-contained=true -c Release -p:UseAppHost=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None" jobs: diff --git a/.gitignore b/.gitignore index 45f6cef2..22033cc8 100644 --- a/.gitignore +++ b/.gitignore @@ -409,6 +409,7 @@ FodyWeavers.xsd # translations /MinecraftClient/Resources/Translations/Translations.*.resx /MinecraftClient/Resources/AsciiArt/AsciiArt.*.resx +/MinecraftClient/Resources/ConfigComments/ConfigComments.*.resx /docs/.vuepress/translations/*.json !/docs/.vuepress/translations/en.json diff --git a/MinecraftClient.sln b/MinecraftClient.sln index d769e6ae..8f0049d8 100644 --- a/MinecraftClient.sln +++ b/MinecraftClient.sln @@ -26,8 +26,8 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - RESX_SortFileContentOnSave = True - SolutionGuid = {6DED60F4-9CF4-4DB3-8966-582B2EBE8487} RESX_ShowErrorsInErrorList = False + SolutionGuid = {6DED60F4-9CF4-4DB3-8966-582B2EBE8487} + RESX_SortFileContentOnSave = False EndGlobalSection EndGlobal diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs index b9af993e..8fe17223 100644 --- a/MinecraftClient/ChatBots/Alerts.cs +++ b/MinecraftClient/ChatBots/Alerts.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/AntiAFK.cs b/MinecraftClient/ChatBots/AntiAFK.cs index 94f16433..bb43045d 100644 --- a/MinecraftClient/ChatBots/AntiAFK.cs +++ b/MinecraftClient/ChatBots/AntiAFK.cs @@ -1,5 +1,6 @@ using System; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/AutoAttack.cs b/MinecraftClient/ChatBots/AutoAttack.cs index 6b577884..8cd8ee2f 100644 --- a/MinecraftClient/ChatBots/AutoAttack.cs +++ b/MinecraftClient/ChatBots/AutoAttack.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/AutoCraft.cs b/MinecraftClient/ChatBots/AutoCraft.cs index 521764fe..52d691ab 100644 --- a/MinecraftClient/ChatBots/AutoCraft.cs +++ b/MinecraftClient/ChatBots/AutoCraft.cs @@ -2,8 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; using static MinecraftClient.ChatBots.AutoCraft.Configs; @@ -11,6 +16,8 @@ namespace MinecraftClient.ChatBots { public class AutoCraft : ChatBot { + public const string CommandName = "autocraft"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -40,7 +47,7 @@ namespace MinecraftClient.ChatBots Name: "Recipe-Name-2", Type: CraftTypeConfig.table, Result: ItemType.StoneBricks, - Slots: new ItemType[9] { + Slots: new ItemType[9] { ItemType.Stone, ItemType.Stone, ItemType.Null, ItemType.Stone, ItemType.Stone, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, @@ -117,7 +124,7 @@ namespace MinecraftClient.ChatBots public ItemType Result = ItemType.Air; - public ItemType[] Slots = new ItemType[9] { + public ItemType[] Slots = new ItemType[9] { ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, ItemType.Null, @@ -288,81 +295,94 @@ namespace MinecraftClient.ChatBots UnloadBot(); return; } - RegisterChatBotCommand("autocraft", Translations.bot_autoCraft_cmd, GetHelp(), CommandHandler); - RegisterChatBotCommand("ac", Translations.bot_autoCraft_alias, GetHelp(), CommandHandler); + + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Then(l => l.Literal("list") + .Executes(r => OnCommandHelp(r.Source, "list"))) + .Then(l => l.Literal("start") + .Executes(r => OnCommandHelp(r.Source, "start"))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandHelp(r.Source, "stop"))) + .Then(l => l.Literal("help") + .Executes(r => OnCommandHelp(r.Source, "help"))) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("list") + .Executes(r => OnCommandList(r.Source))) + .Then(l => l.Literal("start") + .Then(l => l.Argument("RecipeName", MccArguments.AutoCraftRecipeName()) + .Executes(r => OnCommandStart(r.Source, Arguments.GetString(r, "RecipeName"))))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); } - public string CommandHandler(string cmd, string[] args) + public override void OnUnload() { - if (args.Length > 0) - { - switch (args[0]) - { - case "list": - StringBuilder nameList = new(); - foreach (RecipeConfig recipe in Config.Recipes) - nameList.Append(recipe.Name).Append(", "); - return string.Format(Translations.bot_autoCraft_cmd_list, Config.Recipes.Length, nameList.ToString()); - case "start": - if (args.Length >= 2) - { - string name = args[1]; - - bool hasRecipe = false; - RecipeConfig? recipe = null; - foreach (RecipeConfig recipeConfig in Config.Recipes) - { - if (recipeConfig.Name == name) - { - hasRecipe = true; - recipe = recipeConfig; - break; - } - } - - if (hasRecipe) - { - ResetVar(); - PrepareCrafting(recipe!); - return ""; - } - else - return Translations.bot_autoCraft_recipe_not_exist; - } - else - return Translations.bot_autoCraft_no_recipe_name; - case "stop": - StopCrafting(); - return Translations.bot_autoCraft_stop; - case "help": - return GetCommandHelp(args.Length >= 2 ? args[1] : ""); - default: - return GetHelp(); - } - } - else return GetHelp(); + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); } - private static string GetHelp() + private int OnCommandHelp(CmdResult r, string? cmd) { - return string.Format(Translations.bot_autoCraft_available_cmd, "load, list, reload, resetcfg, start, stop, help"); - } - - private string GetCommandHelp(string cmd) - { - return cmd.ToLower() switch + return r.SetAndReturn(cmd switch { #pragma warning disable format // @formatter:off - "load" => Translations.bot_autoCraft_help_load, "list" => Translations.bot_autoCraft_help_list, - "reload" => Translations.bot_autoCraft_help_reload, - "resetcfg" => Translations.bot_autoCraft_help_resetcfg, "start" => Translations.bot_autoCraft_help_start, "stop" => Translations.bot_autoCraft_help_stop, "help" => Translations.bot_autoCraft_help_help, - _ => GetHelp(), + _ => string.Format(Translations.bot_autoCraft_available_cmd, "load, list, reload, resetcfg, start, stop, help") + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), #pragma warning restore format // @formatter:on - }; + }); + } + + private int OnCommandList(CmdResult r) + { + StringBuilder nameList = new(); + foreach (RecipeConfig recipe in Config.Recipes) + nameList.Append(recipe.Name).Append(", "); + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_autoCraft_cmd_list, Config.Recipes.Length, nameList.ToString())); + } + + private int OnCommandStart(CmdResult r, string name) + { + bool hasRecipe = false; + RecipeConfig? recipe = null; + foreach (RecipeConfig recipeConfig in Config.Recipes) + { + if (recipeConfig.Name == name) + { + hasRecipe = true; + recipe = recipeConfig; + break; + } + } + + if (hasRecipe) + { + ResetVar(); + PrepareCrafting(recipe!); + return r.SetAndReturn(CmdResult.Status.Done); + } + else + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_autoCraft_recipe_not_exist); + } + } + + private int OnCommandStop(CmdResult r) + { + StopCrafting(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoCraft_stop); } #region Core part of auto-crafting @@ -442,7 +462,7 @@ namespace MinecraftClient.ChatBots ItemType ResultItem = recipeConfig.Result; - ContainerType CraftingAreaType = + ContainerType CraftingAreaType = (recipeConfig.Type == CraftTypeConfig.player) ? ContainerType.PlayerInventory : ContainerType.Crafting; PrepareCrafting(new Recipe(materials, ResultItem, CraftingAreaType)); diff --git a/MinecraftClient/ChatBots/AutoDig.cs b/MinecraftClient/ChatBots/AutoDig.cs index 00beb136..cddb6ffd 100644 --- a/MinecraftClient/ChatBots/AutoDig.cs +++ b/MinecraftClient/ChatBots/AutoDig.cs @@ -1,13 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots { public class AutoDig : ChatBot { + public const string CommandName = "autodig"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -60,7 +66,7 @@ namespace MinecraftClient.ChatBots { if (Auto_Start_Delay >= 0) Auto_Start_Delay = Math.Max(0.1, Auto_Start_Delay); - + if (Dig_Timeout >= 0) Dig_Timeout = Math.Max(0.1, Dig_Timeout); @@ -117,32 +123,68 @@ namespace MinecraftClient.ChatBots if (!inventoryEnabled && Config.Auto_Tool_Switch) LogToConsole(Translations.bot_autodig_no_inv_handle); - RegisterChatBotCommand("digbot", Translations.bot_autodig_cmd, GetHelp(), CommandHandler); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Then(l => l.Literal("start") + .Executes(r => OnCommandHelp(r.Source, "start"))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandHelp(r.Source, "stop"))) + .Then(l => l.Literal("help") + .Executes(r => OnCommandHelp(r.Source, "help"))) + ) + ); + + var cmd = McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("start") + .Executes(r => OnCommandStart(r.Source))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); + + McClient.dispatcher.Register(l => l.Literal("digbot") + .Redirect(cmd) + ); } - public string CommandHandler(string cmd, string[] args) + public override void OnUnload() { - if (args.Length > 0) + McClient.dispatcher.Unregister("digbot"); + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - switch (args[0]) - { - case "start": - lock (stateLock) - { - counter = 0; - state = State.WaitingStart; - } - return Translations.bot_autodig_start; - case "stop": - StopDigging(); - return Translations.bot_autodig_stop; - case "help": - return GetCommandHelp(args.Length >= 2 ? args[1] : ""); - default: - return GetHelp(); - } +#pragma warning disable format // @formatter:off + "start" => Translations.bot_autodig_help_start, + "stop" => Translations.bot_autodig_help_stop, + "help" => Translations.bot_autodig_help_help, + _ => string.Format(Translations.bot_autodig_available_cmd, "start, stop, help") + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandStart(CmdResult r) + { + lock (stateLock) + { + counter = 0; + state = State.WaitingStart; } - else return GetHelp(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autodig_start); + } + + private int OnCommandStop(CmdResult r) + { + StopDigging(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autodig_stop); } private void StartDigging() @@ -240,7 +282,7 @@ namespace MinecraftClient.ChatBots else if ((Config.List_Type == Configs.ListType.whitelist && Config.Blocks.Contains(block.Type)) || (Config.List_Type == Configs.ListType.blacklist && !Config.Blocks.Contains(block.Type))) { - if (Config.Mode == Configs.ModeType.lookat || + if (Config.Mode == Configs.ModeType.lookat || (Config.Mode == Configs.ModeType.both && Config._Locations.Contains(blockLoc))) { if (DigBlock(blockLoc, lookAtBlock: false)) @@ -288,8 +330,8 @@ namespace MinecraftClient.ChatBots foreach (Location location in Config._Locations) { Block block = GetWorld().GetBlock(location); - if (block.Type != Material.Air && - ((Config.List_Type == Configs.ListType.whitelist && Config.Blocks.Contains(block.Type)) || + if (block.Type != Material.Air && + ((Config.List_Type == Configs.ListType.whitelist && Config.Blocks.Contains(block.Type)) || (Config.List_Type == Configs.ListType.blacklist && !Config.Blocks.Contains(block.Type)))) { double distance = current.Distance(location); @@ -401,23 +443,5 @@ namespace MinecraftClient.ChatBots return base.OnDisconnect(reason, message); } - - private static string GetHelp() - { - return string.Format(Translations.bot_autodig_available_cmd, "start, stop, help"); - } - - private string GetCommandHelp(string cmd) - { - return cmd.ToLower() switch - { -#pragma warning disable format // @formatter:off - "start" => Translations.bot_autodig_help_start, - "stop" => Translations.bot_autodig_help_stop, - "help" => Translations.bot_autodig_help_help, - _ => GetHelp(), -#pragma warning restore format // @formatter:on - }; - } } } diff --git a/MinecraftClient/ChatBots/AutoDrop.cs b/MinecraftClient/ChatBots/AutoDrop.cs index 9c63774e..36263a86 100644 --- a/MinecraftClient/ChatBots/AutoDrop.cs +++ b/MinecraftClient/ChatBots/AutoDrop.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; +using MinecraftClient.Scripting; using Tomlet.Attributes; using static MinecraftClient.ChatBots.AutoDrop.Configs; @@ -9,6 +13,8 @@ namespace MinecraftClient.ChatBots { public class AutoDrop : ChatBot { + public const string CommandName = "autodrop"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -38,110 +44,6 @@ namespace MinecraftClient.ChatBots private readonly int updateDebounceValue = 2; private int inventoryUpdated = -1; - public string CommandHandler(string cmd, string[] args) - { - if (args.Length > 0) - { - switch (args[0].ToLower()) - { - case "on": - Config.Enabled = true; - inventoryUpdated = 0; - OnUpdateFinish(); - return Translations.bot_autoDrop_on; - case "off": - Config.Enabled = false; - return Translations.bot_autoDrop_off; - case "add": - if (args.Length >= 2) - { - if (Enum.TryParse(args[1], true, out ItemType item)) - { - Config.Items.Add(item); - return string.Format(Translations.bot_autoDrop_added, item.ToString()); - } - else - { - return string.Format(Translations.bot_autoDrop_incorrect_name, args[1]); - } - } - else - { - return Translations.cmd_inventory_help_usage + ": add "; - } - case "remove": - if (args.Length >= 2) - { - if (Enum.TryParse(args[1], true, out ItemType item)) - { - if (Config.Items.Contains(item)) - { - Config.Items.Remove(item); - return string.Format(Translations.bot_autoDrop_removed, item.ToString()); - } - else - { - return Translations.bot_autoDrop_not_in_list; - } - } - else - { - return string.Format(Translations.bot_autoDrop_incorrect_name, args[1]); - } - } - else - { - return Translations.cmd_inventory_help_usage + ": remove "; - } - case "list": - if (Config.Items.Count > 0) - { - return string.Format(Translations.bot_autoDrop_list, Config.Items.Count, string.Join("\n", Config.Items)); - } - else - { - return Translations.bot_autoDrop_no_item; - } - case "mode": - if (args.Length >= 2) - { - switch (args[1].ToLower()) - { - case "include": - Config.Mode = DropMode.include; - break; - case "exclude": - Config.Mode = DropMode.exclude; - break; - case "everything": - Config.Mode = DropMode.everything; - break; - default: - return Translations.bot_autoDrop_unknown_mode; // Unknwon mode. Available modes: Include, Exclude, Everything - } - inventoryUpdated = 0; - OnUpdateFinish(); - return string.Format(Translations.bot_autoDrop_switched, Config.Mode.ToString()); // Switched to {0} mode. - } - else - { - return Translations.bot_autoDrop_unknown_mode; - } - default: - return GetHelp(); - } - } - else - { - return GetHelp(); - } - } - - private static string GetHelp() - { - return string.Format(Translations.general_available_cmd, "on, off, add, remove, list, mode"); - } - public override void Initialize() { if (!GetInventoryEnabled()) @@ -151,8 +53,114 @@ namespace MinecraftClient.ChatBots UnloadBot(); return; } - RegisterChatBotCommand("autodrop", Translations.bot_autoDrop_cmd, GetHelp(), CommandHandler); - RegisterChatBotCommand("ad", Translations.bot_autoDrop_alias, GetHelp(), CommandHandler); + + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Then(l => l.Literal("add") + .Executes(r => OnCommandHelp(r.Source, "add"))) + .Then(l => l.Literal("remove") + .Executes(r => OnCommandHelp(r.Source, "remove"))) + .Then(l => l.Literal("mode") + .Executes(r => OnCommandHelp(r.Source, "mode"))) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("on") + .Executes(r => OnCommandEnable(r.Source, true))) + .Then(l => l.Literal("off") + .Executes(r => OnCommandEnable(r.Source, false))) + .Then(l => l.Literal("add") + .Then(l => l.Argument("ItemType", MccArguments.ItemType()) + .Executes(r => OnCommandAdd(r.Source, MccArguments.GetItemType(r, "ItemType"))))) + .Then(l => l.Literal("remove") + .Then(l => l.Argument("ItemType", MccArguments.ItemType()) + .Executes(r => OnCommandRemove(r.Source, MccArguments.GetItemType(r, "ItemType"))))) + .Then(l => l.Literal("list") + .Executes(r => OnCommandList(r.Source))) + .Then(l => l.Literal("mode") + .Then(l => l.Literal("include") + .Executes(r => OnCommandMode(r.Source, DropMode.include))) + .Then(l => l.Literal("exclude") + .Executes(r => OnCommandMode(r.Source, DropMode.exclude))) + .Then(l => l.Literal("everything") + .Executes(r => OnCommandMode(r.Source, DropMode.everything)))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); + } + + public override void OnUnload() + { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + "add" => Translations.cmd_inventory_help_usage + ": add ", + "remove" => Translations.cmd_inventory_help_usage + ": remove ", + "mode" => Translations.bot_autoDrop_unknown_mode, + _ => string.Format(Translations.general_available_cmd, "on, off, add, remove, list, mode") + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandEnable(CmdResult r, bool enable) + { + if (enable) + { + Config.Enabled = true; + inventoryUpdated = 0; + OnUpdateFinish(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoDrop_on); + } + else + { + Config.Enabled = false; + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoDrop_off); + } + } + + private int OnCommandAdd(CmdResult r, ItemType item) + { + Config.Items.Add(item); + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_autoDrop_added, item.ToString())); + } + + private int OnCommandRemove(CmdResult r, ItemType item) + { + if (Config.Items.Contains(item)) + { + Config.Items.Remove(item); + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_autoDrop_removed, item.ToString())); + } + else + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_autoDrop_not_in_list); + } + } + + private int OnCommandList(CmdResult r) + { + if (Config.Items.Count > 0) + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_autoDrop_list, Config.Items.Count, string.Join("\n", Config.Items))); + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_autoDrop_no_item); + } + + private int OnCommandMode(CmdResult r, DropMode mode) + { + Config.Mode = mode; + inventoryUpdated = 0; + OnUpdateFinish(); + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_autoDrop_switched, Config.Mode.ToString())); } public override void Update() diff --git a/MinecraftClient/ChatBots/AutoEat.cs b/MinecraftClient/ChatBots/AutoEat.cs index eb9c3762..eb9eea07 100644 --- a/MinecraftClient/ChatBots/AutoEat.cs +++ b/MinecraftClient/ChatBots/AutoEat.cs @@ -1,5 +1,6 @@ using System; using MinecraftClient.Inventory; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/AutoFishing.cs b/MinecraftClient/ChatBots/AutoFishing.cs index 1ee283eb..09ee140f 100644 --- a/MinecraftClient/ChatBots/AutoFishing.cs +++ b/MinecraftClient/ChatBots/AutoFishing.cs @@ -2,8 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; using static MinecraftClient.ChatBots.AutoFishing.Configs; @@ -15,6 +19,8 @@ namespace MinecraftClient.ChatBots /// public class AutoFishing : ChatBot { + public const string CommandName = "autofishing"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -176,80 +182,113 @@ namespace MinecraftClient.ChatBots LogToConsole(Translations.extra_entity_required); state = FishingState.WaitJoinGame; } + inventoryEnabled = GetInventoryEnabled(); if (!inventoryEnabled) LogToConsole(Translations.bot_autoFish_no_inv_handle); - RegisterChatBotCommand("fish", Translations.bot_autoFish_cmd, GetHelp(), CommandHandler); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Then(l => l.Literal("start") + .Executes(r => OnCommandHelp(r.Source, "start"))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandHelp(r.Source, "stop"))) + .Then(l => l.Literal("status") + .Executes(r => OnCommandHelp(r.Source, "status"))) + .Then(l => l.Literal("help") + .Executes(r => OnCommandHelp(r.Source, "help"))) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("start") + .Executes(r => OnCommandStart(r.Source))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("status") + .Executes(r => OnCommandStatus(r.Source)) + .Then(l => l.Literal("clear") + .Executes(r => OnCommandStatusClear(r.Source)))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); } - public string CommandHandler(string cmd, string[] args) + public override void OnUnload() { - if (args.Length >= 1) + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - switch (args[0]) - { - case "start": - isFishing = false; - lock (stateLock) - { - isFishing = false; - counter = 0; - state = FishingState.StartMove; - } - return Translations.bot_autoFish_start; - case "stop": - isFishing = false; - lock (stateLock) - { - isFishing = false; - if (state == FishingState.WaitingFishToBite) - UseFishRod(); - state = FishingState.Stopping; - } - StopFishing(); - return Translations.bot_autoFish_stop; - case "status": - if (args.Length >= 2) - { - if (args[1] == "clear") - { - fishItemCnt = new(); - return Translations.bot_autoFish_status_clear; - } - else - { - return GetCommandHelp("status"); - } - } - else - { - if (fishItemCnt.Count == 0) - return Translations.bot_autoFish_status_info; +#pragma warning disable format // @formatter:off + "start" => Translations.bot_autoFish_help_start, + "stop" => Translations.bot_autoFish_help_stop, + "status" => Translations.bot_autoFish_help_status, + "help" => Translations.bot_autoFish_help_help, + _ => string.Format(Translations.bot_autoFish_available_cmd, "start, stop, status, help") + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } - List> orderedList = fishItemCnt.OrderBy(x => x.Value).ToList(); - int maxLen = orderedList[^1].Value.ToString().Length; - StringBuilder sb = new(); - sb.Append(Translations.bot_autoFish_status_info); - foreach ((ItemType type, uint cnt) in orderedList) - { - sb.Append(Environment.NewLine); - - string cntStr = cnt.ToString(); - sb.Append(' ', maxLen - cntStr.Length).Append(cntStr); - sb.Append(" x "); - sb.Append(Item.GetTypeString(type)); - } - return sb.ToString(); - } - case "help": - return GetCommandHelp(args.Length >= 2 ? args[1] : ""); - default: - return GetHelp(); - } + private int OnCommandStart(CmdResult r) + { + isFishing = false; + lock (stateLock) + { + isFishing = false; + counter = 0; + state = FishingState.StartMove; } - else - return GetHelp(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoFish_start); + } + + private int OnCommandStop(CmdResult r) + { + isFishing = false; + lock (stateLock) + { + isFishing = false; + if (state == FishingState.WaitingFishToBite) + UseFishRod(); + state = FishingState.Stopping; + } + StopFishing(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoFish_stop); + } + + private int OnCommandStatus(CmdResult r) + { + if (fishItemCnt.Count == 0) + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoFish_status_info); + + List> orderedList = fishItemCnt.OrderBy(x => x.Value).ToList(); + int maxLen = orderedList[^1].Value.ToString().Length; + StringBuilder sb = new(); + sb.Append(Translations.bot_autoFish_status_info); + foreach ((ItemType type, uint cnt) in orderedList) + { + sb.Append(Environment.NewLine); + + string cntStr = cnt.ToString(); + sb.Append(' ', maxLen - cntStr.Length).Append(cntStr); + sb.Append(" x "); + sb.Append(Item.GetTypeString(type)); + } + LogToConsole(sb.ToString()); + return r.SetAndReturn(CmdResult.Status.Done); + } + + private int OnCommandStatusClear(CmdResult r) + { + fishItemCnt = new(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_autoFish_status_clear); } private void StartFishing() @@ -386,7 +425,7 @@ namespace MinecraftClient.ChatBots public override void OnEntitySpawn(Entity entity) { if (fishItemCounter < 15 && entity.Type == EntityType.Item && Math.Abs(entity.Location.Y - LastPos.Y) < 2.2 && - Math.Abs(entity.Location.X - LastPos.X) < 0.12 && Math.Abs(entity.Location.Z - LastPos.Z) < 0.12) + Math.Abs(entity.Location.X - LastPos.X) < 0.12 && Math.Abs(entity.Location.Z - LastPos.Z) < 0.12) { if (Config.Log_Fish_Bobber) LogToConsole(string.Format("Item ({0}) spawn at {1}, distance = {2:0.00}", entity.ID, entity.Location, entity.Location.Distance(LastPos))); @@ -440,7 +479,7 @@ namespace MinecraftClient.ChatBots public override void OnEntityMove(Entity entity) { - if (isFishing && entity != null && fishingBobber!.ID == entity.ID && + if (isFishing && entity != null && fishingBobber!.ID == entity.ID && (state == FishingState.WaitingFishToBite || state == FishingState.WaitingFishingBobber)) { Location Pos = entity.Location; @@ -620,24 +659,5 @@ namespace MinecraftClient.ChatBots return false; } } - - private static string GetHelp() - { - return string.Format(Translations.bot_autoFish_available_cmd, "start, stop, status, help"); - } - - private string GetCommandHelp(string cmd) - { - return cmd.ToLower() switch - { -#pragma warning disable format // @formatter:off - "start" => Translations.bot_autoFish_help_start, - "stop" => Translations.bot_autoFish_help_stop, - "status" => Translations.bot_autoFish_help_status, - "help" => Translations.bot_autoFish_help_help, - _ => GetHelp(), -#pragma warning restore format // @formatter:on - }; - } } } diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index e952e959..c384eb7c 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -1,4 +1,5 @@ using System; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots @@ -43,7 +44,7 @@ namespace MinecraftClient.ChatBots if (Delay.min > Delay.max) (Delay.min, Delay.max) = (Delay.max, Delay.min); - + if (Retries == -1) Retries = int.MaxValue; @@ -83,6 +84,11 @@ namespace MinecraftClient.ChatBots } public override void Initialize() + { + _Initialize(); + } + + private void _Initialize() { McClient.ReconnectionAttemptsLeft = Config.Retries; if (Config.Ignore_Kick_Message) diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 2b571dc8..3b5a2991 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.IO; using System.Text; using System.Text.RegularExpressions; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; using Tomlet.Attributes; using static MinecraftClient.Settings; @@ -304,12 +306,12 @@ namespace MinecraftClient.ChatBots { Dictionary localVars = new(); string? toPerform = rule.Match(sender, message, msgType, localVars); - if (!String.IsNullOrEmpty(toPerform)) + if (!string.IsNullOrEmpty(toPerform)) { - string? response = null; + CmdResult response = new(); LogToConsole(string.Format(Translations.bot_autoRespond_match_run, toPerform)); PerformInternalCommand(toPerform, ref response, localVars); - if (!String.IsNullOrEmpty(response)) + if (response.status != CmdResult.Status.Done || !string.IsNullOrWhiteSpace(response.result)) LogToConsole(response); } } diff --git a/MinecraftClient/ChatBots/ChatLog.cs b/MinecraftClient/ChatBots/ChatLog.cs index b6ad94ef..37aecd36 100644 --- a/MinecraftClient/ChatBots/ChatLog.cs +++ b/MinecraftClient/ChatBots/ChatLog.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots @@ -115,7 +117,7 @@ namespace MinecraftClient.ChatBots } } - public override void OnInternalCommand(string commandName, string commandParams, string result) + public override void OnInternalCommand(string commandName, string commandParams, CmdResult result) { if (saveInternal) { diff --git a/MinecraftClient/ChatBots/DiscordBridge.cs b/MinecraftClient/ChatBots/DiscordBridge.cs index 41a07c8c..b46ad770 100644 --- a/MinecraftClient/ChatBots/DiscordBridge.cs +++ b/MinecraftClient/ChatBots/DiscordBridge.cs @@ -3,16 +3,22 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Brigadier.NET.Builder; using DSharpPlus; using DSharpPlus.Entities; using DSharpPlus.Exceptions; using Microsoft.Extensions.Logging; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots { public class DiscordBridge : ChatBot { + public const string CommandName = "dscbridge"; + private enum BridgeDirection { Both = 0, @@ -70,17 +76,74 @@ namespace MinecraftClient.ChatBots public override void Initialize() { - RegisterChatBotCommand("dscbridge", "bot.DiscordBridge.desc", "dscbridge direction ", OnDscCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("direction") + .Then(l => l.Literal("both") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Both))) + .Then(l => l.Literal("mc") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Minecraft))) + .Then(l => l.Literal("discord") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Discord))) + ) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); Task.Run(async () => await MainAsync()); } - ~DiscordBridge() + public override void OnUnload() { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); Disconnect(); } - public override void OnUnload() + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => "dscbridge direction " + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandDirection(CmdResult r, BridgeDirection direction) + { + string bridgeName; + switch (direction) + { + case BridgeDirection.Both: + bridgeName = Translations.bot_DiscordBridge_direction_both; + bridgeDirection = BridgeDirection.Both; + break; + + case BridgeDirection.Minecraft: + bridgeName = Translations.bot_DiscordBridge_direction_minecraft; + bridgeDirection = BridgeDirection.Minecraft; + break; + + case BridgeDirection.Discord: + bridgeName = Translations.bot_DiscordBridge_direction_discord; + bridgeDirection = BridgeDirection.Discord; + break; + + default: + goto case BridgeDirection.Both; + } + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_DiscordBridge_direction, bridgeName)); + } + + ~DiscordBridge() { Disconnect(); } @@ -100,7 +163,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_canceled_sending); LogDebugToConsole(e); } @@ -114,47 +177,6 @@ namespace MinecraftClient.ChatBots return instance; } - private string OnDscCommand(string cmd, string[] args) - { - if (args.Length == 2) - { - if (args[0].ToLower().Equals("direction")) - { - string direction = args[1].ToLower().Trim(); - - string bridgeName; - switch (direction) - { - case "b": - case "both": - bridgeName = Translations.bot_DiscordBridge_direction_both; - bridgeDirection = BridgeDirection.Both; - break; - - case "mc": - case "minecraft": - bridgeName = Translations.bot_DiscordBridge_direction_minecraft; - bridgeDirection = BridgeDirection.Minecraft; - break; - - case "d": - case "dcs": - case "discord": - bridgeName = Translations.bot_DiscordBridge_direction_discord; - bridgeDirection = BridgeDirection.Discord; - break; - - default: - return Translations.bot_DiscordBridge_invalid_direction; - } - - return string.Format(Translations.bot_DiscordBridge_direction, bridgeName); - }; - } - - return "dscbridge direction "; - } - public override void GetText(string text) { if (!CanSendMessages()) @@ -211,7 +233,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_canceled_sending); LogDebugToConsole(e); } } @@ -227,7 +249,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_canceled_sending); LogDebugToConsole(e); } } @@ -243,7 +265,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_canceled_sending); LogDebugToConsole(e); } } @@ -269,7 +291,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_canceled_sending); LogDebugToConsole(e); } } @@ -369,9 +391,8 @@ namespace MinecraftClient.ChatBots message = message[1..]; await e.Message.CreateReactionAsync(DiscordEmoji.FromName(discordBotClient, ":gear:")); - string? result = ""; + CmdResult result = new(); PerformInternalCommand(message, ref result); - result = string.IsNullOrEmpty(result) ? "-" : result; await e.Message.DeleteOwnReactionAsync(DiscordEmoji.FromName(discordBotClient, ":gear:")); await e.Message.CreateReactionAsync(DiscordEmoji.FromName(discordBotClient, ":white_check_mark:")); @@ -399,12 +420,12 @@ namespace MinecraftClient.ChatBots }); IsConnected = true; - LogToConsole("§y§l§f" + Translations.bot_DiscordBridge_connected); + LogToConsole("§§2§l§f" + Translations.bot_DiscordBridge_connected); await Task.Delay(-1); } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_DiscordBridge_unknown_error); + LogToConsole("§§4§l§f" + Translations.bot_DiscordBridge_unknown_error); LogToConsole(e); return; } diff --git a/MinecraftClient/ChatBots/Farmer.cs b/MinecraftClient/ChatBots/Farmer.cs index 368754e1..b035e22c 100644 --- a/MinecraftClient/ChatBots/Farmer.cs +++ b/MinecraftClient/ChatBots/Farmer.cs @@ -3,34 +3,22 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; using MinecraftClient.Mapping; using MinecraftClient.Protocol.Handlers; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots { - enum State - { - SearchingForCropsToBreak = 0, - SearchingForFarmlandToPlant, - PlantingCrops, - BonemealingCrops - } - - enum CropType - { - Beetroot, - Carrot, - Melon, - Netherwart, - Pumpkin, - Potato, - Wheat - } - public class Farmer : ChatBot { + public const string CommandName = "farmer"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -42,15 +30,34 @@ namespace MinecraftClient.ChatBots public bool Enabled = false; [TomlInlineComment("$ChatBot.Farmer.Delay_Between_Tasks$")] - public int Delay_Between_Tasks = 1; + public double Delay_Between_Tasks = 1.0; public void OnSettingUpdate() { - if (Delay_Between_Tasks <= 0) - Delay_Between_Tasks = 1; + if (Delay_Between_Tasks < 1.0) + Delay_Between_Tasks = 1.0; } } + public enum State + { + SearchingForCropsToBreak = 0, + SearchingForFarmlandToPlant, + PlantingCrops, + BonemealingCrops + } + + public enum CropType + { + Beetroot, + Carrot, + Melon, + Netherwart, + Pumpkin, + Potato, + Wheat + } + private State state = State.SearchingForCropsToBreak; private CropType cropType = CropType.Wheat; private int farmingRadius = 30; @@ -59,6 +66,8 @@ namespace MinecraftClient.ChatBots private bool allowTeleport = false; private bool debugEnabled = false; + public int Delay_Between_Tasks_Millisecond => (int)Math.Round(Config.Delay_Between_Tasks * 1000); + private const string commandDescription = "farmer [radius:] [unsafe:] [teleport:] [debug:]|stop>"; public override void Initialize() @@ -81,130 +90,153 @@ namespace MinecraftClient.ChatBots return; } - RegisterChatBotCommand("farmer", "bot.farmer.desc", commandDescription, OnFarmCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("start") + .Then(l => l.Argument("CropType", MccArguments.FarmerCropType()) + .Executes(r => OnCommandStart(r.Source, MccArguments.GetFarmerCropType(r, "CropType"), null)) + .Then(l => l.Argument("OtherArgs", Arguments.GreedyString()) + .Executes(r => OnCommandStart(r.Source, MccArguments.GetFarmerCropType(r, "CropType"), Arguments.GetString(r, "OtherArgs")))))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); } - private string OnFarmCommand(string cmd, string[] args) + public override void OnUnload() { - if (args.Length > 0) + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - if (args[0].Equals("stop", StringComparison.OrdinalIgnoreCase)) - { - if (!running) - return Translations.bot_farmer_already_stopped; +#pragma warning disable format // @formatter:off + _ => Translations.bot_farmer_desc + ": " + commandDescription + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } - running = false; - return Translations.bot_farmer_stopping; - } + private int OnCommandStop(CmdResult r) + { + if (!running) + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_farmer_already_stopped); + } + else + { + running = false; + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_farmer_stopping); + } + } - if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase)) + private int OnCommandStart(CmdResult r, CropType whatToFarm, string? otherArgs) + { + if (running) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_farmer_already_running); + + int radius = 30; + + state = State.SearchingForFarmlandToPlant; + cropType = whatToFarm; + allowUnsafe = false; + allowTeleport = false; + debugEnabled = false; + + if (!string.IsNullOrWhiteSpace(otherArgs)) + { + string[] args = otherArgs.ToLower().Split(' ', StringSplitOptions.TrimEntries); + foreach (string currentArg in args) { - if (args.Length >= 2) + if (!currentArg.Contains(':')) { - if (running) - return Translations.bot_farmer_already_running; + LogToConsole("§§6§1§0" + string.Format(Translations.bot_farmer_warining_invalid_parameter, currentArg)); + continue; + } - if (!Enum.TryParse(args[1], true, out CropType whatToFarm)) - return Translations.bot_farmer_invalid_crop_type; + string[] parts = currentArg.Split(":", StringSplitOptions.TrimEntries); - int radius = 30; + if (parts.Length != 2) + { + LogToConsole("§§6§1§0" + string.Format(Translations.bot_farmer_warining_invalid_parameter, currentArg)); + continue; + } - state = State.SearchingForFarmlandToPlant; - cropType = whatToFarm; - allowUnsafe = false; - allowTeleport = false; - debugEnabled = false; + switch (parts[0]) + { + case "r": + case "radius": + if (!int.TryParse(parts[1], NumberStyles.Any, CultureInfo.CurrentCulture, out radius)) + LogToConsole("§§6§1§0" + Translations.bot_farmer_invalid_radius); - if (args.Length >= 3) - { - for (int i = 2; i < args.Length; i++) + if (radius <= 0) { - string currentArg = args[i].Trim().ToLower(); - - if (!currentArg.Contains(':')) - { - LogToConsole("§x§1§0" + string.Format(Translations.bot_farmer_warining_invalid_parameter, currentArg)); - continue; - } - - string[] parts = currentArg.Split(":", StringSplitOptions.TrimEntries); - - if (parts.Length != 2) - { - LogToConsole("§x§1§0" + string.Format(Translations.bot_farmer_warining_invalid_parameter, currentArg)); - continue; - } - - switch (parts[0]) - { - case "r": - case "radius": - if (!int.TryParse(parts[1], NumberStyles.Any, CultureInfo.CurrentCulture, out radius)) - LogToConsole("§x§1§0" + Translations.bot_farmer_invalid_radius); - - if (radius <= 0) - { - LogToConsole("§x§1§0" + Translations.bot_farmer_invalid_radius); - radius = 30; - } - - break; - - case "f": - case "unsafe": - if (allowUnsafe) - break; - - if (parts[1].Equals("true") || parts[1].Equals("1")) - { - LogToConsole("§x§1§0" + Translations.bot_farmer_warining_force_unsafe); - allowUnsafe = true; - } - else allowUnsafe = false; - - break; - - case "t": - case "teleport": - if (allowTeleport) - break; - - if (parts[1].Equals("true") || parts[1].Equals("1")) - { - LogToConsole("§w§1§f" + Translations.bot_farmer_warining_allow_teleport); - allowTeleport = true; - } - else allowTeleport = false; - - break; - - case "d": - case "debug": - if (debugEnabled) - break; - - if (parts[1].Equals("true") || parts[1].Equals("1")) - { - LogToConsole("Debug enabled!"); - debugEnabled = true; - } - else debugEnabled = false; - - break; - } + LogToConsole("§§6§1§0" + Translations.bot_farmer_invalid_radius); + radius = 30; } - } - farmingRadius = radius; - running = true; - new Thread(() => MainPorcess()).Start(); + break; - return ""; + case "f": + case "unsafe": + if (allowUnsafe) + break; + + if (parts[1].Equals("true") || parts[1].Equals("1")) + { + LogToConsole("§§6§1§0" + Translations.bot_farmer_warining_force_unsafe); + allowUnsafe = true; + } + else allowUnsafe = false; + + break; + + case "t": + case "teleport": + if (allowTeleport) + break; + + if (parts[1].Equals("true") || parts[1].Equals("1")) + { + LogToConsole("§§4§1§f" + Translations.bot_farmer_warining_allow_teleport); + allowTeleport = true; + } + else allowTeleport = false; + + break; + + case "d": + case "debug": + if (debugEnabled) + break; + + if (parts[1].Equals("true") || parts[1].Equals("1")) + { + LogToConsole("Debug enabled!"); + debugEnabled = true; + } + else debugEnabled = false; + + break; } } } - return Translations.bot_farmer_desc + ": " + commandDescription; + farmingRadius = radius; + running = true; + new Thread(() => MainPorcess()).Start(); + + return r.SetAndReturn(CmdResult.Status.Done); } public override void AfterGameJoined() @@ -220,9 +252,9 @@ namespace MinecraftClient.ChatBots private void MainPorcess() { - LogToConsole("§y§1§f" + Translations.bot_farmer_started); - LogToConsole("§y§1§f " + Translations.bot_farmer_crop_type + ": " + cropType); - LogToConsole("§y§1§f " + Translations.bot_farmer_radius + ": " + farmingRadius); + LogToConsole("§§2§1§f" + Translations.bot_farmer_started); + LogToConsole("§§2§1§f " + Translations.bot_farmer_crop_type + ": " + cropType); + LogToConsole("§§2§1§f " + Translations.bot_farmer_radius + ": " + farmingRadius); while (running) { @@ -230,7 +262,7 @@ namespace MinecraftClient.ChatBots if (AutoEat.Eating) { LogDebug("Eating..."); - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -246,7 +278,7 @@ namespace MinecraftClient.ChatBots { LogDebug("No seeds, trying to find some crops to break"); state = State.SearchingForCropsToBreak; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -256,7 +288,7 @@ namespace MinecraftClient.ChatBots { LogDebug("Could not find any farmland, trying to find some crops to break"); state = State.SearchingForCropsToBreak; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -272,7 +304,7 @@ namespace MinecraftClient.ChatBots { LogDebug("Ran out of seeds, looking for crops to break..."); state = State.SearchingForCropsToBreak; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } } @@ -321,7 +353,7 @@ namespace MinecraftClient.ChatBots { LogToConsole("No crops to break, trying to bonemeal ungrown ones"); state = State.BonemealingCrops; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -367,7 +399,7 @@ namespace MinecraftClient.ChatBots if (cropType == CropType.Netherwart) { state = State.SearchingForFarmlandToPlant; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -376,7 +408,7 @@ namespace MinecraftClient.ChatBots { LogDebug("No bonemeal, searching for some farmland to plant seeds on"); state = State.SearchingForFarmlandToPlant; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -386,7 +418,7 @@ namespace MinecraftClient.ChatBots { LogDebug("No crops to bonemeal, searching for farmland to plant seeds on"); state = State.SearchingForFarmlandToPlant; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } @@ -402,7 +434,7 @@ namespace MinecraftClient.ChatBots { LogDebug("Ran out of Bone Meal, looking for farmland to plant on..."); state = State.SearchingForFarmlandToPlant; - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + Thread.Sleep(Delay_Between_Tasks_Millisecond); continue; } } @@ -438,8 +470,8 @@ namespace MinecraftClient.ChatBots break; } - LogDebug("Waiting for " + Config.Delay_Between_Tasks + " seconds for next cycle."); - Thread.Sleep(Config.Delay_Between_Tasks * 1000); + LogDebug(string.Format("Waiting for {0:0.00} seconds for next cycle.", Config.Delay_Between_Tasks)); + Thread.Sleep(Delay_Between_Tasks_Millisecond); } LogToConsole(Translations.bot_farmer_stopped); @@ -815,6 +847,7 @@ namespace MinecraftClient.ChatBots { return GetPlayerInventory().SearchItem(itemType).Length > 0; } + private void LogDebug(object text) { if (debugEnabled) diff --git a/MinecraftClient/ChatBots/FollowPlayer.cs b/MinecraftClient/ChatBots/FollowPlayer.cs index d14d3e8f..0561bf0f 100644 --- a/MinecraftClient/ChatBots/FollowPlayer.cs +++ b/MinecraftClient/ChatBots/FollowPlayer.cs @@ -1,12 +1,19 @@ using System; using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots { public class FollowPlayer : ChatBot { + public const string CommandName = "follow"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -55,58 +62,86 @@ namespace MinecraftClient.ChatBots return; } - RegisterChatBotCommand("follow", "cmd.follow.desc", "follow ", OnFollowCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("start") + .Then(l => l.Argument("PlayerName", MccArguments.PlayerName()) + .Executes(r => OnCommandStart(r.Source, Arguments.GetString(r, "PlayerName"), takeRisk: false)) + .Then(l => l.Literal("-f") + .Executes(r => OnCommandStart(r.Source, Arguments.GetString(r, "PlayerName"), takeRisk: true))))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); } - private string OnFollowCommand(string cmd, string[] args) + public override void OnUnload() { - if (args.Length > 0) + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - if (args[0].Equals("stop", StringComparison.OrdinalIgnoreCase)) - { - if (_playerToFollow == null) - return Translations.cmd_follow_already_stopped; +#pragma warning disable format // @formatter:off + _ => Translations.cmd_follow_desc + ": " + Translations.cmd_follow_usage + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } - _playerToFollow = null; - return Translations.cmd_follow_stopping; - } - else - { - if (!IsValidName(args[0])) - return Translations.cmd_follow_invalid_name; + private int OnCommandStart(CmdResult r, string name, bool takeRisk) + { + if (!IsValidName(name)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_invalid_name); - Entity? player = GetEntities().Values.ToList().Find(entity => - entity.Type == EntityType.Player && !string.IsNullOrEmpty(entity.Name) && entity.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase)); + Entity? player = GetEntities().Values.ToList().Find(entity => + entity.Type == EntityType.Player + && !string.IsNullOrEmpty(entity.Name) + && entity.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); - if (player == null) - return Translations.cmd_follow_invalid_player; + if (player == null) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_invalid_player); - if (!CanMoveThere(player.Location)) - return Translations.cmd_follow_cant_reach_player; + if (!CanMoveThere(player.Location)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_cant_reach_player); - if (_playerToFollow != null && _playerToFollow.Equals(args[0], StringComparison.OrdinalIgnoreCase)) - return string.Format(Translations.cmd_follow_already_following, _playerToFollow); + if (_playerToFollow != null && _playerToFollow.Equals(name, StringComparison.OrdinalIgnoreCase)) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_follow_already_following, _playerToFollow)); - string result; - if (_playerToFollow != null) - result = string.Format(Translations.cmd_follow_switched, player.Name!); - else - result = string.Format(Translations.cmd_follow_started, player.Name!); - _playerToFollow = args[0].Trim().ToLower(); + string result; + if (_playerToFollow != null) + result = string.Format(Translations.cmd_follow_switched, player.Name!); + else + result = string.Format(Translations.cmd_follow_started, player.Name!); + _playerToFollow = name.ToLower(); - LogToConsole(Translations.cmd_follow_note); - - if (args.Length == 2 && args[1].Equals("-f", StringComparison.OrdinalIgnoreCase)) - { - _unsafeEnabled = true; - LogToConsole(Translations.cmd_follow_unsafe_enabled); - } - - return result; - } + if (takeRisk) + { + _unsafeEnabled = true; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_note + '\n' + Translations.cmd_follow_unsafe_enabled); } + else + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_note); + } - return Translations.cmd_follow_desc + ": " + Translations.cmd_follow_usage; + private int OnCommandStop(CmdResult r) + { + if (_playerToFollow == null) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_follow_already_stopped); + + _playerToFollow = null; + + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_follow_stopping); } public override void Update() diff --git a/MinecraftClient/ChatBots/HangmanGame.cs b/MinecraftClient/ChatBots/HangmanGame.cs index 0eb923c9..ac04dd9e 100644 --- a/MinecraftClient/ChatBots/HangmanGame.cs +++ b/MinecraftClient/ChatBots/HangmanGame.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/Mailer.cs b/MinecraftClient/ChatBots/Mailer.cs index 2eb20db5..41132d71 100644 --- a/MinecraftClient/ChatBots/Mailer.cs +++ b/MinecraftClient/ChatBots/Mailer.cs @@ -3,6 +3,11 @@ using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots @@ -12,6 +17,8 @@ namespace MinecraftClient.ChatBots /// public class Mailer : ChatBot { + public const string CommandName = "mailer"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -251,7 +258,101 @@ namespace MinecraftClient.ChatBots mailDbFileMonitor = new FileMonitor(Path.GetDirectoryName(Config.DatabaseFile)!, Path.GetFileName(Config.DatabaseFile), FileMonitorCallback); ignoreListFileMonitor = new FileMonitor(Path.GetDirectoryName(Config.IgnoreListFile)!, Path.GetFileName(Config.IgnoreListFile), FileMonitorCallback); - RegisterChatBotCommand("mailer", Translations.bot_mailer_cmd, "mailer ", ProcessInternalCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty))) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("getmails") + .Executes(r => OnCommandGetMails())) + .Then(l => l.Literal("getignored") + .Executes(r => OnCommandGetIgnored())) + .Then(l => l.Literal("addignored") + .Then(l => l.Argument("username", Arguments.String()) + .Executes(r => OnCommandAddIgnored(Arguments.GetString(r, "username"))))) + .Then(l => l.Literal("removeignored") + .Then(l => l.Argument("username", Arguments.String()) + .Executes(r => OnCommandRemoveIgnored(Arguments.GetString(r, "username"))))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); + } + + public override void OnUnload() + { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => Translations.bot_mailer_cmd_help + ": /mailer " + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandGetMails() + { + LogToConsole(string.Format(Translations.bot_mailer_cmd_getmails, string.Join("\n", mailDatabase))); + return 1; + } + + private int OnCommandGetIgnored() + { + LogToConsole(string.Format(Translations.bot_mailer_cmd_getignored, string.Join("\n", ignoreList))); + return 1; + } + + private int OnCommandAddIgnored(string username) + { + if (IsValidName(username)) + { + username = username.ToLower(); + lock (readWriteLock) + { + if (!ignoreList.Contains(username)) + { + ignoreList.Add(username); + ignoreList.SaveToFile(Config.IgnoreListFile); + } + } + LogToConsole(string.Format(Translations.bot_mailer_cmd_ignore_added, username)); + return 1; + } + else + { + LogToConsole(string.Format(Translations.bot_mailer_cmd_ignore_invalid, "addignored")); + return 0; + } + } + + private int OnCommandRemoveIgnored(string username) + { + if (IsValidName(username)) + { + username = username.ToLower(); + lock (readWriteLock) + { + if (ignoreList.Contains(username)) + { + ignoreList.Remove(username); + ignoreList.SaveToFile(Config.IgnoreListFile); + } + } + LogToConsole(string.Format(Translations.bot_mailer_cmd_ignore_removed, username)); + return 1; + } + else + { + LogToConsole(string.Format(Translations.bot_mailer_cmd_ignore_invalid, "removeignored")); + return 0; + } } /// @@ -365,57 +466,5 @@ namespace MinecraftClient.ChatBots ignoreList = IgnoreList.FromFile(Config.IgnoreListFile); } } - - /// - /// Interprets local commands. - /// - private string ProcessInternalCommand(string cmd, string[] args) - { - if (args.Length > 0) - { - string commandName = args[0].ToLower(); - switch (commandName) - { - case "getmails": // Sorry, I (ReinforceZwei) replaced "=" to "-" because it would affect the parsing of translation file (key=value) - return string.Format(Translations.bot_mailer_cmd_getmails, string.Join("\n", mailDatabase)); - - case "getignored": - return string.Format(Translations.bot_mailer_cmd_getignored, string.Join("\n", ignoreList)); - - case "addignored": - case "removeignored": - if (args.Length > 1 && IsValidName(args[1])) - { - string username = args[1].ToLower(); - if (commandName == "addignored") - { - lock (readWriteLock) - { - if (!ignoreList.Contains(username)) - { - ignoreList.Add(username); - ignoreList.SaveToFile(Config.IgnoreListFile); - } - } - return string.Format(Translations.bot_mailer_cmd_ignore_added, args[1]); - } - else - { - lock (readWriteLock) - { - if (ignoreList.Contains(username)) - { - ignoreList.Remove(username); - ignoreList.SaveToFile(Config.IgnoreListFile); - } - } - return string.Format(Translations.bot_mailer_cmd_ignore_removed, args[1]); - } - } - else return string.Format(Translations.bot_mailer_cmd_ignore_invalid, commandName); - } - } - return Translations.bot_mailer_cmd_help + ": /help mailer"; - } } } diff --git a/MinecraftClient/ChatBots/Map.cs b/MinecraftClient/ChatBots/Map.cs index c33a4bc4..fef48a90 100644 --- a/MinecraftClient/ChatBots/Map.cs +++ b/MinecraftClient/ChatBots/Map.cs @@ -1,18 +1,24 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Text; using System.Threading.Tasks; +using Brigadier.NET; +using Brigadier.NET.Builder; using ImageMagick; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Mapping; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots { public class Map : ChatBot { + public const string CommandName = "maps"; + public static Configs Config = new(); public struct QueuedMap @@ -63,7 +69,7 @@ namespace MinecraftClient.ChatBots private readonly string baseDirectory = @"Rendered_Maps"; - private readonly Dictionary cachedMaps = new(); + internal readonly Dictionary cachedMaps = new(); private readonly Queue discordQueue = new(); @@ -74,14 +80,79 @@ namespace MinecraftClient.ChatBots DeleteRenderedMaps(); - RegisterChatBotCommand("maps", "bot.map.cmd.desc", "maps list|render or maps l|r ", OnMapCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Executes(r => OnCommandList(r.Source)) + .Then(l => l.Literal("list") + .Executes(r => OnCommandList(r.Source))) + .Then(l => l.Literal("render") + .Then(l => l.Argument("MapID", MccArguments.MapBotMapId()) + .Executes(r => OnCommandRender(r.Source, Arguments.GetInteger(r, "MapID"))))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); } public override void OnUnload() { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); DeleteRenderedMaps(); } + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => Translations.error_usage + ": /maps >" + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandList(CmdResult r) + { + if (cachedMaps.Count == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_map_no_maps); + + LogToConsole(Translations.bot_map_received); + + foreach (var (key, value) in new SortedDictionary(cachedMaps)) + LogToConsole(string.Format(Translations.bot_map_list_item, key, value.LastUpdated)); + + return r.SetAndReturn(CmdResult.Status.Done); + } + + private int OnCommandRender(CmdResult r, int mapId) + { + if (!cachedMaps.ContainsKey(mapId)) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.bot_map_cmd_not_found, mapId)); + + try + { + McMap map = cachedMaps[mapId]; + if (Config.Save_To_File) + SaveToFile(map); + + if (Config.Render_In_Console) + RenderInConsole(map); + + return r.SetAndReturn(CmdResult.Status.Done); + } + catch (Exception e) + { + LogDebugToConsole(e.StackTrace!); + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.bot_map_failed_to_render, mapId)); + } + } + private void DeleteRenderedMaps() { if (Config.Delete_All_On_Unload) @@ -94,56 +165,6 @@ namespace MinecraftClient.ChatBots } } - public string OnMapCommand(string command, string[] args) - { - if (args.Length == 0 || (args.Length == 1 && (args[0].ToLower().Equals("list") || args[0].ToLower().Equals("l")))) - { - if (cachedMaps.Count == 0) - return Translations.bot_map_no_maps; - - LogToConsole(Translations.bot_map_received); - - foreach (var (key, value) in new SortedDictionary(cachedMaps)) - LogToConsole(string.Format(Translations.bot_map_list_item, key, value.LastUpdated)); - - return ""; - } - - if (args.Length > 1) - { - if (args[0].ToLower().Equals("render") || args[0].ToLower().Equals("r")) - { - if (args.Length < 2) - return "maps > | maps >"; - - if (int.TryParse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture, out int mapId)) - { - if (!cachedMaps.ContainsKey(mapId)) - return string.Format(Translations.bot_map_cmd_not_found, mapId); - - try - { - McMap map = cachedMaps[mapId]; - if (Config.Save_To_File) - SaveToFile(map); - - if (Config.Render_In_Console) - RenderInConsole(map); - - return ""; - } - catch (Exception e) - { - LogDebugToConsole(e.StackTrace!); - return string.Format(Translations.bot_map_failed_to_render, mapId); - } - } - return Translations.bot_map_cmd_invalid_id; - } - } - return ""; - } - public override void OnMapData(int mapid, byte scale, bool trackingPosition, bool locked, List icons, byte columnsUpdated, byte rowsUpdated, byte mapCoulmnX, byte mapRowZ, byte[]? colors) { if (columnsUpdated == 0 && cachedMaps.ContainsKey(mapid)) @@ -320,7 +341,7 @@ namespace MinecraftClient.ChatBots } } - private void RenderInConsole(McMap map) + private static void RenderInConsole(McMap map) { StringBuilder sb = new(); @@ -406,7 +427,7 @@ namespace MinecraftClient.ChatBots public DateTime LastUpdated { get; set; } } - class MapColors + internal class MapColors { // When colors are updated in a new update, you can get them using the game code: net\minecraft\world\level\material\MaterialColor.java public static Dictionary Colors = new() diff --git a/MinecraftClient/ChatBots/PlayerListLogger.cs b/MinecraftClient/ChatBots/PlayerListLogger.cs index ea571aaf..333ff1fd 100644 --- a/MinecraftClient/ChatBots/PlayerListLogger.cs +++ b/MinecraftClient/ChatBots/PlayerListLogger.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots diff --git a/MinecraftClient/ChatBots/RemoteControl.cs b/MinecraftClient/ChatBots/RemoteControl.cs index ac1d544e..301e16f8 100644 --- a/MinecraftClient/ChatBots/RemoteControl.cs +++ b/MinecraftClient/ChatBots/RemoteControl.cs @@ -1,4 +1,6 @@ using System; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots @@ -32,17 +34,9 @@ namespace MinecraftClient.ChatBots string command = "", sender = ""; if (IsPrivateMessage(text, ref command, ref sender) && Settings.Config.Main.Advanced.BotOwners.Contains(sender.ToLower().Trim())) { - string? response = ""; + CmdResult response = new(); PerformInternalCommand(command, ref response); - response = GetVerbatim(response); - foreach (char disallowedChar in McClient.GetDisallowedChatCharacters()) - { - response = response.Replace(disallowedChar.ToString(), String.Empty); - } - if (response.Length > 0) - { - SendPrivateMessage(sender, response); - } + SendPrivateMessage(sender, response.ToString()); } else if (Config.AutoTpaccept && IsTeleportRequest(text, ref sender) diff --git a/MinecraftClient/ChatBots/ReplayCapture.cs b/MinecraftClient/ChatBots/ReplayCapture.cs index d5efa47e..eea74911 100644 --- a/MinecraftClient/ChatBots/ReplayCapture.cs +++ b/MinecraftClient/ChatBots/ReplayCapture.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Protocol; +using MinecraftClient.Scripting; using Tomlet.Attributes; namespace MinecraftClient.ChatBots @@ -10,6 +14,8 @@ namespace MinecraftClient.ChatBots /// public class ReplayCapture : ChatBot { + public const string CommandName = "replay"; + public static Configs Config = new(); [TomlDoNotInlineObject] @@ -40,7 +46,74 @@ namespace MinecraftClient.ChatBots replay.MetaData.serverName = GetServerHost() + GetServerPort(); backupCounter = Settings.DoubleToTick(Config.Backup_Interval); - RegisterChatBotCommand("replay", Translations.bot_replayCapture_cmd, "replay ", Command); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("save") + .Executes(r => OnCommandSave(r.Source))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); + } + + public override void OnUnload() + { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => string.Format(Translations.general_available_cmd, "save, stop") + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandSave(CmdResult r) + { + try + { + if (replay!.RecordRunning) + { + replay.CreateBackupReplay(@"replay_recordings\" + replay.GetReplayDefaultName()); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_replayCapture_created); + } + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_replayCapture_restart); + } + catch (Exception e) + { + return r.SetAndReturn(CmdResult.Status.Fail, e.Message); + } + } + + private int OnCommandStop(CmdResult r) + { + try + { + if (replay!.RecordRunning) + { + replay.OnShutDown(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.bot_replayCapture_stopped); + } + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.bot_replayCapture_restart); + } + catch (Exception e) + { + return r.SetAndReturn(CmdResult.Status.Fail, e.Message); + } } public override void OnNetworkPacket(int packetID, List packetData, bool isLogin, bool isInbound) @@ -66,37 +139,5 @@ namespace MinecraftClient.ChatBots replay!.OnShutDown(); return base.OnDisconnect(reason, message); } - - public string Command(string cmd, string[] args) - { - try - { - if (replay!.RecordRunning) - { - if (args.Length > 0) - { - switch (args[0].ToLower()) - { - case "save": - { - replay.CreateBackupReplay(@"replay_recordings\" + replay.GetReplayDefaultName()); - return Translations.bot_replayCapture_created; - } - case "stop": - { - replay.OnShutDown(); - return Translations.bot_replayCapture_stopped; - } - } - } - return string.Format(Translations.general_available_cmd, "save, stop"); - } - else return Translations.bot_replayCapture_restart; - } - catch (Exception e) - { - return e.Message; - } - } } } diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs index 11b9d97e..8ed7cb42 100644 --- a/MinecraftClient/ChatBots/Script.cs +++ b/MinecraftClient/ChatBots/Script.cs @@ -5,6 +5,7 @@ using System.IO; using System.Reflection; using System.Text; using System.Threading; +using MinecraftClient.Scripting; namespace MinecraftClient.ChatBots { diff --git a/MinecraftClient/ChatBots/ScriptScheduler.cs b/MinecraftClient/ChatBots/ScriptScheduler.cs index 21e9a977..bf8680a4 100644 --- a/MinecraftClient/ChatBots/ScriptScheduler.cs +++ b/MinecraftClient/ChatBots/ScriptScheduler.cs @@ -1,4 +1,5 @@ using System; +using MinecraftClient.Scripting; using Tomlet.Attributes; using static MinecraftClient.ChatBots.ScriptScheduler.Configs; diff --git a/MinecraftClient/ChatBots/TelegramBridge.cs b/MinecraftClient/ChatBots/TelegramBridge.cs index 0d8edcb8..eac48542 100644 --- a/MinecraftClient/ChatBots/TelegramBridge.cs +++ b/MinecraftClient/ChatBots/TelegramBridge.cs @@ -3,6 +3,10 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; +using MinecraftClient.Scripting; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Polling; @@ -16,6 +20,8 @@ namespace MinecraftClient.ChatBots { public class TelegramBridge : ChatBot { + public const string CommandName = "tgbridge"; + private enum BridgeDirection { Both = 0, @@ -70,17 +76,73 @@ namespace MinecraftClient.ChatBots public override void Initialize() { - RegisterChatBotCommand("tgbridge", "bot.TelegramBridge.desc", "tgbridge direction ", OnTgCommand); + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + ) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("direction") + .Then(l => l.Literal("both") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Both))) + .Then(l => l.Literal("mc") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Minecraft))) + .Then(l => l.Literal("telegram") + .Executes(r => OnCommandDirection(r.Source, BridgeDirection.Telegram)))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); Task.Run(async () => await MainAsync()); } - ~TelegramBridge() + public override void OnUnload() { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); Disconnect(); } - public override void OnUnload() + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => Translations.error_usage + ": /tgbridge direction " + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandDirection(CmdResult r, BridgeDirection direction) + { + string bridgeName; + switch (direction) + { + case BridgeDirection.Both: + bridgeName = Translations.bot_TelegramBridge_direction_both; + bridgeDirection = BridgeDirection.Both; + break; + + case BridgeDirection.Minecraft: + bridgeName = Translations.bot_TelegramBridge_direction_minecraft; + bridgeDirection = BridgeDirection.Minecraft; + break; + + case BridgeDirection.Telegram: + bridgeName = Translations.bot_TelegramBridge_direction_Telegram; + bridgeDirection = BridgeDirection.Telegram; + break; + + default: + goto case BridgeDirection.Both; + } + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_TelegramBridge_direction, bridgeName)); + } + + ~TelegramBridge() { Disconnect(); } @@ -97,7 +159,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_TelegramBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_TelegramBridge_canceled_sending); LogDebugToConsole(e); } @@ -110,47 +172,6 @@ namespace MinecraftClient.ChatBots return instance; } - private string OnTgCommand(string cmd, string[] args) - { - if (args.Length == 2) - { - if (args[0].ToLower().Equals("direction")) - { - string direction = args[1].ToLower().Trim(); - - string bridgeName; - switch (direction) - { - case "b": - case "both": - bridgeName = Translations.bot_TelegramBridge_direction_both; - bridgeDirection = BridgeDirection.Both; - break; - - case "mc": - case "minecraft": - bridgeName = Translations.bot_TelegramBridge_direction_minecraft; - bridgeDirection = BridgeDirection.Minecraft; - break; - - case "t": - case "tg": - case "telegram": - bridgeName = Translations.bot_TelegramBridge_direction_Telegram; - bridgeDirection = BridgeDirection.Telegram; - break; - - default: - return Translations.bot_TelegramBridge_invalid_direction; - } - - return string.Format(Translations.bot_TelegramBridge_direction, bridgeName); - }; - } - - return "dscbridge direction "; - } - public override void GetText(string text) { if (!CanSendMessages()) @@ -188,7 +209,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_TelegramBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_TelegramBridge_canceled_sending); LogDebugToConsole(e); } } @@ -211,7 +232,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole("§w§l§f" + Translations.bot_TelegramBridge_canceled_sending); + LogToConsole("§§4§l§f" + Translations.bot_TelegramBridge_canceled_sending); LogDebugToConsole(e); } } @@ -233,7 +254,7 @@ namespace MinecraftClient.ChatBots } if (string.IsNullOrEmpty(Config.ChannelId.Trim())) - LogToConsole("§w§l§f" + Translations.bot_TelegramBridge_missing_channel_id); + LogToConsole("§§4§l§f" + Translations.bot_TelegramBridge_missing_channel_id); botClient = new TelegramBotClient(Config.Token.Trim()); cancellationToken = new CancellationTokenSource(); @@ -252,12 +273,12 @@ namespace MinecraftClient.ChatBots IsConnected = true; SendMessage($"✅ {Translations.bot_TelegramBridge_connected}"); - LogToConsole($"§y§l§f{Translations.bot_TelegramBridge_connected}"); + LogToConsole($"§§2§l§f{Translations.bot_TelegramBridge_connected}"); if (Config.Authorized_Chat_Ids.Length == 0) { SendMessage($"⚠️ *{Translations.bot_TelegramBridge_missing_authorized_channels}* ⚠️"); - LogToConsole($"§w§l§f{Translations.bot_TelegramBridge_missing_authorized_channels}"); + LogToConsole($"§§4§l§f{Translations.bot_TelegramBridge_missing_authorized_channels}"); return; } @@ -265,7 +286,7 @@ namespace MinecraftClient.ChatBots } catch (Exception e) { - LogToConsole($"§w§l§f{Translations.bot_TelegramBridge_unknown_error}"); + LogToConsole($"§§4§l§f{Translations.bot_TelegramBridge_unknown_error}"); LogToConsole(e); return; } @@ -324,9 +345,8 @@ namespace MinecraftClient.ChatBots { var command = text[1..]; - string? result = ""; + CmdResult result = new(); PerformInternalCommand(command, ref result); - result = string.IsNullOrEmpty(result) ? "-" : result; await botClient.SendTextMessageAsync( chatId: chatId, @@ -348,7 +368,7 @@ namespace MinecraftClient.ChatBots _ => exception.ToString() }; - LogToConsole("§w§l§f" + ErrorMessage); + LogToConsole("§§4§l§f" + ErrorMessage); return Task.CompletedTask; } } diff --git a/MinecraftClient/ChatBots/TestBot.cs b/MinecraftClient/ChatBots/TestBot.cs index f96e9e8a..8b103535 100644 --- a/MinecraftClient/ChatBots/TestBot.cs +++ b/MinecraftClient/ChatBots/TestBot.cs @@ -1,4 +1,6 @@ -namespace MinecraftClient.ChatBots +using MinecraftClient.Scripting; + +namespace MinecraftClient.ChatBots { /// /// Example of message receiving. diff --git a/MinecraftClient/ColorHelper.cs b/MinecraftClient/ColorHelper.cs index 3f35b304..fe91a780 100644 --- a/MinecraftClient/ColorHelper.cs +++ b/MinecraftClient/ColorHelper.cs @@ -1,5 +1,5 @@ using System; -using static MinecraftClient.Settings.MainConfigHealper.MainConfig.AdvancedConfig; +using static MinecraftClient.Settings.ConsoleConfigHealper.ConsoleConfig; namespace MinecraftClient { @@ -75,14 +75,37 @@ namespace MinecraftClient public static string GetColorEscapeCode(byte R, byte G, byte B, bool foreground) { - return GetColorEscapeCode(R, G, B, foreground, Settings.Config.Main.Advanced.TerminalColorDepth); + return GetColorEscapeCode(R, G, B, foreground, Settings.Config.Console.General.ConsoleColorMode); } - public static string GetColorEscapeCode(byte R, byte G, byte B, bool foreground, TerminalColorDepthType colorDepth) + public static string GetColorEscapeCode(byte R, byte G, byte B, bool foreground, ConsoleColorModeType colorDepth) { switch (colorDepth) { - case TerminalColorDepthType.bit_4: + case ConsoleColorModeType.disable: + return string.Empty; + + case ConsoleColorModeType.legacy_4bit: + { + ColorRGBA color = new(R, G, B); + int best_idx = 0; + double min_distance = ColorMap4[0].Item1.Distance(color); + for (int i = 1; i < ColorMap4.Length; ++i) + { + double distance = ColorMap4[i].Item1.Distance(color); + if (distance < min_distance) + { + min_distance = distance; + best_idx = i; + } + } + if (foreground) + return $"§{best_idx:X}"; + else + return $"§§{best_idx:X}"; + } + + case ConsoleColorModeType.vt100_4bit: { ColorRGBA color = new(R, G, B); int best_idx = 0; @@ -99,7 +122,7 @@ namespace MinecraftClient return string.Format("\u001b[{0}m", ColorMap4[best_idx].Item2 - (foreground ? 10 : 0)); } - case TerminalColorDepthType.bit_8: + case ConsoleColorModeType.vt100_8bit: { ColorRGBA color = new(R, G, B); int R_idx = (int)(R <= 95 ? Math.Round(R / 95.0) : 1 + Math.Round((R - 95.0) / 40.0)); @@ -126,7 +149,7 @@ namespace MinecraftClient return string.Format("\u001B[{0};5;{1}m", (foreground ? 38 : 48), ColorMap8[best_idx].Item2); } - case TerminalColorDepthType.bit_24: + case ConsoleColorModeType.vt100_24bit: return string.Format("\u001B[{0};2;{1};{2};{3}m", (foreground ? 38 : 48), R, G, B); default: diff --git a/MinecraftClient/Command.cs b/MinecraftClient/Command.cs index e17e8f33..e606f0dd 100644 --- a/MinecraftClient/Command.cs +++ b/MinecraftClient/Command.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Text; +using Brigadier.NET; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; namespace MinecraftClient { @@ -28,8 +31,13 @@ namespace MinecraftClient /// Translated command description public string GetCmdDescTranslated() { - string s = (string.IsNullOrEmpty(CmdUsage) || string.IsNullOrEmpty(CmdDesc)) ? "" : ": "; // If either one is empty, no colon : - return CmdUsage + s + CmdDesc; + char cmdChar = Settings.Config.Main.Advanced.InternalCmdChar.ToChar(); + + StringBuilder sb = new(); + string s = (string.IsNullOrEmpty(CmdUsage) || string.IsNullOrEmpty(CmdDesc)) ? string.Empty : ": "; // If either one is empty, no colon : + sb.Append("§e").Append(cmdChar).Append(CmdUsage).Append("§r").Append(s).AppendLine(CmdDesc); + sb.Append(McClient.dispatcher.GetAllUsageString(CmdName, false)); + return sb.ToString(); } /// @@ -38,18 +46,9 @@ namespace MinecraftClient public abstract string CmdUsage { get; } /// - /// Perform the command + /// Register the command. /// - /// The full command, eg: 'mycommand arg1 arg2' - /// Local variables passed along with the command (may be null) - /// A confirmation/error message, or "" if no message - public abstract string Run(McClient handler, string command, Dictionary? localVars); - - /// - /// Return a list of aliases for this command. - /// Override this method if you wish to put aliases to the command - /// - public virtual IEnumerable GetCMDAliases() { return Array.Empty(); } + public abstract void RegisterCommand(CommandDispatcher dispatcher); /// /// Check if at least one argument has been passed to the command diff --git a/MinecraftClient/CommandHandler/ArgumentType/AccountNickArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/AccountNickArgumentType.cs new file mode 100644 index 00000000..09d11de1 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/AccountNickArgumentType.cs @@ -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 + { + public override string Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadString(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + var accountList = Settings.Config.Main.Advanced.AccountList; + foreach (var account in accountList) + builder.Suggest(account.Key); + return builder.BuildFuture(); + } + } +} \ No newline at end of file diff --git a/MinecraftClient/CommandHandler/ArgumentType/AutoCraftRecipeNameArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/AutoCraftRecipeNameArgumentType.cs new file mode 100644 index 00000000..0d2bc0eb --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/AutoCraftRecipeNameArgumentType.cs @@ -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 + { + public override string Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadString(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + var recipeList = Settings.Config.ChatBot.AutoCraft.Recipes; + foreach (var recipe in recipeList) + builder.Suggest(recipe.Name); + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/BotNameArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/BotNameArgumentType.cs new file mode 100644 index 00000000..1f49da98 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/BotNameArgumentType.cs @@ -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 + { + public override string Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadString(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + if (client != null) + { + var botList = client.GetLoadedChatBots(); + foreach (var bot in botList) + builder.Suggest(bot.GetType().Name); + } + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/EntityTypeArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/EntityTypeArgumentType.cs new file mode 100644 index 00000000..fc3ce124 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/EntityTypeArgumentType.cs @@ -0,0 +1,50 @@ +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 + { + 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 ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + foreach (EntityType result in Enum.GetValues()) + { + string name = result.ToString(); + string localName = Entity.GetTypeString(result); + bool same = true; + for (int i = 0, j = 0; i < name.Length; ++i, ++j) + { + while (j < localName.Length && localName[j] == ' ') + ++j; + if (j >= localName.Length || name[i] != localName[j]) + { + same = false; + break; + } + } + if (same) + builder.Suggest(name); + else + builder.Suggest(name, new SuggestionTooltip(localName)); + } + + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/FarmerCropTypeArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/FarmerCropTypeArgumentType.cs new file mode 100644 index 00000000..16ecdad4 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/FarmerCropTypeArgumentType.cs @@ -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 + { + 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 ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + foreach (var result in Enum.GetNames(typeof(CropType))) + builder.Suggest(result); + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/HotbarSlotArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/HotbarSlotArgumentType.cs new file mode 100644 index 00000000..ebf05813 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/HotbarSlotArgumentType.cs @@ -0,0 +1,43 @@ +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 HotbarSlotArgumentType : ArgumentType + { + public override int Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadInt(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + if (client != null) + { + Inventory.Container? inventory = client.GetInventory(0); + if (inventory != null) + { + for (int i = 1; i <= 9; ++i) + { + if (inventory.Items.TryGetValue(i - 1 + 36, out Inventory.Item? item)) + { + string slotStr = i.ToString(); + if (slotStr.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase)) + { + string itemDesc = item.Count == 1 ? item.GetTypeString() : string.Format("{0}x{1}", item.Count, item.GetTypeString()); + builder.Suggest(slotStr, new SuggestionTooltip(itemDesc)); + } + } + } + } + } + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/InventoryActionArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/InventoryActionArgumentType.cs new file mode 100644 index 00000000..89616af6 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/InventoryActionArgumentType.cs @@ -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 + { + 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 ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + foreach (var action in SupportActions) + builder.Suggest(action.ToString()); + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/InventoryIdArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/InventoryIdArgumentType.cs new file mode 100644 index 00000000..15164956 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/InventoryIdArgumentType.cs @@ -0,0 +1,40 @@ +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 + { + public override int Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadInt(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + if (client != null) + { + var invList = client.GetInventories(); + foreach (var inv in invList) + { + string invName = inv.Key.ToString(); + if (invName.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase)) + { + string? invTitle = inv.Value.Title; + if (!string.IsNullOrWhiteSpace(invTitle)) + builder.Suggest(invName, new SuggestionTooltip(invTitle)); + else + builder.Suggest(invName); + } + } + } + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/InventorySlotArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/InventorySlotArgumentType.cs new file mode 100644 index 00000000..2536aa1e --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/InventorySlotArgumentType.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +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 InventorySlotArgumentType : ArgumentType + { + public override int Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadInt(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + if (client != null && context.Nodes.Count >= 2) + { + string invName = context.Nodes[1].Range.Get(builder.Input); + if (!int.TryParse(invName, out int invId)) + invId = invName switch + { + "creativegive" => 0, + "creativedelete" => 0, + "player" => 0, + "container" => client.GetInventories().Keys.ToList().Max(), + _ => -1, + }; + + Inventory.Container? inventory = client.GetInventory(invId); + if (inventory != null) + { + foreach ((int slot, Inventory.Item item) in inventory.Items) + { + if (item != null && item.Count > 0) + { + string slotStr = slot.ToString(); + if (slotStr.StartsWith(builder.RemainingLowerCase, StringComparison.InvariantCultureIgnoreCase)) + { + string itemDesc = item.Count == 1 ? item.GetTypeString() : string.Format("{0}x{1}", item.Count, item.GetTypeString()); + builder.Suggest(slotStr, new SuggestionTooltip(itemDesc)); + } + } + } + } + } + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/ItemTypeArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/ItemTypeArgumentType.cs new file mode 100644 index 00000000..fcd270bf --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/ItemTypeArgumentType.cs @@ -0,0 +1,52 @@ +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 + { + 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 ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + foreach (ItemType result in Enum.GetValues()) + { + if (result == ItemType.Unknown || result == ItemType.Null) + continue; + + string name = result.ToString(); + string localName = Item.GetTypeString(result); + bool same = true; + for (int i = 0, j = 0; i < name.Length; ++i, ++j) + { + while (j < localName.Length && localName[j] == ' ') + ++j; + if (j >= localName.Length || name[i] != localName[j]) + { + same = false; + break; + } + } + if (same) + builder.Suggest(name); + else + builder.Suggest(name, new SuggestionTooltip(localName)); + } + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/LocationArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/LocationArgumentType.cs new file mode 100644 index 00000000..f2ef9b0a --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/LocationArgumentType.cs @@ -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 + { + 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 ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + 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(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/MapBotMapIdArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/MapBotMapIdArgumentType.cs new file mode 100644 index 00000000..bd1ffee6 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/MapBotMapIdArgumentType.cs @@ -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 + { + public override int Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadInt(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + 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(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/PlayerNameArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/PlayerNameArgumentType.cs new file mode 100644 index 00000000..b5092251 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/PlayerNameArgumentType.cs @@ -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 + { + public override string Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadString(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + McClient? client = CmdResult.currentHandler; + 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(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/ServerNickArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/ServerNickArgumentType.cs new file mode 100644 index 00000000..ae520f7d --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/ServerNickArgumentType.cs @@ -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 + { + public override string Parse(IStringReader reader) + { + reader.SkipWhitespace(); + return reader.ReadString(); + } + + public override Task ListSuggestions(CommandContext context, SuggestionsBuilder builder) + { + var serverList = Settings.Config.Main.Advanced.ServerList; + foreach (var server in serverList) + builder.Suggest(server.Key); + return builder.BuildFuture(); + } + } +} diff --git a/MinecraftClient/CommandHandler/ArgumentType/TupleArgumentType.cs b/MinecraftClient/CommandHandler/ArgumentType/TupleArgumentType.cs new file mode 100644 index 00000000..55157d99 --- /dev/null +++ b/MinecraftClient/CommandHandler/ArgumentType/TupleArgumentType.cs @@ -0,0 +1,18 @@ +using System; +using Brigadier.NET; +using Brigadier.NET.ArgumentTypes; + +namespace MinecraftClient.CommandHandler.ArgumentType +{ + public class TupleArgumentType : ArgumentType> + { + public override Tuple Parse(IStringReader reader) + { + reader.SkipWhitespace(); + int int1 = reader.ReadInt(); + reader.SkipWhitespace(); + int int2 = reader.ReadInt(); + return new(int1, int2); + } + } +} diff --git a/MinecraftClient/CommandHandler/CmdResult.cs b/MinecraftClient/CommandHandler/CmdResult.cs new file mode 100644 index 00000000..4be40a4a --- /dev/null +++ b/MinecraftClient/CommandHandler/CmdResult.cs @@ -0,0 +1,96 @@ +using System; + +namespace MinecraftClient.CommandHandler +{ + public class CmdResult + { + public static readonly CmdResult Empty = new(); + + internal static McClient? currentHandler; + + 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(); + } + } +} diff --git a/MinecraftClient/CommandHandler/MccArguments.cs b/MinecraftClient/CommandHandler/MccArguments.cs new file mode 100644 index 00000000..c659ee3e --- /dev/null +++ b/MinecraftClient/CommandHandler/MccArguments.cs @@ -0,0 +1,114 @@ +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(CommandContext context, string name) + { + return context.GetArgument(name); + } + + public static TupleArgumentType Tuple() + { + return new TupleArgumentType(); + } + + public static Tuple GetTuple(CommandContext context, string name) + { + return context.GetArgument>(name); + } + + public static EntityTypeArgumentType EntityType() + { + return new EntityTypeArgumentType(); + } + + public static Mapping.EntityType GetEntityType(CommandContext context, string name) + { + return context.GetArgument(name); + } + + public static ItemTypeArgumentType ItemType() + { + return new ItemTypeArgumentType(); + } + + public static Inventory.ItemType GetItemType(CommandContext context, string name) + { + return context.GetArgument(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 InventorySlotArgumentType InventorySlot() + { + return new InventorySlotArgumentType(); + } + + public static Inventory.WindowActionType GetInventoryAction(CommandContext context, string name) + { + return context.GetArgument(name); + } + + public static AutoCraftRecipeNameArgumentType AutoCraftRecipeName() + { + return new AutoCraftRecipeNameArgumentType(); + } + + public static FarmerCropTypeArgumentType FarmerCropType() + { + return new FarmerCropTypeArgumentType(); + } + + public static ChatBots.Farmer.CropType GetFarmerCropType(CommandContext context, string name) + { + return context.GetArgument(name); + } + + public static PlayerNameArgumentType PlayerName() + { + return new PlayerNameArgumentType(); + } + + public static MapBotMapIdArgumentType MapBotMapId() + { + return new MapBotMapIdArgumentType(); + } + + public static HotbarSlotArgumentType HotbarSlot() + { + return new HotbarSlotArgumentType(); + } + } +} diff --git a/MinecraftClient/CommandHandler/Patch/CommandDispatcherExtensions.cs b/MinecraftClient/CommandHandler/Patch/CommandDispatcherExtensions.cs new file mode 100644 index 00000000..37ef9cc4 --- /dev/null +++ b/MinecraftClient/CommandHandler/Patch/CommandDispatcherExtensions.cs @@ -0,0 +1,43 @@ +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 commandDispatcher, string commandname) + { + commandDispatcher.GetRoot().RemoveChild(commandname); + } + + public static string GetAllUsageString(this CommandDispatcher commandDispatcher, string commandName, bool restricted) + { + char cmdChar = Settings.Config.Main.Advanced.InternalCmdChar.ToChar(); + try + { + 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(); + } + catch + { + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/MinecraftClient/CommandHandler/Patch/CommandNodeExtensions.cs b/MinecraftClient/CommandHandler/Patch/CommandNodeExtensions.cs new file mode 100644 index 00000000..23e2a87f --- /dev/null +++ b/MinecraftClient/CommandHandler/Patch/CommandNodeExtensions.cs @@ -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 commandNode, string name) + { + var children = (IDictionary>) + typeof(CommandNode) + .GetField("_children", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(commandNode)!; + var literals = (IDictionary>) + typeof(CommandNode) + .GetField("_literals", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(commandNode)!; + var arguments = (IDictionary>) + typeof(CommandNode) + .GetField("_arguments", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(commandNode)!; + + children.Remove(name); + literals.Remove(name); + } + } +} \ No newline at end of file diff --git a/MinecraftClient/CommandHandler/SuggestionTooltip.cs b/MinecraftClient/CommandHandler/SuggestionTooltip.cs new file mode 100644 index 00000000..c235f061 --- /dev/null +++ b/MinecraftClient/CommandHandler/SuggestionTooltip.cs @@ -0,0 +1,14 @@ +using Brigadier.NET; + +namespace MinecraftClient.CommandHandler +{ + internal class SuggestionTooltip : IMessage + { + public SuggestionTooltip(string tooltip) + { + String = tooltip; + } + + public string String { get; set; } + } +} diff --git a/MinecraftClient/Commands/Animation.cs b/MinecraftClient/Commands/Animation.cs index 6c142bde..dab51b6f 100644 --- a/MinecraftClient/Commands/Animation.cs +++ b/MinecraftClient/Commands/Animation.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,34 +10,46 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "animation "; } } public override string CmdDesc { get { return Translations.cmd_animation_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("mainhand") + .Executes(r => GetUsage(r.Source, "mainhand"))) + .Then(l => l.Literal("offhand") + .Executes(r => GetUsage(r.Source, "offhand"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoAnimation(r.Source, mainhand: true)) + .Then(l => l.Literal("mainhand") + .Executes(r => DoAnimation(r.Source, mainhand: true))) + .Then(l => l.Literal("offhand") + .Executes(r => DoAnimation(r.Source, mainhand: false))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] args = GetArgs(command); - if (args.Length > 0) - { - if (args[0] == "mainhand" || args[0] == "0") - { - handler.DoAnimation(0); - return Translations.general_done; - } - else if (args[0] == "offhand" || args[0] == "1") - { - handler.DoAnimation(1); - return Translations.general_done; - } - else - { - return GetCmdDescTranslated(); - } - } - else - { - return GetCmdDescTranslated(); - } - } - else return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + "mainhand" => GetCmdDescTranslated(), + "offhand" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private static int DoAnimation(CmdResult r, bool mainhand) + { + McClient handler = CmdResult.currentHandler!; + return r.SetAndReturn(handler.DoAnimation(mainhand ? 1 : 0)); } } } diff --git a/MinecraftClient/Commands/Bed.cs b/MinecraftClient/Commands/Bed.cs index 7ac4c828..f3d4b995 100644 --- a/MinecraftClient/Commands/Bed.cs +++ b/MinecraftClient/Commands/Bed.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -13,138 +16,166 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "bed leave|sleep |sleep "; } } public override string CmdDesc { get { return Translations.cmd_bed_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - string[] args = GetArgs(command); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("leave") + .Executes(r => GetUsage(r.Source, "leave"))) + .Then(l => l.Literal("sleep") + .Executes(r => GetUsage(r.Source, "sleep"))) + ) + ); - if (args.Length >= 1) + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Literal("leave") + .Executes(r => DoLeaveBed(r.Source))) + .Then(l => l.Literal("sleep") + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => DoSleepBedWithLocation(r.Source, MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Argument("Radius", Arguments.Double()) + .Executes(r => DoSleepBedWithRadius(r.Source, Arguments.GetDouble(r, "Radius"))))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string subcommand = args[0].ToLower().Trim(); +#pragma warning disable format // @formatter:off + "leave" => GetCmdDescTranslated(), + "sleep" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (subcommand.Equals("leave") || subcommand.Equals("l")) + private static int DoLeaveBed(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + return r.SetAndReturn(Translations.cmd_bed_leaving, handler.SendEntityAction(Protocol.EntityActionType.LeaveBed)); + } + + private static int DoSleepBedWithRadius(CmdResult r, double radius) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + handler.Log.Info(string.Format(Translations.cmd_bed_searching, radius)); + + Location current = handler.GetCurrentLocation(); + Location bedLocation = current; + + Material[] bedMaterialList = new Material[] + { + Material.BlackBed, + Material.BlueBed, + Material.BrownBed, + Material.CyanBed, + Material.GrayBed, + Material.GreenBed, + Material.LightBlueBed, + Material.LightGrayBed, + Material.LimeBed, + Material.MagentaBed, + Material.OrangeBed, + Material.PinkBed, + Material.PurpleBed, + Material.RedBed, + Material.WhiteBed, + Material.YellowBed + }; + + bool found = false; + foreach (Material material in bedMaterialList) + { + List beds = handler.GetWorld().FindBlock(current, material, radius); + + if (beds.Count > 0) { - handler.SendEntityAction(Protocol.EntityActionType.LeaveBed); - return Translations.cmd_bed_leaving; - } - - if (subcommand.Equals("sleep") || subcommand.Equals("s")) - { - if (!handler.GetTerrainEnabled()) - return Translations.error_terrain_not_enabled; - - if (args.Length == 2) - { - if (!int.TryParse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture, out int radius)) - return CmdUsage; - - handler.GetLogger().Info(string.Format(Translations.cmd_bed_searching, radius)); - - Location current = handler.GetCurrentLocation(); - Location bedLocation = current; - - Material[] bedMaterialList = new Material[]{ - Material.BlackBed, - Material.BlueBed, - Material.BrownBed, - Material.CyanBed, - Material.GrayBed, - Material.GreenBed, - Material.LightBlueBed, - Material.LightGrayBed, - Material.LimeBed, - Material.MagentaBed, - Material.OrangeBed, - Material.PinkBed, - Material.PurpleBed, - Material.RedBed, - Material.WhiteBed, - Material.YellowBed - }; - - bool found = false; - foreach (Material material in bedMaterialList) - { - List beds = handler.GetWorld().FindBlock(current, material, radius); - - if (beds.Count > 0) - { - found = true; - bedLocation = beds.First(); - break; - } - } - - if (!found) - return Translations.cmd_bed_bed_not_found; - - handler.Log.Info(string.Format(Translations.cmd_bed_found_a_bed_at, bedLocation.X, bedLocation.Y, bedLocation.Z)); - - if (!Movement.CheckChunkLoading(handler.GetWorld(), current, bedLocation)) - return string.Format(Translations.cmd_move_chunk_not_loaded, bedLocation.X, bedLocation.Y, bedLocation.Z); - - if (handler.MoveTo(bedLocation)) - { - Task.Factory.StartNew(() => - { - bool atTheLocation = false; - DateTime timeout = DateTime.Now.AddSeconds(60); - - while (DateTime.Now < timeout) - { - if (handler.GetCurrentLocation() == bedLocation || handler.GetCurrentLocation().Distance(bedLocation) <= 2.0) - { - atTheLocation = true; - break; - } - } - - if (!atTheLocation) - { - handler.Log.Info(string.Format(Translations.cmd_bed_failed_to_reach_in_time, bedLocation.X, bedLocation.Y, bedLocation.Z)); - return; - } - - handler.Log.Info(string.Format(Translations.cmd_bed_moving, bedLocation.X, bedLocation.Y, bedLocation.Z)); - - bool res = handler.PlaceBlock(bedLocation, Direction.Down); - - handler.Log.Info(string.Format( - Translations.cmd_bed_trying_to_use, - bedLocation.X, - bedLocation.Y, - bedLocation.Z, - res ? Translations.cmd_bed_in : Translations.cmd_bed_not_in - )); - }); - - return ""; - } - - return Translations.cmd_bed_cant_reach_safely; - } - - if (args.Length >= 3) - { - Location block = Location.Parse(handler.GetCurrentLocation(), args[1], args[2], args[3]).ToFloor(); - Location blockCenter = block.ToCenter(); - - if (!handler.GetWorld().GetBlock(block).Type.IsBed()) - return string.Format(Translations.cmd_bed_not_a_bed, blockCenter.X, blockCenter.Y, blockCenter.Z); - - bool res = handler.PlaceBlock(block, Direction.Down); - - return string.Format( - Translations.cmd_bed_trying_to_use, - blockCenter.X, - blockCenter.Y, - blockCenter.Z, - res ? Translations.cmd_bed_in : Translations.cmd_bed_not_in - ); - } + found = true; + bedLocation = beds.First(); + break; } } - return CmdUsage; + if (!found) + return r.SetAndReturn(Status.Fail, Translations.cmd_bed_bed_not_found); + + handler.Log.Info(string.Format(Translations.cmd_bed_found_a_bed_at, bedLocation.X, bedLocation.Y, bedLocation.Z)); + + if (!Movement.CheckChunkLoading(handler.GetWorld(), current, bedLocation)) + return r.SetAndReturn(Status.FailChunkNotLoad, + string.Format(Translations.cmd_move_chunk_not_loaded, bedLocation.X, bedLocation.Y, bedLocation.Z)); + + if (handler.MoveTo(bedLocation)) + { + Task.Factory.StartNew(() => + { + bool atTheLocation = false; + DateTime timeout = DateTime.Now.AddSeconds(60); + + while (DateTime.Now < timeout) + { + if (handler.GetCurrentLocation() == bedLocation || handler.GetCurrentLocation().Distance(bedLocation) <= 2.0) + { + atTheLocation = true; + break; + } + } + + if (!atTheLocation) + { + handler.Log.Info(string.Format(Translations.cmd_bed_failed_to_reach_in_time, bedLocation.X, bedLocation.Y, bedLocation.Z)); + return; + } + + handler.Log.Info(string.Format(Translations.cmd_bed_moving, bedLocation.X, bedLocation.Y, bedLocation.Z)); + + bool res = handler.PlaceBlock(bedLocation, Direction.Down); + + handler.Log.Info(string.Format( + Translations.cmd_bed_trying_to_use, + bedLocation.X, + bedLocation.Y, + bedLocation.Z, + res ? Translations.cmd_bed_in : Translations.cmd_bed_not_in + )); + }); + + return r.SetAndReturn(Status.Done); + } + else + { + return r.SetAndReturn(Status.Fail, Translations.cmd_bed_cant_reach_safely); + } + } + + private static int DoSleepBedWithLocation(CmdResult r, Location block) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + block.ToAbsolute(handler.GetCurrentLocation()); + Location blockCenter = block.ToCenter(); + + if (!handler.GetWorld().GetBlock(block).Type.IsBed()) + return r.SetAndReturn(Status.Fail, + string.Format(Translations.cmd_bed_not_a_bed, blockCenter.X, blockCenter.Y, blockCenter.Z)); + + return r.SetAndReturn(Status.Done, string.Format( + Translations.cmd_bed_trying_to_use, + blockCenter.X, + blockCenter.Y, + blockCenter.Z, + handler.PlaceBlock(block, Direction.Down) ? Translations.cmd_bed_in : Translations.cmd_bed_not_in + )); } } } \ No newline at end of file diff --git a/MinecraftClient/Commands/BlockInfo.cs b/MinecraftClient/Commands/BlockInfo.cs index 79ebfe55..61e177d0 100644 --- a/MinecraftClient/Commands/BlockInfo.cs +++ b/MinecraftClient/Commands/BlockInfo.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; -using System.Text; +using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -10,36 +13,67 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "blockinfo [-s]"; } } public override string CmdDesc { get { return Translations.cmd_blockinfo_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("-s") + .Executes(r => GetUsage(r.Source, "-s"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => LogBlockInfo(r.Source, null, false)) + .Then(l => l.Literal("-s") + .Executes(r => LogBlockInfo(r.Source, null, true))) + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => LogBlockInfo(r.Source, MccArguments.GetLocation(r, "Location"), false)) + .Then(l => l.Literal("-s") + .Executes(r => LogBlockInfo(r.Source, MccArguments.GetLocation(r, "Location"), true)))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + "-s" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private static int LogBlockInfo(CmdResult r, Location? targetBlock, bool reportSurrounding) + { + McClient handler = CmdResult.currentHandler!; if (!handler.GetTerrainEnabled()) - return Translations.error_terrain_not_enabled; + return r.SetAndReturn(Status.FailNeedTerrain); - string[] args = GetArgs(command); + if (targetBlock.HasValue) + targetBlock.Value.ToAbsolute(handler.GetCurrentLocation()); + else + targetBlock = handler.GetCurrentLocation(); - if (args.Length < 3) - return CmdUsage; - - bool reportSurrounding = args.Length >= 4 && args[3].Equals("-s", System.StringComparison.OrdinalIgnoreCase); - - Location current = handler.GetCurrentLocation(); - Location targetBlockLocation = Location.Parse(current, args[0], args[1], args[2]); - - Block block = handler.GetWorld().GetBlock(targetBlockLocation); + Block block = handler.GetWorld().GetBlock(targetBlock.Value); handler.Log.Info($"{Translations.cmd_blockinfo_BlockType}: {block.GetTypeString()}"); - if (reportSurrounding) { StringBuilder sb = new(); sb.AppendLine($"{Translations.cmd_blockinfo_BlocksAround}:"); - Block blockXPositive = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X + 1, targetBlockLocation.Y, targetBlockLocation.Z)); - Block blockXNegative = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X - 1, targetBlockLocation.Y, targetBlockLocation.Z)); - Block blockYPositive = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X, targetBlockLocation.Y + 1, targetBlockLocation.Z)); - Block blockYNegative = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X, targetBlockLocation.Y - 1, targetBlockLocation.Z)); - Block blockZPositive = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X, targetBlockLocation.Y, targetBlockLocation.Z + 1)); - Block blockZNegative = handler.GetWorld().GetBlock(new Location(targetBlockLocation.X, targetBlockLocation.Y, targetBlockLocation.Z - 1)); + double X = targetBlock.Value.X, Y = targetBlock.Value.Y, Z = targetBlock.Value.Z; + Block blockXPositive = handler.GetWorld().GetBlock(new Location(X + 1, Y, Z)); + Block blockXNegative = handler.GetWorld().GetBlock(new Location(X - 1, Y, Z)); + Block blockYPositive = handler.GetWorld().GetBlock(new Location(X, Y + 1, Z)); + Block blockYNegative = handler.GetWorld().GetBlock(new Location(X, Y - 1, Z)); + Block blockZPositive = handler.GetWorld().GetBlock(new Location(X, Y, Z + 1)); + Block blockZNegative = handler.GetWorld().GetBlock(new Location(X, Y, Z - 1)); sb.AppendLine($"[X {Translations.cmd_blockinfo_Positive}] {Translations.cmd_blockinfo_BlockType}: {blockXPositive.GetTypeString()}"); sb.AppendLine($"[X {Translations.cmd_blockinfo_Negative}] {Translations.cmd_blockinfo_BlockType}: {blockXNegative.GetTypeString()}"); @@ -56,9 +90,7 @@ namespace MinecraftClient.Commands handler.Log.Info(sb.ToString()); } - - - return ""; + return r.SetAndReturn(Status.Done); } } } diff --git a/MinecraftClient/Commands/Bots.cs b/MinecraftClient/Commands/Bots.cs index 09e9d8b9..73574a14 100644 --- a/MinecraftClient/Commands/Bots.cs +++ b/MinecraftClient/Commands/Bots.cs @@ -1,6 +1,9 @@ using System; -using System.Collections.Generic; using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; namespace MinecraftClient.Commands { @@ -10,65 +13,85 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "bots [list|unload ]"; } } public override string CmdDesc { get { return Translations.cmd_bots_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("list") + .Executes(r => GetUsage(r.Source, "list"))) + .Then(l => l.Literal("unload") + .Executes(r => GetUsage(r.Source, "unload"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoListBot(r.Source)) + .Then(l => l.Literal("list") + .Executes(r => DoListBot(r.Source))) + .Then(l => l.Literal("unload") + .Then(l => l.Argument("BotName", MccArguments.BotName()) + .Executes(r => DoUnloadBot(r.Source, Arguments.GetString(r, "BotName"))))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] args = GetArgs(command); +#pragma warning disable format // @formatter:off + "list" => GetCmdDescTranslated(), + "unload" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (args.Length == 1) - { - if (args[0].Equals("list", StringComparison.OrdinalIgnoreCase)) - { - StringBuilder sb = new(); + private int DoListBot(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + int length = handler.GetLoadedChatBots().Count; + if (length == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded); - int length = handler.GetLoadedChatBots().Count; - - if (length == 0) - return Translations.cmd_bots_noloaded; - - for (int i = 0; i < length; i++) - { - sb.Append(handler.GetLoadedChatBots()[i].GetType().Name); - - if (i != length - 1) - sb.Append(" ,"); - - } - - return Translations.cmd_bots_list + ": " + sb.ToString(); - } - - } - else if (args.Length == 2) - { - if (args[0].Equals("unload", StringComparison.OrdinalIgnoreCase)) - { - string botName = args[1].Trim(); - - if (botName.ToLower().Equals("all", StringComparison.OrdinalIgnoreCase)) - { - if (handler.GetLoadedChatBots().Count == 0) - return Translations.cmd_bots_noloaded; - - handler.UnloadAllBots(); - return Translations.cmd_bots_unloaded_all; - } - else - { - ChatBot? bot = handler.GetLoadedChatBots().Find(bot => bot.GetType().Name.ToLower() == botName.ToLower()); - - if (bot == null) - return string.Format(Translations.cmd_bots_notfound, botName); - - handler.BotUnLoad(bot); - return string.Format(Translations.cmd_bots_unloaded, botName); - } - } - } + StringBuilder sb = new(); + for (int i = 0; i < length; i++) + { + sb.Append(handler.GetLoadedChatBots()[i].GetType().Name); + if (i != length - 1) + sb.Append(" ,"); } - return GetCmdDescTranslated(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_bots_list + ": " + sb.ToString()); + } + + private int DoUnloadBot(CmdResult r, string botName) + { + McClient handler = CmdResult.currentHandler!; + if (botName.ToLower().Equals("all", StringComparison.OrdinalIgnoreCase)) + { + if (handler.GetLoadedChatBots().Count == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_bots_noloaded); + else + { + handler.UnloadAllBots(); + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_bots_unloaded_all); + } + } + else + { + ChatBot? bot = handler.GetLoadedChatBots().Find(bot => bot.GetType().Name.ToLower() == botName.ToLower()); + if (bot == null) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_bots_notfound, botName)); + else + { + handler.BotUnLoad(bot); + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_bots_unloaded, botName)); + } + } } } } diff --git a/MinecraftClient/Commands/ChangeSlot.cs b/MinecraftClient/Commands/ChangeSlot.cs index 1af95bff..acbe2e8d 100644 --- a/MinecraftClient/Commands/ChangeSlot.cs +++ b/MinecraftClient/Commands/ChangeSlot.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -9,35 +11,43 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "changeslot <1-9>"; } } public override string CmdDesc { get { return Translations.cmd_changeSlot_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (!handler.GetInventoryEnabled()) - return Translations.extra_inventory_required; + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); - if (HasArg(command)) + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Slot", MccArguments.HotbarSlot()) + .Executes(r => DoChangeSlot(r.Source, Arguments.GetInteger(r, "Slot")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - short slot; - try - { - slot = Convert.ToInt16(GetArg(command)); - } - catch (FormatException) - { - return Translations.cmd_changeSlot_nan; - } - if (slot >= 1 && slot <= 9) - { - if (handler.ChangeSlot(slot -= 1)) - { - return string.Format(Translations.cmd_changeSlot_changed, (slot += 1)); - } - else - { - return Translations.cmd_changeSlot_fail; - } - } - } - return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoChangeSlot(CmdResult r, int slot) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(Status.FailNeedInventory); + + if (handler.ChangeSlot((short)(slot - 1))) + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_changeSlot_changed, slot)); + else + return r.SetAndReturn(Status.Fail, Translations.cmd_changeSlot_fail); } } } diff --git a/MinecraftClient/Commands/Chunk.cs b/MinecraftClient/Commands/Chunk.cs index 35551897..cdc26e14 100644 --- a/MinecraftClient/Commands/Chunk.cs +++ b/MinecraftClient/Commands/Chunk.cs @@ -1,8 +1,10 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -12,248 +14,272 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "chunk status [chunkX chunkZ|locationX locationY locationZ]"; } } public override string CmdDesc { get { return Translations.cmd_chunk_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) - { - string[] args = GetArgs(command); - if (args.Length > 0) - { - if (args[0] == "status") - { - World world = handler.GetWorld(); - Location current = handler.GetCurrentLocation(); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("status") + .Executes(r => GetUsage(r.Source, "status"))) + .Then(l => l.Literal("_setloading") + .Executes(r => GetUsage(r.Source, "_setloading"))) + .Then(l => l.Literal("_setloaded") + .Executes(r => GetUsage(r.Source, "_setloaded"))) + .Then(l => l.Literal("_delete") + .Executes(r => GetUsage(r.Source, "_delete"))) + ) + ); - Tuple? markedChunkPos = ParseChunkPos(args); - (int markChunkX, int markChunkZ) = markedChunkPos ?? (new(current.ChunkX, current.ChunkZ)); - - StringBuilder sb = new(); - - sb.Append(World.GetChunkLoadingStatus(handler.GetWorld())); - sb.Append('\n'); - - sb.AppendLine(string.Format(Translations.cmd_chunk_current, current, current.ChunkX, current.ChunkZ)); - if (markedChunkPos != null) - { - sb.Append(Translations.cmd_chunk_marked); - if (args.Length == 1 + 3) - sb.Append(String.Format("X:{0:0.00} Y:{1:0.00} Z:{2:0.00}, ", - double.Parse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture), - double.Parse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture), - double.Parse(args[3], NumberStyles.Any, CultureInfo.CurrentCulture))); - sb.AppendLine(string.Format(Translations.cmd_chunk_chunk_pos, markChunkX, markChunkZ));; - } - - int consoleHeight = Math.Max(Math.Max(Console.BufferHeight, Settings.Config.Main.Advanced.MinTerminalHeight) - 2, 25); - if (consoleHeight % 2 == 0) - --consoleHeight; - - int consoleWidth = Math.Max(Math.Max(Console.BufferWidth, Settings.Config.Main.Advanced.MinTerminalWidth) / 2, 17); - if (consoleWidth % 2 == 0) - --consoleWidth; - - int startZ = current.ChunkZ - consoleHeight, endZ = current.ChunkZ + consoleHeight; - int startX = current.ChunkX - consoleWidth, endX = current.ChunkX + consoleWidth; - - int leftMost = endX, rightMost = startX, topMost = endZ, bottomMost = startZ; - for (int z = startZ; z <= endZ; z++) - { - for (int x = startX; x <= endX; ++x) - { - if (world[x, z] != null) - { - leftMost = Math.Min(leftMost, x); - rightMost = Math.Max(rightMost, x); - topMost = Math.Min(topMost, z); - bottomMost = Math.Max(bottomMost, z); - } - } - } - - // Include the player's location - topMost = Math.Min(topMost, current.ChunkZ); - bottomMost = Math.Max(bottomMost, current.ChunkZ); - leftMost = Math.Min(leftMost, current.ChunkX); - rightMost = Math.Max(rightMost, current.ChunkX); - - // Empty one row and one column each - --leftMost; ++rightMost; --topMost; ++bottomMost; - - // Resize according to limitations - if ((bottomMost - topMost + 1) > consoleHeight) - { - int delta = (bottomMost - topMost + 1) - consoleHeight; - if (bottomMost - (delta + 1) / 2 < current.ChunkZ + 1) - { - int bottomReduce = bottomMost - (current.ChunkZ + 1); - bottomMost -= bottomReduce; - topMost += delta - bottomReduce; - } - else if (topMost + delta / 2 > current.ChunkZ - 1) - { - int topAdd = topMost - (current.ChunkZ - 1); - topMost += topAdd; - bottomMost -= delta - topAdd; - } - else - { - topMost += delta / 2; - bottomMost -= (delta + 1) / 2; - } - } - if ((rightMost - leftMost + 1) > consoleWidth) - { - int delta = (rightMost - leftMost + 1) - consoleWidth; - if (rightMost - (delta + 1) / 2 < current.ChunkX + 1) - { - int rightReduce = rightMost - (current.ChunkX + 1); - rightMost -= rightReduce; - leftMost += delta - rightReduce; - } - else if (leftMost + delta / 2 > current.ChunkX - 1) - { - int leftAdd = leftMost - (current.ChunkX - 1); - leftMost += leftAdd; - rightMost -= delta - leftAdd; - } - else - { - leftMost += delta / 2; - rightMost -= (delta + 1) / 2; - } - } - - // Try to include the marker chunk - if (markedChunkPos != null && - (((Math.Max(bottomMost, markChunkZ) - Math.Min(topMost, markChunkZ) + 1) > consoleHeight) || - ((Math.Max(rightMost, markChunkX) - Math.Min(leftMost, markChunkX) + 1) > consoleWidth))) - sb.AppendLine("§x§0" + Translations.cmd_chunk_outside); - else - { - topMost = Math.Min(topMost, markChunkZ); - bottomMost = Math.Max(bottomMost, markChunkZ); - leftMost = Math.Min(leftMost, markChunkX); - rightMost = Math.Max(rightMost, markChunkX); - } - - - // \ud83d\udd33: 🔳, \ud83d\udfe8: 🟨, \ud83d\udfe9: 🟩, \u25A1: □, \u25A3: ▣, \u25A0: ■ - string[] chunkStatusStr = Settings.Config.Main.Advanced.EnableEmoji ? - new string[] { "\ud83d\udd33", "\ud83d\udfe8", "\ud83d\udfe9" } : new string[] { "\u25A1", "\u25A3", "\u25A0" }; - - // Output - for (int z = topMost; z <= bottomMost; ++z) - { - for (int x = leftMost; x <= rightMost; ++x) - { - if (z == current.ChunkZ && x == current.ChunkX) - sb.Append("§z"); // Player Location: background gray - else if (z == markChunkZ && x == markChunkX) - sb.Append("§w"); // Marked chunk: background red - - ChunkColumn? chunkColumn = world[x, z]; - if (chunkColumn == null) - sb.Append(chunkStatusStr[0]); - else if (chunkColumn.FullyLoaded) - sb.Append(chunkStatusStr[2]); - else - sb.Append(chunkStatusStr[1]); - - if ((z == current.ChunkZ && x == current.ChunkX) || (z == markChunkZ && x == markChunkX)) - sb.Append("§r"); // Reset background color - } - sb.Append('\n'); - } - - sb.AppendLine(string.Format(Translations.cmd_chunk_icon, "§z §r", "§w §r", chunkStatusStr[0], chunkStatusStr[1], chunkStatusStr[2])); - return sb.ToString(); - } - else if (args[0] == "setloading") // For debugging - { - Tuple? chunkPos = ParseChunkPos(args); - if (chunkPos != null) - { - handler.Log.Info("§x§0" + Translations.cmd_chunk_for_debug); - World world = handler.GetWorld(); - (int chunkX, int chunkZ) = chunkPos; - ChunkColumn? chunkColumn = world[chunkX, chunkZ]; - if (chunkColumn != null) - chunkColumn.FullyLoaded = false; - return (chunkColumn == null) ? "Fail: chunk dosen't exist!" : - String.Format("Successfully marked chunk ({0}, {1}) as loading.", chunkX, chunkZ); - } - else - return GetCmdDescTranslated(); - } - else if (args[0] == "setloaded") // For debugging - { - Tuple? chunkPos = ParseChunkPos(args); - if (chunkPos != null) - { - handler.Log.Info("§x§0" + Translations.cmd_chunk_for_debug); - World world = handler.GetWorld(); - (int chunkX, int chunkZ) = chunkPos; - ChunkColumn? chunkColumn = world[chunkX, chunkZ]; - if (chunkColumn != null) - chunkColumn.FullyLoaded = true; - return (chunkColumn == null) ? "Fail: chunk dosen't exist!" : - String.Format("Successfully marked chunk ({0}, {1}) as loaded.", chunkX, chunkZ); - } - else - return GetCmdDescTranslated(); - } - else if (args[0] == "delete") // For debugging - { - Tuple? chunkPos = ParseChunkPos(args); - if (chunkPos != null) - { - handler.Log.Info("§x§0" + Translations.cmd_chunk_for_debug); - World world = handler.GetWorld(); - (int chunkX, int chunkZ) = chunkPos; - world[chunkX, chunkZ] = null; - return String.Format("Successfully deleted chunk ({0}, {1}).", chunkX, chunkZ); - } - else - return GetCmdDescTranslated(); - } - else - return GetCmdDescTranslated(); - } - else - return GetCmdDescTranslated(); - } - else - return GetCmdDescTranslated(); + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Literal("status") + .Executes(r => LogChunkStatus(r.Source)) + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => LogChunkStatus(r.Source, pos: MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Argument("Chunk", MccArguments.Tuple()) + .Executes(r => LogChunkStatus(r.Source, markedChunkPos: MccArguments.GetTuple(r, "Chunk"))))) + .Then(l => l.Literal("_setloading") + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => DebugSetLoading(r.Source, pos: MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Argument("Chunk", MccArguments.Tuple()) + .Executes(r => DebugSetLoading(r.Source, markedChunkPos: MccArguments.GetTuple(r, "Chunk"))))) + .Then(l => l.Literal("_setloaded") + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => DebugSetLoaded(r.Source, pos: MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Argument("Chunk", MccArguments.Tuple()) + .Executes(r => DebugSetLoaded(r.Source, markedChunkPos: MccArguments.GetTuple(r, "Chunk"))))) + .Then(l => l.Literal("_delete") + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => DebugDelete(r.Source, pos: MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Argument("Chunk", MccArguments.Tuple()) + .Executes(r => DebugDelete(r.Source, markedChunkPos: MccArguments.GetTuple(r, "Chunk"))))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); } - private static Tuple? ParseChunkPos(string[] args) + private int GetUsage(CmdResult r, string? cmd) { - try + return r.SetAndReturn(cmd switch { - int chunkX, chunkZ; - if (args.Length == 1 + 3) +#pragma warning disable format // @formatter:off + "status" => GetCmdDescTranslated(), + "_setloading" => GetCmdDescTranslated(), + "_setloaded" => GetCmdDescTranslated(), + "_delete" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private static int LogChunkStatus(CmdResult r, Location? pos = null, Tuple? markedChunkPos = null) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + World world = handler.GetWorld(); + Location current = handler.GetCurrentLocation(); + if (pos.HasValue) + pos.Value.ToAbsolute(current); + + (int markChunkX, int markChunkZ) = markedChunkPos ?? + (pos.HasValue ? new(pos.Value.ChunkX, pos.Value.ChunkZ) : new(current.ChunkX, current.ChunkZ)); + + StringBuilder sb = new(); + + sb.Append(World.GetChunkLoadingStatus(handler.GetWorld())); + sb.Append('\n'); + + sb.AppendLine(string.Format(Translations.cmd_chunk_current, current, current.ChunkX, current.ChunkZ)); + if (markedChunkPos != null) + { + sb.Append(Translations.cmd_chunk_marked); + if (pos.HasValue) + sb.Append(string.Format("X:{0:0.00} Y:{1:0.00} Z:{2:0.00}, ", pos.Value.X, pos.Value.Y, pos.Value.Z)); + sb.AppendLine(string.Format(Translations.cmd_chunk_chunk_pos, markChunkX, markChunkZ)); ; + } + + int consoleHeight = Math.Max(Math.Max(Console.BufferHeight, Settings.Config.Main.Advanced.MinTerminalHeight) - 2, 25); + if (consoleHeight % 2 == 0) + --consoleHeight; + + int consoleWidth = Math.Max(Math.Max(Console.BufferWidth, Settings.Config.Main.Advanced.MinTerminalWidth) / 2, 17); + if (consoleWidth % 2 == 0) + --consoleWidth; + + int startZ = current.ChunkZ - consoleHeight, endZ = current.ChunkZ + consoleHeight; + int startX = current.ChunkX - consoleWidth, endX = current.ChunkX + consoleWidth; + + int leftMost = endX, rightMost = startX, topMost = endZ, bottomMost = startZ; + for (int z = startZ; z <= endZ; z++) + { + for (int x = startX; x <= endX; ++x) { - Location pos = new( - double.Parse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture), - double.Parse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture), - double.Parse(args[3], NumberStyles.Any, CultureInfo.CurrentCulture) - ); - chunkX = pos.ChunkX; - chunkZ = pos.ChunkZ; + if (world[x, z] != null) + { + leftMost = Math.Min(leftMost, x); + rightMost = Math.Max(rightMost, x); + topMost = Math.Min(topMost, z); + bottomMost = Math.Max(bottomMost, z); + } } - else if (args.Length == 1 + 2) + } + + // Include the player's location + topMost = Math.Min(topMost, current.ChunkZ); + bottomMost = Math.Max(bottomMost, current.ChunkZ); + leftMost = Math.Min(leftMost, current.ChunkX); + rightMost = Math.Max(rightMost, current.ChunkX); + + // Empty one row and one column each + --leftMost; ++rightMost; --topMost; ++bottomMost; + + // Resize according to limitations + if ((bottomMost - topMost + 1) > consoleHeight) + { + int delta = (bottomMost - topMost + 1) - consoleHeight; + if (bottomMost - (delta + 1) / 2 < current.ChunkZ + 1) { - chunkX = int.Parse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture); - chunkZ = int.Parse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture); + int bottomReduce = bottomMost - (current.ChunkZ + 1); + bottomMost -= bottomReduce; + topMost += delta - bottomReduce; + } + else if (topMost + delta / 2 > current.ChunkZ - 1) + { + int topAdd = topMost - (current.ChunkZ - 1); + topMost += topAdd; + bottomMost -= delta - topAdd; } else - return null; - return new(chunkX, chunkZ); + { + topMost += delta / 2; + bottomMost -= (delta + 1) / 2; + } } - catch (FormatException) + if ((rightMost - leftMost + 1) > consoleWidth) { - return null; + int delta = (rightMost - leftMost + 1) - consoleWidth; + if (rightMost - (delta + 1) / 2 < current.ChunkX + 1) + { + int rightReduce = rightMost - (current.ChunkX + 1); + rightMost -= rightReduce; + leftMost += delta - rightReduce; + } + else if (leftMost + delta / 2 > current.ChunkX - 1) + { + int leftAdd = leftMost - (current.ChunkX - 1); + leftMost += leftAdd; + rightMost -= delta - leftAdd; + } + else + { + leftMost += delta / 2; + rightMost -= (delta + 1) / 2; + } } + + // Try to include the marker chunk + if (markedChunkPos != null && + (((Math.Max(bottomMost, markChunkZ) - Math.Min(topMost, markChunkZ) + 1) > consoleHeight) || + ((Math.Max(rightMost, markChunkX) - Math.Min(leftMost, markChunkX) + 1) > consoleWidth))) + sb.AppendLine(Translations.cmd_chunk_outside); + else + { + topMost = Math.Min(topMost, markChunkZ); + bottomMost = Math.Max(bottomMost, markChunkZ); + leftMost = Math.Min(leftMost, markChunkX); + rightMost = Math.Max(rightMost, markChunkX); + } + + + // \ud83d\udd33: 🔳, \ud83d\udfe8: 🟨, \ud83d\udfe9: 🟩, \u25A1: □, \u25A3: ▣, \u25A0: ■ + string[] chunkStatusStr = Settings.Config.Main.Advanced.EnableEmoji ? + new string[] { "\ud83d\udd33", "\ud83d\udfe8", "\ud83d\udfe9" } : new string[] { "\u25A1", "\u25A3", "\u25A0" }; + + // Output + for (int z = topMost; z <= bottomMost; ++z) + { + for (int x = leftMost; x <= rightMost; ++x) + { + if (z == current.ChunkZ && x == current.ChunkX) + sb.Append("§§7"); // Player Location: background gray + else if (z == markChunkZ && x == markChunkX) + sb.Append("§§4"); // Marked chunk: background red + + ChunkColumn? chunkColumn = world[x, z]; + if (chunkColumn == null) + sb.Append(chunkStatusStr[0]); + else if (chunkColumn.FullyLoaded) + sb.Append(chunkStatusStr[2]); + else + sb.Append(chunkStatusStr[1]); + + if ((z == current.ChunkZ && x == current.ChunkX) || (z == markChunkZ && x == markChunkX)) + sb.Append("§§r"); // Reset background color + } + sb.Append('\n'); + } + + sb.Append(string.Format(Translations.cmd_chunk_icon, "§§7 §§r", "§§4 §§r", chunkStatusStr[0], chunkStatusStr[1], chunkStatusStr[2])); + handler.Log.Info(sb.ToString()); + + return r.SetAndReturn(Status.Done); + } + + private static int DebugSetLoading(CmdResult r, Location? pos = null, Tuple? markedChunkPos = null) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + if (pos.HasValue) + pos.Value.ToAbsolute(handler.GetCurrentLocation()); + handler.Log.Info(Translations.cmd_chunk_for_debug); + (int chunkX, int chunkZ) = markedChunkPos ?? new(pos!.Value.ChunkX, pos!.Value.ChunkZ); + ChunkColumn? chunkColumn = handler.GetWorld()[chunkX, chunkZ]; + if (chunkColumn != null) + chunkColumn.FullyLoaded = false; + + if (chunkColumn == null) + return r.SetAndReturn(Status.Fail, "Fail: chunk dosen't exist!"); + else + return r.SetAndReturn(Status.Done, string.Format("Successfully marked chunk ({0}, {1}) as loading.", chunkX, chunkZ)); + } + + private static int DebugSetLoaded(CmdResult r, Location? pos = null, Tuple? markedChunkPos = null) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + if (pos.HasValue) + pos.Value.ToAbsolute(handler.GetCurrentLocation()); + handler.Log.Info(Translations.cmd_chunk_for_debug); + (int chunkX, int chunkZ) = markedChunkPos ?? new(pos!.Value.ChunkX, pos!.Value.ChunkZ); + ChunkColumn? chunkColumn = handler.GetWorld()[chunkX, chunkZ]; + if (chunkColumn != null) + chunkColumn.FullyLoaded = false; + + if (chunkColumn == null) + return r.SetAndReturn(Status.Fail, "Fail: chunk dosen't exist!"); + else + return r.SetAndReturn(Status.Done, string.Format("Successfully marked chunk ({0}, {1}) as loaded.", chunkX, chunkZ)); + } + + private static int DebugDelete(CmdResult r, Location? pos = null, Tuple? markedChunkPos = null) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + if (pos.HasValue) + pos.Value.ToAbsolute(handler.GetCurrentLocation()); + handler.Log.Info(Translations.cmd_chunk_for_debug); + (int chunkX, int chunkZ) = markedChunkPos ?? new(pos!.Value.ChunkX, pos!.Value.ChunkZ); + handler.GetWorld()[chunkX, chunkZ] = null; + + return r.SetAndReturn(Status.Done, string.Format("Successfully deleted chunk ({0}, {1}).", chunkX, chunkZ)); } } } diff --git a/MinecraftClient/Commands/Connect.cs b/MinecraftClient/Commands/Connect.cs index 5fd5c669..c8691340 100644 --- a/MinecraftClient/Commands/Connect.cs +++ b/MinecraftClient/Commands/Connect.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -8,27 +11,66 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "connect [account]"; } } public override string CmdDesc { get { return Translations.cmd_connect_desc; } } - public override string Run(McClient? handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) - { - string[] args = GetArgs(command); - if (args.Length > 1) - { - if (!Settings.Config.Main.Advanced.SetAccount(args[1])) - { - return string.Format(Translations.cmd_connect_unknown, args[1]); - } - } + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); - if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(args[0]), true)) - { - Program.Restart(keepAccountAndServerSettings: true); - return ""; - } - else return string.Format(Translations.cmd_connect_invalid_ip, args[0]); + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("ServerNick", MccArguments.ServerNick()) + .Executes(r => DoConnect(r.Source, Arguments.GetString(r, "ServerNick"), string.Empty)) + .Then(l => l.Argument("AccountNick", MccArguments.AccountNick()) + .Executes(r => DoConnect(r.Source, Arguments.GetString(r, "ServerNick"), Arguments.GetString(r, "AccountNick"))))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoConnect(CmdResult r, string server, string account) + { + if (!string.IsNullOrWhiteSpace(account) && !Settings.Config.Main.Advanced.SetAccount(account)) + return r.SetAndReturn(Status.Fail, string.Format(Translations.cmd_connect_unknown, account)); + + if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(server), true)) + { + Program.Restart(keepAccountAndServerSettings: true); + return r.SetAndReturn(Status.Done); + } + else + { + return r.SetAndReturn(Status.Fail, string.Format(Translations.cmd_connect_invalid_ip, server)); + } + } + + internal static string DoConnect(string command) + { + string[] args = GetArgs(command); + if (args.Length > 1 && !Settings.Config.Main.Advanced.SetAccount(args[1])) + return string.Format(Translations.cmd_connect_unknown, args[1]); + + if (Settings.Config.Main.SetServerIP(new Settings.MainConfigHealper.MainConfig.ServerInfoConfig(args[0]), true)) + { + Program.Restart(keepAccountAndServerSettings: true); + return string.Empty; + } + else + { + return string.Format(Translations.cmd_connect_invalid_ip, args[0]); } - else return GetCmdDescTranslated(); } } } diff --git a/MinecraftClient/Commands/Debug.cs b/MinecraftClient/Commands/Debug.cs index 56e5f0a4..0302ea05 100644 --- a/MinecraftClient/Commands/Debug.cs +++ b/MinecraftClient/Commands/Debug.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,16 +10,47 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "debug [on|off]"; } } public override string CmdDesc { get { return Translations.cmd_debug_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) - Settings.Config.Logging.DebugMessages = (GetArg(command).ToLower() == "on"); - else + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => SetDebugMode(r.Source, true)) + .Then(l => l.Literal("on") + .Executes(r => SetDebugMode(r.Source, false, true))) + .Then(l => l.Literal("off") + .Executes(r => SetDebugMode(r.Source, false, false))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int SetDebugMode(CmdResult r, bool flip, bool mode = false) + { + if (flip) Settings.Config.Logging.DebugMessages = !Settings.Config.Logging.DebugMessages; - if (Settings.Config.Logging.DebugMessages) - return Translations.cmd_debug_state_on; else - return Translations.cmd_debug_state_off; + Settings.Config.Logging.DebugMessages = mode; + + if (Settings.Config.Logging.DebugMessages) + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_debug_state_on); + else + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_debug_state_off); } } } diff --git a/MinecraftClient/Commands/Dig.cs b/MinecraftClient/Commands/Dig.cs index 2891b1a7..3a30619a 100644 --- a/MinecraftClient/Commands/Dig.cs +++ b/MinecraftClient/Commands/Dig.cs @@ -1,6 +1,9 @@ using System; -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -10,49 +13,71 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "dig "; } } public override string CmdDesc { get { return Translations.cmd_dig_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (!handler.GetTerrainEnabled()) - return Translations.extra_terrainandmovement_required; + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); - string[] args = GetArgs(command); - if (args.Length == 0) + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DigLookAt(r.Source)) + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => DigAt(r.Source, MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - (bool hasBlock, Location blockLoc, Block block) = RaycastHelper.RaycastBlock(handler, 4.5, false); - if (!hasBlock) - return Translations.cmd_dig_too_far; - else if (block.Type == Material.Air) - return Translations.cmd_dig_no_block; - else if (handler.DigBlock(blockLoc, lookAtBlock: false)) - return string.Format(Translations.cmd_dig_dig, blockLoc.X, blockLoc.Y, blockLoc.Z, block.GetTypeString()); - else - return Translations.cmd_dig_fail; - } - else if (args.Length == 3) +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DigAt(CmdResult r, Location blockToBreak) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + blockToBreak = blockToBreak.ToAbsolute(current); + if (blockToBreak.DistanceSquared(current.EyesLocation()) > 25) + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_too_far); + Block block = handler.GetWorld().GetBlock(blockToBreak); + if (block.Type == Material.Air) + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_no_block); + else if (handler.DigBlock(blockToBreak)) { - try - { - Location current = handler.GetCurrentLocation(); - Location blockToBreak = Location.Parse(current.ToFloor(), args[0], args[1], args[2]); - if (blockToBreak.DistanceSquared(current.EyesLocation()) > 25) - return Translations.cmd_dig_too_far; - Block block = handler.GetWorld().GetBlock(blockToBreak); - if (block.Type == Material.Air) - return Translations.cmd_dig_no_block; - else if (handler.DigBlock(blockToBreak)) - { - blockToBreak = blockToBreak.ToCenter(); - return string.Format(Translations.cmd_dig_dig, blockToBreak.X, blockToBreak.Y, blockToBreak.Z, block.GetTypeString()); - } - else - return Translations.cmd_dig_fail; - } - catch (FormatException) { return GetCmdDescTranslated(); } + blockToBreak = blockToBreak.ToCenter(); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_dig_dig, blockToBreak.X, blockToBreak.Y, blockToBreak.Z, block.GetTypeString())); } else - { - return GetCmdDescTranslated(); - } + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_fail); + } + + private int DigLookAt(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + (bool hasBlock, Location blockLoc, Block block) = RaycastHelper.RaycastBlock(handler, 4.5, false); + if (!hasBlock) + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_too_far); + else if (block.Type == Material.Air) + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_no_block); + else if (handler.DigBlock(blockLoc, lookAtBlock: false)) + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_dig_dig, blockLoc.X, blockLoc.Y, blockLoc.Z, block.GetTypeString())); + else + return r.SetAndReturn(Status.Fail, Translations.cmd_dig_fail); } } } diff --git a/MinecraftClient/Commands/DropItem.cs b/MinecraftClient/Commands/DropItem.cs index ec1f77d0..38cfc4b6 100644 --- a/MinecraftClient/Commands/DropItem.cs +++ b/MinecraftClient/Commands/DropItem.cs @@ -1,52 +1,66 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Inventory; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { class DropItem : Command { public override string CmdName { get { return "dropitem"; } } - public override string CmdUsage { get { return "/dropitem "; } } + public override string CmdUsage { get { return "dropitem "; } } public override string CmdDesc { get { return Translations.cmd_dropItem_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("ItemType", MccArguments.ItemType()) + .Executes(r => DoDropItem(r.Source, MccArguments.GetItemType(r, "ItemType")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoDropItem(CmdResult r, ItemType itemType) + { + McClient handler = CmdResult.currentHandler!; if (!handler.GetInventoryEnabled()) - { - return Translations.extra_inventory_required; - } - if (HasArg(command)) - { - string arg = GetArg(command); - if (Enum.TryParse(arg, true, out ItemType itemType)) - { - int inventoryId; - var inventories = handler.GetInventories(); - List availableIds = inventories.Keys.ToList(); - availableIds.Remove(0); // remove player inventory ID from list - if (availableIds.Count == 1) - inventoryId = availableIds[0]; // one container, use it - else - inventoryId = 0; - var p = inventories[inventoryId]; - int[] targetItems = p.SearchItem(itemType); - foreach (int slot in targetItems) - { - handler.DoWindowAction(inventoryId, slot, WindowActionType.DropItemStack); - } - return string.Format(Translations.cmd_dropItem_dropped, itemType.ToString(), inventoryId); - } - else - { - return string.Format(Translations.cmd_dropItem_unknown_item, arg); - } - } + return r.SetAndReturn(Status.FailNeedTerrain); + + int inventoryId; + var inventories = handler.GetInventories(); + List availableIds = inventories.Keys.ToList(); + availableIds.Remove(0); // remove player inventory ID from list + if (availableIds.Count == 1) + inventoryId = availableIds[0]; // one container, use it else - { - return CmdUsage; - } + inventoryId = 0; + var p = inventories[inventoryId]; + int[] targetItems = p.SearchItem(itemType); + foreach (int slot in targetItems) + handler.DoWindowAction(inventoryId, slot, WindowActionType.DropItemStack); + + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_dropItem_dropped, Item.GetTypeString(itemType), inventoryId)); } } } diff --git a/MinecraftClient/Commands/Enchant.cs b/MinecraftClient/Commands/Enchant.cs index 263d351a..813c734f 100644 --- a/MinecraftClient/Commands/Enchant.cs +++ b/MinecraftClient/Commands/Enchant.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Inventory; namespace MinecraftClient.Commands @@ -10,76 +12,98 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "enchant "; } } public override string CmdDesc { get { return Translations.cmd_enchant_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (!handler.GetInventoryEnabled()) - return Translations.error_inventoryhandling_not_enabled; + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("top") + .Executes(r => GetUsage(r.Source, "top"))) + .Then(l => l.Literal("middle") + .Executes(r => GetUsage(r.Source, "middle"))) + .Then(l => l.Literal("bottom") + .Executes(r => GetUsage(r.Source, "bottom"))) + ) + ); - if (HasArg(command)) + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Literal("top") + .Executes(r => DoEnchant(r.Source, slotId: 0))) + .Then(l => l.Literal("middle") + .Executes(r => DoEnchant(r.Source, slotId: 1))) + .Then(l => l.Literal("bottom") + .Executes(r => DoEnchant(r.Source, slotId: 2))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string slot = GetArg(command).ToLower().Trim(); +#pragma warning disable format // @formatter:off + "top" => GetCmdDescTranslated(), + "middle" => GetCmdDescTranslated(), + "bottom" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - int slotId = slot switch + private int DoEnchant(CmdResult r, int slotId) + { + McClient handler = CmdResult.currentHandler!; + Container? enchantingTable = null; + + foreach (var (id, container) in handler.GetInventories()) + { + if (container.Type == ContainerType.Enchantment) { - "top" => 0, - "middle" => 1, - "bottom" => 2, - _ => -1 - }; - - if (slotId == -1) - return Translations.cmd_enchant_invalid_slot; - - Container? enchantingTable = null; - - foreach (var (id, container) in handler.GetInventories()) - { - if (container.Type == ContainerType.Enchantment) - { - enchantingTable = container; - break; - } + enchantingTable = container; + break; } - - if (enchantingTable == null) - return Translations.cmd_enchant_enchanting_table_not_opened; - - int[] emptySlots = enchantingTable.GetEmpytSlots(); - - if (emptySlots.Contains(0)) - return Translations.cmd_enchant_enchanting_no_item; - - if (emptySlots.Contains(1)) - return Translations.cmd_enchant_enchanting_no_lapis; - - Item lapisSlot = enchantingTable.Items[1]; - - if (lapisSlot.Type != ItemType.LapisLazuli) - return Translations.cmd_enchant_enchanting_no_lapis; - - if (lapisSlot.Count < 3) - return Translations.cmd_enchant_enchanting_no_lapis; - - EnchantmentData? enchantment = handler.GetLastEnchantments(); - - if (enchantment == null) - return Translations.cmd_enchant_no_enchantments; - - short requiredLevel = slotId switch - { - 0 => enchantment.TopEnchantmentLevelRequirement, - 1 => enchantment.MiddleEnchantmentLevelRequirement, - 2 => enchantment.BottomEnchantmentLevelRequirement, - _ => 9999 - }; - - if (handler.GetLevel() < requiredLevel) - return string.Format(Translations.cmd_enchant_no_levels, handler.GetLevel(), requiredLevel); - - return handler.ClickContainerButton(enchantingTable.ID, slotId) ? Translations.cmd_enchant_clicked : Translations.cmd_enchant_not_clicked; } - return GetCmdDescTranslated(); + if (enchantingTable == null) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_enchant_enchanting_table_not_opened); + + int[] emptySlots = enchantingTable.GetEmpytSlots(); + + if (emptySlots.Contains(0)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_enchant_enchanting_no_item); + + if (emptySlots.Contains(1)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_enchant_enchanting_no_lapis); + + Item lapisSlot = enchantingTable.Items[1]; + + if (lapisSlot.Type != ItemType.LapisLazuli || lapisSlot.Count < 3) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_enchant_enchanting_no_lapis); + + EnchantmentData? enchantment = handler.GetLastEnchantments(); + + if (enchantment == null) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_enchant_no_enchantments); + + short requiredLevel = slotId switch + { + 0 => enchantment.TopEnchantmentLevelRequirement, + 1 => enchantment.MiddleEnchantmentLevelRequirement, + 2 => enchantment.BottomEnchantmentLevelRequirement, + _ => 9999 + }; + + if (handler.GetLevel() < requiredLevel) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_enchant_no_levels, handler.GetLevel(), requiredLevel)); + else + { + if (handler.ClickContainerButton(enchantingTable.ID, slotId)) + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_enchant_clicked); + else + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_enchant_not_clicked); + } } } } diff --git a/MinecraftClient/Commands/Entitycmd.cs b/MinecraftClient/Commands/Entitycmd.cs index 868b1197..7cd4d8b4 100644 --- a/MinecraftClient/Commands/Entitycmd.cs +++ b/MinecraftClient/Commands/Entitycmd.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Inventory; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -14,6 +17,187 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "entity [near] "; } } public override string CmdDesc { get { return string.Empty; } } + private enum ActionType { Attack, Use, List }; + + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("near") + .Executes(r => GetUsage(r.Source, "near"))) + .Then(l => l.Literal("attack") + .Executes(r => GetUsage(r.Source, "attack"))) + .Then(l => l.Literal("use") + .Executes(r => GetUsage(r.Source, "use"))) + .Then(l => l.Literal("list") + .Executes(r => GetUsage(r.Source, "list"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => GetFullEntityList(r.Source)) + .Then(l => l.Literal("near") + .Executes(r => GetClosetEntityList(r.Source)) + .Then(l => l.Argument("EntityID", Arguments.Integer()) + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.List)) + .Then(l => l.Literal("attack") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.Attack))) + .Then(l => l.Literal("use") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.Use))) + .Then(l => l.Literal("list") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.List)))) + .Then(l => l.Argument("EntityType", MccArguments.EntityType()) + .Executes(r => OperateWithType(r.Source, true, MccArguments.GetEntityType(r, "EntityType"), ActionType.List)) + .Then(l => l.Literal("attack") + .Executes(r => OperateWithType(r.Source, near: true, MccArguments.GetEntityType(r, "EntityType"), ActionType.Attack))) + .Then(l => l.Literal("use") + .Executes(r => OperateWithType(r.Source, near: true, MccArguments.GetEntityType(r, "EntityType"), ActionType.Use))) + .Then(l => l.Literal("list") + .Executes(r => OperateWithType(r.Source, near: true, MccArguments.GetEntityType(r, "EntityType"), ActionType.List))))) + .Then(l => l.Argument("EntityID", Arguments.Integer()) + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.List)) + .Then(l => l.Literal("attack") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.Attack))) + .Then(l => l.Literal("use") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.Use))) + .Then(l => l.Literal("list") + .Executes(r => OperateWithId(r.Source, Arguments.GetInteger(r, "EntityID"), ActionType.List)))) + .Then(l => l.Argument("EntityType", MccArguments.EntityType()) + .Executes(r => OperateWithType(r.Source, true, MccArguments.GetEntityType(r, "EntityType"), ActionType.List)) + .Then(l => l.Literal("attack") + .Executes(r => OperateWithType(r.Source, near: false, MccArguments.GetEntityType(r, "EntityType"), ActionType.Attack))) + .Then(l => l.Literal("use") + .Executes(r => OperateWithType(r.Source, near: false, MccArguments.GetEntityType(r, "EntityType"), ActionType.Use))) + .Then(l => l.Literal("list") + .Executes(r => OperateWithType(r.Source, near: false, MccArguments.GetEntityType(r, "EntityType"), ActionType.List)))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + "near" => GetCmdDescTranslated(), + "attack" => GetCmdDescTranslated(), + "use" => GetCmdDescTranslated(), + "list" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int GetFullEntityList(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetEntityHandlingEnabled()) + return r.SetAndReturn(Status.FailNeedEntity); + + Dictionary entities = handler.GetEntities(); + StringBuilder response = new(); + response.AppendLine(Translations.cmd_entityCmd_entities); + foreach (var entity2 in entities) + response.AppendLine(GetEntityInfoShort(entity2.Value)); + response.Append(GetCmdDescTranslated()); + handler.Log.Info(response.ToString()); + + return r.SetAndReturn(Status.Done); + } + + private int GetClosetEntityList(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetEntityHandlingEnabled()) + return r.SetAndReturn(Status.FailNeedEntity); + + if (TryGetClosetEntity(handler.GetEntities(), handler.GetCurrentLocation(), null, out Entity? closest)) + { + handler.Log.Info(GetEntityInfoDetailed(handler, closest)); + return r.SetAndReturn(Status.Done); + } + else + return r.SetAndReturn(Status.Fail, Translations.cmd_entityCmd_not_found); + } + + private int OperateWithId(CmdResult r, int entityID, ActionType action) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetEntityHandlingEnabled()) + return r.SetAndReturn(Status.FailNeedEntity); + + if (handler.GetEntities().TryGetValue(entityID, out Entity? entity)) + { + handler.Log.Info(InteractionWithEntity(handler, entity, action)); + return r.SetAndReturn(Status.Done); + } + else + return r.SetAndReturn(Status.Fail, Translations.cmd_entityCmd_not_found); + } + + private int OperateWithType(CmdResult r, bool near, EntityType entityType, ActionType action) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetEntityHandlingEnabled()) + return r.SetAndReturn(Status.FailNeedEntity); + + if (near) + { + if (TryGetClosetEntity(handler.GetEntities(), handler.GetCurrentLocation(), entityType, out Entity? closest)) + { + handler.Log.Info(InteractionWithEntity(handler, closest, action)); + return r.SetAndReturn(Status.Done); + } + else + return r.SetAndReturn(Status.Fail, Translations.cmd_entityCmd_not_found); + } + else + { + if (action == ActionType.Attack || action == ActionType.Use) + { + string actionst = Translations.cmd_entityCmd_attacked; + int actioncount = 0; + foreach (var entity2 in handler.GetEntities()) + { + if (entity2.Value.Type == entityType) + { + if (action == ActionType.Attack) + { + handler.InteractEntity(entity2.Key, InteractType.Attack); + actionst = Translations.cmd_entityCmd_attacked; + } + else if (action == ActionType.Use) + { + handler.InteractEntity(entity2.Key, InteractType.Interact); + actionst = Translations.cmd_entityCmd_used; + } + actioncount++; + } + } + handler.Log.Info(actioncount + " " + actionst); + return r.SetAndReturn(Status.Done); + } + else + { + StringBuilder response = new(); + response.AppendLine(Translations.cmd_entityCmd_entities); + foreach (var entity2 in handler.GetEntities()) + { + if (entity2.Value.Type == entityType) + { + response.AppendLine(GetEntityInfoShort(entity2.Value)); + } + } + response.Append(GetCmdDescTranslated()); + handler.Log.Info(response.ToString()); + return r.SetAndReturn(Status.Done); + } + } + } + private static string GetEntityInfoShort(Entity entity) { int id = entity.ID; @@ -122,122 +306,21 @@ namespace MinecraftClient.Commands return find; } - private static string InteractionWithEntity(McClient handler, Entity entity, string action) + private static string InteractionWithEntity(McClient handler, Entity entity, ActionType action) { switch (action) { - case "attack": + case ActionType.Attack: handler.InteractEntity(entity.ID, InteractType.Attack); return Translations.cmd_entityCmd_attacked; - case "use": + case ActionType.Use: handler.InteractEntity(entity.ID, InteractType.Interact); return Translations.cmd_entityCmd_used; - default: + case ActionType.List: return GetEntityInfoDetailed(handler, entity); + default: + goto case ActionType.List; } } - - public override string Run(McClient handler, string command, Dictionary? localVars) - { - if (handler.GetEntityHandlingEnabled()) - { - string[] args = GetArgs(command); - if (args.Length > 0) - { - if (int.TryParse(args[0], NumberStyles.Any, CultureInfo.CurrentCulture, out int entityID)) - { - if (handler.GetEntities().TryGetValue(entityID, out Entity? entity)) - return InteractionWithEntity(handler, entity, args.Length > 1 ? args[1].ToLower() : "list"); - else - return Translations.cmd_entityCmd_not_found; - } - else if (Enum.TryParse(args[0], true, out EntityType interacttype)) - { - string action = args.Length > 1 - ? args[1].ToLower() - : "list"; - if (action == "attack" || action == "use") - { - string actionst = Translations.cmd_entityCmd_attacked; - int actioncount = 0; - foreach (var entity2 in handler.GetEntities()) - { - if (entity2.Value.Type == interacttype) - { - if (action == "attack") - { - handler.InteractEntity(entity2.Key, InteractType.Attack); - actionst = Translations.cmd_entityCmd_attacked; - actioncount++; - } - else if (action == "use") - { - handler.InteractEntity(entity2.Key, InteractType.Interact); - actionst = Translations.cmd_entityCmd_used; - actioncount++; - } - else return GetCmdDescTranslated(); - } - } - return actioncount + " " + actionst; - } - - StringBuilder response = new(); - response.AppendLine(Translations.cmd_entityCmd_entities); - foreach (var entity2 in handler.GetEntities()) - { - if (entity2.Value.Type == interacttype) - { - response.AppendLine(GetEntityInfoShort(entity2.Value)); - } - } - response.Append(GetCmdDescTranslated()); - return response.ToString(); - } - else if (args[0] == "near") - { - if (args.Length > 1) - { - if (Enum.TryParse(args[1], true, out EntityType entityType)) - { - if (TryGetClosetEntity(handler.GetEntities(), handler.GetCurrentLocation(), entityType, out Entity? closest)) - return InteractionWithEntity(handler, closest, args.Length > 2 ? args[2].ToLower() : "list"); - else - return Translations.cmd_entityCmd_not_found; - } - else if (int.TryParse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture, out int entityID2)) - { - if (handler.GetEntities().TryGetValue(entityID2, out Entity? entity)) - return InteractionWithEntity(handler, entity, args.Length > 1 ? args[1].ToLower() : "list"); - else - return Translations.cmd_entityCmd_not_found; - } - else return GetCmdDescTranslated(); - } - else - { - if (TryGetClosetEntity(handler.GetEntities(), handler.GetCurrentLocation(), null, out Entity? closest)) - return GetEntityInfoDetailed(handler, closest); - else - return Translations.cmd_entityCmd_not_found; - } - } - else return GetCmdDescTranslated(); - } - else - { - Dictionary entities = handler.GetEntities(); - StringBuilder response = new(); - response.AppendLine(Translations.cmd_entityCmd_entities); - foreach (var entity2 in entities) - { - response.AppendLine(GetEntityInfoShort(entity2.Value)); - } - response.Append(GetCmdDescTranslated()); - return response.ToString(); - } - } - else return Translations.extra_entity_required; - } } -} +} \ No newline at end of file diff --git a/MinecraftClient/Commands/ExecIf.cs b/MinecraftClient/Commands/ExecIf.cs index 8cf8acd5..1e441794 100644 --- a/MinecraftClient/Commands/ExecIf.cs +++ b/MinecraftClient/Commands/ExecIf.cs @@ -1,94 +1,95 @@ using System; using System.Collections.Generic; -using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; using DynamicExpresso; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { class ExecIf : Command { public override string CmdName { get { return "execif"; } } - public override string CmdUsage { get { return "execif ---> "; } } + public override string CmdUsage { get { return "execif \"\" \"\""; } } public override string CmdDesc { get { return Translations.cmd_execif_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) - { - string commandsString = GetArg(command); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); - if (!commandsString.Contains("--->")) - return GetCmdDescTranslated(); - - string[] parts = commandsString.Split("--->", StringSplitOptions.TrimEntries) - .ToList() - .ConvertAll(command => command.Trim()) - .ToArray(); - - if (parts.Length == 0) - return GetCmdDescTranslated(); - - string expressionText = parts[0]; - string resultCommand = parts[1]; - - try - { - var interpreter = new Interpreter(); - interpreter.SetVariable("MCC", handler); - - foreach (KeyValuePair entry in Settings.Config.AppVar.GetVariables()) - interpreter.SetVariable(entry.Key, entry.Value); - - var result = interpreter.Eval(expressionText); - - bool shouldExec = result; - - /*if (result is bool) - shouldExec = (bool)result; - else if (result is string) - shouldExec = !string.IsNullOrEmpty((string)result) && ((string)result).Trim().Contains("true", StringComparison.OrdinalIgnoreCase); - else if (result is int) - shouldExec = (int)result > 0; - else if (result is double) - shouldExec = (double)result > 0; - else if (result is float) - shouldExec = (float)result > 0; - else if (result is Int16) - shouldExec = (Int16)result > 0; - else if (result is Int32) - shouldExec = (Int32)result > 0; - else if (result is Int64) - shouldExec = (Int64)result > 0; - */ - - handler.Log.Debug("[Execif] Result Type: " + result.GetType().Name); - - if (shouldExec) - { - string? output = ""; - handler.PerformInternalCommand(resultCommand, ref output); - - if (string.IsNullOrEmpty(output)) - handler.Log.Debug(string.Format(Translations.cmd_execif_executed_no_output, expressionText, resultCommand)); - else - handler.Log.Debug(string.Format(Translations.cmd_execif_executed, expressionText, resultCommand, output)); - - return ""; - } - - return ""; - } - catch (Exception e) - { - handler.Log.Error(string.Format(Translations.cmd_execif_error_occured, command)); - handler.Log.Error(string.Format(Translations.cmd_execif_error, e.Message)); - return ""; - } - } - - return GetCmdDescTranslated(); + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Condition", Arguments.String()) + .Then(l => l.Argument("Command", Arguments.String()) + .Executes(r => HandleCommand(r.Source, Arguments.GetString(r, "Condition"), Arguments.GetString(r, "Command"))))) + ); } + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + private int HandleCommand(CmdResult r, string expressionText, string resultCommand) + { + McClient handler = CmdResult.currentHandler!; + try + { + var interpreter = new Interpreter(); + interpreter.SetVariable("MCC", handler); + + foreach (KeyValuePair entry in Settings.Config.AppVar.GetVariables()) + interpreter.SetVariable(entry.Key, entry.Value); + + var result = interpreter.Eval(expressionText); + + bool shouldExec = result; + + /*if (result is bool) + shouldExec = (bool)result; + else if (result is string) + shouldExec = !string.IsNullOrEmpty((string)result) && ((string)result).Trim().Contains("true", StringComparison.OrdinalIgnoreCase); + else if (result is int) + shouldExec = (int)result > 0; + else if (result is double) + shouldExec = (double)result > 0; + else if (result is float) + shouldExec = (float)result > 0; + else if (result is Int16) + shouldExec = (Int16)result > 0; + else if (result is Int32) + shouldExec = (Int32)result > 0; + else if (result is Int64) + shouldExec = (Int64)result > 0; + */ + + handler.Log.Debug("[Execif] Result Type: " + result.GetType().Name); + + if (shouldExec) + { + CmdResult output = new(); + handler.PerformInternalCommand(resultCommand, ref output); + + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_execif_executed, expressionText, resultCommand, output)); + } + else + { + return r.SetAndReturn(CmdResult.Status.Done); + } + } + catch (Exception e) + { + handler.Log.Error(string.Format(Translations.cmd_execif_error, e.Message)); + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_execif_error_occured, expressionText + " ---> " + resultCommand)); + } + } } } diff --git a/MinecraftClient/Commands/ExecMulti.cs b/MinecraftClient/Commands/ExecMulti.cs index da034470..98c2fe7c 100644 --- a/MinecraftClient/Commands/ExecMulti.cs +++ b/MinecraftClient/Commands/ExecMulti.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -10,38 +13,48 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "execmulti -> -> -> ..."; } } public override string CmdDesc { get { return Translations.cmd_execmulti_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Commands", Arguments.GreedyString()) + .Executes(r => HandleCommand(r.Source, Arguments.GetString(r, "Commands")))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string commandsString = GetArg(command); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (commandsString.Contains("execmulti", StringComparison.OrdinalIgnoreCase) || commandsString.Contains("execif", StringComparison.OrdinalIgnoreCase)) - return Translations.cmd_execmulti_prevent; + private int HandleCommand(CmdResult r, string commandsString) + { + McClient handler = CmdResult.currentHandler!; + if (commandsString.Contains("execmulti", StringComparison.OrdinalIgnoreCase) || commandsString.Contains("execif", StringComparison.OrdinalIgnoreCase)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_execmulti_prevent); - IEnumerable commands = commandsString.Split("->", StringSplitOptions.TrimEntries) - .ToList() - .FindAll(command => !string.IsNullOrEmpty(command)); + IEnumerable commands = commandsString.Split("->", StringSplitOptions.TrimEntries) + .ToList() + .FindAll(command => !string.IsNullOrEmpty(command)); - foreach (string cmd in commands) - { - string? output = ""; - handler.PerformInternalCommand(cmd, ref output); - - string log = string.Format( - Translations.cmd_execmulti_executed, cmd, - string.IsNullOrEmpty(output) ? Translations.cmd_execmulti_no_result : string.Format(Translations.cmd_execmulti_result, output)); - - if (output != null && output.Contains("unknown command", StringComparison.OrdinalIgnoreCase)) - handler.Log.Error(log); - else - handler.Log.Info(log); - } - - return ""; + foreach (string cmd in commands) + { + CmdResult output = new(); + handler.PerformInternalCommand(cmd, ref output); + handler.Log.Info(string.Format(Translations.cmd_execmulti_executed, cmd, string.Format(Translations.cmd_execmulti_result, output))); } - return GetCmdDescTranslated(); + return r.SetAndReturn(CmdResult.Status.Done); } } } diff --git a/MinecraftClient/Commands/Exit.cs b/MinecraftClient/Commands/Exit.cs index be1a894b..d2ac389e 100644 --- a/MinecraftClient/Commands/Exit.cs +++ b/MinecraftClient/Commands/Exit.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,15 +10,49 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "exit"; } } public override string CmdDesc { get { return Translations.cmd_exit_desc; } } - public override string Run(McClient? handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - Program.Exit(); - return ""; + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + var exit = dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoExit(r.Source, 0)) + .Then(l => l.Argument("ExitCode", Arguments.Integer()) + .Executes(r => DoExit(r.Source, Arguments.GetInteger(r, "ExitCode")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + + dispatcher.Register(l => l.Literal("quit") + .Executes(r => DoExit(r.Source, 0)) + .Redirect(exit) + ); } - public override IEnumerable GetCMDAliases() + private int GetUsage(CmdResult r, string? cmd) { - return new string[] { "quit" }; + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoExit(CmdResult r, int code = 0) + { + Program.Exit(code); + return r.SetAndReturn(CmdResult.Status.Done); + } + + internal static string DoExit(string command) + { + Program.Exit(); + return string.Empty; } } } diff --git a/MinecraftClient/Commands/Health.cs b/MinecraftClient/Commands/Health.cs index 9632d3f1..21df0efd 100644 --- a/MinecraftClient/Commands/Health.cs +++ b/MinecraftClient/Commands/Health.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,9 +10,36 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "health"; } } public override string CmdDesc { get { return Translations.cmd_health_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - return string.Format(Translations.cmd_health_response, handler.GetHealth(), handler.GetSaturation(), handler.GetLevel(), handler.GetTotalExperience()); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => LogHealth(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int LogHealth(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_health_response, handler.GetHealth(), handler.GetSaturation(), handler.GetLevel(), handler.GetTotalExperience())); } } } diff --git a/MinecraftClient/Commands/Help.cs b/MinecraftClient/Commands/Help.cs new file mode 100644 index 00000000..18dbaf3e --- /dev/null +++ b/MinecraftClient/Commands/Help.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; + +namespace MinecraftClient.Commands +{ + internal class Help : Command + { + public override string CmdName => throw new NotImplementedException(); + public override string CmdDesc => throw new NotImplementedException(); + public override string CmdUsage => throw new NotImplementedException(); + + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => + l.Literal("help") + .Executes(r => LogHelp(r.Source, dispatcher)) + ); + } + + private int LogHelp(CmdResult r, CommandDispatcher dispatcher) + { + McClient handler = CmdResult.currentHandler!; + var usage = dispatcher.GetSmartUsage(dispatcher.GetRoot(), CmdResult.Empty); + StringBuilder sb = new(); + foreach (var item in usage) + sb.AppendLine(Settings.Config.Main.Advanced.InternalCmdChar.ToChar() + item.Value); + handler.Log.Info(string.Format(Translations.icmd_list, sb.ToString(), Settings.Config.Main.Advanced.InternalCmdChar.ToChar())); + return r.SetAndReturn(CmdResult.Status.Done); + } + } +} diff --git a/MinecraftClient/Commands/Inventory.cs b/MinecraftClient/Commands/Inventory.cs index 1a616ba9..9b8325ea 100644 --- a/MinecraftClient/Commands/Inventory.cs +++ b/MinecraftClient/Commands/Inventory.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Inventory; namespace MinecraftClient.Commands @@ -13,266 +15,384 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return GetBasicUsage(); } } public override string CmdDesc { get { return Translations.cmd_inventory_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (handler.GetInventoryEnabled()) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("list") + .Executes(r => GetUsage(r.Source, "list"))) + .Then(l => l.Literal("close") + .Executes(r => GetUsage(r.Source, "close"))) + .Then(l => l.Literal("click") + .Executes(r => GetUsage(r.Source, "click"))) + .Then(l => l.Literal("drop") + .Executes(r => GetUsage(r.Source, "drop"))) + .Then(l => l.Literal("creativegive") + .Executes(r => GetUsage(r.Source, "creativegive"))) + .Then(l => l.Literal("creativedelete") + .Executes(r => GetUsage(r.Source, "creativedelete"))) + .Then(l => l.Literal("inventories") + .Executes(r => GetUsage(r.Source, "inventories"))) + .Then(l => l.Literal("search") + .Executes(r => GetUsage(r.Source, "search"))) + .Then(l => l.Literal("help") + .Executes(r => GetUsage(r.Source, "help"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => ListAllInventories(r.Source)) + .Then(l => l.Literal("creativegive") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Then(l => l.Argument("ItemType", MccArguments.ItemType()) + .Then(l => l.Argument("Count", Arguments.Integer(min: 1)) + .Executes(r => DoCreativeGive(r.Source, Arguments.GetInteger(r, "Slot"), MccArguments.GetItemType(r, "ItemType"), Arguments.GetInteger(r, "Count"))))))) + .Then(l => l.Literal("creativedelete") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoCreativeDelete(r.Source, Arguments.GetInteger(r, "Slot"))))) + .Then(l => l.Literal("inventories") + .Executes(r => ListAvailableInventories(r.Source))) + .Then(l => l.Literal("search") + .Then(l => l.Argument("ItemType", MccArguments.ItemType()) + .Executes(r => SearchItem(r.Source, MccArguments.GetItemType(r, "ItemType"), null)) + .Then(l => l.Argument("Count", Arguments.Integer(0, 64)) + .Executes(r => SearchItem(r.Source, MccArguments.GetItemType(r, "ItemType"), Arguments.GetInteger(r, "Count")))))) + .Then(l => l.Argument("InventoryId", MccArguments.InventoryId()) + .Then(l => l.Literal("close") + .Executes(r => DoCloseAction(r.Source, Arguments.GetInteger(r, "InventoryId")))) + .Then(l => l.Literal("list") + .Executes(r => DoListAction(r.Source, Arguments.GetInteger(r, "InventoryId")))) + .Then(l => l.Literal("click") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoClickAction(r.Source, Arguments.GetInteger(r, "InventoryId"), Arguments.GetInteger(r, "Slot"), WindowActionType.LeftClick)) + .Then(l => l.Argument("Action", MccArguments.InventoryAction()) + .Executes(r => DoClickAction(r.Source, Arguments.GetInteger(r, "InventoryId"), Arguments.GetInteger(r, "Slot"), MccArguments.GetInventoryAction(r, "Action")))))) + .Then(l => l.Literal("drop") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoDropAction(r.Source, Arguments.GetInteger(r, "InventoryId"), Arguments.GetInteger(r, "Slot"), WindowActionType.DropItem)) + .Then(l => l.Literal("all") + .Executes(r => DoDropAction(r.Source, Arguments.GetInteger(r, "InventoryId"), Arguments.GetInteger(r, "Slot"), WindowActionType.DropItemStack)))))) + .Then(l => l.Literal("player") + .Then(l => l.Literal("list") + .Executes(r => DoListAction(r.Source, inventoryId: 0))) + .Then(l => l.Literal("click") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoClickAction(r.Source, inventoryId: 0, Arguments.GetInteger(r, "Slot"), WindowActionType.LeftClick)) + .Then(l => l.Argument("Action", MccArguments.InventoryAction()) + .Executes(r => DoClickAction(r.Source, inventoryId: 0, Arguments.GetInteger(r, "Slot"), MccArguments.GetInventoryAction(r, "Action")))))) + .Then(l => l.Literal("drop") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoDropAction(r.Source, inventoryId: 0, Arguments.GetInteger(r, "Slot"), WindowActionType.DropItem)) + .Then(l => l.Literal("all") + .Executes(r => DoDropAction(r.Source, inventoryId: 0, Arguments.GetInteger(r, "Slot"), WindowActionType.DropItemStack)))))) + .Then(l => l.Literal("container") + .Then(l => l.Literal("close") + .Executes(r => DoCloseAction(r.Source, inventoryId: null))) + .Then(l => l.Literal("list") + .Executes(r => DoListAction(r.Source, inventoryId: null))) + .Then(l => l.Literal("click") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoClickAction(r.Source, inventoryId: null, Arguments.GetInteger(r, "Slot"), WindowActionType.LeftClick)) + .Then(l => l.Argument("Action", MccArguments.InventoryAction()) + .Executes(r => DoClickAction(r.Source, inventoryId: null, Arguments.GetInteger(r, "Slot"), MccArguments.GetInventoryAction(r, "Action")))))) + .Then(l => l.Literal("drop") + .Then(l => l.Argument("Slot", MccArguments.InventorySlot()) + .Executes(r => DoDropAction(r.Source, inventoryId: null, Arguments.GetInteger(r, "Slot"), WindowActionType.DropItem)) + .Then(l => l.Literal("all") + .Executes(r => DoDropAction(r.Source, inventoryId: null, Arguments.GetInteger(r, "Slot"), WindowActionType.DropItemStack)))))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + string usageStr = ' ' + Translations.cmd_inventory_help_usage + ": "; + return r.SetAndReturn(cmd switch { - string[] args = GetArgs(command); - if (args.Length >= 1) - { - int inventoryId; - if (args[0].ToLower() == "creativegive") - { - if (args.Length >= 4) - { - if (!int.TryParse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture, out int slot)) - return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + "list" => Translations.cmd_inventory_help_list + usageStr + "/inventory > list", + "close" => Translations.cmd_inventory_help_close + usageStr + "/inventory > close", + "click" => Translations.cmd_inventory_help_click + usageStr + "/inventory > click [left|right|middle|shift]\nDefault is left click", + "drop" => Translations.cmd_inventory_help_drop + usageStr + "/inventory > drop [all]\nAll means drop full stack", + "creativegive" => Translations.cmd_inventory_help_creativegive + usageStr + "/inventory creativegive ", + "creativedelete" => Translations.cmd_inventory_help_creativedelete + usageStr + "/inventory creativedelete ", + "inventories" => Translations.cmd_inventory_help_inventories + usageStr + "/inventory inventories", + "search" => Translations.cmd_inventory_help_search + usageStr + "/inventory search [count]", + "help" => GetCmdDescTranslated(), + "action" => Translations.cmd_inventory_help_unknown + GetAvailableActions(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (Enum.TryParse(args[2], true, out ItemType itemType)) - { - if (handler.GetGamemode() == 1) - { - if (!int.TryParse(args[3], NumberStyles.Any, CultureInfo.CurrentCulture, out int count)) - return GetCmdDescTranslated(); + private int GetMaximumInventoryId(McClient handler) + { + List availableIds = handler.GetInventories().Keys.ToList(); + return availableIds.Max(); // use foreground container + } - if (handler.DoCreativeGive(slot, itemType, count, null)) - return string.Format(Translations.cmd_inventory_creative_done, itemType, count, slot); - else - return Translations.cmd_inventory_creative_fail; - } - else - return Translations.cmd_inventory_need_creative; - } - else - return GetCmdDescTranslated(); - } - else - return GetCmdDescTranslated(); - } - else if (args[0].ToLower() == "creativedelete") - { - if (args.Length >= 2) - { - if (!int.TryParse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture, out int slot)) - return GetCmdDescTranslated(); + private int ListAllInventories(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + { + handler.Log.Info(Translations.extra_inventory_required); + return -1; + } + StringBuilder response = new(); + response.Append(Translations.cmd_inventory_inventories).Append(":\n"); + foreach ((int invId, Container inv) in handler.GetInventories()) + response.AppendLine(String.Format(" #{0}: {1}§8", invId, inv.Title)); + response.Append(CmdUsage); + handler.Log.Info(response.ToString()); + return r.SetAndReturn(CmdResult.Status.Done); + } - if (handler.GetGamemode() == 1) - { - if (handler.DoCreativeGive(slot, ItemType.Null, 0, null)) - return string.Format(Translations.cmd_inventory_creative_delete, slot); - else - return Translations.cmd_inventory_creative_fail; - } - else - return Translations.cmd_inventory_need_creative; - } - else - return GetCmdDescTranslated(); - } - else if (args[0].ToLower().StartsWith("p")) - { - // player inventory is always ID 0 - inventoryId = 0; - } - else if (args[0].ToLower().StartsWith("c")) - { - List availableIds = handler.GetInventories().Keys.ToList(); - availableIds.Remove(0); // remove player inventory ID from list - if (availableIds.Count > 0) - inventoryId = availableIds.Max(); // use foreground container - else - return Translations.cmd_inventory_container_not_found; - } - else if (args[0].ToLower().StartsWith("inventories") || args[0].ToLower().StartsWith("i")) - { - Dictionary inventories = handler.GetInventories(); - List availableIds = inventories.Keys.ToList(); - StringBuilder response = new(); - response.AppendLine(Translations.cmd_inventory_inventories_available); + private int DoCreativeGive(CmdResult r, int slot, ItemType itemType, int count) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); - foreach (int id in availableIds) - response.AppendLine(String.Format(" #{0} - {1}§8", id, inventories[id].Title)); - - return response.ToString(); - } - else if (args[0].ToLower().StartsWith("search") || args[0].ToLower().StartsWith("s")) - { - if (args.Length < 2) - return GetCmdDescTranslated(); - - if (!Enum.TryParse(args[1], true, out ItemType parsedItemType)) - return GetCmdDescTranslated(); - - bool shouldUseItemCount = args.Length >= 3; - int itemCount = 0; - - if (shouldUseItemCount && !int.TryParse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture, out itemCount)) - return GetCmdDescTranslated(); - - Dictionary inventories = handler.GetInventories(); - Dictionary> foundItems = new(); - - List availableInventories = inventories.Values.ToList(); - - availableInventories.ForEach(inventory => - { - inventory.Items.Values - .ToList() - .FindAll(item => item.Type == parsedItemType && (!shouldUseItemCount || item.Count == itemCount)) - .ForEach(item => - { - if (!foundItems.ContainsKey(inventory.ID)) - { - foundItems.Add(inventory.ID, new List() { item }); - return; - } - - List invItems = foundItems[inventory.ID]; - invItems.Add(item); - foundItems.Remove(inventory.ID); - foundItems.Add(inventory.ID, invItems); - }); - }); - - if (foundItems.Count == 0) - return Translations.cmd_inventory_no_found_items; - - StringBuilder response = new(); - - response.AppendLine(Translations.cmd_inventory_found_items + ":"); - - foreach ((int invId, List itemsList) in new SortedDictionary>(foundItems)) - { - if (itemsList.Count > 0) - { - response.AppendLine(String.Format("{0} (#{1}):", inventories[invId].Title, invId)); - - foreach (Item item in itemsList) - response.AppendLine(String.Format("\t- {0}", item.ToFullString())); - - response.AppendLine(" "); - } - } - - return response.ToString(); - } - else if (args[0].ToLower() == "help") - { - if (args.Length >= 2) - return GetSubCommandHelp(args[1]); - else - return GetHelp(); - } - else if (!int.TryParse(args[0], NumberStyles.Any, CultureInfo.CurrentCulture, out inventoryId)) - return GetCmdDescTranslated(); - - Container? inventory = handler.GetInventory(inventoryId); - if (inventory == null) - return string.Format(Translations.cmd_inventory_not_exist, inventoryId); - - string action = args.Length > 1 ? args[1].ToLower() : "list"; - if (action == "close") - { - if (handler.CloseInventory(inventoryId)) - return string.Format(Translations.cmd_inventory_close, inventoryId); - else - return string.Format(Translations.cmd_inventory_close_fail, inventoryId); - } - else if (action == "list") - { - StringBuilder response = new(); - response.Append(Translations.cmd_inventory_inventory); - response.AppendLine(String.Format(" #{0} - {1}§8", inventoryId, inventory.Title)); - - string? asciiArt = inventory.Type.GetAsciiArt(); - if (asciiArt != null && Settings.Config.Main.Advanced.ShowInventoryLayout) - response.AppendLine(asciiArt); - - int selectedHotbar = handler.GetCurrentSlot() + 1; - foreach ((int itemId, Item item) in new SortedDictionary(inventory.Items)) - { - bool isHotbar = inventory.IsHotbar(itemId, out int hotbar); - string hotbarString = isHotbar ? (hotbar + 1).ToString() : " "; - if ((hotbar + 1) == selectedHotbar) - hotbarString = ">" + hotbarString; - response.AppendLine(String.Format("{0,2} | #{1,-2}: {2}", hotbarString, itemId, item.ToFullString())); - } - - if (inventoryId == 0) - response.AppendLine(string.Format(Translations.cmd_inventory_hotbar, (handler.GetCurrentSlot() + 1))); - - response.Remove(response.Length - 1, 1); // Remove last '\n' - return response.ToString(); - } - else if (action == "click" && args.Length >= 3) - { - if (!int.TryParse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture, out int slot)) - return GetCmdDescTranslated(); - - WindowActionType actionType = WindowActionType.LeftClick; - string keyName = Translations.cmd_inventory_left; - if (args.Length >= 4) - { - string b = args[3]; - if (b.ToLower()[0] == 'r') - (actionType, keyName) = (WindowActionType.RightClick, Translations.cmd_inventory_right); - else if (b.ToLower()[0] == 'm') - (actionType, keyName) = (WindowActionType.MiddleClick, Translations.cmd_inventory_middle); - } - - handler.DoWindowAction(inventoryId, slot, actionType); - return string.Format(Translations.cmd_inventory_clicking, keyName, slot, inventoryId); - } - else if (action == "shiftclick" && args.Length >= 3) - { - if (!int.TryParse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture, out int slot)) - return GetCmdDescTranslated(); - - if (!handler.DoWindowAction(inventoryId, slot, WindowActionType.ShiftClick)) - return Translations.cmd_inventory_shiftclick_fail; - - return string.Format(Translations.cmd_inventory_shiftclick, slot, inventoryId); - } - else if (action == "drop" && args.Length >= 3) - { - if (!int.TryParse(args[2], NumberStyles.Any, CultureInfo.CurrentCulture, out int slot)) - return GetCmdDescTranslated(); - - // check item exist - if (!inventory.Items.ContainsKey(slot)) - return string.Format(Translations.cmd_inventory_no_item, slot); - - WindowActionType actionType = WindowActionType.DropItem; - if (args.Length >= 4 && args[3].ToLower() == "all") - actionType = WindowActionType.DropItemStack; - - if (handler.DoWindowAction(inventoryId, slot, actionType)) - { - if (actionType == WindowActionType.DropItemStack) - return string.Format(Translations.cmd_inventory_drop_stack, slot); - else - return string.Format(Translations.cmd_inventory_drop, slot); - } - else - return "Failed"; - } - else - return GetCmdDescTranslated(); - } + if (handler.GetGamemode() == 1) + { + if (handler.DoCreativeGive(slot, itemType, count, null)) + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_inventory_creative_done, itemType, count, slot)); else - { - StringBuilder response = new(); - response.Append(Translations.cmd_inventory_inventories).Append(":\n"); - foreach ((int invId, Container inv) in handler.GetInventories()) - response.AppendLine(String.Format(" #{0}: {1}§8", invId, inv.Title)); - response.Append(CmdUsage); - return response.ToString(); - } + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_creative_fail); } else - return Translations.extra_inventory_required; + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_need_creative); + } } + private int DoCreativeDelete(CmdResult r, int slot) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + if (handler.GetGamemode() == 1) + { + if (handler.DoCreativeGive(slot, ItemType.Null, 0, null)) + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_inventory_creative_delete, slot)); + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_creative_fail); + } + else + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_need_creative); + } + } + + private int ListAvailableInventories(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + Dictionary inventories = handler.GetInventories(); + List availableIds = inventories.Keys.ToList(); + StringBuilder response = new(); + response.AppendLine(Translations.cmd_inventory_inventories_available); + + foreach (int id in availableIds) + response.AppendLine(String.Format(" #{0} - {1}§8", id, inventories[id].Title)); + + handler.Log.Info(response.ToString()); + return r.SetAndReturn(CmdResult.Status.Done); + } + + private int SearchItem(CmdResult r, ItemType itemType, int? itemCount) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + Dictionary inventories = handler.GetInventories(); + Dictionary> foundItems = new(); + + List availableInventories = inventories.Values.ToList(); + + availableInventories.ForEach(inventory => + { + inventory.Items.Values + .ToList() + .FindAll(item => item.Type == itemType && (!itemCount.HasValue || item.Count == itemCount)) + .ForEach(item => + { + if (!foundItems.ContainsKey(inventory.ID)) + { + foundItems.Add(inventory.ID, new List() { item }); + return; + } + + List invItems = foundItems[inventory.ID]; + invItems.Add(item); + foundItems.Remove(inventory.ID); + foundItems.Add(inventory.ID, invItems); + }); + }); + + if (foundItems.Count == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_no_found_items); + + StringBuilder response = new(); + + response.AppendLine(Translations.cmd_inventory_found_items + ":"); + + foreach ((int invId, List itemsList) in new SortedDictionary>(foundItems)) + { + if (itemsList.Count > 0) + { + response.AppendLine(String.Format("{0} (#{1}):", inventories[invId].Title, invId)); + + foreach (Item item in itemsList) + response.AppendLine(String.Format("\t- {0}", item.ToFullString())); + + response.AppendLine(" "); + } + } + + handler.Log.Info(response.ToString()); + return r.SetAndReturn(CmdResult.Status.Done); + } + + private int DoCloseAction(CmdResult r, int? inventoryId) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + if (!inventoryId.HasValue) + { + inventoryId = GetMaximumInventoryId(handler); + if (inventoryId == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_container_not_found); + } + + Container? inventory = handler.GetInventory(inventoryId.Value); + if (inventory == null) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_not_exist, inventoryId)); + + if (handler.CloseInventory(inventoryId.Value)) + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_inventory_close, inventoryId)); + else + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_close_fail, inventoryId)); + } + + private int DoListAction(CmdResult r, int? inventoryId) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + if (!inventoryId.HasValue) + { + inventoryId = GetMaximumInventoryId(handler); + if (inventoryId == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_container_not_found); + } + + Container? inventory = handler.GetInventory(inventoryId.Value); + if (inventory == null) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_not_exist, inventoryId)); + + StringBuilder response = new(); + response.Append(Translations.cmd_inventory_inventory); + response.AppendLine(String.Format(" #{0} - {1}§8", inventoryId, inventory.Title)); + + string? asciiArt = inventory.Type.GetAsciiArt(); + if (asciiArt != null && Settings.Config.Main.Advanced.ShowInventoryLayout) + response.AppendLine(asciiArt); + + int selectedHotbar = handler.GetCurrentSlot() + 1; + foreach ((int itemId, Item item) in new SortedDictionary(inventory.Items)) + { + bool isHotbar = inventory.IsHotbar(itemId, out int hotbar); + string hotbarString = isHotbar ? (hotbar + 1).ToString() : " "; + if ((hotbar + 1) == selectedHotbar) + hotbarString = ">" + hotbarString; + response.AppendLine(String.Format("{0,2} | #{1,-2}: {2}", hotbarString, itemId, item.ToFullString())); + } + + if (inventoryId == 0) + response.AppendLine(string.Format(Translations.cmd_inventory_hotbar, (handler.GetCurrentSlot() + 1))); + + response.Remove(response.Length - 1, 1); // Remove last '\n' + handler.Log.Info(response.ToString()); + return r.SetAndReturn(CmdResult.Status.Done); + } + + private int DoClickAction(CmdResult r, int? inventoryId, int slot, WindowActionType actionType) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + if (!inventoryId.HasValue) + { + inventoryId = GetMaximumInventoryId(handler); + if (inventoryId == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_container_not_found); + } + + Container? inventory = handler.GetInventory(inventoryId.Value); + if (inventory == null) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_not_exist, inventoryId)); + + string keyName = actionType switch + { + WindowActionType.LeftClick => Translations.cmd_inventory_left, + WindowActionType.RightClick => Translations.cmd_inventory_right, + WindowActionType.MiddleClick => Translations.cmd_inventory_middle, + WindowActionType.ShiftClick => Translations.cmd_inventory_shiftclick, + _ => "unknown", + }; + + handler.Log.Info(string.Format(Translations.cmd_inventory_clicking, keyName, slot, inventoryId)); + return r.SetAndReturn(handler.DoWindowAction(inventoryId.Value, slot, actionType)); + } + + private int DoDropAction(CmdResult r, int? inventoryId, int slot, WindowActionType actionType) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + + if (!inventoryId.HasValue) + { + inventoryId = GetMaximumInventoryId(handler); + if (inventoryId == 0) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_inventory_container_not_found); + } + + Container? inventory = handler.GetInventory(inventoryId.Value); + if (inventory == null) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_not_exist, inventoryId)); + + // check item exist + if (!inventory.Items.ContainsKey(slot)) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_inventory_no_item, slot)); + + if (handler.DoWindowAction(inventoryId.Value, slot, actionType)) + { + if (actionType == WindowActionType.DropItemStack) + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_inventory_drop_stack, slot)); + else + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_inventory_drop, slot)); + } + else + { + return r.SetAndReturn(CmdResult.Status.Fail, "Drop Failed"); + } + } + + #region Methods for commands help private static string GetAvailableActions() @@ -285,31 +405,6 @@ namespace MinecraftClient.Commands return Translations.cmd_inventory_help_basic + ": /inventory > ."; } - private static string GetHelp() - { - return string.Format(Translations.cmd_inventory_help_help, GetAvailableActions()); - } - - private static string GetSubCommandHelp(string cmd) - { - string usageStr = ' ' + Translations.cmd_inventory_help_usage + ": "; - return cmd switch - { -#pragma warning disable format // @formatter:off - "list" => Translations.cmd_inventory_help_list + usageStr + "/inventory > list", - "close" => Translations.cmd_inventory_help_close + usageStr + "/inventory > close", - "click" => Translations.cmd_inventory_help_click + usageStr + "/inventory > click [left|right|middle]\nDefault is left click", - "shiftclick" => Translations.cmd_inventory_help_shiftclick + usageStr + "/inventory > shiftclick ", - "drop" => Translations.cmd_inventory_help_drop + usageStr + "/inventory > drop [all]\nAll means drop full stack", - "creativegive" => Translations.cmd_inventory_help_creativegive + usageStr + "/inventory creativegive ", - "creativedelete" => Translations.cmd_inventory_help_creativedelete + usageStr + "/inventory creativedelete ", - "inventories" => Translations.cmd_inventory_help_inventories + usageStr + "/inventory inventories", - "search" => Translations.cmd_inventory_help_search + usageStr + "/inventory search [count]", - "help" => GetHelp(), - _ => Translations.cmd_inventory_help_unknown + GetAvailableActions(), -#pragma warning restore format // @formatter:on - }; - } #endregion } } diff --git a/MinecraftClient/Commands/List.cs b/MinecraftClient/Commands/List.cs index 6678fa16..7e991f55 100644 --- a/MinecraftClient/Commands/List.cs +++ b/MinecraftClient/Commands/List.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -9,9 +11,36 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "list"; } } public override string CmdDesc { get { return Translations.cmd_list_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - return string.Format(Translations.cmd_list_players, String.Join(", ", handler.GetOnlinePlayers())); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoListPlayers(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoListPlayers(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_list_players, String.Join(", ", handler.GetOnlinePlayers()))); } } } diff --git a/MinecraftClient/Commands/Log.cs b/MinecraftClient/Commands/Log.cs index ea930d67..8cdf3ecf 100644 --- a/MinecraftClient/Commands/Log.cs +++ b/MinecraftClient/Commands/Log.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,14 +10,35 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "log "; } } public override string CmdDesc { get { return Translations.cmd_log_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("String", Arguments.GreedyString()) + .Executes(r => DoLog(r.Source, Arguments.GetString(r, "String")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - ConsoleIO.WriteLogLine(GetArg(command)); - return ""; - } - else return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoLog(CmdResult r, string command) + { + return r.SetAndReturn(CmdResult.Status.Done, command); } } } diff --git a/MinecraftClient/Commands/Look.cs b/MinecraftClient/Commands/Look.cs index f763d6ab..69f0dc2d 100644 --- a/MinecraftClient/Commands/Look.cs +++ b/MinecraftClient/Commands/Look.cs @@ -1,7 +1,9 @@ using System; -using System.Collections.Generic; -using System.Globalization; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -11,70 +13,107 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "look "; } } public override string CmdDesc { get { return Translations.cmd_look_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (handler.GetTerrainEnabled()) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("direction") + .Executes(r => GetUsage(r.Source, "direction"))) + .Then(l => l.Literal("angle") + .Executes(r => GetUsage(r.Source, "angle"))) + .Then(l => l.Literal("location") + .Executes(r => GetUsage(r.Source, "location"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => LogCurrentLooking(r.Source)) + .Then(l => l.Literal("up") + .Executes(r => LookAtDirection(r.Source, Direction.Up))) + .Then(l => l.Literal("down") + .Executes(r => LookAtDirection(r.Source, Direction.Down))) + .Then(l => l.Literal("east") + .Executes(r => LookAtDirection(r.Source, Direction.East))) + .Then(l => l.Literal("west") + .Executes(r => LookAtDirection(r.Source, Direction.West))) + .Then(l => l.Literal("north") + .Executes(r => LookAtDirection(r.Source, Direction.North))) + .Then(l => l.Literal("south") + .Executes(r => LookAtDirection(r.Source, Direction.South))) + .Then(l => l.Argument("Yaw", Arguments.Float()) + .Then(l => l.Argument("Pitch", Arguments.Float()) + .Executes(r => LookAtAngle(r.Source, Arguments.GetFloat(r, "Yaw"), Arguments.GetFloat(r, "Pitch"))))) + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => LookAtLocation(r.Source, MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] args = GetArgs(command); - if (args.Length == 0) - { - const double maxDistance = 8.0; - (bool hasBlock, Location target, Block block) = RaycastHelper.RaycastBlock(handler, maxDistance, false); - if (!hasBlock) - return string.Format(Translations.cmd_look_noinspection, maxDistance); - else - { - Location current = handler.GetCurrentLocation(), target_center = target.ToCenter(); - return string.Format(Translations.cmd_look_inspection, block.Type, target.X, target.Y, target.Z, - current.Distance(target_center), current.EyesLocation().Distance(target_center)); - } - } - else if (args.Length == 1) - { - string dirStr = GetArg(command).Trim().ToLower(); - Direction direction; - switch (dirStr) - { - case "up": direction = Direction.Up; break; - case "down": direction = Direction.Down; break; - case "east": direction = Direction.East; break; - case "west": direction = Direction.West; break; - case "north": direction = Direction.North; break; - case "south": direction = Direction.South; break; - default: return string.Format(Translations.cmd_look_unknown, dirStr); - } +#pragma warning disable format // @formatter:off + "direction" => GetCmdDescTranslated(), + "angle" => GetCmdDescTranslated(), + "location" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - handler.UpdateLocation(handler.GetCurrentLocation(), direction); - return "Looking " + dirStr; - } - else if (args.Length == 2) - { - try - { - float yaw = float.Parse(args[0], NumberStyles.Any, CultureInfo.CurrentCulture); - float pitch = float.Parse(args[1], NumberStyles.Any, CultureInfo.CurrentCulture); + private int LogCurrentLooking(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); - handler.UpdateLocation(handler.GetCurrentLocation(), yaw, pitch); - return string.Format(Translations.cmd_look_at, yaw.ToString("0.00"), pitch.ToString("0.00")); - } - catch (FormatException) { return GetCmdDescTranslated(); } - } - else if (args.Length == 3) - { - try - { - Location current = handler.GetCurrentLocation(); - Location block = Location.Parse(current, args[0], args[1], args[2]); - handler.UpdateLocation(current, block); - - return string.Format(Translations.cmd_look_block, block); - } - catch (FormatException) { return CmdUsage; } - - } - else return GetCmdDescTranslated(); + const double maxDistance = 8.0; + (bool hasBlock, Location target, Block block) = RaycastHelper.RaycastBlock(handler, maxDistance, false); + if (!hasBlock) + { + return r.SetAndReturn(Status.Fail, string.Format(Translations.cmd_look_noinspection, maxDistance)); } - else return Translations.extra_terrainandmovement_required; + else + { + Location current = handler.GetCurrentLocation(), target_center = target.ToCenter(); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_look_inspection, block.Type, target.X, target.Y, target.Z, + current.Distance(target_center), current.EyesLocation().Distance(target_center))); + } + } + + private int LookAtDirection(CmdResult r, Direction direction) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + handler.UpdateLocation(handler.GetCurrentLocation(), direction); + return r.SetAndReturn(Status.Done, "Looking " + direction.ToString()); + } + + private int LookAtAngle(CmdResult r, float yaw, float pitch) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + handler.UpdateLocation(handler.GetCurrentLocation(), yaw, pitch); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_look_at, yaw.ToString("0.00"), pitch.ToString("0.00"))); + } + + private int LookAtLocation(CmdResult r, Location location) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + handler.UpdateLocation(current, location); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_look_block, location)); } } } diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 52760ba1..fa94a06b 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -1,7 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -11,107 +13,190 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "move [-f]"; } } public override string CmdDesc { get { return Translations.cmd_move_desc + " \"-f\": " + Translations.cmd_move_desc_force; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - List args = GetArgs(command.ToLower()).ToList(); - bool takeRisk = false; + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("enable") + .Executes(r => GetUsage(r.Source, "enable"))) + .Then(l => l.Literal("gravity") + .Executes(r => GetUsage(r.Source, "gravity"))) + .Then(l => l.Literal("direction") + .Executes(r => GetUsage(r.Source, "direction"))) + .Then(l => l.Literal("center") + .Executes(r => GetUsage(r.Source, "center"))) + .Then(l => l.Literal("get") + .Executes(r => GetUsage(r.Source, "get"))) + .Then(l => l.Literal("location") + .Executes(r => GetUsage(r.Source, "location"))) + .Then(l => l.Literal("-f") + .Executes(r => GetUsage(r.Source, "-f"))) + ) + ); - if (args.Count < 1) + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Literal("on") + .Executes(r => SetMovementEnable(r.Source, enable: true))) + .Then(l => l.Literal("off") + .Executes(r => SetMovementEnable(r.Source, enable: false))) + .Then(l => l.Literal("gravity") + .Executes(r => SetGravityEnable(r.Source, enable: null)) + .Then(l => l.Literal("on") + .Executes(r => SetGravityEnable(r.Source, enable: true))) + .Then(l => l.Literal("off") + .Executes(r => SetGravityEnable(r.Source, enable: false)))) + .Then(l => l.Literal("up") + .Executes(r => MoveOnDirection(r.Source, Direction.Up, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.Up, true)))) + .Then(l => l.Literal("down") + .Executes(r => MoveOnDirection(r.Source, Direction.Down, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.Down, true)))) + .Then(l => l.Literal("east") + .Executes(r => MoveOnDirection(r.Source, Direction.East, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.East, true)))) + .Then(l => l.Literal("west") + .Executes(r => MoveOnDirection(r.Source, Direction.West, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.West, true)))) + .Then(l => l.Literal("north") + .Executes(r => MoveOnDirection(r.Source, Direction.North, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.North, true)))) + .Then(l => l.Literal("south") + .Executes(r => MoveOnDirection(r.Source, Direction.South, false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveOnDirection(r.Source, Direction.South, true)))) + .Then(l => l.Literal("center") + .Executes(r => MoveToCenter(r.Source))) + .Then(l => l.Literal("get") + .Executes(r => GetCurrentLocation(r.Source))) + .Then(l => l.Argument("location", MccArguments.Location()) + .Executes(r => MoveToLocation(r.Source, MccArguments.GetLocation(r, "location"), false)) + .Then(l => l.Literal("-f") + .Executes(r => MoveToLocation(r.Source, MccArguments.GetLocation(r, "location"), true)))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string desc = GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + "enable" => GetCmdDescTranslated(), + "gravity" => GetCmdDescTranslated(), + "direction" => GetCmdDescTranslated(), + "center" => GetCmdDescTranslated(), + "get" => GetCmdDescTranslated(), + "location" => GetCmdDescTranslated(), + "-f" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (handler.GetTerrainEnabled()) - handler.Log.Info(World.GetChunkLoadingStatus(handler.GetWorld())); - - return desc; - } - - if (args.Contains("-f")) - { - takeRisk = true; - args.Remove("-f"); - } - - if (args[0] == "on") + private int SetMovementEnable(CmdResult r, bool enable) + { + McClient handler = CmdResult.currentHandler!; + if (enable) { handler.SetTerrainEnabled(true); - return Translations.cmd_move_enable; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_move_enable); } - else if (args[0] == "off") + else { handler.SetTerrainEnabled(false); - return Translations.cmd_move_disable; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_move_disable); } - else if (args[0] == "gravity") + } + + private int SetGravityEnable(CmdResult r, bool? enable) + { + McClient handler = CmdResult.currentHandler!; + if (enable.HasValue) + Settings.InternalConfig.GravityEnabled = enable.Value; + + if (Settings.InternalConfig.GravityEnabled) + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_move_gravity_enabled); + else + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_move_gravity_disabled); + } + + private int GetCurrentLocation(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + return r.SetAndReturn(Status.Done, handler.GetCurrentLocation().ToString()); + } + + private int MoveToCenter(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + Location currentCenter = new(Math.Floor(current.X) + 0.5, current.Y, Math.Floor(current.Z) + 0.5); + handler.MoveTo(currentCenter, allowDirectTeleport: true); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_move_walk, currentCenter, current)); + } + + private int MoveOnDirection(CmdResult r, Direction direction, bool takeRisk) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location goal = Movement.Move(handler.GetCurrentLocation(), direction); + + if (!Movement.CheckChunkLoading(handler.GetWorld(), handler.GetCurrentLocation(), goal)) + return r.SetAndReturn(Status.FailChunkNotLoad, string.Format(Translations.cmd_move_chunk_not_loaded, goal.X, goal.Y, goal.Z)); + + if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) { - if (args.Count >= 2) - Settings.InternalConfig.GravityEnabled = (args[1] == "on"); - if (Settings.InternalConfig.GravityEnabled) - return Translations.cmd_move_gravity_enabled; - else return Translations.cmd_move_gravity_disabled; + if (handler.MoveTo(goal, allowUnsafe: takeRisk)) + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_move_moving, direction.ToString())); + else + return r.SetAndReturn(Status.Fail, takeRisk ? Translations.cmd_move_dir_fail : Translations.cmd_move_suggestforce); } - else if (handler.GetTerrainEnabled()) + else { - if (args.Count == 1) - { - Direction direction; - switch (args[0]) - { - case "up": direction = Direction.Up; break; - case "down": direction = Direction.Down; break; - case "east": direction = Direction.East; break; - case "west": direction = Direction.West; break; - case "north": direction = Direction.North; break; - case "south": direction = Direction.South; break; - case "center": - Location current = handler.GetCurrentLocation(); - Location currentCenter = new(Math.Floor(current.X) + 0.5, current.Y, Math.Floor(current.Z) + 0.5); - handler.MoveTo(currentCenter, allowDirectTeleport: true); - return string.Format(Translations.cmd_move_walk, currentCenter, current); - case "get": return handler.GetCurrentLocation().ToString(); - default: return string.Format(Translations.cmd_look_unknown, args[0]); - } - - Location goal = Movement.Move(handler.GetCurrentLocation(), direction); - - if (!Movement.CheckChunkLoading(handler.GetWorld(), handler.GetCurrentLocation(), goal)) - return string.Format(Translations.cmd_move_chunk_not_loaded, goal.X, goal.Y, goal.Z); - - if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) - { - if (handler.MoveTo(goal, allowUnsafe: takeRisk)) - return string.Format(Translations.cmd_move_moving, args[0]); - else - return takeRisk ? Translations.cmd_move_dir_fail : Translations.cmd_move_suggestforce; - } - else return Translations.cmd_move_dir_fail; - } - else if (args.Count == 3) - { - try - { - Location current = handler.GetCurrentLocation(), currentCenter = current.ToCenter(); - Location goal = Location.Parse(current, args[0], args[1], args[2]); - - if (!Movement.CheckChunkLoading(handler.GetWorld(), current, goal)) - return string.Format(Translations.cmd_move_chunk_not_loaded, goal.X, goal.Y, goal.Z); - - if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal)) - { - if (current.ToFloor() == goal.ToFloor()) - handler.MoveTo(goal, allowDirectTeleport: true); - else if (!handler.MoveTo(goal, allowUnsafe: takeRisk)) - return takeRisk ? string.Format(Translations.cmd_move_fail, goal) : string.Format(Translations.cmd_move_suggestforce, goal); - return string.Format(Translations.cmd_move_walk, goal, current); - } - else - return string.Format(Translations.cmd_move_suggestforce, goal); - } - catch (FormatException) { return GetCmdDescTranslated(); } - } - else return GetCmdDescTranslated(); + return r.SetAndReturn(Status.Fail, Translations.cmd_move_dir_fail); + } + } + + private int MoveToLocation(CmdResult r, Location goal, bool takeRisk) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(), currentCenter = current.ToCenter(); + goal.ToAbsolute(current); + + if (!Movement.CheckChunkLoading(handler.GetWorld(), current, goal)) + return r.SetAndReturn(Status.FailChunkNotLoad, string.Format(Translations.cmd_move_chunk_not_loaded, goal.X, goal.Y, goal.Z)); + + if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal)) + { + if (current.ToFloor() == goal.ToFloor()) + handler.MoveTo(goal, allowDirectTeleport: true); + else if (!handler.MoveTo(goal, allowUnsafe: takeRisk)) + return r.SetAndReturn(Status.Fail, takeRisk ? string.Format(Translations.cmd_move_fail, goal) : string.Format(Translations.cmd_move_suggestforce, goal)); + return r.SetAndReturn(Status.Done, string.Format(Translations.cmd_move_walk, goal, current)); + } + else + { + return r.SetAndReturn(Status.Fail, string.Format(Translations.cmd_move_suggestforce, goal)); } - else return Translations.extra_terrainandmovement_required; } } } diff --git a/MinecraftClient/Commands/Reco.cs b/MinecraftClient/Commands/Reco.cs index 53688d3e..482b4e0b 100644 --- a/MinecraftClient/Commands/Reco.cs +++ b/MinecraftClient/Commands/Reco.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,18 +11,59 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "reco [account]"; } } public override string CmdDesc { get { return Translations.cmd_reco_desc; } } - public override string Run(McClient? handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) + { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoReconnect(r.Source, string.Empty)) + .Then(l => l.Argument("AccountNick", MccArguments.AccountNick()) + .Executes(r => DoReconnect(r.Source, Arguments.GetString(r, "AccountNick")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoReconnect(CmdResult r, string account) + { + if (!string.IsNullOrWhiteSpace(account)) + { + account = account.Trim(); + if (!Settings.Config.Main.Advanced.SetAccount(account)) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_connect_unknown, account)); + } + Program.Restart(keepAccountAndServerSettings: true); + return r.SetAndReturn(CmdResult.Status.Done); + } + + internal static string DoReconnect(string command) { string[] args = GetArgs(command); if (args.Length > 0) { - if (!Settings.Config.Main.Advanced.SetAccount(args[0])) + string account = args[0].Trim(); + if (!Settings.Config.Main.Advanced.SetAccount(account)) { - return string.Format(Translations.cmd_connect_unknown, args[0]); + return string.Format(Translations.cmd_connect_unknown, account); } } Program.Restart(keepAccountAndServerSettings: true); - return ""; + return String.Empty; } } } diff --git a/MinecraftClient/Commands/Reload.cs b/MinecraftClient/Commands/Reload.cs index e41e3f96..95359159 100644 --- a/MinecraftClient/Commands/Reload.cs +++ b/MinecraftClient/Commands/Reload.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,8 +10,35 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "reload"; } } public override string CmdDesc { get { return Translations.cmd_reload_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoReload(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoReload(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; handler.Log.Info(Translations.cmd_reload_started); handler.ReloadSettings(); handler.Log.Warn(Translations.cmd_reload_warning1); @@ -17,7 +46,7 @@ namespace MinecraftClient.Commands handler.Log.Warn(Translations.cmd_reload_warning3); handler.Log.Warn(Translations.cmd_reload_warning4); - return Translations.cmd_reload_finished; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_reload_finished); } } } diff --git a/MinecraftClient/Commands/Respawn.cs b/MinecraftClient/Commands/Respawn.cs index 11f05a76..f8dd542a 100644 --- a/MinecraftClient/Commands/Respawn.cs +++ b/MinecraftClient/Commands/Respawn.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,10 +10,37 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "respawn"; } } public override string CmdDesc { get { return Translations.cmd_respawn_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoRespawn(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoRespawn(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; handler.SendRespawnPacket(); - return Translations.cmd_respawn_done; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_respawn_done); } } } diff --git a/MinecraftClient/Commands/Script.cs b/MinecraftClient/Commands/Script.cs index 80cda2a8..d2f310dd 100644 --- a/MinecraftClient/Commands/Script.cs +++ b/MinecraftClient/Commands/Script.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,14 +11,38 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "script "; } } public override string CmdDesc { get { return Translations.cmd_script_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Script", Arguments.GreedyString()) + .Executes(r => DoExecuteScript(r.Source, Arguments.GetString(r, "Script"), null))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - handler.BotLoad(new ChatBots.Script(GetArg(command), null, localVars)); - return ""; - } - else return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoExecuteScript(CmdResult r, string command, Dictionary? localVars) + { + McClient handler = CmdResult.currentHandler!; + handler.BotLoad(new ChatBots.Script(command.Trim(), null, localVars)); + return r.SetAndReturn(CmdResult.Status.Done); } } } diff --git a/MinecraftClient/Commands/Send.cs b/MinecraftClient/Commands/Send.cs index 9e868a08..cf6964ec 100644 --- a/MinecraftClient/Commands/Send.cs +++ b/MinecraftClient/Commands/Send.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,14 +10,35 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "send "; } } public override string CmdDesc { get { return Translations.cmd_send_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("any", Arguments.GreedyString()) + .Executes(r => DoSendText(r.Source, Arguments.GetString(r, "any")))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - handler.SendText(GetArg(command)); - return ""; - } - else return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoSendText(CmdResult r, string command) + { + McClient handler = CmdResult.currentHandler!; + handler.SendText(command); + return r.SetAndReturn(CmdResult.Status.Done); } } } diff --git a/MinecraftClient/Commands/Set.cs b/MinecraftClient/Commands/Set.cs index f3210664..75d8bbd0 100644 --- a/MinecraftClient/Commands/Set.cs +++ b/MinecraftClient/Commands/Set.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,23 +10,51 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "set varname=value"; } } public override string CmdDesc { get { return Translations.cmd_set_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Expression", Arguments.GreedyString()) + .Executes(r => DoSetVar(r.Source, Arguments.GetString(r, "Expression")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] temp = GetArg(command).Split('='); - if (temp.Length > 1) +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoSetVar(CmdResult r, string command) + { + string[] temp = command.Trim().Split('='); + if (temp.Length > 1) + { + if (Settings.Config.AppVar.SetVar(temp[0], command[(temp[0].Length + 1)..])) { - if (Settings.Config.AppVar.SetVar(temp[0], GetArg(command).Substring(temp[0].Length + 1))) - return ""; //Success - else - return Translations.cmd_set_format; + return r.SetAndReturn(CmdResult.Status.Done); //Success } else - return GetCmdDescTranslated(); + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_set_format); + } } else - return GetCmdDescTranslated(); + { + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_set_format); + } } } } diff --git a/MinecraftClient/Commands/SetRnd.cs b/MinecraftClient/Commands/SetRnd.cs index 5d7a1a20..c792b5d7 100644 --- a/MinecraftClient/Commands/SetRnd.cs +++ b/MinecraftClient/Commands/SetRnd.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -10,61 +13,61 @@ namespace MinecraftClient.Commands public override string CmdDesc { get { return Translations.cmd_setrnd_desc; } } private static readonly Random rand = new(); - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("range") + .Executes(r => GetUsage(r.Source, "range"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("VarName", Arguments.String()) + .Then(l => l.Argument("Min", Arguments.Long()) + .Then(l => l.Literal("to") + .Then(l => l.Argument("Max", Arguments.Long()) + .Executes(r => DoSetRnd(r.Source, Arguments.GetString(r, "VarName"), Arguments.GetLong(r, "Min"), Arguments.GetLong(r, "Max")))))) + .Then(l => l.Argument("Expression", Arguments.GreedyString()) + .Executes(r => DoSetRnd(r.Source, Arguments.GetString(r, "VarName"), Arguments.GetString(r, "Expression"))))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] args = GetArg(command).Split(' '); +#pragma warning disable format // @formatter:off + "range" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } - if (args.Length > 1) - { - // detect "to" keyword in string - if (args.Length == 2 && args[1].Contains("to")) - { - int num1; - int num2; + private int DoSetRnd(CmdResult r, string var, string argString) + { + // process all arguments similar to regular terminals with quotes and escaping + List values = ParseCommandLine(argString); - // try to extract the two numbers from the string - try - { - num1 = Convert.ToInt32(args[1][..args[1].IndexOf('t')]); - num2 = Convert.ToInt32(args[1].Substring(args[1].IndexOf('o') + 1, args[1].Length - 1 - args[1].IndexOf('o'))); - } - catch (Exception) - { - return Translations.cmd_setrndnum_format; - } + // create a variable or set it to one of the values + if (values.Count > 0 && Settings.Config.AppVar.SetVar(var, values[rand.Next(0, values.Count)])) + return r.SetAndReturn(CmdResult.Status.Done, string.Format("Set %{0}% to {1}.", var, Settings.Config.AppVar.GetVar(var))); + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_setrndstr_format); + } - // switch the values if they were entered in the wrong way - if (num2 < num1) - (num2, num1) = (num1, num2); + private int DoSetRnd(CmdResult r, string var, long min, long max) + { + // switch the values if they were entered in the wrong way + if (max < min) + (max, min) = (min, max); - // create a variable or set it to num1 <= varlue < num2 - if (Settings.Config.AppVar.SetVar(args[0], rand.Next(num1, num2))) - { - return string.Format("Set %{0}% to {1}.", args[0], Settings.Config.AppVar.GetVar(args[0])); //Success - } - else return Translations.cmd_setrndnum_format; - } - else - { - // extract all arguments of the command - string argString = command[(8 + command.Split(' ')[1].Length)..]; - - // process all arguments similar to regular terminals with quotes and escaping - List values = ParseCommandLine(argString); - - // create a variable or set it to one of the values - if (values.Count > 0 && Settings.Config.AppVar.SetVar(args[0], values[rand.Next(0, values.Count)])) - { - return string.Format("Set %{0}% to {1}.", args[0], Settings.Config.AppVar.GetVar(args[0])); //Success - } - else return Translations.cmd_setrndstr_format; - } - } - else return GetCmdDescTranslated(); - } - else return GetCmdDescTranslated(); + // create a variable or set it to num1 <= varlue < num2 + if (Settings.Config.AppVar.SetVar(var, rand.NextInt64(min, max))) + return r.SetAndReturn(CmdResult.Status.Fail, string.Format("Set %{0}% to {1}.", var, Settings.Config.AppVar.GetVar(var))); + else + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_setrndstr_format); } } } diff --git a/MinecraftClient/Commands/Sneak.cs b/MinecraftClient/Commands/Sneak.cs index 2ea4308a..01d470fa 100644 --- a/MinecraftClient/Commands/Sneak.cs +++ b/MinecraftClient/Commands/Sneak.cs @@ -1,29 +1,64 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { public class Sneak : Command { private bool sneaking = false; - public override string CmdName { get { return "Sneak"; } } - public override string CmdUsage { get { return "Sneak"; } } + public override string CmdName { get { return "sneak"; } } + public override string CmdUsage { get { return "sneak"; } } public override string CmdDesc { get { return Translations.cmd_sneak_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoSneak(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoSneak(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; if (sneaking) { var result = handler.SendEntityAction(Protocol.EntityActionType.StopSneaking); if (result) sneaking = false; - return result ? Translations.cmd_sneak_off : Translations.general_fail; + if (result) + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_sneak_off); + else + return r.SetAndReturn(CmdResult.Status.Fail); } else { var result = handler.SendEntityAction(Protocol.EntityActionType.StartSneaking); if (result) sneaking = true; - return result ? Translations.cmd_sneak_on : Translations.general_fail; + if (result) + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_sneak_on); + else + return r.SetAndReturn(CmdResult.Status.Fail); } } } diff --git a/MinecraftClient/Commands/Tps.cs b/MinecraftClient/Commands/Tps.cs index ced7b9af..a30749bb 100644 --- a/MinecraftClient/Commands/Tps.cs +++ b/MinecraftClient/Commands/Tps.cs @@ -1,5 +1,7 @@ using System; -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -9,17 +11,44 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "tps"; } } public override string CmdDesc { get { return Translations.cmd_tps_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoLogTps(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoLogTps(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; var tps = Math.Round(handler.GetServerTPS(), 2); string color; if (tps < 10) color = "§c"; // Red else if (tps < 15) color = "§e"; // Yellow - else + else color = "§a"; // Green - return Translations.cmd_tps_current + ": " + color + tps; + return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_tps_current + ": " + color + tps); } } } diff --git a/MinecraftClient/Commands/Upgrade.cs b/MinecraftClient/Commands/Upgrade.cs index 265a027b..35ae3783 100644 --- a/MinecraftClient/Commands/Upgrade.cs +++ b/MinecraftClient/Commands/Upgrade.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; namespace MinecraftClient.Commands { @@ -8,45 +10,69 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "upgrade [-f|check|cancel|download]"; } } public override string CmdDesc { get { return string.Empty; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (HasArg(command)) - { - string[] args = GetArgs(command); - return args[0] switch - { - "-f" => DownloadUpdate(force: true), - "-force" => DownloadUpdate(force: true), - "cancel" => CancelDownloadUpdate(), - "check" => CheckUpdate(), - "download" => DownloadUpdate(force: args.Length > 1 && (args[1] == "-f" || args[1] == "-force")), - _ => GetCmdDescTranslated(), - }; - } - else - { - return DownloadUpdate(force: false); - } + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + .Then(l => l.Literal("cancel") + .Executes(r => GetUsage(r.Source, "cancel"))) + .Then(l => l.Literal("check") + .Executes(r => GetUsage(r.Source, "check"))) + .Then(l => l.Literal("download") + .Executes(r => GetUsage(r.Source, "download"))) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DownloadUpdate(r.Source, force: false)) + .Then(l => l.Literal("-f") + .Executes(r => DownloadUpdate(r.Source, force: true))) + .Then(l => l.Literal("download") + .Executes(r => DownloadUpdate(r.Source, force: false)) + .Then(l => l.Literal("-f") + .Executes(r => DownloadUpdate(r.Source, force: true)))) + .Then(l => l.Literal("check") + .Executes(r => CheckUpdate(r.Source))) + .Then(l => l.Literal("cancel") + .Executes(r => CancelDownloadUpdate(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); } - private static string DownloadUpdate(bool force) + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + "cancel" => GetCmdDescTranslated(), + "check" => GetCmdDescTranslated(), + "download" => GetCmdDescTranslated(), + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private static int DownloadUpdate(CmdResult r, bool force) { if (UpgradeHelper.DownloadLatestBuild(force)) - return Translations.mcc_update_start; + return r.SetAndReturn(CmdResult.Status.Done, Translations.mcc_update_start); else - return Translations.mcc_update_already_running; + return r.SetAndReturn(CmdResult.Status.Fail, Translations.mcc_update_already_running); } - private static string CancelDownloadUpdate() + private static int CancelDownloadUpdate(CmdResult r) { UpgradeHelper.CancelDownloadUpdate(); - return Translations.mcc_update_cancel; + return r.SetAndReturn(CmdResult.Status.Done, Translations.mcc_update_cancel); } - private static string CheckUpdate() + private static int CheckUpdate(CmdResult r) { UpgradeHelper.CheckUpdate(forceUpdate: true); - return Translations.mcc_update_start; + return r.SetAndReturn(CmdResult.Status.Done, Translations.mcc_update_start); } } } diff --git a/MinecraftClient/Commands/UseItem.cs b/MinecraftClient/Commands/UseItem.cs index 773a8040..4a0fe1f6 100644 --- a/MinecraftClient/Commands/UseItem.cs +++ b/MinecraftClient/Commands/UseItem.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -8,14 +11,40 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "useitem"; } } public override string CmdDesc { get { return Translations.cmd_useitem_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (handler.GetInventoryEnabled()) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Executes(r => DoUseItem(r.Source)) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - handler.UseItemOnHand(); - return Translations.cmd_useitem_use; - } - else return Translations.extra_inventory_required; +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int DoUseItem(CmdResult r) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetInventoryEnabled()) + return r.SetAndReturn(Status.FailNeedInventory); + + handler.UseItemOnHand(); + return r.SetAndReturn(Status.Done, Translations.cmd_useitem_use); } } } diff --git a/MinecraftClient/Commands/Useblock.cs b/MinecraftClient/Commands/Useblock.cs index d885bec6..994e34ae 100644 --- a/MinecraftClient/Commands/Useblock.cs +++ b/MinecraftClient/Commands/Useblock.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using Brigadier.NET; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; using MinecraftClient.Mapping; +using static MinecraftClient.CommandHandler.CmdResult; namespace MinecraftClient.Commands { @@ -9,25 +12,44 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return "useblock "; } } public override string CmdDesc { get { return Translations.cmd_useblock_desc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - if (!handler.GetTerrainEnabled()) - return Translations.extra_terrainandmovement_required; - else if (HasArg(command)) + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CmdName) + .Executes(r => GetUsage(r.Source, string.Empty)) + ) + ); + + dispatcher.Register(l => l.Literal(CmdName) + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => UseBlockAtLocation(r.Source, MccArguments.GetLocation(r, "Location")))) + .Then(l => l.Literal("_help") + .Executes(r => GetUsage(r.Source, string.Empty)) + .Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName))) + ); + } + + private int GetUsage(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch { - string[] args = GetArgs(command); - if (args.Length >= 3) - { - Location block = Location.Parse(handler.GetCurrentLocation().ToFloor(), args[0], args[1], args[2]).ToFloor(); - Location blockCenter = block.ToCenter(); - bool res = handler.PlaceBlock(block, Direction.Down); - return string.Format(Translations.cmd_useblock_use, blockCenter.X, blockCenter.Y, blockCenter.Z, res ? "succeeded" : "failed"); - } - else - return GetCmdDescTranslated(); - } - else - return GetCmdDescTranslated(); +#pragma warning disable format // @formatter:off + _ => GetCmdDescTranslated(), +#pragma warning restore format // @formatter:on + }); + } + + private int UseBlockAtLocation(CmdResult r, Location block) + { + McClient handler = CmdResult.currentHandler!; + if (!handler.GetTerrainEnabled()) + return r.SetAndReturn(Status.FailNeedTerrain); + + Location current = handler.GetCurrentLocation(); + block = block.ToAbsolute(current).ToFloor(); + Location blockCenter = block.ToCenter(); + bool res = handler.PlaceBlock(block, Direction.Down); + return r.SetAndReturn(string.Format(Translations.cmd_useblock_use, blockCenter.X, blockCenter.Y, blockCenter.Z, res ? "succeeded" : "failed"), res); } } } diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 13b55989..f5262f73 100644 --- a/MinecraftClient/ConsoleIO.cs +++ b/MinecraftClient/ConsoleIO.cs @@ -1,6 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Brigadier.NET; +using FuzzySharp; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; +using static MinecraftClient.Settings; namespace MinecraftClient { @@ -175,15 +183,162 @@ namespace MinecraftClient #endregion - public static void AutocompleteHandler(object? sender, ConsoleKey e) + internal static bool AutoCompleteDone = false; + internal static string[] AutoCompleteResult = Array.Empty(); + + private static HashSet Commands = new(); + private static string[] CommandsFromAutoComplete = Array.Empty(); + private static string[] CommandsFromDeclareCommands = Array.Empty(); + + private static Task _latestTask = Task.CompletedTask; + private static CancellationTokenSource? _cancellationTokenSource; + + private static void MccAutocompleteHandler(ConsoleInteractive.ConsoleReader.Buffer buffer) { - if (e != ConsoleKey.Tab) return; - - if (autocomplete_engine == null) + string fullCommand = buffer.Text; + if (string.IsNullOrEmpty(fullCommand)) + { + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); return; + } - var buffer = ConsoleInteractive.ConsoleReader.GetBufferContent(); - autocomplete_engine.AutoComplete(buffer.Text[..buffer.CursorPosition]); + var InternalCmdChar = Config.Main.Advanced.InternalCmdChar; + if (InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none || fullCommand[0] == InternalCmdChar.ToChar()) + { + int offset = InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none ? 0 : 1; + if (buffer.CursorPosition - offset < 0) + { + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); + return; + } + _cancellationTokenSource?.Cancel(); + using var cts = new CancellationTokenSource(); + _cancellationTokenSource = cts; + var previousTask = _latestTask; + var newTask = new Task(async () => + { + string command = fullCommand[offset..]; + if (command.Length == 0) + { + List sugList = new(); + + sugList.Add(new("/")); + + var childs = McClient.dispatcher.GetRoot().Children; + if (childs != null) + foreach (var child in childs) + sugList.Add(new(child.Name)); + + foreach (var cmd in Commands) + sugList.Add(new(cmd)); + + ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList.ToArray(), new(offset, offset)); + } + else if (command.Length > 0 && command[0] == '/' && !command.Contains(' ')) + { + var sorted = Process.ExtractSorted(command[1..], Commands); + var sugList = new ConsoleInteractive.ConsoleSuggestion.Suggestion[sorted.Count()]; + + int index = 0; + foreach (var sug in sorted) + sugList[index++] = new(sug.Value); + ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList, new(offset, offset + command.Length)); + } + else + { + CommandDispatcher? dispatcher = McClient.dispatcher; + if (dispatcher == null) + return; + + ParseResults parse = dispatcher.Parse(command, CmdResult.Empty); + + Brigadier.NET.Suggestion.Suggestions suggestions = await dispatcher.GetCompletionSuggestions(parse, buffer.CursorPosition - offset); + + int sugLen = suggestions.List.Count; + if (sugLen == 0) + { + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); + return; + } + + Dictionary dictionary = new(); + foreach (var sug in suggestions.List) + dictionary.Add(sug.Text, sug.Tooltip?.String); + + var sugList = new ConsoleInteractive.ConsoleSuggestion.Suggestion[sugLen]; + if (cts.IsCancellationRequested) + return; + + Tuple range = new(suggestions.Range.Start + offset, suggestions.Range.End + offset); + var sorted = Process.ExtractSorted(fullCommand[range.Item1..range.Item2], dictionary.Keys); + if (cts.IsCancellationRequested) + return; + + int index = 0; + foreach (var sug in sorted) + sugList[index++] = new(sug.Value, dictionary[sug.Value] ?? string.Empty); + + ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList, range); + } + }, cts.Token); + _latestTask = newTask; + try { newTask.Start(); } catch { } + if (_cancellationTokenSource == cts) _cancellationTokenSource = null; + } + else + { + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); + return; + } + } + + public static void AutocompleteHandler(object? sender, ConsoleInteractive.ConsoleReader.Buffer buffer) + { + if (Settings.Config.Console.CommandSuggestion.Enable) + MccAutocompleteHandler(buffer); + } + + public static void CancelAutocomplete() + { + _cancellationTokenSource?.Cancel(); + _latestTask = Task.CompletedTask; + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); + + AutoCompleteDone = false; + AutoCompleteResult = Array.Empty(); + CommandsFromAutoComplete = Array.Empty(); + CommandsFromDeclareCommands = Array.Empty(); + } + + private static void MergeCommands() + { + Commands.Clear(); + foreach (string cmd in CommandsFromAutoComplete) + Commands.Add('/' + cmd); + foreach (string cmd in CommandsFromDeclareCommands) + Commands.Add('/' + cmd); + } + + public static void OnAutoCompleteDone(int transactionId, string[] result) + { + AutoCompleteResult = result; + if (transactionId == 0) + { + CommandsFromAutoComplete = result; + MergeCommands(); + } + AutoCompleteDone = true; + } + + public static void OnDeclareMinecraftCommand(string[] rootCommands) + { + CommandsFromDeclareCommands = rootCommands; + MergeCommands(); + } + + public static void InitCommandList(CommandDispatcher dispatcher) + { + autocomplete_engine!.AutoComplete("/"); } } @@ -198,6 +353,6 @@ namespace MinecraftClient /// /// Text behind the cursor, e.g. "my input comm" /// List of auto-complete words, e.g. ["command", "comment"] - IEnumerable AutoComplete(string BehindCursor); + int AutoComplete(string BehindCursor); } } diff --git a/MinecraftClient/Crypto/AesCfb8Stream.cs b/MinecraftClient/Crypto/AesCfb8Stream.cs index f653546c..d50aaa54 100644 --- a/MinecraftClient/Crypto/AesCfb8Stream.cs +++ b/MinecraftClient/Crypto/AesCfb8Stream.cs @@ -9,7 +9,7 @@ namespace MinecraftClient.Crypto { public class AesCfb8Stream : Stream { - public static readonly int blockSize = 16; + public const int blockSize = 16; private readonly Aes? Aes = null; private readonly FastAes? FastAes = null; @@ -109,7 +109,7 @@ namespace MinecraftClient.Crypto if (inStreamEnded) return 0; - Span blockOutput = FastAes != null ? stackalloc byte[blockSize] : null; + Span blockOutput = stackalloc byte[blockSize]; byte[] inputBuf = new byte[blockSize + required]; Array.Copy(ReadStreamIV, inputBuf, blockSize); @@ -135,18 +135,12 @@ namespace MinecraftClient.Crypto } else { - OrderablePartitioner> rangePartitioner = curRead <= 256 ? - Partitioner.Create(readed, processEnd, 32) : Partitioner.Create(readed, processEnd); - Parallel.ForEach(rangePartitioner, (range, loopState) => + for (int idx = readed; idx < processEnd; idx++) { - Span blockOutput = stackalloc byte[blockSize]; - for (int idx = range.Item1; idx < range.Item2; idx++) - { - ReadOnlySpan blockInput = new(inputBuf, idx, blockSize); - Aes!.EncryptEcb(blockInput, blockOutput, PaddingMode.None); - buffer[outOffset + idx] = (byte)(blockOutput[0] ^ inputBuf[idx + blockSize]); - } - }); + ReadOnlySpan blockInput = new(inputBuf, idx, blockSize); + Aes!.EncryptEcb(blockInput, blockOutput, PaddingMode.None); + buffer[outOffset + idx] = (byte)(blockOutput[0] ^ inputBuf[idx + blockSize]); + } } } diff --git a/MinecraftClient/Inventory/EnchantmentMapping.cs b/MinecraftClient/Inventory/EnchantmentMapping.cs index d6ffba32..badadb99 100644 --- a/MinecraftClient/Inventory/EnchantmentMapping.cs +++ b/MinecraftClient/Inventory/EnchantmentMapping.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using MinecraftClient.Protocol.Handlers; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Inventory { @@ -158,7 +159,7 @@ namespace MinecraftClient.Inventory public static string GetEnchantmentName(Enchantment enchantment) { - string? trans = Protocol.ChatParser.TranslateString("enchantment.minecraft." + enchantment.ToString().ToUnderscoreCase()); + string? trans = ChatParser.TranslateString("enchantment.minecraft." + enchantment.ToString().ToUnderscoreCase()); if (string.IsNullOrEmpty(trans)) return "Unknown Enchantment with ID: " + ((short)enchantment) + " (Probably not named in the code yet)"; else diff --git a/MinecraftClient/Inventory/Item.cs b/MinecraftClient/Inventory/Item.cs index 28a5754e..d0c523b3 100644 --- a/MinecraftClient/Inventory/Item.cs +++ b/MinecraftClient/Inventory/Item.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Inventory { @@ -64,7 +65,7 @@ namespace MinecraftClient.Inventory { string? displayName = displayProperties["Name"] as string; if (!String.IsNullOrEmpty(displayName)) - return MinecraftClient.Protocol.ChatParser.ParseText(displayProperties["Name"].ToString() ?? string.Empty); + return ChatParser.ParseText(displayProperties["Name"].ToString() ?? string.Empty); } } return null; @@ -85,7 +86,7 @@ namespace MinecraftClient.Inventory { object[] displayName = (object[])displayProperties["Lore"]; lores.AddRange(from string st in displayName - let str = MinecraftClient.Protocol.ChatParser.ParseText(st.ToString()) + let str = ChatParser.ParseText(st.ToString()) select str); return lores.ToArray(); } @@ -117,10 +118,10 @@ namespace MinecraftClient.Inventory { string type_str = type.ToString(); string type_renamed = type_str.ToUnderscoreCase(); - string? res1 = Protocol.ChatParser.TranslateString("item.minecraft." + type_renamed); + string? res1 = ChatParser.TranslateString("item.minecraft." + type_renamed); if (!string.IsNullOrEmpty(res1)) return res1; - string? res2 = Protocol.ChatParser.TranslateString("block.minecraft." + type_renamed); + string? res2 = ChatParser.TranslateString("block.minecraft." + type_renamed); if (!string.IsNullOrEmpty(res2)) return res2; return type_str; @@ -145,8 +146,8 @@ namespace MinecraftClient.Inventory short level = (short)enchantment["lvl"]; string id = ((string)enchantment["id"]).Replace(':', '.'); sb.AppendFormat(" | {0} {1}", - Protocol.ChatParser.TranslateString("enchantment." + id) ?? id, - Protocol.ChatParser.TranslateString("enchantment.level." + level) ?? level.ToString()); + ChatParser.TranslateString("enchantment." + id) ?? id, + ChatParser.TranslateString("enchantment.level." + level) ?? level.ToString()); } } } diff --git a/MinecraftClient/Inventory/ItemMovingHelper.cs b/MinecraftClient/Inventory/ItemMovingHelper.cs index 07b1e686..da9a0097 100644 --- a/MinecraftClient/Inventory/ItemMovingHelper.cs +++ b/MinecraftClient/Inventory/ItemMovingHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using MinecraftClient.Scripting; namespace MinecraftClient.Inventory { diff --git a/MinecraftClient/Inventory/VillagerInfo.cs b/MinecraftClient/Inventory/VillagerInfo.cs index 28f793af..c93781e8 100644 --- a/MinecraftClient/Inventory/VillagerInfo.cs +++ b/MinecraftClient/Inventory/VillagerInfo.cs @@ -1,4 +1,4 @@ -namespace MinecraftClient.Mapping +namespace MinecraftClient.Inventory { /// /// Properties of a villager diff --git a/MinecraftClient/Json.cs b/MinecraftClient/Json.cs index e77e32bf..be70b48e 100644 --- a/MinecraftClient/Json.cs +++ b/MinecraftClient/Json.cs @@ -103,7 +103,7 @@ namespace MinecraftClient && IsHex(toparse[cursorpos + 5])) { //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string - data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), + data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber)); cursorpos += 6; continue; } diff --git a/MinecraftClient/Logger/FileLogLogger.cs b/MinecraftClient/Logger/FileLogLogger.cs index 8418ec6c..9614a5c4 100644 --- a/MinecraftClient/Logger/FileLogLogger.cs +++ b/MinecraftClient/Logger/FileLogLogger.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using MinecraftClient.Scripting; namespace MinecraftClient.Logger { diff --git a/MinecraftClient/Mapping/Block.cs b/MinecraftClient/Mapping/Block.cs index 065699a7..ffbe499c 100644 --- a/MinecraftClient/Mapping/Block.cs +++ b/MinecraftClient/Mapping/Block.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using MinecraftClient.Mapping.BlockPalettes; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Mapping { @@ -115,7 +116,7 @@ namespace MinecraftClient.Mapping public string GetTypeString() { string typeStr = Type.ToString(); - string? trans = Protocol.ChatParser.TranslateString("block.minecraft." + typeStr.ToUnderscoreCase()); + string? trans = ChatParser.TranslateString("block.minecraft." + typeStr.ToUnderscoreCase()); return string.IsNullOrEmpty(trans) ? typeStr : trans; } diff --git a/MinecraftClient/Mapping/Dimension.cs b/MinecraftClient/Mapping/Dimension.cs index 3aea6744..fe52b0e0 100644 --- a/MinecraftClient/Mapping/Dimension.cs +++ b/MinecraftClient/Mapping/Dimension.cs @@ -139,9 +139,9 @@ namespace MinecraftClient.Mapping try { var monsterSpawnLightLevelObj = nbt["monster_spawn_light_level"]; - try - { - monsterSpawnMinLightLevel = monsterSpawnMaxLightLevel = Convert.ToInt32(monsterSpawnLightLevelObj); + try + { + monsterSpawnMinLightLevel = monsterSpawnMaxLightLevel = Convert.ToInt32(monsterSpawnLightLevelObj); } catch (Exception) { diff --git a/MinecraftClient/Mapping/Entity.cs b/MinecraftClient/Mapping/Entity.cs index 52a69c45..3d1dd67e 100644 --- a/MinecraftClient/Mapping/Entity.cs +++ b/MinecraftClient/Mapping/Entity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MinecraftClient.Inventory; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Mapping { @@ -155,11 +156,16 @@ namespace MinecraftClient.Mapping Pitch = pitch * (1F / 256) * 360; } + public static string GetTypeString(EntityType type) + { + string typeStr = type.ToString(); + string? trans = ChatParser.TranslateString("entity.minecraft." + typeStr.ToUnderscoreCase()); + return string.IsNullOrEmpty(trans) ? typeStr : trans; + } + public string GetTypeString() { - string typeStr = Type.ToString(); - string? trans = Protocol.ChatParser.TranslateString("entity.minecraft." + typeStr.ToUnderscoreCase()); - return string.IsNullOrEmpty(trans) ? typeStr : trans; + return GetTypeString(Type); } } } diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 5094648f..4a6a7320 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -26,6 +26,15 @@ namespace MinecraftClient.Mapping /// public double Z; + /// + /// Identifies whether the coordinates are absolute or relative. + /// true for relative coordinates, false for absolute coordinates. + /// X-axis: ((Status & (1 << 0)) > 0) + /// Y-axis: ((Status & (1 << 1)) > 0) + /// Z-axis: ((Status & (1 << 2)) > 0) + /// + public byte Status; + /// /// Create a new location /// @@ -34,6 +43,18 @@ namespace MinecraftClient.Mapping X = x; Y = y; Z = z; + Status = 0; + } + + /// + /// Create a new location + /// + public Location(double x, double y, double z, byte status) + { + X = x; + Y = y; + Z = z; + Status = status; } /// @@ -44,6 +65,7 @@ namespace MinecraftClient.Mapping X = loc.X; Y = loc.Y; Z = loc.Z; + Status = loc.Status; } /// @@ -60,6 +82,19 @@ namespace MinecraftClient.Mapping X = chunkX * Chunk.SizeX + blockX; Y = blockY; Z = chunkZ * Chunk.SizeZ + blockZ; + Status = 0; + } + + public Location ToAbsolute(Location based) + { + if ((Status & (1 << 0)) > 0) + X += based.X; + if ((Status & (1 << 1)) > 0) + Y += based.Y; + if ((Status & (1 << 2)) > 0) + Z += based.Z; + Status = 0; + return this; } /// diff --git a/MinecraftClient/Mapping/RaycastHelper.cs b/MinecraftClient/Mapping/RaycastHelper.cs index a1afecef..483b0b86 100644 --- a/MinecraftClient/Mapping/RaycastHelper.cs +++ b/MinecraftClient/Mapping/RaycastHelper.cs @@ -36,7 +36,7 @@ namespace MinecraftClient.Mapping { if (start == end) return new(false, Location.Zero, Block.Air); - + double start_x = MathHelper.Lerp(-1.0E-7, start.X, end.X); double start_y = MathHelper.Lerp(-1.0E-7, start.Y, end.Y); double start_z = MathHelper.Lerp(-1.0E-7, start.Z, end.Z); @@ -87,7 +87,7 @@ namespace MinecraftClient.Mapping res_location.Z += dz_sign; z_frac += z_step; } - + block = CheckRaycastResult(world, res_location, includeFluids); if (block.Type != Material.Air) return new(true, res_location, block); diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs index 97792cfe..0b2e02f9 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -153,7 +153,7 @@ namespace MinecraftClient.Mapping /// Block type /// Search radius - larger is slower: O^3 complexity /// Block matching the specified block type - public List FindBlock(Location from, Material block, int radius) + public List FindBlock(Location from, Material block, double radius) { return FindBlock(from, block, radius, radius, radius); } @@ -167,7 +167,7 @@ namespace MinecraftClient.Mapping /// Search radius on the Y axis /// Search radius on the Z axis /// Block matching the specified block type - public List FindBlock(Location from, Material block, int radiusx, int radiusy, int radiusz) + public List FindBlock(Location from, Material block, double radiusx, double radiusy, double radiusz) { Location minPoint = new Location(from.X - radiusx, from.Y - radiusy, from.Z - radiusz); Location maxPoint = new Location(from.X + radiusx, from.Y + radiusy, from.Z + radiusz); diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index de92a500..003dcbc2 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -4,16 +4,21 @@ using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; +using Brigadier.NET; +using Brigadier.NET.Exceptions; using MinecraftClient.ChatBots; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; using MinecraftClient.Inventory; using MinecraftClient.Logger; using MinecraftClient.Mapping; using MinecraftClient.Protocol; using MinecraftClient.Protocol.Handlers.Forge; -using MinecraftClient.Protocol.Keys; using MinecraftClient.Protocol.Message; +using MinecraftClient.Protocol.ProfileKey; using MinecraftClient.Protocol.Session; using MinecraftClient.Proxy; +using MinecraftClient.Scripting; using static MinecraftClient.Settings; namespace MinecraftClient @@ -25,8 +30,7 @@ namespace MinecraftClient { public static int ReconnectionAttemptsLeft = 0; - private static readonly List cmd_names = new(); - private static readonly Dictionary cmds = new(); + public static CommandDispatcher dispatcher = new(); private readonly Dictionary onlinePlayers = new(); private static bool commandsLoaded = false; @@ -146,6 +150,7 @@ namespace MinecraftClient /// ForgeInfo item stating that Forge is enabled public McClient(SessionToken session, PlayerKeyPair? playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo? forgeInfo) { + CmdResult.currentHandler = this; terrainAndMovementsEnabled = Config.Main.Advanced.TerrainAndMovements; inventoryHandlingEnabled = Config.Main.Advanced.InventoryHandling; entityHandlingEnabled = Config.Main.Advanced.EntityHandling; @@ -198,9 +203,9 @@ namespace MinecraftClient Log.Info(string.Format(Translations.mcc_joined, Config.Main.Advanced.InternalCmdChar.ToLogString())); cmdprompt = new CancellationTokenSource(); - ConsoleInteractive.ConsoleReader.BeginReadThread(cmdprompt); + ConsoleInteractive.ConsoleReader.BeginReadThread(); ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived; - ConsoleInteractive.ConsoleReader.OnKeyInput += ConsoleIO.AutocompleteHandler; + ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler; } else { @@ -224,7 +229,7 @@ namespace MinecraftClient return; - Retry: + Retry: if (timeoutdetector != null) { timeoutdetector.Item2.Cancel(); @@ -241,7 +246,7 @@ namespace MinecraftClient { ConsoleInteractive.ConsoleReader.StopReadThread(); ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived; - ConsoleInteractive.ConsoleReader.OnKeyInput -= ConsoleIO.AutocompleteHandler; + ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler; Program.HandleFailure(); } @@ -450,6 +455,8 @@ namespace MinecraftClient /// public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) { + ConsoleIO.CancelAutocomplete(); + handler.Dispose(); world.Clear(); @@ -508,7 +515,7 @@ namespace MinecraftClient { ConsoleInteractive.ConsoleReader.StopReadThread(); ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived; - ConsoleInteractive.ConsoleReader.OnKeyInput -= ConsoleIO.AutocompleteHandler; + ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler; Program.HandleFailure(); } } @@ -547,7 +554,9 @@ namespace MinecraftClient switch (command[0].ToLower()) { case "autocomplete": - if (command.Length > 1) { ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00 + handler.AutoComplete(command[1])); } + int id = handler.AutoComplete(command[1]); + while (!ConsoleIO.AutoCompleteDone) { Thread.Sleep(100); } + if (command.Length > 1) { ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00 + ConsoleIO.AutoCompleteResult); } else ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00); break; } @@ -555,68 +564,44 @@ namespace MinecraftClient else { text = text.Trim(); - if (text.Length > 0) + + if (text.Length > 1 + && Config.Main.Advanced.InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none + && text[0] == '/') { - if (Config.Main.Advanced.InternalCmdChar.ToChar() == ' ' || text[0] == Config.Main.Advanced.InternalCmdChar.ToChar()) + SendText(text); + } + else if (text.Length > 2 + && Config.Main.Advanced.InternalCmdChar != MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none + && text[0] == Config.Main.Advanced.InternalCmdChar.ToChar() + && text[1] == '/') + { + SendText(text[1..]); + } + else if (text.Length > 0) + { + if (Config.Main.Advanced.InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none + || text[0] == Config.Main.Advanced.InternalCmdChar.ToChar()) { - string? response_msg = ""; + CmdResult result = new(); string command = Config.Main.Advanced.InternalCmdChar.ToChar() == ' ' ? text : text[1..]; - if (!PerformInternalCommand(Config.AppVar.ExpandVars(command), ref response_msg, Settings.Config.AppVar.GetVariables()) && Config.Main.Advanced.InternalCmdChar.ToChar() == '/') + if (!PerformInternalCommand(Config.AppVar.ExpandVars(command), ref result, Settings.Config.AppVar.GetVariables()) && Config.Main.Advanced.InternalCmdChar.ToChar() == '/') { SendText(text); } - else if (!String.IsNullOrEmpty(response_msg)) + else if (result.status != CmdResult.Status.NotRun && (result.status != CmdResult.Status.Done || !string.IsNullOrWhiteSpace(result.result))) { - Log.Info(response_msg); + Log.Info(result); } } - else SendText(text); + else + { + SendText(text); + } } } } - /// - /// Register a custom console command - /// - /// Name of the command - /// Description/usage of the command - /// Method for handling the command - /// True if successfully registered - public bool RegisterCommand(string cmdName, string cmdDesc, string cmdUsage, ChatBot.CommandRunner callback) - { - if (cmds.ContainsKey(cmdName.ToLower())) - { - return false; - } - else - { - Command cmd = new ChatBot.ChatBotCommand(cmdName, cmdDesc, cmdUsage, callback); - cmds.Add(cmdName.ToLower(), cmd); - cmd_names.Add(cmdName.ToLower()); - return true; - } - } - - /// - /// Unregister a console command - /// - /// - /// There is no check for the command is registered by above method or is embedded command. - /// Which mean this can unload any command - /// - /// The name of command to be unregistered - /// - public bool UnregisterCommand(string cmdName) - { - if (cmds.ContainsKey(cmdName.ToLower())) - { - cmds.Remove(cmdName.ToLower()); - cmd_names.Remove(cmdName.ToLower()); - return true; - } - else return false; - } - /// /// Perform an internal MCC command (not a server command, use SendText() instead for that!) /// @@ -624,37 +609,29 @@ namespace MinecraftClient /// May contain a confirmation or error message after processing the command, or "" otherwise. /// Local variables passed along with the command /// TRUE if the command was indeed an internal MCC command - public bool PerformInternalCommand(string command, ref string? response_msg, Dictionary? localVars = null) + public bool PerformInternalCommand(string command, ref CmdResult result, Dictionary? localVars = null) { /* Process the provided command */ - - string command_name = command.Split(' ')[0].ToLower(); - if (command_name == "help") + ParseResults parse; + try { - if (Command.HasArg(command)) - { - string help_cmdname = Command.GetArgs(command)[0].ToLower(); - if (help_cmdname == "help") - { - response_msg = Translations.icmd_help; - } - else if (cmds.ContainsKey(help_cmdname)) - { - response_msg = cmds[help_cmdname].GetCmdDescTranslated(); - } - else response_msg = string.Format(Translations.icmd_unknown, command_name); - } - else response_msg = string.Format(Translations.icmd_list, String.Join(", ", cmd_names.ToArray()), Config.Main.Advanced.InternalCmdChar.ToChar()); + parse = dispatcher.Parse(command, result); } - else if (cmds.ContainsKey(command_name)) + catch (Exception e) { - response_msg = cmds[command_name].Run(this, command, localVars); + Log.Debug("Parse fail: " + e.GetFullMessage()); + return false; + } + + try + { + dispatcher.Execute(parse); foreach (ChatBot bot in bots.ToArray()) { try { - bot.OnInternalCommand(command_name, string.Join(" ", Command.GetArgs(command)), response_msg); + bot.OnInternalCommand(command, string.Join(" ", Command.GetArgs(command)), result); } catch (Exception e) { @@ -665,14 +642,22 @@ namespace MinecraftClient else throw; //ThreadAbortException should not be caught } } - } - else - { - response_msg = string.Format(Translations.icmd_unknown, command_name); - return false; - } - return true; + return true; + } + catch (CommandSyntaxException e) + { + if (parse.Context.Nodes.Count == 0) + { + return false; + } + else + { + Log.Info("§e" + e.Message ?? e.StackTrace ?? "Incorrect argument."); + Log.Info(dispatcher.GetAllUsageString(parse.Context.Nodes[0].Node.Name, false)); + return true; + } + } } public void LoadCommands() @@ -689,10 +674,7 @@ namespace MinecraftClient try { Command cmd = (Command)Activator.CreateInstance(type)!; - cmds[Settings.ToLowerIfNeed(cmd.CmdName)] = cmd; - cmd_names.Add(Settings.ToLowerIfNeed(cmd.CmdName)); - foreach (string alias in cmd.GetCMDAliases()) - cmds[Settings.ToLowerIfNeed(alias)] = cmd; + cmd.RegisterCommand(dispatcher); } catch (Exception e) { @@ -844,7 +826,8 @@ namespace MinecraftClient } b.OnUnload(); - bots.RemoveAll(item => object.ReferenceEquals(item, b)); + + bots.RemoveAll(item => ReferenceEquals(item, b)); // ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon. var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList(); @@ -1064,9 +1047,10 @@ namespace MinecraftClient if (InvokeRequired) return InvokeOnMainThread(() => GetInventory(inventoryID)); - if (inventories.ContainsKey(inventoryID)) - return inventories[inventoryID]; - return null; + if (inventories.TryGetValue(inventoryID, out Container? inv)) + return inv; + else + return null; } /// @@ -2413,6 +2397,8 @@ namespace MinecraftClient ClearInventories(); DispatchBotEvent(bot => bot.AfterGameJoined()); + + ConsoleIO.InitCommandList(dispatcher); } /// @@ -3435,6 +3421,16 @@ namespace MinecraftClient DispatchBotEvent(bot => bot.OnBlockChange(location, block)); } + /// + /// Called when "AutoComplete" completes. + /// + /// The number of this result. + /// All commands. + public void OnAutoCompleteDone(int transactionId, string[] result) + { + ConsoleIO.OnAutoCompleteDone(transactionId, result); + } + /// /// Send a click container button packet to the server. /// Used for Enchanting table, Lectern, stone cutter and loom diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 501f78f4..b0faada9 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 Exe publish\ false @@ -27,21 +27,23 @@ - + + - + + + - - + NU1701 - + @@ -83,6 +85,11 @@ True ConfigComments.resx + + True + True + MinecraftAssets.resx + True True @@ -100,6 +107,11 @@ ConfigComments.Designer.cs MinecraftClient + + ResXFileCodeGenerator + MinecraftAssets.Designer.cs + MinecraftClient + ResXFileCodeGenerator Translations.Designer.cs diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 66ebde33..7eaaafa8 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -12,11 +12,13 @@ using MinecraftClient.Mapping.BlockPalettes; using MinecraftClient.Mapping.EntityPalettes; using MinecraftClient.Protocol; using MinecraftClient.Protocol.Handlers.Forge; -using MinecraftClient.Protocol.Keys; +using MinecraftClient.Protocol.ProfileKey; using MinecraftClient.Protocol.Session; +using MinecraftClient.Scripting; using MinecraftClient.WinAPI; using Tomlet; using static MinecraftClient.Settings; +using static MinecraftClient.Settings.ConsoleConfigHealper.ConsoleConfig; using static MinecraftClient.Settings.MainConfigHealper.MainConfig.AdvancedConfig; using static MinecraftClient.Settings.MainConfigHealper.MainConfig.GeneralConfig; @@ -97,9 +99,7 @@ namespace MinecraftClient //Build information to facilitate processing of bug reports if (BuildInfo != null) - { ConsoleIO.WriteLineFormatted("§8" + BuildInfo); - } //Debug input ? if (args.Length == 1 && args[0] == "--keyboard-debug") @@ -287,7 +287,7 @@ namespace MinecraftClient } } - if (Config.Main.Advanced.ConsoleTitle != "") + if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(Config.Main.Advanced.ConsoleTitle)) { InternalConfig.Username = "New Window"; Console.Title = Config.AppVar.ExpandVars(Config.Main.Advanced.ConsoleTitle); @@ -318,28 +318,28 @@ namespace MinecraftClient Random random = new(); { // Test 8 bit color StringBuilder sb = new(); - sb.Append("[0123456789]: (8bit)["); + sb.Append("[0123456789]: (vt100 8bit)["); for (int i = 0; i < 10; ++i) { sb.Append(ColorHelper.GetColorEscapeCode((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255), true, - TerminalColorDepthType.bit_8)).Append(i); + ConsoleColorModeType.vt100_8bit)).Append(i); } sb.Append(ColorHelper.GetResetEscapeCode()).Append(']'); ConsoleIO.WriteLine(string.Format(Translations.debug_color_test, sb.ToString())); } { // Test 24 bit color StringBuilder sb = new(); - sb.Append("[0123456789]: (24bit)["); + sb.Append("[0123456789]: (vt100 24bit)["); for (int i = 0; i < 10; ++i) { sb.Append(ColorHelper.GetColorEscapeCode((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255), true, - TerminalColorDepthType.bit_24)).Append(i); + ConsoleColorModeType.vt100_24bit)).Append(i); } sb.Append(ColorHelper.GetResetEscapeCode()).Append(']'); ConsoleIO.WriteLine(string.Format(Translations.debug_color_test, sb.ToString())); @@ -463,7 +463,7 @@ namespace MinecraftClient InternalConfig.Username = session.PlayerName; bool isRealms = false; - if (Config.Main.Advanced.ConsoleTitle != "") + if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(Config.Main.Advanced.ConsoleTitle)) Console.Title = Config.AppVar.ExpandVars(Config.Main.Advanced.ConsoleTitle); if (Config.Main.Advanced.PlayerHeadAsIcon && OperatingSystem.IsWindows()) @@ -543,7 +543,7 @@ namespace MinecraftClient } //Retrieve server info if version is not manually set OR if need to retrieve Forge information - if (!isRealms && (protocolversion == 0 || (Config.Main.Advanced.EnableForge == ForgeConfigType.auto) || + if (!isRealms && (protocolversion == 0 || (Config.Main.Advanced.EnableForge == ForgeConfigType.auto) || ((Config.Main.Advanced.EnableForge == ForgeConfigType.force) && !ProtocolHandler.ProtocolMayForceForge(protocolversion)))) { if (protocolversion != 0) @@ -557,9 +557,9 @@ namespace MinecraftClient } } - if (Config.Main.General.AccountType == LoginType.microsoft + if (Config.Main.General.AccountType == LoginType.microsoft && (InternalConfig.Account.Password != "-" || Config.Main.General.Method == LoginMethod.browser) - && Config.Signature.LoginWithSecureProfile + && Config.Signature.LoginWithSecureProfile && protocolversion >= 759 /* 1.19 and above */) { // Load cached profile key from disk if necessary @@ -614,7 +614,7 @@ namespace MinecraftClient client = new McClient(session, playerKeyPair, InternalConfig.ServerIP, InternalConfig.ServerPort, protocolversion, forgeInfo); //Update console title - if (Config.Main.Advanced.ConsoleTitle != "") + if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(Config.Main.Advanced.ConsoleTitle)) Console.Title = Config.AppVar.ExpandVars(Config.Main.Advanced.ConsoleTitle); } catch (NotSupportedException) @@ -653,7 +653,7 @@ namespace MinecraftClient /// public static void ReloadSettings(bool keepAccountAndServerSettings = false) { - if(Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings).Item1) + if (Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings).Item1) ConsoleIO.WriteLine(string.Format(Translations.config_load, settingsIniPath)); } @@ -690,6 +690,7 @@ namespace MinecraftClient public static void DoExit(int exitcode = 0) { WriteBackSettings(true); + ConsoleInteractive.ConsoleSuggestion.ClearSuggestions(); ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath)); if (client != null) { client.Disconnect(); ConsoleIO.Reset(); } @@ -777,7 +778,7 @@ namespace MinecraftClient if (command.StartsWith("reco")) { - message = new Commands.Reco().Run(null, Config.AppVar.ExpandVars(command), null); + message = Commands.Reco.DoReconnect(Config.AppVar.ExpandVars(command)); if (message == "") { exitThread = true; @@ -786,7 +787,7 @@ namespace MinecraftClient } else if (command.StartsWith("connect")) { - message = new Commands.Connect().Run(null, Config.AppVar.ExpandVars(command), null); + message = Commands.Connect.DoConnect(Config.AppVar.ExpandVars(command)); if (message == "") { exitThread = true; @@ -795,7 +796,7 @@ namespace MinecraftClient } else if (command.StartsWith("exit") || command.StartsWith("quit")) { - message = new Commands.Exit().Run(null, Config.AppVar.ExpandVars(command), null); + message = Commands.Exit.DoExit(Config.AppVar.ExpandVars(command)); } else if (command.StartsWith("help")) { @@ -814,7 +815,7 @@ namespace MinecraftClient } else { - _ = new Commands.Exit().Run(null, Config.AppVar.ExpandVars(command), null); + Commands.Exit.DoExit(Config.AppVar.ExpandVars(command)); } } diff --git a/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs b/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs index a850dd2a..22835b61 100644 --- a/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs +++ b/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs @@ -54,7 +54,22 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c Nodes[i] = new(flags, childs, redirectNode, name, paser, suggestionsType); } - RootIdx = dataTypes.ReadNextVarInt(packetData); + RootIdx = dataTypes.ReadNextVarInt(packetData); + + ConsoleIO.OnDeclareMinecraftCommand(ExtractRootCommand()); + } + + private static string[] ExtractRootCommand() + { + List commands = new(); + CommandNode root = Nodes[RootIdx]; + foreach (var child in root.Clildren) + { + string? childName = Nodes[child].Name; + if (childName != null) + commands.Add(childName); + } + return commands.ToArray(); } public static List> CollectSignArguments(string command) @@ -113,7 +128,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c public string? Name; public Paser? Paser; public string? SuggestionsType; - + public CommandNode(byte Flags, int[] Clildren, diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 6ca5a609..9bb7efcf 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -9,10 +9,11 @@ using System.Threading; using MinecraftClient.Crypto; using MinecraftClient.Inventory; using MinecraftClient.Mapping; -using MinecraftClient.Protocol.Keys; using MinecraftClient.Protocol.Message; +using MinecraftClient.Protocol.ProfileKey; using MinecraftClient.Protocol.Session; using MinecraftClient.Proxy; +using MinecraftClient.Scripting; using static MinecraftClient.Settings; namespace MinecraftClient.Protocol.Handlers @@ -24,8 +25,6 @@ namespace MinecraftClient.Protocol.Handlers class Protocol16Handler : IMinecraftCom { readonly IMinecraftComHandler handler; - private bool autocomplete_received = false; - private string autocomplete_result = ""; private bool encrypted = false; private readonly int protocolversion; private Tuple? netRead = null; @@ -193,7 +192,14 @@ namespace MinecraftClient.Protocol.Handlers if (online) { handler.OnPlayerJoin(new PlayerInfo(name, FakeUUID)); } else { handler.OnPlayerLeave(FakeUUID); } break; case 0xCA: if (protocolversion >= 72) { ReadData(9); } else ReadData(3); break; - case 0xCB: autocomplete_result = ReadNextString(); autocomplete_received = true; break; + case 0xCB: + string resultString = ReadNextString(); + if (!string.IsNullOrEmpty(resultString)) + { + string[] result = resultString.Split((char)0x00); + handler.OnAutoCompleteDone(0, result); + } + break; case 0xCC: ReadNextString(); ReadData(4); break; case 0xCD: ReadData(1); break; case 0xCE: if (protocolversion > 51) { ReadNextString(); ReadNextString(); ReadData(1); } break; @@ -820,27 +826,21 @@ namespace MinecraftClient.Protocol.Handlers catch (System.IO.IOException) { return false; } } - IEnumerable IAutoComplete.AutoComplete(string BehindCursor) + int IAutoComplete.AutoComplete(string BehindCursor) { if (String.IsNullOrEmpty(BehindCursor)) - return Array.Empty(); + return -1; byte[] autocomplete = new byte[3 + (BehindCursor.Length * 2)]; autocomplete[0] = 0xCB; byte[] msglen = BitConverter.GetBytes((short)BehindCursor.Length); - Array.Reverse(msglen); msglen.CopyTo(autocomplete, 1); + Array.Reverse(msglen); + msglen.CopyTo(autocomplete, 1); byte[] msg = Encoding.BigEndianUnicode.GetBytes(BehindCursor); msg.CopyTo(autocomplete, 3); - - autocomplete_received = false; - autocomplete_result = BehindCursor; + ConsoleIO.AutoCompleteDone = false; Send(autocomplete); - - int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) - while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; } - if (!String.IsNullOrEmpty(autocomplete_result) && autocomplete_received) - ConsoleIO.WriteLineFormatted("§8" + autocomplete_result.Replace((char)0x00, ' '), false); - return autocomplete_result.Split((char)0x00); + return 0; } private static byte[] ConcatBytes(params byte[][] bytes) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 2d410e5e..1a3123c1 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -19,10 +19,11 @@ using MinecraftClient.Mapping.EntityPalettes; using MinecraftClient.Protocol.Handlers.Forge; using MinecraftClient.Protocol.Handlers.packet.s2c; using MinecraftClient.Protocol.Handlers.PacketPalettes; -using MinecraftClient.Protocol.Keys; using MinecraftClient.Protocol.Message; +using MinecraftClient.Protocol.ProfileKey; using MinecraftClient.Protocol.Session; using MinecraftClient.Proxy; +using MinecraftClient.Scripting; using static MinecraftClient.Settings; namespace MinecraftClient.Protocol.Handlers @@ -64,9 +65,7 @@ namespace MinecraftClient.Protocol.Handlers internal const int MC_1_19_2_Version = 760; private int compression_treshold = 0; - private bool autocomplete_received = false; private int autocomplete_transaction_id = 0; - private readonly List autocomplete_result = new(); private readonly Dictionary window_actions = new(); private bool login_phase = true; private readonly int protocolVersion; @@ -1325,6 +1324,7 @@ namespace MinecraftClient.Protocol.Handlers } break; case PacketTypesIn.TabComplete: + int old_transaction_id = autocomplete_transaction_id; if (protocolVersion >= MC_1_13_Version) { autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData); @@ -1333,20 +1333,19 @@ namespace MinecraftClient.Protocol.Handlers } int autocomplete_count = dataTypes.ReadNextVarInt(packetData); - autocomplete_result.Clear(); + string[] autocomplete_result = new string[autocomplete_count]; for (int i = 0; i < autocomplete_count; i++) { - autocomplete_result.Add(dataTypes.ReadNextString(packetData)); + autocomplete_result[i] = dataTypes.ReadNextString(packetData); if (protocolVersion >= MC_1_13_Version) { - // Skip optional tooltip for each tab-complete result + // Skip optional tooltip for each tab-complete resul`t if (dataTypes.ReadNextBool(packetData)) dataTypes.SkipNextString(packetData); } } - - autocomplete_received = true; + handler.OnAutoCompleteDone(old_transaction_id, autocomplete_result); break; case PacketTypesIn.PluginMessage: String channel = dataTypes.ReadNextString(packetData); @@ -2110,11 +2109,10 @@ namespace MinecraftClient.Protocol.Handlers /// /// Text behind cursor /// Completed text - IEnumerable IAutoComplete.AutoComplete(string BehindCursor) + int IAutoComplete.AutoComplete(string BehindCursor) { - - if (String.IsNullOrEmpty(BehindCursor)) - return Array.Empty(); + if (string.IsNullOrEmpty(BehindCursor)) + return -1; byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); byte[] assume_command = new byte[] { 0x00 }; @@ -2127,16 +2125,14 @@ namespace MinecraftClient.Protocol.Handlers if (protocolVersion >= MC_1_13_Version) { tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id); - tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); + tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor.Replace(' ', (char)0x00))); } else { tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor)); if (protocolVersion >= MC_1_9_Version) - { tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command); - } tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position); } @@ -2145,22 +2141,9 @@ namespace MinecraftClient.Protocol.Handlers { tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor)); } - - autocomplete_received = false; - autocomplete_result.Clear(); - autocomplete_result.Add(BehindCursor); + ConsoleIO.AutoCompleteDone = false; SendPacket(PacketTypesOut.TabComplete, tabcomplete_packet); - - int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) - ThreadStart start = new(delegate - { - while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; } - if (autocomplete_result.Count > 0) - ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false); - }); - Thread t1 = new(start); - t1.Start(); - return autocomplete_result; + return autocomplete_transaction_id; } /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index 05a30927..e7ff7cc2 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Threading; using MinecraftClient.Protocol.Handlers.Forge; +using MinecraftClient.Protocol.Message; +using MinecraftClient.Scripting; namespace MinecraftClient.Protocol.Handlers { diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index babd004e..4c0513dd 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using MinecraftClient.Inventory; using MinecraftClient.Mapping; -using MinecraftClient.Protocol.Keys; +using MinecraftClient.Protocol.ProfileKey; namespace MinecraftClient.Protocol { diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index eab385c3..61e24642 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -4,6 +4,7 @@ using MinecraftClient.Inventory; using MinecraftClient.Logger; using MinecraftClient.Mapping; using MinecraftClient.Protocol.Message; +using MinecraftClient.Scripting; namespace MinecraftClient.Protocol { @@ -456,6 +457,13 @@ namespace MinecraftClient.Protocol /// The block public void OnBlockChange(Location location, Block block); + /// + /// Called when "AutoComplete" completes. + /// + /// The number of this result. + /// All commands. + public void OnAutoCompleteDone(int transactionId, string[] result); + /// /// Send a click container button packet to the server. /// Used for Enchanting table, Lectern, stone cutter and loom diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index c0eef1bc..2aa87b00 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; -using MinecraftClient.Protocol.Message; using static MinecraftClient.Settings; -namespace MinecraftClient.Protocol +namespace MinecraftClient.Protocol.Message { /// /// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed. @@ -119,7 +119,7 @@ namespace MinecraftClient.Protocol if (message.isSystemChat) { if (Config.Signature.MarkSystemMessage) - color = "§z §r "; // Custom color code §z : Background Gray + color = "§§7 §§r "; // Background Gray } else { @@ -128,18 +128,18 @@ namespace MinecraftClient.Protocol if (Config.Signature.ShowModifiedChat && message.unsignedContent != null) { if (Config.Signature.MarkModifiedMsg) - color = "§x §r "; // Custom color code §x : Background Yellow + color = "§§6 §§r "; // Background Yellow } else { if (Config.Signature.MarkLegallySignedMsg) - color = "§y §r "; // Custom color code §y : Background Green + color = "§§2 §§r "; // Background Green } } else { if (Config.Signature.MarkIllegallySignedMsg) - color = "§w §r "; // Custom color code §w : Background Red + color = "§§4 §§r "; // Background Red } } return color + text; @@ -187,7 +187,7 @@ namespace MinecraftClient.Protocol /// /// Set of translation rules for formatting text /// - private static readonly Dictionary TranslationRules = new(); + private static Dictionary TranslationRules = new(); /// /// Initialize translation rules. @@ -200,90 +200,85 @@ namespace MinecraftClient.Protocol /// private static void InitRules() { - //Small default dictionnary of translation rules - TranslationRules["chat.type.admin"] = "[%s: %s]"; - TranslationRules["chat.type.announcement"] = "§d[%s] %s"; - TranslationRules["chat.type.emote"] = " * %s %s"; - TranslationRules["chat.type.text"] = "<%s> %s"; - TranslationRules["multiplayer.player.joined"] = "§e%s joined the game."; - TranslationRules["multiplayer.player.left"] = "§e%s left the game."; - TranslationRules["commands.message.display.incoming"] = "§7%s whispers to you: %s"; - TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s"; + if (Config.Main.Advanced.Language == "en_us") + { + TranslationRules = JsonSerializer.Deserialize>((byte[])MinecraftAssets.ResourceManager.GetObject("en_us.json")!)!; + return; + } //Language file in a subfolder, depending on the language setting if (!Directory.Exists("lang")) Directory.CreateDirectory("lang"); - string Language_File = "lang" + Path.DirectorySeparatorChar + Config.Main.Advanced.Language + ".lang"; + string languageFilePath = "lang" + Path.DirectorySeparatorChar + Config.Main.Advanced.Language + ".json"; - //File not found? Try downloading language file from Mojang's servers? - if (!File.Exists(Language_File)) + // Load the external dictionnary of translation rules or display an error message + if (File.Exists(languageFilePath)) { - ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_download, Config.Main.Advanced.Language)); - HttpClient httpClient = new(); try { - Task fetch_index = httpClient.GetStringAsync(Settings.TranslationsFile_Website_Index); - fetch_index.Wait(); - string assets_index = fetch_index.Result; - fetch_index.Dispose(); + TranslationRules = JsonSerializer.Deserialize>(File.OpenRead(languageFilePath))!; + } + catch (IOException) { } + catch (JsonException) { } + } - string[] tmp = assets_index.Split(new string[] { "minecraft/lang/" + Config.Main.Advanced.Language.ToLower() + ".json" }, StringSplitOptions.None); - tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None); - string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers - string translation_file_location = Settings.TranslationsFile_Website_Download + '/' + hash[..2] + '/' + hash; - if (Settings.Config.Logging.DebugMessages) - ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_request, translation_file_location)); + if (TranslationRules.TryGetValue("Version", out string? version) && version == Settings.TranslationsFile_Version) + { + if (Config.Logging.DebugMessages) + ConsoleIO.WriteLineFormatted(Translations.chat_loaded, acceptnewlines: true); + return; + } - Task fetch_file = httpClient.GetStringAsync(translation_file_location); + // Try downloading language file from Mojang's servers? + ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_download, Config.Main.Advanced.Language)); + HttpClient httpClient = new(); + try + { + Task fetch_index = httpClient.GetStringAsync(TranslationsFile_Website_Index); + fetch_index.Wait(); + Match match = Regex.Match(fetch_index.Result, $"minecraft/lang/{Config.Main.Advanced.Language}.json" + @""":\s\{""hash"":\s""([\d\w]{40})"""); + fetch_index.Dispose(); + if (match.Success && match.Groups.Count == 2) + { + string hash = match.Groups[1].Value; + string translation_file_location = TranslationsFile_Website_Download + '/' + hash[..2] + '/' + hash; + if (Config.Logging.DebugMessages) + ConsoleIO.WriteLineFormatted(string.Format(Translations.chat_request, translation_file_location)); + + Task fetch_file = httpClient.GetStreamAsync(translation_file_location); fetch_file.Wait(); - string translation_file = fetch_file.Result; + TranslationRules = JsonSerializer.Deserialize>(fetch_file.Result)!; fetch_file.Dispose(); - StringBuilder stringBuilder = new(); - foreach (KeyValuePair entry in Json.ParseJson(translation_file).Properties) - stringBuilder.Append(entry.Key).Append('=').Append(entry.Value.StringValue.Replace("\n", "\\n").Replace("\r", String.Empty)).Append(Environment.NewLine); - File.WriteAllText(Language_File, stringBuilder.ToString()); + TranslationRules["Version"] = TranslationsFile_Version; - ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_done, Language_File)); + File.WriteAllText(languageFilePath, JsonSerializer.Serialize(TranslationRules, typeof(Dictionary)), Encoding.UTF8); + + ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_done, languageFilePath)); + + return; } - catch + else { ConsoleIO.WriteLineFormatted("§8" + Translations.chat_fail, acceptnewlines: true); } + } + catch (HttpRequestException) + { + ConsoleIO.WriteLineFormatted("§8" + Translations.chat_fail, acceptnewlines: true); + } + catch (IOException) + { + ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_save_fail, languageFilePath), acceptnewlines: true); + } + finally + { httpClient.Dispose(); } - //Download Failed? Defaulting to en_GB.lang if the game is installed - if (!File.Exists(Language_File) //Try en_GB.lang - && File.Exists(Settings.TranslationsFile_FromMCDir)) - { - Language_File = Settings.TranslationsFile_FromMCDir; - ConsoleIO.WriteLineFormatted("§8" + Translations.chat_from_dir, acceptnewlines: true); - } - - //Load the external dictionnary of translation rules or display an error message - if (File.Exists(Language_File)) - { - foreach (var line in File.ReadLines(Language_File)) - { - if (line.Length > 0) - { - string[] splitted = line.Split('='); - if (splitted.Length == 2) - { - TranslationRules[splitted[0]] = splitted[1]; - } - } - } - - if (Settings.Config.Logging.DebugMessages) - ConsoleIO.WriteLineFormatted("§8" + Translations.chat_loaded, acceptnewlines: true); - } - else //No external dictionnary found. - { - ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.chat_not_found, Language_File)); - } + TranslationRules = JsonSerializer.Deserialize>((byte[])MinecraftAssets.ResourceManager.GetObject("en_us.json")!)!; + ConsoleIO.WriteLine(Translations.chat_use_default); } public static string? TranslateString(string rulename) diff --git a/MinecraftClient/Protocol/MojangAPI.cs b/MinecraftClient/Protocol/MojangAPI.cs index e0dade07..8ee28b31 100644 --- a/MinecraftClient/Protocol/MojangAPI.cs +++ b/MinecraftClient/Protocol/MojangAPI.cs @@ -214,7 +214,7 @@ namespace MinecraftClient.Protocol fetchTask.Dispose(); } catch (Exception) - { + { return new MojangServiceStatus(); } diff --git a/MinecraftClient/Protocol/PlayerInfo.cs b/MinecraftClient/Protocol/PlayerInfo.cs index b77aea58..31e4300e 100644 --- a/MinecraftClient/Protocol/PlayerInfo.cs +++ b/MinecraftClient/Protocol/PlayerInfo.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -using MinecraftClient.Protocol.Keys; using MinecraftClient.Protocol.Message; +using MinecraftClient.Protocol.ProfileKey; namespace MinecraftClient.Protocol { diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 94cce827..fdd68560 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -4,7 +4,7 @@ using System.Security.Cryptography; using System.Text; using MinecraftClient.Protocol.Message; -namespace MinecraftClient.Protocol.Keys +namespace MinecraftClient.Protocol.ProfileKey { static class KeyUtils { @@ -45,7 +45,7 @@ namespace MinecraftClient.Protocol.Keys } catch (Exception e) { - int code = (response == null) ? 0 : response.StatusCode; + int code = response == null ? 0 : response.StatusCode; ConsoleIO.WriteLineFormatted("§cFetch profile key failed: HttpCode = " + code + ", Error = " + e.Message); if (Settings.Config.Logging.DebugMessages) { @@ -55,7 +55,7 @@ namespace MinecraftClient.Protocol.Keys } } - public static byte[] DecodePemKey(String key, String prefix, String suffix) + public static byte[] DecodePemKey(string key, string prefix, string suffix) { int i = key.IndexOf(prefix); if (i != -1) @@ -64,8 +64,8 @@ namespace MinecraftClient.Protocol.Keys int j = key.IndexOf(suffix, i); key = key[i..j]; } - key = key.Replace("\r", String.Empty); - key = key.Replace("\n", String.Empty); + key = key.Replace("\r", string.Empty); + key = key.Replace("\n", string.Empty); return Convert.FromBase64String(key); } @@ -135,11 +135,11 @@ namespace MinecraftClient.Protocol.Keys char c = src[i]; bool needEscape = c < 32 || c == '"' || c == '\\'; // Broken lead surrogate - needEscape = needEscape || (c >= '\uD800' && c <= '\uDBFF' && - (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF')); + needEscape = needEscape || c >= '\uD800' && c <= '\uDBFF' && + (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF'); // Broken tail surrogate - needEscape = needEscape || (c >= '\uDC00' && c <= '\uDFFF' && - (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF')); + needEscape = needEscape || c >= '\uDC00' && c <= '\uDFFF' && + (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF'); // To produce valid JavaScript needEscape = needEscape || c == '\u2028' || c == '\u2029'; diff --git a/MinecraftClient/Protocol/ProfileKey/KeysCache.cs b/MinecraftClient/Protocol/ProfileKey/KeysCache.cs index d1b5b8b1..a6ea0620 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeysCache.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeysCache.cs @@ -6,7 +6,7 @@ using System.Timers; using static MinecraftClient.Settings; using static MinecraftClient.Settings.MainConfigHealper.MainConfig.AdvancedConfig; -namespace MinecraftClient.Protocol.Keys +namespace MinecraftClient.Protocol.ProfileKey { /// /// Handle keys caching and storage. @@ -115,7 +115,7 @@ namespace MinecraftClient.Protocol.Keys //User-editable keys cache file in text format if (File.Exists(KeysCacheFilePlaintext)) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_loading_keys, KeysCacheFilePlaintext)); try @@ -134,27 +134,27 @@ namespace MinecraftClient.Protocol.Keys { PlayerKeyPair playerKeyPair = PlayerKeyPair.FromString(value); keys[login] = playerKeyPair; - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_loaded_keys, playerKeyPair.ExpiresAt.ToString())); } catch (InvalidDataException e) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message)); } catch (FormatException e) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message)); } catch (ArgumentNullException e) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message)); } } - else if (Settings.Config.Logging.DebugMessages) + else if (Config.Logging.DebugMessages) { ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_line_keys, line)); } diff --git a/MinecraftClient/Protocol/ProfileKey/PlayerKeyPair.cs b/MinecraftClient/Protocol/ProfileKey/PlayerKeyPair.cs index d7b0fe39..572b0d06 100644 --- a/MinecraftClient/Protocol/ProfileKey/PlayerKeyPair.cs +++ b/MinecraftClient/Protocol/ProfileKey/PlayerKeyPair.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; -namespace MinecraftClient.Protocol.Keys +namespace MinecraftClient.Protocol.ProfileKey { public class PlayerKeyPair { @@ -74,17 +74,17 @@ namespace MinecraftClient.Protocol.Keys List datas = new(); datas.Add(Convert.ToBase64String(PublicKey.Key)); if (PublicKey.Signature == null) - datas.Add(String.Empty); + datas.Add(string.Empty); else datas.Add(Convert.ToBase64String(PublicKey.Signature)); if (PublicKey.SignatureV2 == null) - datas.Add(String.Empty); + datas.Add(string.Empty); else datas.Add(Convert.ToBase64String(PublicKey.SignatureV2)); datas.Add(Convert.ToBase64String(PrivateKey.Key)); datas.Add(ExpiresAt.ToString(DataTimeFormat)); datas.Add(RefreshedAfter.ToString(DataTimeFormat)); - return String.Join(",", datas.ToArray()); + return string.Join(",", datas.ToArray()); } } } diff --git a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs index b3dd4093..6cb8103b 100644 --- a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs +++ b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs @@ -2,7 +2,7 @@ using System.Security.Cryptography; using MinecraftClient.Protocol.Message; -namespace MinecraftClient.Protocol.Keys +namespace MinecraftClient.Protocol.ProfileKey { public class PrivateKey { diff --git a/MinecraftClient/Protocol/ProfileKey/PublicKey.cs b/MinecraftClient/Protocol/ProfileKey/PublicKey.cs index 429c3962..2f01fa84 100644 --- a/MinecraftClient/Protocol/ProfileKey/PublicKey.cs +++ b/MinecraftClient/Protocol/ProfileKey/PublicKey.cs @@ -2,7 +2,7 @@ using System.Security.Cryptography; using MinecraftClient.Protocol.Message; -namespace MinecraftClient.Protocol.Keys +namespace MinecraftClient.Protocol.ProfileKey { public class PublicKey { diff --git a/MinecraftClient/Protocol/ProxiedWebRequest.cs b/MinecraftClient/Protocol/ProxiedWebRequest.cs index 1e028abb..06e69a9d 100644 --- a/MinecraftClient/Protocol/ProxiedWebRequest.cs +++ b/MinecraftClient/Protocol/ProxiedWebRequest.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using System.Globalization; using System.IO; +using System.Linq; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; @@ -193,7 +193,7 @@ namespace MinecraftClient.Protocol response.Body = rbody ?? ""; response.StatusCode = statusCode; response.Headers = headers; - + try { stream.Close(); diff --git a/MinecraftClient/Protocol/Session/SessionToken.cs b/MinecraftClient/Protocol/Session/SessionToken.cs index e48567d9..c3c0aee9 100644 --- a/MinecraftClient/Protocol/Session/SessionToken.cs +++ b/MinecraftClient/Protocol/Session/SessionToken.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; +using MinecraftClient.Scripting; namespace MinecraftClient.Protocol.Session { diff --git a/MinecraftClient/Proxy/ProxyHandler.cs b/MinecraftClient/Proxy/ProxyHandler.cs index fca377bd..1cd55e40 100644 --- a/MinecraftClient/Proxy/ProxyHandler.cs +++ b/MinecraftClient/Proxy/ProxyHandler.cs @@ -82,7 +82,7 @@ namespace MinecraftClient.Proxy case Configs.ProxyType.SOCKS5: innerProxytype = ProxyType.Socks5; break; } - if (!string.IsNullOrWhiteSpace(Config.Username)&& !string.IsNullOrWhiteSpace(Config.Password)) + if (!string.IsNullOrWhiteSpace(Config.Username) && !string.IsNullOrWhiteSpace(Config.Password)) proxy = factory.CreateProxyClient(innerProxytype, Config.Server.Host, Config.Server.Port, Config.Username, Config.Password); else proxy = factory.CreateProxyClient(innerProxytype, Config.Server.Host, Config.Server.Port); diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs b/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs index 85527b2c..8f358c62 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs @@ -1075,6 +1075,61 @@ namespace MinecraftClient { } } + /// + /// Looks up a localized string similar to Console-related settings.. + /// + internal static string Console { + get { + return ResourceManager.GetString("Console", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The settings for command completion suggestions. + ///Custom colors are only available when using "vt100_24bit" color mode.. + /// + internal static string Console_CommandSuggestion { + get { + return ResourceManager.GetString("Console.CommandSuggestion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Whether to display command suggestions in the console.. + /// + internal static string Console_CommandSuggestion_Enable { + get { + return ResourceManager.GetString("Console.CommandSuggestion.Enable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable this option if the arrows in the command suggestions are not displayed properly in your terminal.. + /// + internal static string Console_CommandSuggestion_Use_Basic_Arrow { + get { + return ResourceManager.GetString("Console.CommandSuggestion.Use_Basic_Arrow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use "disable", "legacy_4bit", "vt100_4bit", "vt100_8bit" or "vt100_24bit". If a garbled code like "←[0m" appears on the terminal, you can try switching to "legacy_4bit" mode, or just disable it.. + /// + internal static string Console_General_ConsoleColorMode { + get { + return ResourceManager.GetString("Console.General.ConsoleColorMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You can use "Ctrl+P" to print out the current input and cursor position.. + /// + internal static string Console_General_Display_Input { + get { + return ResourceManager.GetString("Console.General.Display_Input", resourceCulture); + } + } + /// /// Looks up a localized string similar to Startup Config File ///Please do not record extraneous data in this file as it will be overwritten by MCC. @@ -1505,15 +1560,6 @@ namespace MinecraftClient { } } - /// - /// Looks up a localized string similar to Use "none", "bit_4", "bit_8" or "bit_24". This can be checked by opening the debug log.. - /// - internal static string Main_Advanced_TerminalColorDepth { - get { - return ResourceManager.GetString("Main.Advanced.TerminalColorDepth", resourceCulture); - } - } - /// /// Looks up a localized string similar to Uses more ram, cpu, bandwidth but allows you to move around.. /// diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx index da56bedc..bd3a27de 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx @@ -535,6 +535,25 @@ When this happens, you'll need to configure chat format below, see https://mccte Whether to use the custom regular expressions below for detection. + + Console-related settings. + + + The settings for command completion suggestions. +Custom colors are only available when using "vt100_24bit" color mode. + + + Whether to display command suggestions in the console. + + + Enable this option if the arrows in the command suggestions are not displayed properly in your terminal. + + + Use "disable", "legacy_4bit", "vt100_4bit", "vt100_8bit" or "vt100_24bit". If a garbled code like "←[0m" appears on the terminal, you can try switching to "legacy_4bit" mode, or just disable it. + + + You can use "Ctrl+P" to print out the current input and cursor position. + Startup Config File Please do not record extraneous data in this file as it will be overwritten by MCC. @@ -683,9 +702,6 @@ Usage examples: "/tell <mybot> connect Server1", "/connect Server2" Temporary fix for Badpacket issue on some servers. - - Use "none", "bit_4", "bit_8" or "bit_24". This can be checked by opening the debug log. - Uses more ram, cpu, bandwidth but allows you to move around. diff --git a/MinecraftClient/Resources/MinecraftAssets.Designer.cs b/MinecraftClient/Resources/MinecraftAssets.Designer.cs new file mode 100644 index 00000000..052c11b7 --- /dev/null +++ b/MinecraftClient/Resources/MinecraftAssets.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MinecraftClient { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class MinecraftAssets { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal MinecraftAssets() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MinecraftClient.Resources.MinecraftAssets", typeof(MinecraftAssets).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] en_us_json { + get { + object obj = ResourceManager.GetObject("en_us.json", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/MinecraftClient/Resources/MinecraftAssets.resx b/MinecraftClient/Resources/MinecraftAssets.resx new file mode 100644 index 00000000..0e01945f --- /dev/null +++ b/MinecraftClient/Resources/MinecraftAssets.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + en_us.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index 4f39f391..9c1b8d84 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -151,7 +151,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Available commands: {0}. Use /autocraft help <cmd name> for more information. You may use /ac as command alias.. + /// Looks up a localized string similar to Available commands: {0}. Use /autocraft help <cmd name> for more information.. /// internal static string bot_autoCraft_available_cmd { get { @@ -367,7 +367,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Available commands: {0}. Use /digbot help <cmd name> for more information.. + /// Looks up a localized string similar to Available commands: {0}. Use /autodig help <cmd name> for more information.. /// internal static string bot_autodig_available_cmd { get { @@ -394,7 +394,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Get the command description. Usage: /digbot help <command name>. + /// Looks up a localized string similar to Get the command description. Usage: /autodig help <command name>. /// internal static string bot_autodig_help_help { get { @@ -2253,7 +2253,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Downloading '{0}.lang' from Mojang servers.... + /// Looks up a localized string similar to Downloading '{0}.json' from Mojang servers.... /// internal static string chat_download { get { @@ -2270,15 +2270,6 @@ namespace MinecraftClient { } } - /// - /// Looks up a localized string similar to Defaulting to en_GB.lang from your Minecraft directory.. - /// - internal static string chat_from_dir { - get { - return ResourceManager.GetString("chat.from_dir", resourceCulture); - } - } - /// /// Looks up a localized string similar to Translations file loaded.. /// @@ -2316,6 +2307,24 @@ namespace MinecraftClient { } } + /// + /// Looks up a localized string similar to Failed to save the file {0}.. + /// + internal static string chat_save_fail { + get { + return ResourceManager.GetString("chat.save_fail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Switch to use Minecraft's default language resource "en_us.json".. + /// + internal static string chat_use_default { + get { + return ResourceManager.GetString("chat.use_default", resourceCulture); + } + } + /// /// Looks up a localized string similar to [{0}] Disconnecting and Reconnecting to the Server. /// @@ -2425,7 +2434,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Searching for a bed in radius of {0}.... + /// Looks up a localized string similar to Searching for a bed in radius of {0:0.00}.... /// internal static string cmd_bed_searching { get { @@ -3996,7 +4005,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to setrnd variable -7to17 OR setrnd variable string1 "\"string2\" string3". + /// Looks up a localized string similar to setrnd variable -7 to 17 OR setrnd variable string1 "\"string2\" string3". /// internal static string cmd_setrnd_format { get { @@ -4005,7 +4014,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to setrnd variable -7to17. + /// Looks up a localized string similar to setrnd variable -7 to 17. /// internal static string cmd_setrndnum_format { get { @@ -4112,6 +4121,15 @@ namespace MinecraftClient { } } + /// + /// Looks up a localized string similar to The color code {1} in {0} is in illegal format and the default value has been restored.. + /// + internal static string config_commandsuggestion_illegal_color { + get { + return ResourceManager.GetString("config.commandsuggestion.illegal_color", resourceCulture); + } + } + /// /// Looks up a localized string similar to Settings have been loaded from {0}. /// @@ -5058,7 +5076,8 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to help <cmdname>. Available commands: {0}. For server help, use '{1}send /help' instead.. + /// Looks up a localized string similar to help <cmdname>. Available commands: + ///{0}For server help, use '{1}send /help' instead.. /// internal static string icmd_list { get { @@ -5322,7 +5341,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Password(invisible): {0}. + /// Looks up a localized string similar to Password(invisible): . /// internal static string mcc_password_hidden { get { diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index c6715caa..73e81493 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -148,7 +148,7 @@ Auto-crafting ChatBot command alias - Available commands: {0}. Use /autocraft help <cmd name> for more information. You may use /ac as command alias. + Available commands: {0}. Use /autocraft help <cmd name> for more information. Inventory #{0} was closed by AutoCraft @@ -220,7 +220,7 @@ Action timeout! Reason: {0} - Available commands: {0}. Use /digbot help <cmd name> for more information. + Available commands: {0}. Use /autodig help <cmd name> for more information. Auto-digging ChatBot command @@ -229,7 +229,7 @@ Digging block timeout, retry. - Get the command description. Usage: /digbot help <command name> + Get the command description. Usage: /autodig help <command name> Start the automatic digging bot. @@ -864,14 +864,11 @@ Add the ID of this chat to "Authorized_Chat_Ids" field in the configuration file Done. File saved as '{0}' - Downloading '{0}.lang' from Mojang servers... + Downloading '{0}.json' from Mojang servers... Failed to download the file. - - Defaulting to en_GB.lang from your Minecraft directory. - Translations file loaded. @@ -885,6 +882,12 @@ Some messages won't be properly printed without this file. Performing request to {0} + + Failed to save the file {0}. + + + Switch to use Minecraft's default language resource "en_us.json". + [{0}] Disconnecting and Reconnecting to the Server @@ -922,7 +925,7 @@ Some messages won't be properly printed without this file. Could not lay in bed. Are you trying to sleep in a bed? (PS: You must use the head block coordinates of the bed) - Searching for a bed in radius of {0}... + Searching for a bed in radius of {0:0.00}... Trying to sleep in a bed on location (X: {0:0.0}, Y: {1:0.0}, Z: {2:0.0}). Result: {3} @@ -1449,10 +1452,10 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s set a custom %variable% randomly to a given value. - setrnd variable -7to17 OR setrnd variable string1 "\"string2\" string3" + setrnd variable -7 to 17 OR setrnd variable string1 "\"string2\" string3" - setrnd variable -7to17 + setrnd variable -7 to 17 setrnd variable string1 "\"string2\" string3" @@ -1487,6 +1490,9 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s Failed to write to backup file {0} + + The color code {1} in {0} is in illegal format and the default value has been restored. + Settings have been loaded from {0} @@ -1803,7 +1809,8 @@ You can use "/chunk status {0:0.0} {1:0.0} {2:0.0}" to check the chunk loading s help <cmdname>: show brief help about a command. - help <cmdname>. Available commands: {0}. For server help, use '{1}send /help' instead. + help <cmdname>. Available commands: +{0}For server help, use '{1}send /help' instead. Unknown command '{0}'. Use 'help' for command list. @@ -1893,7 +1900,7 @@ Type '{0}quit' to leave the server. Please type the password for {0}. - Password(invisible): {0} + Password(invisible): You are dead. Type '{0}respawn' to respawn. diff --git a/MinecraftClient/Resources/en_us.json b/MinecraftClient/Resources/en_us.json new file mode 100644 index 00000000..f02b7bf1 --- /dev/null +++ b/MinecraftClient/Resources/en_us.json @@ -0,0 +1,5822 @@ +{ + "language.name": "English", + "language.region": "United States", + "language.code": "en_us", + "narrator.button.accessibility": "Accessibility", + "narrator.button.language": "Language", + "narrator.button.difficulty_lock": "Difficulty lock", + "narrator.button.difficulty_lock.unlocked": "Unlocked", + "narrator.button.difficulty_lock.locked": "Locked", + "narrator.screen.title": "Title Screen", + "narrator.controls.reset": "Reset %s button", + "narrator.controls.bound": "%s is bound to %s", + "narrator.controls.unbound": "%s is not bound", + "narrator.select": "Selected: %s", + "narrator.select.world": "Selected %s, last played: %s, %s, %s, version: %s", + "narrator.loading": "Loading: %s", + "narrator.loading.done": "Done", + "narrator.joining": "Joining", + "narrator.position.screen": "Screen element %s out of %s", + "narrator.screen.usage": "Use mouse cursor or Tab button to select element", + "narrator.position.list": "Selected list row %s out of %s", + "narrator.position.object_list": "Selected row element %s out of %s", + "narration.suggestion.tooltip": "Selected suggestion %d out of %d: %s (%s)", + "narration.suggestion": "Selected suggestion %d out of %d: %s", + "narration.button": "Button: %s", + "narration.button.usage.focused": "Press Enter to activate", + "narration.button.usage.hovered": "Left click to activate", + "narration.cycle_button.usage.focused": "Press Enter to switch to %s", + "narration.cycle_button.usage.hovered": "Left click to switch to %s", + "narration.checkbox": "Checkbox: %s", + "narration.checkbox.usage.focused": "Press Enter to toggle", + "narration.checkbox.usage.hovered": "Left click to toggle", + "narration.recipe": "Recipe for %s", + "narration.recipe.usage": "Left click to select", + "narration.recipe.usage.more": "Right click to show more recipes", + "narration.selection.usage": "Press up and down buttons to move to another entry", + "narration.component_list.usage": "Press Tab to navigate to next element", + "narration.slider.usage.focused": "Press left or right keyboard buttons to change value", + "narration.slider.usage.hovered": "Drag slider to change value", + "narration.edit_box": "Edit box: %s", + "chat_screen.title": "Chat screen", + "chat_screen.usage": "Input message and press Enter to send", + "chat_screen.message": "Message to send: %s", + "gui.done": "Done", + "gui.cancel": "Cancel", + "gui.back": "Back", + "gui.toTitle": "Back to Title Screen", + "gui.toMenu": "Back to Server List", + "gui.up": "Up", + "gui.down": "Down", + "gui.yes": "Yes", + "gui.no": "No", + "gui.none": "None", + "gui.all": "All", + "gui.ok": "Ok", + "gui.proceed": "Proceed", + "gui.acknowledge": "Acknowledge", + "gui.recipebook.moreRecipes": "Right Click for More", + "gui.recipebook.search_hint": "Search...", + "gui.recipebook.toggleRecipes.all": "Showing All", + "gui.recipebook.toggleRecipes.craftable": "Showing Craftable", + "gui.recipebook.toggleRecipes.smeltable": "Showing Smeltable", + "gui.recipebook.toggleRecipes.blastable": "Showing Blastable", + "gui.recipebook.toggleRecipes.smokable": "Showing Smokable", + "gui.socialInteractions.title": "Social Interactions", + "gui.socialInteractions.tab_all": "All", + "gui.socialInteractions.tab_hidden": "Hidden", + "gui.socialInteractions.tab_blocked": "Blocked", + "gui.socialInteractions.blocking_hint": "Manage with Microsoft account", + "gui.socialInteractions.status_hidden": "Hidden", + "gui.socialInteractions.status_blocked": "Blocked", + "gui.socialInteractions.status_offline": "Offline", + "gui.socialInteractions.status_hidden_offline": "Hidden - Offline", + "gui.socialInteractions.status_blocked_offline": "Blocked - Offline", + "gui.socialInteractions.server_label.single": "%s - %s player", + "gui.socialInteractions.server_label.multiple": "%s - %s players", + "gui.socialInteractions.search_hint": "Search...", + "gui.socialInteractions.search_empty": "Couldn't find any players with that name", + "gui.socialInteractions.empty_hidden": "No players hidden in chat", + "gui.socialInteractions.empty_blocked": "No blocked players in chat", + "gui.socialInteractions.hide": "Hide in Chat", + "gui.socialInteractions.show": "Show in Chat", + "gui.socialInteractions.report": "Report", + "gui.socialInteractions.hidden_in_chat": "Chat messages from %s will be hidden", + "gui.socialInteractions.shown_in_chat": "Chat messages from %s will be shown", + "gui.socialInteractions.tooltip.hide": "Hide messages", + "gui.socialInteractions.tooltip.show": "Show messages", + "gui.socialInteractions.tooltip.report": "Report player", + "gui.socialInteractions.tooltip.report.disabled": "The reporting service is unavailable", + "gui.socialInteractions.tooltip.report.no_messages": "No reportable messages from player %s", + "gui.socialInteractions.tooltip.report.not_reportable": "This player can't be reported, because their chat messages can't be verified on this server", + "gui.socialInteractions.narration.hide": "Hide messages from %s", + "gui.socialInteractions.narration.show": "Show messages from %s", + "gui.socialInteractions.narration.report": "Report player %s", + "gui.narrate.button": "%s button", + "gui.narrate.slider": "%s slider", + "gui.narrate.editBox": "%s edit box: %s", + "gui.chatReport.title": "Report Player", + "gui.chatReport.send": "Send Report", + "gui.chatReport.send.comments_too_long": "Please shorten the comment", + "gui.chatReport.send.no_reason": "Please select a report category", + "gui.chatReport.send.no_reported_messages": "Please select at least one chat message to report", + "gui.chatReport.send.too_many_messages": "Trying to include too many messages in the report", + "gui.chatReport.observed_what": "Why are you reporting this?", + "gui.chatReport.select_reason": "Select Report Category", + "gui.chatReport.more_comments": "Please describe what happened:", + "gui.chatReport.describe": "Sharing details will help us make a well-informed decision.", + "gui.chatReport.comments": "Comments", + "gui.chatReport.read_info": "Learn About Reporting", + "gui.chatReport.select_chat": "Select Chat Messages to Report", + "gui.chatReport.selected_chat": "%s Chat Message(s) Selected to Report", + "gui.chatReport.report_sent_msg": "We’ve successfully received your report. Thank you!\n\nOur team will review it as soon as possible.", + "gui.chatReport.discard.title": "Discard report and comments?", + "gui.chatReport.discard.content": "If you leave, you'll lose this report and your comments.\nAre you sure you want to leave?", + "gui.chatReport.discard.discard": "Leave and Discard Report", + "gui.chatReport.discard.draft": "Save as Draft", + "gui.chatReport.discard.return": "Continue Editing", + "gui.chatReport.draft.title": "Edit draft chat report?", + "gui.chatReport.draft.content": "Would you like to continue editing the existing report or discard it and create a new one?", + "gui.chatReport.draft.quittotitle.title": "You have a draft chat report that will be lost if you quit", + "gui.chatReport.draft.quittotitle.content": "Would you like to continue editing it or discard it?", + "gui.chatReport.draft.discard": "Discard", + "gui.chatReport.draft.edit": "Continue Editing", + "gui.abuseReport.reason.title": "Select Report Category", + "gui.abuseReport.reason.description": "Description:", + "gui.abuseReport.reason.narration": "%s: %s", + "gui.abuseReport.reason.false_reporting": "False Reporting", + "gui.abuseReport.reason.child_sexual_exploitation_or_abuse": "Child sexual exploitation or abuse", + "gui.abuseReport.reason.child_sexual_exploitation_or_abuse.description": "Someone is talking about or otherwise promoting indecent behavior involving children.", + "gui.abuseReport.reason.terrorism_or_violent_extremism": "Terrorism or violent extremism", + "gui.abuseReport.reason.terrorism_or_violent_extremism.description": "Someone is talking about, promoting, or threatening to commit acts of terrorism or violent extremism for political, religious, ideological, or other reasons.", + "gui.abuseReport.reason.hate_speech": "Hate speech", + "gui.abuseReport.reason.hate_speech.description": "Someone is attacking you or another player based on characteristics of their identity, like religion, race, or sexuality.", + "gui.abuseReport.reason.harassment_or_bullying": "Harassment or bullying", + "gui.abuseReport.reason.harassment_or_bullying.description": "Someone is shaming, attacking, or bullying you or someone else. This includes when someone is repeatedly trying to contact you or someone else without consent or posting private personal information about you or someone else without consent (\"doxing\").", + "gui.abuseReport.reason.imminent_harm": "Imminent harm - Threat to harm others", + "gui.abuseReport.reason.imminent_harm.description": "Someone is threatening to harm you or someone else in real life.", + "gui.abuseReport.reason.defamation_impersonation_false_information": "Defamation, impersonation, or false information", + "gui.abuseReport.reason.defamation_impersonation_false_information.description": "Someone is damaging someone else's reputation, pretending to be someone they're not, or sharing false information with the aim to exploit or mislead others.", + "gui.abuseReport.reason.self_harm_or_suicide": "Imminent harm - Self-harm or suicide", + "gui.abuseReport.reason.self_harm_or_suicide.description": "Someone is threatening to harm themselves in real life or talking about harming themselves in real life.", + "gui.abuseReport.reason.alcohol_tobacco_drugs": "Drugs or alcohol", + "gui.abuseReport.reason.alcohol_tobacco_drugs.description": "Someone is encouraging others to partake in illegal drug related activities or encouraging underage drinking.", + "gui.abuseReport.reason.non_consensual_intimate_imagery": "Non-consensual intimate imagery", + "gui.abuseReport.reason.non_consensual_intimate_imagery.description": "Someone is talking about, sharing, or otherwise promoting private and intimate images.", + "gui.abuseReport.sending.title": "Sending your report...", + "gui.abuseReport.sent.title": "Report sent", + "gui.abuseReport.error.title": "Problem sending your report", + "gui.abuseReport.send.generic_error": "Encountered an unexpected error while sending your report.", + "gui.abuseReport.send.error_message": "An error was returned while sending your report:\n'%s'", + "gui.abuseReport.send.service_unavailable": "Unable to reach the Abuse Reporting service. Please make sure you are connected to the internet and try again.", + "gui.abuseReport.send.http_error": "An unexpected HTTP error occurred while sending your report.", + "gui.abuseReport.send.json_error": "Encountered malformed payload while sending your report.", + "gui.chatSelection.title": "Select Chat Messages to Report", + "gui.chatSelection.context": "Messages surrounding this selection will be included to provide additional context", + "gui.chatSelection.selected": "%s/%s message(s) selected", + "gui.chatSelection.heading": "%s %s", + "gui.chatSelection.message.narrate": "%s said: %s at %s", + "gui.chatSelection.fold": "%s message(s) hidden", + "gui.chatSelection.join": "%s joined the chat", + "gui.multiLineEditBox.character_limit": "%s/%s", + "gui.banned.title.temporary": "Account temporarily suspended", + "gui.banned.title.permanent": "Account permanently banned", + "gui.banned.description": "%s\n\n%s\n\nLearn more at the following link: %s", + "gui.banned.description.reason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified it as %s, which goes against the Minecraft Community Standards.", + "gui.banned.description.reason_id": "Code: %s", + "gui.banned.description.reason_id_message": "Code: %s - %s", + "gui.banned.description.unknownreason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified that it goes against the Minecraft Community Standards.", + "gui.banned.description.temporary.duration": "Your account is temporarily suspended and will be reactivated in %s.", + "gui.banned.description.temporary": "%s Until then, you can’t play online or join Realms.", + "gui.banned.description.permanent": "Your account is permanently banned, which means you can’t play online or join Realms.", + "translation.test.none": "Hello, world!", + "translation.test.complex": "Prefix, %s%2$s again %s and %1$s lastly %s and also %1$s again!", + "translation.test.escape": "%%s %%%s %%%%s %%%%%s", + "translation.test.invalid": "hi %", + "translation.test.invalid2": "hi % s", + "translation.test.args": "%s %s", + "translation.test.world": "world", + "menu.game": "Game Menu", + "menu.singleplayer": "Singleplayer", + "menu.multiplayer": "Multiplayer", + "menu.online": "Minecraft Realms", + "menu.options": "Options...", + "menu.quit": "Quit Game", + "menu.returnToMenu": "Save and Quit to Title", + "menu.disconnect": "Disconnect", + "menu.returnToGame": "Back to Game", + "menu.generatingLevel": "Generating world", + "menu.loadingLevel": "Loading world", + "menu.savingLevel": "Saving world", + "menu.working": "Working...", + "menu.savingChunks": "Saving chunks", + "menu.preparingSpawn": "Preparing spawn area: %s%%", + "menu.loadingForcedChunks": "Loading forced chunks for dimension %s", + "menu.generatingTerrain": "Building terrain", + "menu.convertingLevel": "Converting world", + "menu.respawning": "Respawning", + "menu.shareToLan": "Open to LAN", + "menu.sendFeedback": "Give Feedback", + "menu.reportBugs": "Report Bugs", + "menu.playerReporting": "Player Reporting", + "menu.paused": "Game Paused", + "menu.modded": " (Modded)", + "optimizeWorld.confirm.title": "Optimize World", + "optimizeWorld.confirm.description": "This will attempt to optimize your world by making sure all data is stored in the most recent game format. This can take a very long time, depending on your world. Once done, your world may play faster but will no longer be compatible with older versions of the game. Are you sure you wish to proceed?", + "optimizeWorld.title": "Optimizing World '%s'", + "optimizeWorld.stage.counting": "Counting chunks...", + "optimizeWorld.stage.upgrading": "Upgrading all chunks...", + "optimizeWorld.stage.finished": "Finishing up...", + "optimizeWorld.stage.failed": "Failed! :(", + "optimizeWorld.info.converted": "Upgraded chunks: %s", + "optimizeWorld.info.skipped": "Skipped chunks: %s", + "optimizeWorld.info.total": "Total chunks: %s", + "selectWorld.title": "Select World", + "selectWorld.search": "search for worlds", + "selectWorld.world": "World", + "selectWorld.select": "Play Selected World", + "selectWorld.create": "Create New World", + "selectWorld.recreate": "Re-Create", + "selectWorld.createDemo": "Play New Demo World", + "selectWorld.delete": "Delete", + "selectWorld.edit": "Edit", + "selectWorld.edit.title": "Edit World", + "selectWorld.edit.resetIcon": "Reset Icon", + "selectWorld.edit.openFolder": "Open World Folder", + "selectWorld.edit.save": "Save", + "selectWorld.edit.backup": "Make Backup", + "selectWorld.edit.backupFolder": "Open Backups Folder", + "selectWorld.edit.backupFailed": "Backup failed", + "selectWorld.edit.backupCreated": "Backed up: %s", + "selectWorld.edit.backupSize": "size: %s MB", + "selectWorld.edit.optimize": "Optimize World", + "selectWorld.edit.export_worldgen_settings": "Export World Generation Settings", + "selectWorld.edit.export_worldgen_settings.success": "Exported", + "selectWorld.edit.export_worldgen_settings.failure": "Export failed", + "selectWorld.deleteQuestion": "Are you sure you want to delete this world?", + "selectWorld.deleteWarning": "'%s' will be lost forever! (A long time!)", + "selectWorld.deleteButton": "Delete", + "selectWorld.conversion": "Must be converted!", + "selectWorld.conversion.tooltip": "This world must be opened in an older version (like 1.6.4) to be safely converted", + "selectWorld.locked": "Locked by another running instance of Minecraft", + "selectWorld.incompatible_series": "Created by an incompatible version", + "selectWorld.newWorld": "New World", + "selectWorld.enterName": "World Name", + "selectWorld.resultFolder": "Will be saved in:", + "selectWorld.enterSeed": "Seed for the world generator", + "selectWorld.seedInfo": "Leave blank for a random seed", + "selectWorld.cheats": "Cheats", + "selectWorld.experimental": "Experimental", + "selectWorld.customizeType": "Customize", + "selectWorld.version": "Version:", + "selectWorld.versionUnknown": "unknown", + "selectWorld.versionQuestion": "Do you really want to load this world?", + "selectWorld.versionWarning": "This world was last played in version %s and loading it in this version could cause corruption!", + "selectWorld.versionJoinButton": "Load Anyway", + "selectWorld.backupQuestion.snapshot": "Do you really want to load this world?", + "selectWorld.backupWarning.snapshot": "This world was last played in version %s; you are on version %s. Please make a backup in case you experience world corruptions!", + "selectWorld.backupQuestion.downgrade": "Downgrading a world is not supported", + "selectWorld.backupWarning.downgrade": "This world was last played in version %s; you are on version %s. Downgrading a world could cause corruption - we cannot guarantee that it will load or work. If you still want to continue, please make a backup!", + "selectWorld.backupQuestion.customized": "Customized worlds are no longer supported", + "selectWorld.backupWarning.customized": "Unfortunately, we do not support customized worlds in this version of Minecraft. We can still load this world and keep everything the way it was, but any newly generated terrain will no longer be customized. We're sorry for the inconvenience!", + "selectWorld.backupQuestion.experimental": "Worlds using Experimental Settings are not supported", + "selectWorld.backupWarning.experimental": "This world uses experimental settings that could stop working at any time. We cannot guarantee it will load or work. Here be dragons!", + "selectWorld.backupEraseCache": "Erase Cached Data", + "selectWorld.backupJoinConfirmButton": "Create Backup and Load", + "selectWorld.backupJoinSkipButton": "I know what I'm doing!", + "selectWorld.tooltip.fromNewerVersion1": "World was saved in a newer version,", + "selectWorld.tooltip.fromNewerVersion2": "loading this world could cause problems!", + "selectWorld.tooltip.snapshot1": "Don't forget to back up this world", + "selectWorld.tooltip.snapshot2": "before you load it in this snapshot.", + "selectWorld.unable_to_load": "Unable to load worlds", + "selectWorld.futureworld.error.title": "An error occurred!", + "selectWorld.futureworld.error.text": "Something went wrong while trying to load a world from a future version. This was a risky operation to begin with; sorry it didn't work.", + "selectWorld.recreate.error.title": "An error occurred!", + "selectWorld.recreate.error.text": "Something went wrong while trying to recreate a world.", + "selectWorld.recreate.customized.title": "Customized worlds are no longer supported", + "selectWorld.recreate.customized.text": "Customized worlds are no longer supported in this version of Minecraft. We can try to recreate it with the same seed and properties, but any terrain customizations will be lost. We're sorry for the inconvenience!", + "selectWorld.load_folder_access": "Unable to read or access folder where game worlds are saved!", + "selectWorld.access_failure": "Failed to access world", + "selectWorld.delete_failure": "Failed to delete world", + "selectWorld.data_read": "Reading world data...", + "selectWorld.loading_list": "Loading world list", + "createWorld.customize.presets": "Presets", + "createWorld.customize.presets.title": "Select a Preset", + "createWorld.customize.presets.select": "Use Preset", + "createWorld.customize.presets.share": "Want to share your preset with someone? Use the box below!", + "createWorld.customize.presets.list": "Alternatively, here's some we made earlier!", + "createWorld.customize.flat.title": "Superflat Customization", + "createWorld.customize.flat.tile": "Layer Material", + "createWorld.customize.flat.height": "Height", + "createWorld.customize.flat.removeLayer": "Remove Layer", + "createWorld.customize.flat.layer.top": "Top - %s", + "createWorld.customize.flat.layer": "%s", + "createWorld.customize.flat.layer.bottom": "Bottom - %s", + "createWorld.customize.buffet.title": "Buffet world customization", + "createWorld.customize.buffet.biome": "Please select a biome", + "flat_world_preset.unknown": "???", + "flat_world_preset.minecraft.classic_flat": "Classic Flat", + "flat_world_preset.minecraft.tunnelers_dream": "Tunnelers' Dream", + "flat_world_preset.minecraft.water_world": "Water World", + "flat_world_preset.minecraft.overworld": "Overworld", + "flat_world_preset.minecraft.snowy_kingdom": "Snowy Kingdom", + "flat_world_preset.minecraft.bottomless_pit": "Bottomless Pit", + "flat_world_preset.minecraft.desert": "Desert", + "flat_world_preset.minecraft.redstone_ready": "Redstone Ready", + "flat_world_preset.minecraft.the_void": "The Void", + "createWorld.customize.custom.page0": "Basic Settings", + "createWorld.customize.custom.page1": "Ore Settings", + "createWorld.customize.custom.page2": "Advanced Settings (Expert Users Only!)", + "createWorld.customize.custom.page3": "Extra Advanced Settings (Expert Users Only!)", + "createWorld.customize.custom.randomize": "Randomize", + "createWorld.customize.custom.prev": "Previous Page", + "createWorld.customize.custom.next": "Next Page", + "createWorld.customize.custom.defaults": "Defaults", + "createWorld.customize.custom.confirm1": "This will overwrite your current", + "createWorld.customize.custom.confirm2": "settings and cannot be undone.", + "createWorld.customize.custom.confirmTitle": "Warning!", + "createWorld.customize.custom.mainNoiseScaleX": "Main Noise Scale X", + "createWorld.customize.custom.mainNoiseScaleY": "Main Noise Scale Y", + "createWorld.customize.custom.mainNoiseScaleZ": "Main Noise Scale Z", + "createWorld.customize.custom.depthNoiseScaleX": "Depth Noise Scale X", + "createWorld.customize.custom.depthNoiseScaleZ": "Depth Noise Scale Z", + "createWorld.customize.custom.depthNoiseScaleExponent": "Depth Noise Exponent", + "createWorld.customize.custom.baseSize": "Depth Base Size", + "createWorld.customize.custom.coordinateScale": "Coordinate Scale", + "createWorld.customize.custom.heightScale": "Height Scale", + "createWorld.customize.custom.stretchY": "Height Stretch", + "createWorld.customize.custom.upperLimitScale": "Upper Limit Scale", + "createWorld.customize.custom.lowerLimitScale": "Lower Limit Scale", + "createWorld.customize.custom.biomeDepthWeight": "Biome Depth Weight", + "createWorld.customize.custom.biomeDepthOffset": "Biome Depth Offset", + "createWorld.customize.custom.biomeScaleWeight": "Biome Scale Weight", + "createWorld.customize.custom.biomeScaleOffset": "Biome Scale Offset", + "createWorld.customize.custom.seaLevel": "Sea Level", + "createWorld.customize.custom.useCaves": "Caves", + "createWorld.customize.custom.useStrongholds": "Strongholds", + "createWorld.customize.custom.useVillages": "Villages", + "createWorld.customize.custom.useMineShafts": "Mineshafts", + "createWorld.customize.custom.useTemples": "Temples", + "createWorld.customize.custom.useOceanRuins": "Ocean Ruins", + "createWorld.customize.custom.useMonuments": "Ocean Monuments", + "createWorld.customize.custom.useMansions": "Woodland Mansions", + "createWorld.customize.custom.useRavines": "Ravines", + "createWorld.customize.custom.useDungeons": "Dungeons", + "createWorld.customize.custom.dungeonChance": "Dungeon Count", + "createWorld.customize.custom.useWaterLakes": "Water Lakes", + "createWorld.customize.custom.waterLakeChance": "Water Lake Rarity", + "createWorld.customize.custom.useLavaLakes": "Lava Lakes", + "createWorld.customize.custom.lavaLakeChance": "Lava Lake Rarity", + "createWorld.customize.custom.useLavaOceans": "Lava Oceans", + "createWorld.customize.custom.fixedBiome": "Biome", + "createWorld.customize.custom.biomeSize": "Biome Size", + "createWorld.customize.custom.riverSize": "River Size", + "createWorld.customize.custom.size": "Spawn Size", + "createWorld.customize.custom.count": "Spawn Tries", + "createWorld.customize.custom.minHeight": "Min. Height", + "createWorld.customize.custom.maxHeight": "Max. Height", + "createWorld.customize.custom.center": "Center Height", + "createWorld.customize.custom.spread": "Spread Height", + "createWorld.customize.custom.presets.title": "Customize World Presets", + "createWorld.customize.custom.presets": "Presets", + "createWorld.customize.custom.preset.waterWorld": "Water World", + "createWorld.customize.custom.preset.isleLand": "Isle Land", + "createWorld.customize.custom.preset.caveDelight": "Caver's Delight", + "createWorld.customize.custom.preset.mountains": "Mountain Madness", + "createWorld.customize.custom.preset.drought": "Drought", + "createWorld.customize.custom.preset.caveChaos": "Caves of Chaos", + "createWorld.customize.custom.preset.goodLuck": "Good Luck", + "createWorld.preparing": "Preparing for world creation...", + "datapackFailure.title": "Errors in currently selected datapacks prevented the world from loading.\nYou can either try to load it with only the vanilla data pack (\"safe mode\"), or go back to the title screen and fix it manually.", + "datapackFailure.safeMode": "Safe Mode", + "editGamerule.title": "Edit Game Rules", + "editGamerule.default": "Default: %s", + "gameMode.survival": "Survival Mode", + "gameMode.creative": "Creative Mode", + "gameMode.adventure": "Adventure Mode", + "gameMode.spectator": "Spectator Mode", + "gameMode.hardcore": "Hardcore Mode!", + "gameMode.changed": "Your game mode has been updated to %s", + "spectatorMenu.previous_page": "Previous Page", + "spectatorMenu.next_page": "Next Page", + "spectatorMenu.close": "Close Menu", + "spectatorMenu.teleport": "Teleport to Player", + "spectatorMenu.teleport.prompt": "Select a player to teleport to", + "spectatorMenu.team_teleport": "Teleport to Team Member", + "spectatorMenu.team_teleport.prompt": "Select a team to teleport to", + "spectatorMenu.root.prompt": "Press a key to select a command, and again to use it.", + "selectWorld.gameMode": "Game Mode", + "selectWorld.gameMode.survival": "Survival", + "selectWorld.gameMode.survival.line1": "Search for resources, craft, gain", + "selectWorld.gameMode.survival.line2": "levels, health and hunger", + "selectWorld.gameMode.creative": "Creative", + "selectWorld.gameMode.creative.line1": "Unlimited resources, free flying and", + "selectWorld.gameMode.creative.line2": "destroy blocks instantly", + "selectWorld.gameMode.spectator": "Spectator", + "selectWorld.gameMode.spectator.line1": "You can look but don't touch", + "selectWorld.gameMode.spectator.line2": "", + "selectWorld.gameMode.hardcore": "Hardcore", + "selectWorld.gameMode.hardcore.line1": "Same as Survival Mode, locked at hardest", + "selectWorld.gameMode.hardcore.line2": "difficulty, and one life only", + "selectWorld.gameMode.adventure": "Adventure", + "selectWorld.gameMode.adventure.line1": "Same as Survival Mode, but blocks can't", + "selectWorld.gameMode.adventure.line2": "be added or removed", + "selectWorld.moreWorldOptions": "More World Options...", + "selectWorld.gameRules": "Game Rules", + "selectWorld.mapFeatures": "Generate Structures", + "selectWorld.mapFeatures.info": "Villages, dungeons etc.", + "selectWorld.mapType": "World Type", + "selectWorld.mapType.normal": "Normal", + "selectWorld.allowCommands": "Allow Cheats", + "selectWorld.allowCommands.info": "Commands like /gamemode, /experience", + "selectWorld.dataPacks": "Data Packs", + "selectWorld.bonusItems": "Bonus Chest", + "selectWorld.import_worldgen_settings": "Import Settings", + "selectWorld.import_worldgen_settings.select_file": "Select settings file (.json)", + "selectWorld.import_worldgen_settings.failure": "Error importing settings", + "selectWorld.warning.experimental.title": "Warning! These settings are using experimental features", + "selectWorld.warning.experimental.question": "These settings are experimental and could one day stop working. Do you wish to proceed?", + "selectWorld.warning.deprecated.title": "Warning! These settings are using deprecated features", + "selectWorld.warning.deprecated.question": "Some features used are deprecated and will stop working in the future. Do you wish to proceed?", + "selectWorld.experimental.title": "Experimental Features Warning", + "selectWorld.experimental.message": "Be careful!\nSome of the selected packs require features that are still under development. Your world might crash, break or not work with future updates.", + "selectWorld.experimental.details": "Details", + "selectWorld.experimental.details.title": "Experimental feature requirements", + "selectWorld.experimental.details.entry": "Required experimental features: %s", + "generator.custom": "Custom", + "generator.minecraft.normal": "Default", + "generator.minecraft.flat": "Superflat", + "generator.minecraft.large_biomes": "Large Biomes", + "generator.minecraft.amplified": "AMPLIFIED", + "generator.minecraft.amplified.info": "Notice: Just for fun! Requires a beefy computer.", + "generator.minecraft.debug_all_block_states": "Debug Mode", + "generator.minecraft.single_biome_surface": "Single Biome", + "generator.customized": "Old Customized", + "generator.single_biome_caves": "Caves", + "generator.single_biome_floating_islands": "Floating Islands", + "selectServer.title": "Select Server", + "selectServer.select": "Join Server", + "selectServer.direct": "Direct Connection", + "selectServer.edit": "Edit", + "selectServer.delete": "Delete", + "selectServer.add": "Add Server", + "selectServer.defaultName": "Minecraft Server", + "selectServer.deleteQuestion": "Are you sure you want to remove this server?", + "selectServer.deleteWarning": "'%s' will be lost forever! (A long time!)", + "selectServer.deleteButton": "Delete", + "selectServer.refresh": "Refresh", + "selectServer.hiddenAddress": "(Hidden)", + "addServer.title": "Edit Server Info", + "addServer.enterName": "Server Name", + "addServer.enterIp": "Server Address", + "addServer.add": "Done", + "addServer.hideAddress": "Hide Address", + "addServer.resourcePack": "Server Resource Packs", + "addServer.resourcePack.enabled": "Enabled", + "addServer.resourcePack.disabled": "Disabled", + "addServer.resourcePack.prompt": "Prompt", + "lanServer.title": "LAN World", + "lanServer.scanning": "Scanning for games on your local network", + "lanServer.start": "Start LAN World", + "lanServer.otherPlayers": "Settings for Other Players", + "lanServer.port": "Port Number", + "lanServer.port.unavailable": "Port not available.\nLeave the edit box empty or enter a different number between 1024 and 65535.", + "lanServer.port.unavailable.new": "Port not available.\nLeave the edit box empty or enter a different number between %s and %s.", + "lanServer.port.invalid": "Not a valid port.\nLeave the edit box empty or enter a number between 1024 and 65535.", + "lanServer.port.invalid.new": "Not a valid port.\nLeave the edit box empty or enter a number between %s and %s.", + "multiplayerWarning.header": "Caution: Third-Party Online Play", + "multiplayerWarning.message": "Caution: Online play is offered by third-party servers that are not owned, operated, or supervised by Mojang Studios or Microsoft. During online play, you may be exposed to unmoderated chat messages or other types of user-generated content that may not be suitable for everyone.", + "multiplayerWarning.check": "Do not show this screen again", + "multiplayer.title": "Play Multiplayer", + "multiplayer.texturePrompt.line1": "This server recommends the use of a custom resource pack.", + "multiplayer.texturePrompt.line2": "Would you like to download and install it automagically?", + "multiplayer.requiredTexturePrompt.line1": "This server requires the use of a custom resource pack.", + "multiplayer.requiredTexturePrompt.line2": "Rejecting this custom resource pack will disconnect you from this server.", + "multiplayer.requiredTexturePrompt.disconnect": "Server requires a custom resource pack", + "multiplayer.texturePrompt.failure.line1": "Server resource pack couldn't be applied", + "multiplayer.texturePrompt.failure.line2": "Any functionality that requires custom resources might not work as expected", + "multiplayer.texturePrompt.serverPrompt": "%s\n\nMessage from server:\n%s", + "multiplayer.applyingPack": "Applying resource pack", + "multiplayer.downloadingTerrain": "Loading terrain...", + "multiplayer.downloadingStats": "Retrieving statistics...", + "multiplayer.stopSleeping": "Leave Bed", + "multiplayer.message_not_delivered": "Can't deliver chat message, check server logs: %s", + "multiplayer.player.joined": "%s joined the game", + "multiplayer.player.joined.renamed": "%s (formerly known as %s) joined the game", + "multiplayer.player.left": "%s left the game", + "multiplayer.status.and_more": "... and %s more ...", + "multiplayer.status.cancelled": "Cancelled", + "multiplayer.status.cannot_connect": "Can't connect to server", + "multiplayer.status.cannot_resolve": "Can't resolve hostname", + "multiplayer.status.finished": "Finished", + "multiplayer.status.incompatible": "Incompatible version!", + "multiplayer.status.no_connection": "(no connection)", + "multiplayer.status.ping": "%s ms", + "multiplayer.status.old": "Old", + "multiplayer.status.pinging": "Pinging...", + "multiplayer.status.quitting": "Quitting", + "multiplayer.status.unknown": "???", + "multiplayer.status.unrequested": "Received unrequested status", + "multiplayer.status.request_handled": "Status request has been handled", + "multiplayer.disconnect.authservers_down": "Authentication servers are down. Please try again later, sorry!", + "multiplayer.disconnect.banned": "You are banned from this server", + "multiplayer.disconnect.banned.reason": "You are banned from this server.\nReason: %s", + "multiplayer.disconnect.banned.expiration": "\nYour ban will be removed on %s", + "multiplayer.disconnect.banned_ip.reason": "Your IP address is banned from this server.\nReason: %s", + "multiplayer.disconnect.banned_ip.expiration": "\nYour ban will be removed on %s", + "multiplayer.disconnect.duplicate_login": "You logged in from another location", + "multiplayer.disconnect.flying": "Flying is not enabled on this server", + "multiplayer.disconnect.generic": "Disconnected", + "multiplayer.disconnect.idling": "You have been idle for too long!", + "multiplayer.disconnect.illegal_characters": "Illegal characters in chat", + "multiplayer.disconnect.invalid_entity_attacked": "Attempting to attack an invalid entity", + "multiplayer.disconnect.invalid_packet": "Server sent an invalid packet", + "multiplayer.disconnect.invalid_player_data": "Invalid player data", + "multiplayer.disconnect.invalid_player_movement": "Invalid move player packet received", + "multiplayer.disconnect.invalid_vehicle_movement": "Invalid move vehicle packet received", + "multiplayer.disconnect.ip_banned": "You have been IP banned from this server", + "multiplayer.disconnect.kicked": "Kicked by an operator", + "multiplayer.disconnect.incompatible": "Incompatible client! Please use %s", + "multiplayer.disconnect.outdated_client": "Incompatible client! Please use %s", + "multiplayer.disconnect.outdated_server": "Incompatible client! Please use %s", + "multiplayer.disconnect.server_shutdown": "Server closed", + "multiplayer.disconnect.slow_login": "Took too long to log in", + "multiplayer.disconnect.unverified_username": "Failed to verify username!", + "multiplayer.disconnect.not_whitelisted": "You are not white-listed on this server!", + "multiplayer.disconnect.server_full": "The server is full!", + "multiplayer.disconnect.name_taken": "That name is already taken", + "multiplayer.disconnect.unexpected_query_response": "Unexpected custom data from client", + "multiplayer.disconnect.missing_tags": "Incomplete set of tags received from server.\nPlease contact server operator.", + "multiplayer.disconnect.expired_public_key": "Expired profile public key. Check that your system time is synchronized, and try restarting your game.", + "multiplayer.disconnect.invalid_public_key_signature": "Invalid signature for profile public key.\nTry restarting your game.", + "multiplayer.disconnect.out_of_order_chat": "Out-of-order chat packet received. Did your system time change?", + "multiplayer.disconnect.unsigned_chat": "Received chat packet with missing or invalid signature.", + "multiplayer.disconnect.too_many_pending_chats": "Too many unacknowledged chat messages", + "multiplayer.disconnect.chat_validation_failed": "Chat message validation failure", + "multiplayer.socialInteractions.not_available": "Social Interactions are only available in Multiplayer worlds", + "multiplayer.unsecureserver.toast.title": "Chat messages can't be verified", + "multiplayer.unsecureserver.toast": "Messages sent on this server may be modified and might not reflect the original message", + "chat.editBox": "chat", + "chat.cannotSend": "Cannot send chat message", + "chat.disabled.options": "Chat disabled in client options", + "chat.disabled.launcher": "Chat disabled by launcher option. Cannot send message", + "chat.disabled.profile": "Chat not allowed by account settings. Press '%s' again for more information", + "chat.disabled.profile.moreInfo": "Chat not allowed by account settings. Cannot send or view messages.", + "chat.disabled.expiredProfileKey": "Chat disabled due to expired profile public key. Please try reconnecting.", + "chat.disabled.chain_broken": "Chat disabled due to broken chain. Please try reconnecting.", + "chat.disabled.missingProfileKey": "Chat disabled due to missing profile public key. Please try reconnecting.", + "chat.type.text": "<%s> %s", + "chat.type.text.narrate": "%s says %s", + "chat.type.emote": "* %s %s", + "chat.type.announcement": "[%s] %s", + "chat.type.admin": "[%s: %s]", + "chat.type.advancement.task": "%s has made the advancement %s", + "chat.type.advancement.challenge": "%s has completed the challenge %s", + "chat.type.advancement.goal": "%s has reached the goal %s", + "chat.type.team.text": "%s <%s> %s", + "chat.type.team.sent": "-> %s <%s> %s", + "chat.type.team.hover": "Message Team", + "chat.link.confirm": "Are you sure you want to open the following website?", + "chat.link.warning": "Never open links from people that you don't trust!", + "chat.copy": "Copy to Clipboard", + "chat.copy.click": "Click to Copy to Clipboard", + "chat.link.confirmTrusted": "Do you want to open this link or copy it to your clipboard?", + "chat.link.open": "Open in Browser", + "chat.coordinates": "%s, %s, %s", + "chat.coordinates.tooltip": "Click to teleport", + "chat.queue": "[+%s pending lines]", + "chat.square_brackets": "[%s]", + "chat.tag.system": "Server message. Cannot be reported.", + "chat.tag.system_single_player": "Server message.", + "chat.tag.not_secure": "Unverified message. Cannot be reported.", + "chat.tag.modified": "Message modified by the server. Original:", + "chat.filtered_full": "The server has hidden your message for some players.", + "chat.filtered": "Filtered by the server.", + "chat.deleted_marker": "This chat message has been deleted by the server.", + "menu.playdemo": "Play Demo World", + "menu.resetdemo": "Reset Demo World", + "demo.day.1": "This demo will last five game days. Do your best!", + "demo.day.2": "Day Two", + "demo.day.3": "Day Three", + "demo.day.4": "Day Four", + "demo.day.5": "This is your last day!", + "demo.day.warning": "Your time is almost up!", + "demo.day.6": "You have passed your fifth day. Use %s to save a screenshot of your creation.", + "demo.reminder": "The demo time has expired. Buy the game to continue or start a new world!", + "demo.remainingTime": "Remaining time: %s", + "demo.demoExpired": "Demo time's up!", + "demo.help.movement": "Use the %1$s, %2$s, %3$s, %4$s keys and the mouse to move around", + "demo.help.movementShort": "Move by pressing the %1$s, %2$s, %3$s, %4$s keys", + "demo.help.movementMouse": "Look around using the mouse", + "demo.help.jump": "Jump by pressing the %1$s key", + "demo.help.inventory": "Use the %1$s key to open your inventory", + "demo.help.title": "Minecraft Demo Mode", + "demo.help.fullWrapped": "This demo will last 5 in-game days (about 1 hour and 40 minutes of real time). Check the advancements for hints! Have fun!", + "demo.help.buy": "Purchase Now!", + "demo.help.later": "Continue Playing!", + "connect.connecting": "Connecting to the server...", + "connect.aborted": "Aborted", + "connect.authorizing": "Logging in...", + "connect.negotiating": "Negotiating...", + "connect.encrypting": "Encrypting...", + "connect.joining": "Joining world...", + "connect.failed": "Failed to connect to the server", + "disconnect.genericReason": "%s", + "disconnect.unknownHost": "Unknown host", + "disconnect.disconnected": "Disconnected by Server", + "disconnect.lost": "Connection Lost", + "disconnect.kicked": "Was kicked from the game", + "disconnect.timeout": "Timed out", + "disconnect.closed": "Connection closed", + "disconnect.loginFailed": "Failed to log in", + "disconnect.loginFailedInfo": "Failed to log in: %s", + "disconnect.loginFailedInfo.serversUnavailable": "The authentication servers are currently not reachable. Please try again.", + "disconnect.loginFailedInfo.invalidSession": "Invalid session (Try restarting your game and the launcher)", + "disconnect.loginFailedInfo.insufficientPrivileges": "Multiplayer is disabled. Please check your Microsoft account settings.", + "disconnect.loginFailedInfo.userBanned": "You are banned from playing online", + "disconnect.quitting": "Quitting", + "disconnect.endOfStream": "End of stream", + "disconnect.overflow": "Buffer overflow", + "disconnect.spam": "Kicked for spamming", + "disconnect.exceeded_packet_rate": "Kicked for exceeding packet rate limit", + "soundCategory.master": "Master Volume", + "soundCategory.music": "Music", + "soundCategory.record": "Jukebox/Note Blocks", + "soundCategory.weather": "Weather", + "soundCategory.hostile": "Hostile Creatures", + "soundCategory.neutral": "Friendly Creatures", + "soundCategory.player": "Players", + "soundCategory.block": "Blocks", + "soundCategory.ambient": "Ambient/Environment", + "soundCategory.voice": "Voice/Speech", + "record.nowPlaying": "Now Playing: %s", + "options.off": "OFF", + "options.on": "ON", + "options.off.composed": "%s: OFF", + "options.on.composed": "%s: ON", + "options.generic_value": "%s: %s", + "options.pixel_value": "%s: %spx", + "options.percent_value": "%s: %s%%", + "options.percent_add_value": "%s: +%s%%", + "options.visible": "Shown", + "options.hidden": "Hidden", + "options.title": "Options", + "options.controls": "Controls...", + "options.video": "Video Settings...", + "options.language": "Language...", + "options.sounds": "Music & Sounds...", + "options.sounds.title": "Music & Sound Options", + "options.languageWarning": "Language translations may not be 100%% accurate", + "options.videoTitle": "Video Settings", + "options.mouse_settings": "Mouse Settings...", + "options.mouse_settings.title": "Mouse Settings", + "options.customizeTitle": "Customize World Settings", + "options.invertMouse": "Invert Mouse", + "options.fov": "FOV", + "options.fov.min": "Normal", + "options.fov.max": "Quake Pro", + "options.screenEffectScale": "Distortion Effects", + "options.screenEffectScale.tooltip": "Strength of nausea and Nether portal screen distortion effects.\nAt lower values, the nausea effect is replaced with a green overlay.", + "options.fovEffectScale": "FOV Effects", + "options.fovEffectScale.tooltip": "Controls how much the field of view can change with gameplay effects.", + "options.darknessEffectScale": "Darkness Pulsing", + "options.darknessEffectScale.tooltip": "Controls how much the Darkness effect pulses when a Warden or Sculk Shrieker gives it to you.", + "options.biomeBlendRadius": "Biome Blend", + "options.biomeBlendRadius.1": "OFF (Fastest)", + "options.biomeBlendRadius.3": "3x3 (Fast)", + "options.biomeBlendRadius.5": "5x5 (Normal)", + "options.biomeBlendRadius.7": "7x7 (High)", + "options.biomeBlendRadius.9": "9x9 (Very High)", + "options.biomeBlendRadius.11": "11x11 (Extreme)", + "options.biomeBlendRadius.13": "13x13 (Showoff)", + "options.biomeBlendRadius.15": "15x15 (Maximum)", + "options.gamma": "Brightness", + "options.gamma.min": "Moody", + "options.gamma.default": "Default", + "options.gamma.max": "Bright", + "options.sensitivity": "Sensitivity", + "options.sensitivity.min": "*yawn*", + "options.sensitivity.max": "HYPERSPEED!!!", + "options.renderDistance": "Render Distance", + "options.simulationDistance": "Simulation Distance", + "options.entityDistanceScaling": "Entity Distance", + "options.viewBobbing": "View Bobbing", + "options.ao": "Smooth Lighting", + "options.ao.off": "OFF", + "options.ao.min": "Minimum", + "options.ao.max": "Maximum", + "options.prioritizeChunkUpdates": "Chunk Builder", + "options.prioritizeChunkUpdates.none": "Threaded", + "options.prioritizeChunkUpdates.byPlayer": "Semi Blocking", + "options.prioritizeChunkUpdates.nearby": "Fully Blocking", + "options.prioritizeChunkUpdates.none.tooltip": "Nearby chunks are compiled in parallel threads. This may result in brief visual holes when blocks are destroyed.", + "options.prioritizeChunkUpdates.byPlayer.tooltip": "Some actions within a chunk will recompile the chunk immediately. This includes block placing & destroying.", + "options.prioritizeChunkUpdates.nearby.tooltip": "Nearby chunks are always compiled immediately. This may impact game performance when blocks are placed or destroyed.", + "options.chunks": "%s chunks", + "options.framerate": "%s fps", + "options.framerateLimit": "Max Framerate", + "options.framerateLimit.max": "Unlimited", + "options.difficulty": "Difficulty", + "options.difficulty.online": "Server Difficulty", + "options.difficulty.peaceful": "Peaceful", + "options.difficulty.easy": "Easy", + "options.difficulty.normal": "Normal", + "options.difficulty.hard": "Hard", + "options.difficulty.hardcore": "Hardcore", + "options.graphics": "Graphics", + "options.graphics.fabulous.tooltip": "%s graphics uses screen shaders for drawing weather, clouds, and particles behind translucent blocks and water.\nThis may severely impact performance for portable devices and 4K displays.", + "options.graphics.fabulous": "Fabulous!", + "options.graphics.fancy.tooltip": "Fancy graphics balances performance and quality for the majority of machines.\nWeather, clouds, and particles may not appear behind translucent blocks or water.", + "options.graphics.fancy": "Fancy", + "options.graphics.fast.tooltip": "Fast graphics reduces the amount of visible rain and snow.\nTransparency effects are disabled for various blocks such as leaves.", + "options.graphics.fast": "Fast", + "options.graphics.warning.title": "Graphics Device Unsupported", + "options.graphics.warning.message": "Your graphics device is detected as unsupported for the %s graphics option.\n\nYou may ignore this and continue, however support will not be provided for your device if you choose to use %s graphics.", + "options.graphics.warning.renderer": "Renderer detected: [%s]", + "options.graphics.warning.vendor": "Vendor detected: [%s]", + "options.graphics.warning.version": "OpenGL Version detected: [%s]", + "options.graphics.warning.accept": "Continue without Support", + "options.graphics.warning.cancel": "Take me Back", + "options.clouds.fancy": "Fancy", + "options.clouds.fast": "Fast", + "options.guiScale": "GUI Scale", + "options.guiScale.auto": "Auto", + "options.renderClouds": "Clouds", + "options.particles": "Particles", + "options.particles.all": "All", + "options.particles.decreased": "Decreased", + "options.particles.minimal": "Minimal", + "options.multiplayer.title": "Multiplayer Settings...", + "options.chat.title": "Chat Settings...", + "options.chat.visibility": "Chat", + "options.chat.visibility.full": "Shown", + "options.chat.visibility.system": "Commands Only", + "options.chat.visibility.hidden": "Hidden", + "options.chat.color": "Colors", + "options.chat.opacity": "Chat Text Opacity", + "options.chat.line_spacing": "Line Spacing", + "options.chat.links": "Web Links", + "options.chat.links.prompt": "Prompt on Links", + "options.chat.delay_none": "Chat Delay: None", + "options.chat.delay": "Chat Delay: %s seconds", + "options.chat.scale": "Chat Text Size", + "options.chat.width": "Width", + "options.chat.height.focused": "Focused Height", + "options.chat.height.unfocused": "Unfocused Height", + "options.onlyShowSecureChat": "Only Show Secure Chat", + "options.onlyShowSecureChat.tooltip": "Only display messages from other players that can be verified to have been sent by that player, and have not been modified.", + "options.accessibility.title": "Accessibility Settings...", + "options.accessibility.text_background": "Text Background", + "options.accessibility.text_background.chat": "Chat", + "options.accessibility.text_background.everywhere": "Everywhere", + "options.accessibility.text_background_opacity": "Text Background Opacity", + "options.accessibility.panorama_speed": "Panorama Scroll Speed", + "options.accessibility.link": "Accessibility Guide", + "options.telemetry": "Telemetry Data", + "options.telemetry.button": "Data Collection", + "options.telemetry.state.none": "None", + "options.telemetry.state.minimal": "Minimal", + "options.telemetry.state.all": "All", + "options.telemetry.button.tooltip": "\"%s\" includes only the required data.\n\"%s\" includes optional, as well as the required data.", + "options.audioDevice": "Device", + "options.audioDevice.default": "System Default", + "options.key.toggle": "Toggle", + "options.key.hold": "Hold", + "options.skinCustomisation": "Skin Customization...", + "options.skinCustomisation.title": "Skin Customization", + "options.modelPart.cape": "Cape", + "options.modelPart.hat": "Hat", + "options.modelPart.jacket": "Jacket", + "options.modelPart.left_sleeve": "Left Sleeve", + "options.modelPart.right_sleeve": "Right Sleeve", + "options.modelPart.left_pants_leg": "Left Pants Leg", + "options.modelPart.right_pants_leg": "Right Pants Leg", + "options.resourcepack": "Resource Packs...", + "options.fullscreen": "Fullscreen", + "options.vsync": "VSync", + "options.touchscreen": "Touchscreen Mode", + "options.reducedDebugInfo": "Reduced Debug Info", + "options.entityShadows": "Entity Shadows", + "options.mainHand": "Main Hand", + "options.mainHand.left": "Left", + "options.mainHand.right": "Right", + "options.attackIndicator": "Attack Indicator", + "options.attack.crosshair": "Crosshair", + "options.attack.hotbar": "Hotbar", + "options.showSubtitles": "Show Subtitles", + "options.directionalAudio": "Directional Audio", + "options.directionalAudio.on.tooltip": "Uses HRTF-based directional audio to improve the simulation of 3D sound. Requires HRTF compatible audio hardware, and is best experienced with headphones.", + "options.directionalAudio.off.tooltip": "Classic Stereo sound", + "options.online": "Online...", + "options.online.title": "Online Options", + "options.allowServerListing": "Allow Server Listings", + "options.allowServerListing.tooltip": "Servers may list online players as part of their public status.\nWith this option off your name will not show up in such lists.", + "options.realmsNotifications": "Realms Notifications", + "options.autoJump": "Auto-Jump", + "options.operatorItemsTab": "Operator Items Tab", + "options.autoSuggestCommands": "Command Suggestions", + "options.autosaveIndicator": "Autosave Indicator", + "options.discrete_mouse_scroll": "Discrete Scrolling", + "options.mouseWheelSensitivity": "Scroll Sensitivity", + "options.rawMouseInput": "Raw Input", + "options.narrator": "Narrator", + "options.narrator.off": "OFF", + "options.narrator.all": "Narrates All", + "options.narrator.chat": "Narrates Chat", + "options.narrator.system": "Narrates System", + "options.narrator.notavailable": "Not Available", + "options.fullscreen.resolution": "Fullscreen Resolution", + "options.fullscreen.unavailable": "Setting unavailable", + "options.fullscreen.current": "Current", + "options.mipmapLevels": "Mipmap Levels", + "options.forceUnicodeFont": "Force Unicode Font", + "options.hideMatchedNames": "Hide Matched Names", + "options.hideMatchedNames.tooltip": "3rd-party Servers may send chat messages in non-standard formats.\nWith this option on: hidden players will be matched based on chat sender names.", + "options.darkMojangStudiosBackgroundColor": "Monochrome Logo", + "options.darkMojangStudiosBackgroundColor.tooltip": "Changes the Mojang Studios loading screen background color to black.", + "options.hideLightningFlashes": "Hide Lightning Flashes", + "options.hideLightningFlashes.tooltip": "Prevents lightning bolts from making the sky flash. The bolts themselves will still be visible.", + "narrator.toast.disabled": "Narrator Disabled", + "narrator.toast.enabled": "Narrator Enabled", + "difficulty.lock.title": "Lock World Difficulty", + "difficulty.lock.question": "Are you sure you want to lock the difficulty of this world? This will set this world to always be %1$s, and you will never be able to change that again.", + "title.32bit.deprecation": "32-bit system detected: this may prevent you from playing in the future as a 64-bit system will be required!", + "title.32bit.deprecation.realms.header": "32-bit system detected", + "title.32bit.deprecation.realms": "Minecraft will soon require a 64-bit system, which will prevent you from playing or using Realms on this device. You will need to manually cancel any Realms subscription.", + "title.32bit.deprecation.realms.check": "Do not show this screen again", + "title.multiplayer.disabled": "Multiplayer is disabled. Please check your Microsoft account settings.", + "title.multiplayer.disabled.banned.temporary": "Your account is temporarily suspended from online play", + "title.multiplayer.disabled.banned.permanent": "Your account is permanently suspended from online play", + "controls.title": "Controls", + "controls.reset": "Reset", + "controls.resetAll": "Reset Keys", + "controls.keybinds": "Key Binds...", + "controls.keybinds.title": "Key Binds", + "key.sprint": "Sprint", + "key.forward": "Walk Forwards", + "key.left": "Strafe Left", + "key.back": "Walk Backwards", + "key.right": "Strafe Right", + "key.jump": "Jump", + "key.inventory": "Open/Close Inventory", + "key.drop": "Drop Selected Item", + "key.swapOffhand": "Swap Item With Offhand", + "key.chat": "Open Chat", + "key.sneak": "Sneak", + "key.playerlist": "List Players", + "key.attack": "Attack/Destroy", + "key.use": "Use Item/Place Block", + "key.pickItem": "Pick Block", + "key.command": "Open Command", + "key.socialInteractions": "Social Interactions Screen", + "key.screenshot": "Take Screenshot", + "key.togglePerspective": "Toggle Perspective", + "key.smoothCamera": "Toggle Cinematic Camera", + "key.fullscreen": "Toggle Fullscreen", + "key.spectatorOutlines": "Highlight Players (Spectators)", + "key.hotbar.1": "Hotbar Slot 1", + "key.hotbar.2": "Hotbar Slot 2", + "key.hotbar.3": "Hotbar Slot 3", + "key.hotbar.4": "Hotbar Slot 4", + "key.hotbar.5": "Hotbar Slot 5", + "key.hotbar.6": "Hotbar Slot 6", + "key.hotbar.7": "Hotbar Slot 7", + "key.hotbar.8": "Hotbar Slot 8", + "key.hotbar.9": "Hotbar Slot 9", + "key.saveToolbarActivator": "Save Hotbar Activator", + "key.loadToolbarActivator": "Load Hotbar Activator", + "key.advancements": "Advancements", + "key.categories.movement": "Movement", + "key.categories.misc": "Miscellaneous", + "key.categories.multiplayer": "Multiplayer", + "key.categories.gameplay": "Gameplay", + "key.categories.ui": "Game Interface", + "key.categories.inventory": "Inventory", + "key.categories.creative": "Creative Mode", + "key.mouse.left": "Left Button", + "key.mouse.right": "Right Button", + "key.mouse.middle": "Middle Button", + "key.mouse": "Button %1$s", + "key.keyboard.unknown": "Not bound", + "key.keyboard.apostrophe": "'", + "key.keyboard.backslash": "\\", + "key.keyboard.backspace": "Backspace", + "key.keyboard.comma": ",", + "key.keyboard.delete": "Delete", + "key.keyboard.end": "End", + "key.keyboard.enter": "Enter", + "key.keyboard.equal": "=", + "key.keyboard.escape": "Escape", + "key.keyboard.f1": "F1", + "key.keyboard.f2": "F2", + "key.keyboard.f3": "F3", + "key.keyboard.f4": "F4", + "key.keyboard.f5": "F5", + "key.keyboard.f6": "F6", + "key.keyboard.f7": "F7", + "key.keyboard.f8": "F8", + "key.keyboard.f9": "F9", + "key.keyboard.f10": "F10", + "key.keyboard.f11": "F11", + "key.keyboard.f12": "F12", + "key.keyboard.f13": "F13", + "key.keyboard.f14": "F14", + "key.keyboard.f15": "F15", + "key.keyboard.f16": "F16", + "key.keyboard.f17": "F17", + "key.keyboard.f18": "F18", + "key.keyboard.f19": "F19", + "key.keyboard.f20": "F20", + "key.keyboard.f21": "F21", + "key.keyboard.f22": "F22", + "key.keyboard.f23": "F23", + "key.keyboard.f24": "F24", + "key.keyboard.f25": "F25", + "key.keyboard.grave.accent": "`", + "key.keyboard.home": "Home", + "key.keyboard.insert": "Insert", + "key.keyboard.keypad.0": "Keypad 0", + "key.keyboard.keypad.1": "Keypad 1", + "key.keyboard.keypad.2": "Keypad 2", + "key.keyboard.keypad.3": "Keypad 3", + "key.keyboard.keypad.4": "Keypad 4", + "key.keyboard.keypad.5": "Keypad 5", + "key.keyboard.keypad.6": "Keypad 6", + "key.keyboard.keypad.7": "Keypad 7", + "key.keyboard.keypad.8": "Keypad 8", + "key.keyboard.keypad.9": "Keypad 9", + "key.keyboard.keypad.add": "Keypad +", + "key.keyboard.keypad.decimal": "Keypad Decimal", + "key.keyboard.keypad.enter": "Keypad Enter", + "key.keyboard.keypad.equal": "Keypad =", + "key.keyboard.keypad.multiply": "Keypad *", + "key.keyboard.keypad.divide": "Keypad /", + "key.keyboard.keypad.subtract": "Keypad -", + "key.keyboard.left.bracket": "[", + "key.keyboard.right.bracket": "]", + "key.keyboard.minus": "-", + "key.keyboard.num.lock": "Num Lock", + "key.keyboard.caps.lock": "Caps Lock", + "key.keyboard.scroll.lock": "Scroll Lock", + "key.keyboard.page.down": "Page Down", + "key.keyboard.page.up": "Page Up", + "key.keyboard.pause": "Pause", + "key.keyboard.period": ".", + "key.keyboard.left.control": "Left Control", + "key.keyboard.right.control": "Right Control", + "key.keyboard.left.alt": "Left Alt", + "key.keyboard.right.alt": "Right Alt", + "key.keyboard.left.shift": "Left Shift", + "key.keyboard.right.shift": "Right Shift", + "key.keyboard.left.win": "Left Win", + "key.keyboard.right.win": "Right Win", + "key.keyboard.semicolon": ";", + "key.keyboard.slash": "/", + "key.keyboard.space": "Space", + "key.keyboard.tab": "Tab", + "key.keyboard.up": "Up Arrow", + "key.keyboard.down": "Down Arrow", + "key.keyboard.left": "Left Arrow", + "key.keyboard.right": "Right Arrow", + "key.keyboard.menu": "Menu", + "key.keyboard.print.screen": "Print Screen", + "key.keyboard.world.1": "World 1", + "key.keyboard.world.2": "World 2", + "pack.available.title": "Available", + "pack.selected.title": "Selected", + "pack.incompatible": "Incompatible", + "pack.incompatible.old": "(Made for an older version of Minecraft)", + "pack.incompatible.new": "(Made for a newer version of Minecraft)", + "pack.incompatible.confirm.title": "Are you sure you want to load this pack?", + "pack.incompatible.confirm.old": "This pack was made for an older version of Minecraft and may no longer work correctly.", + "pack.incompatible.confirm.new": "This pack was made for a newer version of Minecraft and may not work correctly.", + "pack.dropInfo": "Drag and drop files into this window to add packs", + "pack.dropConfirm": "Do you want to add the following packs to Minecraft?", + "pack.copyFailure": "Failed to copy packs", + "pack.nameAndSource": "%s (%s)", + "pack.openFolder": "Open Pack Folder", + "pack.folderInfo": "(Place pack files here)", + "resourcePack.title": "Select Resource Packs", + "resourcePack.server.name": "World Specific Resources", + "resourcePack.programmer_art.name": "Programmer Art", + "resourcePack.broken_assets": "BROKEN ASSETS DETECTED", + "resourcePack.vanilla.name": "Default", + "resourcePack.vanilla.description": "The default look and feel of Minecraft", + "resourcePack.load_fail": "Resource reload failed", + "dataPack.title": "Select Data Packs", + "dataPack.validation.working": "Validating selected data packs...", + "dataPack.validation.failed": "Data pack validation failed!", + "dataPack.validation.back": "Go Back", + "dataPack.validation.reset": "Reset to Default", + "dataPack.vanilla.name": "Default", + "dataPack.vanilla.description": "The default data for Minecraft", + "dataPack.bundle.description": "Enables experimental Bundle item", + "dataPack.update_1_20.description": "New features and content for Minecraft 1.20", + "sign.edit": "Edit Sign Message", + "hanging_sign.edit": "Edit Hanging Sign Message", + "book.pageIndicator": "Page %1$s of %2$s", + "book.byAuthor": "by %1$s", + "book.signButton": "Sign", + "book.editTitle": "Enter Book Title:", + "book.finalizeButton": "Sign and Close", + "book.finalizeWarning": "Note! When you sign the book, it will no longer be editable.", + "book.generation.0": "Original", + "book.generation.1": "Copy of original", + "book.generation.2": "Copy of a copy", + "book.generation.3": "Tattered", + "book.invalid.tag": "* Invalid book tag *", + "merchant.deprecated": "Villagers restock up to two times per day.", + "merchant.current_level": "Trader's current level", + "merchant.next_level": "Trader's next level", + "merchant.level.1": "Novice", + "merchant.level.2": "Apprentice", + "merchant.level.3": "Journeyman", + "merchant.level.4": "Expert", + "merchant.level.5": "Master", + "merchant.trades": "Trades", + "block.minecraft.air": "Air", + "block.minecraft.barrier": "Barrier", + "block.minecraft.light": "Light", + "block.minecraft.stone": "Stone", + "block.minecraft.granite": "Granite", + "block.minecraft.polished_granite": "Polished Granite", + "block.minecraft.diorite": "Diorite", + "block.minecraft.polished_diorite": "Polished Diorite", + "block.minecraft.andesite": "Andesite", + "block.minecraft.polished_andesite": "Polished Andesite", + "block.minecraft.hay_block": "Hay Bale", + "block.minecraft.grass_block": "Grass Block", + "block.minecraft.dirt": "Dirt", + "block.minecraft.coarse_dirt": "Coarse Dirt", + "block.minecraft.podzol": "Podzol", + "block.minecraft.cobblestone": "Cobblestone", + "block.minecraft.oak_planks": "Oak Planks", + "block.minecraft.spruce_planks": "Spruce Planks", + "block.minecraft.birch_planks": "Birch Planks", + "block.minecraft.jungle_planks": "Jungle Planks", + "block.minecraft.acacia_planks": "Acacia Planks", + "block.minecraft.dark_oak_planks": "Dark Oak Planks", + "block.minecraft.mangrove_planks": "Mangrove Planks", + "block.minecraft.bamboo_planks": "Bamboo Planks", + "block.minecraft.bamboo_mosaic": "Bamboo Mosaic", + "block.minecraft.oak_sapling": "Oak Sapling", + "block.minecraft.spruce_sapling": "Spruce Sapling", + "block.minecraft.birch_sapling": "Birch Sapling", + "block.minecraft.jungle_sapling": "Jungle Sapling", + "block.minecraft.acacia_sapling": "Acacia Sapling", + "block.minecraft.dark_oak_sapling": "Dark Oak Sapling", + "block.minecraft.mangrove_propagule": "Mangrove Propagule", + "block.minecraft.oak_door": "Oak Door", + "block.minecraft.spruce_door": "Spruce Door", + "block.minecraft.birch_door": "Birch Door", + "block.minecraft.jungle_door": "Jungle Door", + "block.minecraft.acacia_door": "Acacia Door", + "block.minecraft.dark_oak_door": "Dark Oak Door", + "block.minecraft.mangrove_door": "Mangrove Door", + "block.minecraft.bamboo_door": "Bamboo Door", + "block.minecraft.bedrock": "Bedrock", + "block.minecraft.water": "Water", + "block.minecraft.lava": "Lava", + "block.minecraft.sand": "Sand", + "block.minecraft.red_sand": "Red Sand", + "block.minecraft.sandstone": "Sandstone", + "block.minecraft.chiseled_sandstone": "Chiseled Sandstone", + "block.minecraft.cut_sandstone": "Cut Sandstone", + "block.minecraft.red_sandstone": "Red Sandstone", + "block.minecraft.chiseled_red_sandstone": "Chiseled Red Sandstone", + "block.minecraft.cut_red_sandstone": "Cut Red Sandstone", + "block.minecraft.gravel": "Gravel", + "block.minecraft.gold_ore": "Gold Ore", + "block.minecraft.deepslate_gold_ore": "Deepslate Gold Ore", + "block.minecraft.nether_gold_ore": "Nether Gold Ore", + "block.minecraft.iron_ore": "Iron Ore", + "block.minecraft.deepslate_iron_ore": "Deepslate Iron Ore", + "block.minecraft.coal_ore": "Coal Ore", + "block.minecraft.deepslate_coal_ore": "Deepslate Coal Ore", + "block.minecraft.oak_wood": "Oak Wood", + "block.minecraft.spruce_wood": "Spruce Wood", + "block.minecraft.birch_wood": "Birch Wood", + "block.minecraft.jungle_wood": "Jungle Wood", + "block.minecraft.acacia_wood": "Acacia Wood", + "block.minecraft.dark_oak_wood": "Dark Oak Wood", + "block.minecraft.mangrove_wood": "Mangrove Wood", + "block.minecraft.oak_log": "Oak Log", + "block.minecraft.spruce_log": "Spruce Log", + "block.minecraft.birch_log": "Birch Log", + "block.minecraft.jungle_log": "Jungle Log", + "block.minecraft.acacia_log": "Acacia Log", + "block.minecraft.dark_oak_log": "Dark Oak Log", + "block.minecraft.mangrove_log": "Mangrove Log", + "block.minecraft.mangrove_roots": "Mangrove Roots", + "block.minecraft.muddy_mangrove_roots": "Muddy Mangrove Roots", + "block.minecraft.bamboo_block": "Block of Bamboo", + "block.minecraft.stripped_oak_log": "Stripped Oak Log", + "block.minecraft.stripped_spruce_log": "Stripped Spruce Log", + "block.minecraft.stripped_birch_log": "Stripped Birch Log", + "block.minecraft.stripped_jungle_log": "Stripped Jungle Log", + "block.minecraft.stripped_acacia_log": "Stripped Acacia Log", + "block.minecraft.stripped_dark_oak_log": "Stripped Dark Oak Log", + "block.minecraft.stripped_mangrove_log": "Stripped Mangrove Log", + "block.minecraft.stripped_bamboo_block": "Block of Stripped Bamboo", + "block.minecraft.stripped_oak_wood": "Stripped Oak Wood", + "block.minecraft.stripped_spruce_wood": "Stripped Spruce Wood", + "block.minecraft.stripped_birch_wood": "Stripped Birch Wood", + "block.minecraft.stripped_jungle_wood": "Stripped Jungle Wood", + "block.minecraft.stripped_acacia_wood": "Stripped Acacia Wood", + "block.minecraft.stripped_dark_oak_wood": "Stripped Dark Oak Wood", + "block.minecraft.stripped_mangrove_wood": "Stripped Mangrove Wood", + "block.minecraft.oak_leaves": "Oak Leaves", + "block.minecraft.spruce_leaves": "Spruce Leaves", + "block.minecraft.birch_leaves": "Birch Leaves", + "block.minecraft.jungle_leaves": "Jungle Leaves", + "block.minecraft.acacia_leaves": "Acacia Leaves", + "block.minecraft.dark_oak_leaves": "Dark Oak Leaves", + "block.minecraft.mangrove_leaves": "Mangrove Leaves", + "block.minecraft.dead_bush": "Dead Bush", + "block.minecraft.grass": "Grass", + "block.minecraft.fern": "Fern", + "block.minecraft.sponge": "Sponge", + "block.minecraft.wet_sponge": "Wet Sponge", + "block.minecraft.glass": "Glass", + "block.minecraft.kelp_plant": "Kelp Plant", + "block.minecraft.kelp": "Kelp", + "block.minecraft.dried_kelp_block": "Dried Kelp Block", + "block.minecraft.white_stained_glass": "White Stained Glass", + "block.minecraft.orange_stained_glass": "Orange Stained Glass", + "block.minecraft.magenta_stained_glass": "Magenta Stained Glass", + "block.minecraft.light_blue_stained_glass": "Light Blue Stained Glass", + "block.minecraft.yellow_stained_glass": "Yellow Stained Glass", + "block.minecraft.lime_stained_glass": "Lime Stained Glass", + "block.minecraft.pink_stained_glass": "Pink Stained Glass", + "block.minecraft.gray_stained_glass": "Gray Stained Glass", + "block.minecraft.light_gray_stained_glass": "Light Gray Stained Glass", + "block.minecraft.cyan_stained_glass": "Cyan Stained Glass", + "block.minecraft.purple_stained_glass": "Purple Stained Glass", + "block.minecraft.blue_stained_glass": "Blue Stained Glass", + "block.minecraft.brown_stained_glass": "Brown Stained Glass", + "block.minecraft.green_stained_glass": "Green Stained Glass", + "block.minecraft.red_stained_glass": "Red Stained Glass", + "block.minecraft.black_stained_glass": "Black Stained Glass", + "block.minecraft.white_stained_glass_pane": "White Stained Glass Pane", + "block.minecraft.orange_stained_glass_pane": "Orange Stained Glass Pane", + "block.minecraft.magenta_stained_glass_pane": "Magenta Stained Glass Pane", + "block.minecraft.light_blue_stained_glass_pane": "Light Blue Stained Glass Pane", + "block.minecraft.yellow_stained_glass_pane": "Yellow Stained Glass Pane", + "block.minecraft.lime_stained_glass_pane": "Lime Stained Glass Pane", + "block.minecraft.pink_stained_glass_pane": "Pink Stained Glass Pane", + "block.minecraft.gray_stained_glass_pane": "Gray Stained Glass Pane", + "block.minecraft.light_gray_stained_glass_pane": "Light Gray Stained Glass Pane", + "block.minecraft.cyan_stained_glass_pane": "Cyan Stained Glass Pane", + "block.minecraft.purple_stained_glass_pane": "Purple Stained Glass Pane", + "block.minecraft.blue_stained_glass_pane": "Blue Stained Glass Pane", + "block.minecraft.brown_stained_glass_pane": "Brown Stained Glass Pane", + "block.minecraft.green_stained_glass_pane": "Green Stained Glass Pane", + "block.minecraft.red_stained_glass_pane": "Red Stained Glass Pane", + "block.minecraft.black_stained_glass_pane": "Black Stained Glass Pane", + "block.minecraft.glass_pane": "Glass Pane", + "block.minecraft.dandelion": "Dandelion", + "block.minecraft.poppy": "Poppy", + "block.minecraft.blue_orchid": "Blue Orchid", + "block.minecraft.allium": "Allium", + "block.minecraft.azure_bluet": "Azure Bluet", + "block.minecraft.red_tulip": "Red Tulip", + "block.minecraft.orange_tulip": "Orange Tulip", + "block.minecraft.white_tulip": "White Tulip", + "block.minecraft.pink_tulip": "Pink Tulip", + "block.minecraft.oxeye_daisy": "Oxeye Daisy", + "block.minecraft.cornflower": "Cornflower", + "block.minecraft.lily_of_the_valley": "Lily of the Valley", + "block.minecraft.wither_rose": "Wither Rose", + "block.minecraft.sunflower": "Sunflower", + "block.minecraft.lilac": "Lilac", + "block.minecraft.tall_grass": "Tall Grass", + "block.minecraft.tall_seagrass": "Tall Seagrass", + "block.minecraft.large_fern": "Large Fern", + "block.minecraft.rose_bush": "Rose Bush", + "block.minecraft.peony": "Peony", + "block.minecraft.seagrass": "Seagrass", + "block.minecraft.sea_pickle": "Sea Pickle", + "block.minecraft.brown_mushroom": "Brown Mushroom", + "block.minecraft.red_mushroom_block": "Red Mushroom Block", + "block.minecraft.brown_mushroom_block": "Brown Mushroom Block", + "block.minecraft.mushroom_stem": "Mushroom Stem", + "block.minecraft.gold_block": "Block of Gold", + "block.minecraft.iron_block": "Block of Iron", + "block.minecraft.smooth_stone": "Smooth Stone", + "block.minecraft.smooth_sandstone": "Smooth Sandstone", + "block.minecraft.smooth_red_sandstone": "Smooth Red Sandstone", + "block.minecraft.smooth_quartz": "Smooth Quartz Block", + "block.minecraft.stone_slab": "Stone Slab", + "block.minecraft.smooth_stone_slab": "Smooth Stone Slab", + "block.minecraft.sandstone_slab": "Sandstone Slab", + "block.minecraft.red_sandstone_slab": "Red Sandstone Slab", + "block.minecraft.cut_sandstone_slab": "Cut Sandstone Slab", + "block.minecraft.cut_red_sandstone_slab": "Cut Red Sandstone Slab", + "block.minecraft.petrified_oak_slab": "Petrified Oak Slab", + "block.minecraft.cobblestone_slab": "Cobblestone Slab", + "block.minecraft.brick_slab": "Brick Slab", + "block.minecraft.stone_brick_slab": "Stone Brick Slab", + "block.minecraft.mud_brick_slab": "Mud Brick Slab", + "block.minecraft.nether_brick_slab": "Nether Brick Slab", + "block.minecraft.quartz_slab": "Quartz Slab", + "block.minecraft.oak_slab": "Oak Slab", + "block.minecraft.spruce_slab": "Spruce Slab", + "block.minecraft.birch_slab": "Birch Slab", + "block.minecraft.jungle_slab": "Jungle Slab", + "block.minecraft.acacia_slab": "Acacia Slab", + "block.minecraft.dark_oak_slab": "Dark Oak Slab", + "block.minecraft.mangrove_slab": "Mangrove Slab", + "block.minecraft.bamboo_slab": "Bamboo Slab", + "block.minecraft.bamboo_mosaic_slab": "Bamboo Mosaic Slab", + "block.minecraft.dark_prismarine_slab": "Dark Prismarine Slab", + "block.minecraft.prismarine_slab": "Prismarine Slab", + "block.minecraft.prismarine_brick_slab": "Prismarine Brick Slab", + "block.minecraft.bricks": "Bricks", + "block.minecraft.tnt": "TNT", + "block.minecraft.bookshelf": "Bookshelf", + "block.minecraft.chiseled_bookshelf": "Chiseled Bookshelf", + "block.minecraft.mossy_cobblestone": "Mossy Cobblestone", + "block.minecraft.obsidian": "Obsidian", + "block.minecraft.torch": "Torch", + "block.minecraft.wall_torch": "Wall Torch", + "block.minecraft.soul_torch": "Soul Torch", + "block.minecraft.soul_wall_torch": "Soul Wall Torch", + "block.minecraft.fire": "Fire", + "block.minecraft.spawner": "Monster Spawner", + "block.minecraft.spawner.desc1": "Interact with Spawn Egg:", + "block.minecraft.spawner.desc2": "Sets Mob Type", + "block.minecraft.respawn_anchor": "Respawn Anchor", + "block.minecraft.oak_stairs": "Oak Stairs", + "block.minecraft.spruce_stairs": "Spruce Stairs", + "block.minecraft.birch_stairs": "Birch Stairs", + "block.minecraft.jungle_stairs": "Jungle Stairs", + "block.minecraft.acacia_stairs": "Acacia Stairs", + "block.minecraft.dark_oak_stairs": "Dark Oak Stairs", + "block.minecraft.mangrove_stairs": "Mangrove Stairs", + "block.minecraft.bamboo_stairs": "Bamboo Stairs", + "block.minecraft.bamboo_mosaic_stairs": "Bamboo Mosaic Stairs", + "block.minecraft.dark_prismarine_stairs": "Dark Prismarine Stairs", + "block.minecraft.prismarine_stairs": "Prismarine Stairs", + "block.minecraft.prismarine_brick_stairs": "Prismarine Brick Stairs", + "block.minecraft.chest": "Chest", + "block.minecraft.trapped_chest": "Trapped Chest", + "block.minecraft.redstone_wire": "Redstone Wire", + "block.minecraft.diamond_ore": "Diamond Ore", + "block.minecraft.deepslate_diamond_ore": "Deepslate Diamond Ore", + "block.minecraft.coal_block": "Block of Coal", + "block.minecraft.diamond_block": "Block of Diamond", + "block.minecraft.crafting_table": "Crafting Table", + "block.minecraft.wheat": "Wheat Crops", + "block.minecraft.farmland": "Farmland", + "block.minecraft.furnace": "Furnace", + "block.minecraft.oak_sign": "Oak Sign", + "block.minecraft.spruce_sign": "Spruce Sign", + "block.minecraft.birch_sign": "Birch Sign", + "block.minecraft.acacia_sign": "Acacia Sign", + "block.minecraft.jungle_sign": "Jungle Sign", + "block.minecraft.dark_oak_sign": "Dark Oak Sign", + "block.minecraft.mangrove_sign": "Mangrove Sign", + "block.minecraft.bamboo_sign": "Bamboo Sign", + "block.minecraft.oak_wall_sign": "Oak Wall Sign", + "block.minecraft.spruce_wall_sign": "Spruce Wall Sign", + "block.minecraft.birch_wall_sign": "Birch Wall Sign", + "block.minecraft.acacia_wall_sign": "Acacia Wall Sign", + "block.minecraft.jungle_wall_sign": "Jungle Wall Sign", + "block.minecraft.dark_oak_wall_sign": "Dark Oak Wall Sign", + "block.minecraft.mangrove_wall_sign": "Mangrove Wall Sign", + "block.minecraft.bamboo_wall_sign": "Bamboo Wall Sign", + "block.minecraft.oak_hanging_sign": "Oak Hanging Sign", + "block.minecraft.spruce_hanging_sign": "Spruce Hanging Sign", + "block.minecraft.birch_hanging_sign": "Birch Hanging Sign", + "block.minecraft.acacia_hanging_sign": "Acacia Hanging Sign", + "block.minecraft.jungle_hanging_sign": "Jungle Hanging Sign", + "block.minecraft.crimson_hanging_sign": "Crimson Hanging Sign", + "block.minecraft.warped_hanging_sign": "Warped Hanging Sign", + "block.minecraft.dark_oak_hanging_sign": "Dark Oak Hanging Sign", + "block.minecraft.mangrove_hanging_sign": "Mangrove Hanging Sign", + "block.minecraft.bamboo_hanging_sign": "Bamboo Hanging Sign", + "block.minecraft.oak_wall_hanging_sign": "Oak Wall Hanging Sign", + "block.minecraft.spruce_wall_hanging_sign": "Spruce Wall Hanging Sign", + "block.minecraft.birch_wall_hanging_sign": "Birch Wall Hanging Sign", + "block.minecraft.acacia_wall_hanging_sign": "Acacia Wall Hanging Sign", + "block.minecraft.jungle_wall_hanging_sign": "Jungle Wall Hanging Sign", + "block.minecraft.dark_oak_wall_hanging_sign": "Dark Oak Wall Hanging Sign", + "block.minecraft.mangrove_wall_hanging_sign": "Mangrove Wall Hanging Sign", + "block.minecraft.crimson_wall_hanging_sign": "Crimson Wall Hanging Sign", + "block.minecraft.warped_wall_hanging_sign": "Warped Wall Hanging Sign", + "block.minecraft.bamboo_wall_hanging_sign": "Bamboo Wall Hanging Sign", + "block.minecraft.ladder": "Ladder", + "block.minecraft.scaffolding": "Scaffolding", + "block.minecraft.rail": "Rail", + "block.minecraft.powered_rail": "Powered Rail", + "block.minecraft.activator_rail": "Activator Rail", + "block.minecraft.detector_rail": "Detector Rail", + "block.minecraft.cobblestone_stairs": "Cobblestone Stairs", + "block.minecraft.sandstone_stairs": "Sandstone Stairs", + "block.minecraft.red_sandstone_stairs": "Red Sandstone Stairs", + "block.minecraft.lever": "Lever", + "block.minecraft.stone_pressure_plate": "Stone Pressure Plate", + "block.minecraft.oak_pressure_plate": "Oak Pressure Plate", + "block.minecraft.spruce_pressure_plate": "Spruce Pressure Plate", + "block.minecraft.birch_pressure_plate": "Birch Pressure Plate", + "block.minecraft.jungle_pressure_plate": "Jungle Pressure Plate", + "block.minecraft.acacia_pressure_plate": "Acacia Pressure Plate", + "block.minecraft.dark_oak_pressure_plate": "Dark Oak Pressure Plate", + "block.minecraft.mangrove_pressure_plate": "Mangrove Pressure Plate", + "block.minecraft.bamboo_pressure_plate": "Bamboo Pressure Plate", + "block.minecraft.light_weighted_pressure_plate": "Light Weighted Pressure Plate", + "block.minecraft.heavy_weighted_pressure_plate": "Heavy Weighted Pressure Plate", + "block.minecraft.iron_door": "Iron Door", + "block.minecraft.redstone_ore": "Redstone Ore", + "block.minecraft.deepslate_redstone_ore": "Deepslate Redstone Ore", + "block.minecraft.redstone_torch": "Redstone Torch", + "block.minecraft.redstone_wall_torch": "Redstone Wall Torch", + "block.minecraft.stone_button": "Stone Button", + "block.minecraft.oak_button": "Oak Button", + "block.minecraft.spruce_button": "Spruce Button", + "block.minecraft.birch_button": "Birch Button", + "block.minecraft.jungle_button": "Jungle Button", + "block.minecraft.acacia_button": "Acacia Button", + "block.minecraft.dark_oak_button": "Dark Oak Button", + "block.minecraft.mangrove_button": "Mangrove Button", + "block.minecraft.bamboo_button": "Bamboo Button", + "block.minecraft.snow": "Snow", + "block.minecraft.white_carpet": "White Carpet", + "block.minecraft.orange_carpet": "Orange Carpet", + "block.minecraft.magenta_carpet": "Magenta Carpet", + "block.minecraft.light_blue_carpet": "Light Blue Carpet", + "block.minecraft.yellow_carpet": "Yellow Carpet", + "block.minecraft.lime_carpet": "Lime Carpet", + "block.minecraft.pink_carpet": "Pink Carpet", + "block.minecraft.gray_carpet": "Gray Carpet", + "block.minecraft.light_gray_carpet": "Light Gray Carpet", + "block.minecraft.cyan_carpet": "Cyan Carpet", + "block.minecraft.purple_carpet": "Purple Carpet", + "block.minecraft.blue_carpet": "Blue Carpet", + "block.minecraft.brown_carpet": "Brown Carpet", + "block.minecraft.green_carpet": "Green Carpet", + "block.minecraft.red_carpet": "Red Carpet", + "block.minecraft.black_carpet": "Black Carpet", + "block.minecraft.ice": "Ice", + "block.minecraft.frosted_ice": "Frosted Ice", + "block.minecraft.packed_ice": "Packed Ice", + "block.minecraft.blue_ice": "Blue Ice", + "block.minecraft.cactus": "Cactus", + "block.minecraft.clay": "Clay", + "block.minecraft.white_terracotta": "White Terracotta", + "block.minecraft.orange_terracotta": "Orange Terracotta", + "block.minecraft.magenta_terracotta": "Magenta Terracotta", + "block.minecraft.light_blue_terracotta": "Light Blue Terracotta", + "block.minecraft.yellow_terracotta": "Yellow Terracotta", + "block.minecraft.lime_terracotta": "Lime Terracotta", + "block.minecraft.pink_terracotta": "Pink Terracotta", + "block.minecraft.gray_terracotta": "Gray Terracotta", + "block.minecraft.light_gray_terracotta": "Light Gray Terracotta", + "block.minecraft.cyan_terracotta": "Cyan Terracotta", + "block.minecraft.purple_terracotta": "Purple Terracotta", + "block.minecraft.blue_terracotta": "Blue Terracotta", + "block.minecraft.brown_terracotta": "Brown Terracotta", + "block.minecraft.green_terracotta": "Green Terracotta", + "block.minecraft.red_terracotta": "Red Terracotta", + "block.minecraft.black_terracotta": "Black Terracotta", + "block.minecraft.terracotta": "Terracotta", + "block.minecraft.sugar_cane": "Sugar Cane", + "block.minecraft.jukebox": "Jukebox", + "block.minecraft.oak_fence": "Oak Fence", + "block.minecraft.spruce_fence": "Spruce Fence", + "block.minecraft.birch_fence": "Birch Fence", + "block.minecraft.jungle_fence": "Jungle Fence", + "block.minecraft.acacia_fence": "Acacia Fence", + "block.minecraft.dark_oak_fence": "Dark Oak Fence", + "block.minecraft.mangrove_fence": "Mangrove Fence", + "block.minecraft.bamboo_fence": "Bamboo Fence", + "block.minecraft.oak_fence_gate": "Oak Fence Gate", + "block.minecraft.spruce_fence_gate": "Spruce Fence Gate", + "block.minecraft.birch_fence_gate": "Birch Fence Gate", + "block.minecraft.jungle_fence_gate": "Jungle Fence Gate", + "block.minecraft.acacia_fence_gate": "Acacia Fence Gate", + "block.minecraft.dark_oak_fence_gate": "Dark Oak Fence Gate", + "block.minecraft.mangrove_fence_gate": "Mangrove Fence Gate", + "block.minecraft.bamboo_fence_gate": "Bamboo Fence Gate", + "block.minecraft.pumpkin_stem": "Pumpkin Stem", + "block.minecraft.attached_pumpkin_stem": "Attached Pumpkin Stem", + "block.minecraft.pumpkin": "Pumpkin", + "block.minecraft.carved_pumpkin": "Carved Pumpkin", + "block.minecraft.jack_o_lantern": "Jack o'Lantern", + "block.minecraft.netherrack": "Netherrack", + "block.minecraft.soul_sand": "Soul Sand", + "block.minecraft.glowstone": "Glowstone", + "block.minecraft.nether_portal": "Nether Portal", + "block.minecraft.white_wool": "White Wool", + "block.minecraft.orange_wool": "Orange Wool", + "block.minecraft.magenta_wool": "Magenta Wool", + "block.minecraft.light_blue_wool": "Light Blue Wool", + "block.minecraft.yellow_wool": "Yellow Wool", + "block.minecraft.lime_wool": "Lime Wool", + "block.minecraft.pink_wool": "Pink Wool", + "block.minecraft.gray_wool": "Gray Wool", + "block.minecraft.light_gray_wool": "Light Gray Wool", + "block.minecraft.cyan_wool": "Cyan Wool", + "block.minecraft.purple_wool": "Purple Wool", + "block.minecraft.blue_wool": "Blue Wool", + "block.minecraft.brown_wool": "Brown Wool", + "block.minecraft.green_wool": "Green Wool", + "block.minecraft.red_wool": "Red Wool", + "block.minecraft.black_wool": "Black Wool", + "block.minecraft.lapis_ore": "Lapis Lazuli Ore", + "block.minecraft.deepslate_lapis_ore": "Deepslate Lapis Lazuli Ore", + "block.minecraft.lapis_block": "Block of Lapis Lazuli", + "block.minecraft.dispenser": "Dispenser", + "block.minecraft.dropper": "Dropper", + "block.minecraft.note_block": "Note Block", + "block.minecraft.cake": "Cake", + "block.minecraft.bed.occupied": "This bed is occupied", + "block.minecraft.bed.obstructed": "This bed is obstructed", + "block.minecraft.bed.no_sleep": "You can sleep only at night or during thunderstorms", + "block.minecraft.bed.too_far_away": "You may not rest now; the bed is too far away", + "block.minecraft.bed.not_safe": "You may not rest now; there are monsters nearby", + "block.minecraft.spawn.not_valid": "You have no home bed or charged respawn anchor, or it was obstructed", + "block.minecraft.set_spawn": "Respawn point set", + "block.minecraft.oak_trapdoor": "Oak Trapdoor", + "block.minecraft.spruce_trapdoor": "Spruce Trapdoor", + "block.minecraft.birch_trapdoor": "Birch Trapdoor", + "block.minecraft.jungle_trapdoor": "Jungle Trapdoor", + "block.minecraft.acacia_trapdoor": "Acacia Trapdoor", + "block.minecraft.dark_oak_trapdoor": "Dark Oak Trapdoor", + "block.minecraft.mangrove_trapdoor": "Mangrove Trapdoor", + "block.minecraft.bamboo_trapdoor": "Bamboo Trapdoor", + "block.minecraft.iron_trapdoor": "Iron Trapdoor", + "block.minecraft.cobweb": "Cobweb", + "block.minecraft.stone_bricks": "Stone Bricks", + "block.minecraft.mossy_stone_bricks": "Mossy Stone Bricks", + "block.minecraft.cracked_stone_bricks": "Cracked Stone Bricks", + "block.minecraft.chiseled_stone_bricks": "Chiseled Stone Bricks", + "block.minecraft.packed_mud": "Packed Mud", + "block.minecraft.mud_bricks": "Mud Bricks", + "block.minecraft.infested_stone": "Infested Stone", + "block.minecraft.infested_cobblestone": "Infested Cobblestone", + "block.minecraft.infested_stone_bricks": "Infested Stone Bricks", + "block.minecraft.infested_mossy_stone_bricks": "Infested Mossy Stone Bricks", + "block.minecraft.infested_cracked_stone_bricks": "Infested Cracked Stone Bricks", + "block.minecraft.infested_chiseled_stone_bricks": "Infested Chiseled Stone Bricks", + "block.minecraft.piston": "Piston", + "block.minecraft.sticky_piston": "Sticky Piston", + "block.minecraft.iron_bars": "Iron Bars", + "block.minecraft.melon": "Melon", + "block.minecraft.brick_stairs": "Brick Stairs", + "block.minecraft.stone_brick_stairs": "Stone Brick Stairs", + "block.minecraft.mud_brick_stairs": "Mud Brick Stairs", + "block.minecraft.vine": "Vines", + "block.minecraft.nether_bricks": "Nether Bricks", + "block.minecraft.nether_brick_fence": "Nether Brick Fence", + "block.minecraft.nether_brick_stairs": "Nether Brick Stairs", + "block.minecraft.nether_wart": "Nether Wart", + "block.minecraft.warped_wart_block": "Warped Wart Block", + "block.minecraft.warped_stem": "Warped Stem", + "block.minecraft.stripped_warped_stem": "Stripped Warped Stem", + "block.minecraft.warped_hyphae": "Warped Hyphae", + "block.minecraft.stripped_warped_hyphae": "Stripped Warped Hyphae", + "block.minecraft.crimson_stem": "Crimson Stem", + "block.minecraft.stripped_crimson_stem": "Stripped Crimson Stem", + "block.minecraft.crimson_hyphae": "Crimson Hyphae", + "block.minecraft.stripped_crimson_hyphae": "Stripped Crimson Hyphae", + "block.minecraft.warped_nylium": "Warped Nylium", + "block.minecraft.crimson_nylium": "Crimson Nylium", + "block.minecraft.warped_fungus": "Warped Fungus", + "block.minecraft.crimson_fungus": "Crimson Fungus", + "block.minecraft.crimson_roots": "Crimson Roots", + "block.minecraft.warped_roots": "Warped Roots", + "block.minecraft.nether_sprouts": "Nether Sprouts", + "block.minecraft.shroomlight": "Shroomlight", + "block.minecraft.weeping_vines": "Weeping Vines", + "block.minecraft.weeping_vines_plant": "Weeping Vines Plant", + "block.minecraft.twisting_vines": "Twisting Vines", + "block.minecraft.twisting_vines_plant": "Twisting Vines Plant", + "block.minecraft.soul_soil": "Soul Soil", + "block.minecraft.basalt": "Basalt", + "block.minecraft.polished_basalt": "Polished Basalt", + "block.minecraft.warped_planks": "Warped Planks", + "block.minecraft.warped_slab": "Warped Slab", + "block.minecraft.warped_pressure_plate": "Warped Pressure Plate", + "block.minecraft.warped_fence": "Warped Fence", + "block.minecraft.warped_trapdoor": "Warped Trapdoor", + "block.minecraft.warped_fence_gate": "Warped Fence Gate", + "block.minecraft.warped_stairs": "Warped Stairs", + "block.minecraft.warped_button": "Warped Button", + "block.minecraft.warped_door": "Warped Door", + "block.minecraft.warped_sign": "Warped Sign", + "block.minecraft.warped_wall_sign": "Warped Wall Sign", + "block.minecraft.crimson_planks": "Crimson Planks", + "block.minecraft.crimson_slab": "Crimson Slab", + "block.minecraft.crimson_pressure_plate": "Crimson Pressure Plate", + "block.minecraft.crimson_fence": "Crimson Fence", + "block.minecraft.crimson_trapdoor": "Crimson Trapdoor", + "block.minecraft.crimson_fence_gate": "Crimson Fence Gate", + "block.minecraft.crimson_stairs": "Crimson Stairs", + "block.minecraft.crimson_button": "Crimson Button", + "block.minecraft.crimson_door": "Crimson Door", + "block.minecraft.crimson_sign": "Crimson Sign", + "block.minecraft.crimson_wall_sign": "Crimson Wall Sign", + "block.minecraft.soul_fire": "Soul Fire", + "block.minecraft.cauldron": "Cauldron", + "block.minecraft.water_cauldron": "Water Cauldron", + "block.minecraft.lava_cauldron": "Lava Cauldron", + "block.minecraft.powder_snow_cauldron": "Powder Snow Cauldron", + "block.minecraft.enchanting_table": "Enchanting Table", + "block.minecraft.anvil": "Anvil", + "block.minecraft.chipped_anvil": "Chipped Anvil", + "block.minecraft.damaged_anvil": "Damaged Anvil", + "block.minecraft.end_stone": "End Stone", + "block.minecraft.end_portal_frame": "End Portal Frame", + "block.minecraft.mycelium": "Mycelium", + "block.minecraft.lily_pad": "Lily Pad", + "block.minecraft.dragon_egg": "Dragon Egg", + "block.minecraft.redstone_lamp": "Redstone Lamp", + "block.minecraft.cocoa": "Cocoa", + "block.minecraft.ender_chest": "Ender Chest", + "block.minecraft.emerald_ore": "Emerald Ore", + "block.minecraft.deepslate_emerald_ore": "Deepslate Emerald Ore", + "block.minecraft.emerald_block": "Block of Emerald", + "block.minecraft.redstone_block": "Block of Redstone", + "block.minecraft.tripwire": "Tripwire", + "block.minecraft.tripwire_hook": "Tripwire Hook", + "block.minecraft.command_block": "Command Block", + "block.minecraft.repeating_command_block": "Repeating Command Block", + "block.minecraft.chain_command_block": "Chain Command Block", + "block.minecraft.beacon": "Beacon", + "block.minecraft.beacon.primary": "Primary Power", + "block.minecraft.beacon.secondary": "Secondary Power", + "block.minecraft.cobblestone_wall": "Cobblestone Wall", + "block.minecraft.mossy_cobblestone_wall": "Mossy Cobblestone Wall", + "block.minecraft.carrots": "Carrots", + "block.minecraft.potatoes": "Potatoes", + "block.minecraft.daylight_detector": "Daylight Detector", + "block.minecraft.nether_quartz_ore": "Nether Quartz Ore", + "block.minecraft.hopper": "Hopper", + "block.minecraft.quartz_block": "Block of Quartz", + "block.minecraft.chiseled_quartz_block": "Chiseled Quartz Block", + "block.minecraft.quartz_pillar": "Quartz Pillar", + "block.minecraft.quartz_stairs": "Quartz Stairs", + "block.minecraft.slime_block": "Slime Block", + "block.minecraft.prismarine": "Prismarine", + "block.minecraft.prismarine_bricks": "Prismarine Bricks", + "block.minecraft.dark_prismarine": "Dark Prismarine", + "block.minecraft.sea_lantern": "Sea Lantern", + "block.minecraft.end_rod": "End Rod", + "block.minecraft.chorus_plant": "Chorus Plant", + "block.minecraft.chorus_flower": "Chorus Flower", + "block.minecraft.purpur_block": "Purpur Block", + "block.minecraft.purpur_pillar": "Purpur Pillar", + "block.minecraft.purpur_stairs": "Purpur Stairs", + "block.minecraft.purpur_slab": "Purpur Slab", + "block.minecraft.end_stone_bricks": "End Stone Bricks", + "block.minecraft.beetroots": "Beetroots", + "block.minecraft.dirt_path": "Dirt Path", + "block.minecraft.magma_block": "Magma Block", + "block.minecraft.nether_wart_block": "Nether Wart Block", + "block.minecraft.red_nether_bricks": "Red Nether Bricks", + "block.minecraft.bone_block": "Bone Block", + "block.minecraft.observer": "Observer", + "block.minecraft.shulker_box": "Shulker Box", + "block.minecraft.white_shulker_box": "White Shulker Box", + "block.minecraft.orange_shulker_box": "Orange Shulker Box", + "block.minecraft.magenta_shulker_box": "Magenta Shulker Box", + "block.minecraft.light_blue_shulker_box": "Light Blue Shulker Box", + "block.minecraft.yellow_shulker_box": "Yellow Shulker Box", + "block.minecraft.lime_shulker_box": "Lime Shulker Box", + "block.minecraft.pink_shulker_box": "Pink Shulker Box", + "block.minecraft.gray_shulker_box": "Gray Shulker Box", + "block.minecraft.light_gray_shulker_box": "Light Gray Shulker Box", + "block.minecraft.cyan_shulker_box": "Cyan Shulker Box", + "block.minecraft.purple_shulker_box": "Purple Shulker Box", + "block.minecraft.blue_shulker_box": "Blue Shulker Box", + "block.minecraft.brown_shulker_box": "Brown Shulker Box", + "block.minecraft.green_shulker_box": "Green Shulker Box", + "block.minecraft.red_shulker_box": "Red Shulker Box", + "block.minecraft.black_shulker_box": "Black Shulker Box", + "block.minecraft.white_glazed_terracotta": "White Glazed Terracotta", + "block.minecraft.orange_glazed_terracotta": "Orange Glazed Terracotta", + "block.minecraft.magenta_glazed_terracotta": "Magenta Glazed Terracotta", + "block.minecraft.light_blue_glazed_terracotta": "Light Blue Glazed Terracotta", + "block.minecraft.yellow_glazed_terracotta": "Yellow Glazed Terracotta", + "block.minecraft.lime_glazed_terracotta": "Lime Glazed Terracotta", + "block.minecraft.pink_glazed_terracotta": "Pink Glazed Terracotta", + "block.minecraft.gray_glazed_terracotta": "Gray Glazed Terracotta", + "block.minecraft.light_gray_glazed_terracotta": "Light Gray Glazed Terracotta", + "block.minecraft.cyan_glazed_terracotta": "Cyan Glazed Terracotta", + "block.minecraft.purple_glazed_terracotta": "Purple Glazed Terracotta", + "block.minecraft.blue_glazed_terracotta": "Blue Glazed Terracotta", + "block.minecraft.brown_glazed_terracotta": "Brown Glazed Terracotta", + "block.minecraft.green_glazed_terracotta": "Green Glazed Terracotta", + "block.minecraft.red_glazed_terracotta": "Red Glazed Terracotta", + "block.minecraft.black_glazed_terracotta": "Black Glazed Terracotta", + "block.minecraft.black_concrete": "Black Concrete", + "block.minecraft.red_concrete": "Red Concrete", + "block.minecraft.green_concrete": "Green Concrete", + "block.minecraft.brown_concrete": "Brown Concrete", + "block.minecraft.blue_concrete": "Blue Concrete", + "block.minecraft.purple_concrete": "Purple Concrete", + "block.minecraft.cyan_concrete": "Cyan Concrete", + "block.minecraft.light_gray_concrete": "Light Gray Concrete", + "block.minecraft.gray_concrete": "Gray Concrete", + "block.minecraft.pink_concrete": "Pink Concrete", + "block.minecraft.lime_concrete": "Lime Concrete", + "block.minecraft.yellow_concrete": "Yellow Concrete", + "block.minecraft.light_blue_concrete": "Light Blue Concrete", + "block.minecraft.magenta_concrete": "Magenta Concrete", + "block.minecraft.orange_concrete": "Orange Concrete", + "block.minecraft.white_concrete": "White Concrete", + "block.minecraft.black_concrete_powder": "Black Concrete Powder", + "block.minecraft.red_concrete_powder": "Red Concrete Powder", + "block.minecraft.green_concrete_powder": "Green Concrete Powder", + "block.minecraft.brown_concrete_powder": "Brown Concrete Powder", + "block.minecraft.blue_concrete_powder": "Blue Concrete Powder", + "block.minecraft.purple_concrete_powder": "Purple Concrete Powder", + "block.minecraft.cyan_concrete_powder": "Cyan Concrete Powder", + "block.minecraft.light_gray_concrete_powder": "Light Gray Concrete Powder", + "block.minecraft.gray_concrete_powder": "Gray Concrete Powder", + "block.minecraft.pink_concrete_powder": "Pink Concrete Powder", + "block.minecraft.lime_concrete_powder": "Lime Concrete Powder", + "block.minecraft.yellow_concrete_powder": "Yellow Concrete Powder", + "block.minecraft.light_blue_concrete_powder": "Light Blue Concrete Powder", + "block.minecraft.magenta_concrete_powder": "Magenta Concrete Powder", + "block.minecraft.orange_concrete_powder": "Orange Concrete Powder", + "block.minecraft.white_concrete_powder": "White Concrete Powder", + "block.minecraft.turtle_egg": "Turtle Egg", + "block.minecraft.piston_head": "Piston Head", + "block.minecraft.moving_piston": "Moving Piston", + "block.minecraft.red_mushroom": "Red Mushroom", + "block.minecraft.snow_block": "Snow Block", + "block.minecraft.attached_melon_stem": "Attached Melon Stem", + "block.minecraft.melon_stem": "Melon Stem", + "block.minecraft.brewing_stand": "Brewing Stand", + "block.minecraft.end_portal": "End Portal", + "block.minecraft.flower_pot": "Flower Pot", + "block.minecraft.potted_oak_sapling": "Potted Oak Sapling", + "block.minecraft.potted_spruce_sapling": "Potted Spruce Sapling", + "block.minecraft.potted_birch_sapling": "Potted Birch Sapling", + "block.minecraft.potted_jungle_sapling": "Potted Jungle Sapling", + "block.minecraft.potted_acacia_sapling": "Potted Acacia Sapling", + "block.minecraft.potted_dark_oak_sapling": "Potted Dark Oak Sapling", + "block.minecraft.potted_mangrove_propagule": "Potted Mangrove Propagule", + "block.minecraft.potted_fern": "Potted Fern", + "block.minecraft.potted_dandelion": "Potted Dandelion", + "block.minecraft.potted_poppy": "Potted Poppy", + "block.minecraft.potted_blue_orchid": "Potted Blue Orchid", + "block.minecraft.potted_allium": "Potted Allium", + "block.minecraft.potted_azure_bluet": "Potted Azure Bluet", + "block.minecraft.potted_red_tulip": "Potted Red Tulip", + "block.minecraft.potted_orange_tulip": "Potted Orange Tulip", + "block.minecraft.potted_white_tulip": "Potted White Tulip", + "block.minecraft.potted_pink_tulip": "Potted Pink Tulip", + "block.minecraft.potted_oxeye_daisy": "Potted Oxeye Daisy", + "block.minecraft.potted_cornflower": "Potted Cornflower", + "block.minecraft.potted_lily_of_the_valley": "Potted Lily of the Valley", + "block.minecraft.potted_wither_rose": "Potted Wither Rose", + "block.minecraft.potted_red_mushroom": "Potted Red Mushroom", + "block.minecraft.potted_brown_mushroom": "Potted Brown Mushroom", + "block.minecraft.potted_dead_bush": "Potted Dead Bush", + "block.minecraft.potted_cactus": "Potted Cactus", + "block.minecraft.potted_bamboo": "Potted Bamboo", + "block.minecraft.potted_crimson_fungus": "Potted Crimson Fungus", + "block.minecraft.potted_warped_fungus": "Potted Warped Fungus", + "block.minecraft.potted_crimson_roots": "Potted Crimson Roots", + "block.minecraft.potted_warped_roots": "Potted Warped Roots", + "block.minecraft.potted_azalea_bush": "Potted Azalea", + "block.minecraft.potted_flowering_azalea_bush": "Potted Flowering Azalea", + "block.minecraft.skeleton_wall_skull": "Skeleton Wall Skull", + "block.minecraft.skeleton_skull": "Skeleton Skull", + "block.minecraft.wither_skeleton_wall_skull": "Wither Skeleton Wall Skull", + "block.minecraft.wither_skeleton_skull": "Wither Skeleton Skull", + "block.minecraft.zombie_wall_head": "Zombie Wall Head", + "block.minecraft.zombie_head": "Zombie Head", + "block.minecraft.player_wall_head": "Player Wall Head", + "block.minecraft.player_head": "Player Head", + "block.minecraft.player_head.named": "%s's Head", + "block.minecraft.creeper_wall_head": "Creeper Wall Head", + "block.minecraft.creeper_head": "Creeper Head", + "block.minecraft.dragon_wall_head": "Dragon Wall Head", + "block.minecraft.dragon_head": "Dragon Head", + "block.minecraft.piglin_wall_head": "Piglin Wall Head", + "block.minecraft.piglin_head": "Piglin Head", + "block.minecraft.end_gateway": "End Gateway", + "block.minecraft.structure_void": "Structure Void", + "block.minecraft.structure_block": "Structure Block", + "block.minecraft.void_air": "Void Air", + "block.minecraft.cave_air": "Cave Air", + "block.minecraft.bubble_column": "Bubble Column", + "block.minecraft.dead_tube_coral_block": "Dead Tube Coral Block", + "block.minecraft.dead_brain_coral_block": "Dead Brain Coral Block", + "block.minecraft.dead_bubble_coral_block": "Dead Bubble Coral Block", + "block.minecraft.dead_fire_coral_block": "Dead Fire Coral Block", + "block.minecraft.dead_horn_coral_block": "Dead Horn Coral Block", + "block.minecraft.tube_coral_block": "Tube Coral Block", + "block.minecraft.brain_coral_block": "Brain Coral Block", + "block.minecraft.bubble_coral_block": "Bubble Coral Block", + "block.minecraft.fire_coral_block": "Fire Coral Block", + "block.minecraft.horn_coral_block": "Horn Coral Block", + "block.minecraft.tube_coral": "Tube Coral", + "block.minecraft.brain_coral": "Brain Coral", + "block.minecraft.bubble_coral": "Bubble Coral", + "block.minecraft.fire_coral": "Fire Coral", + "block.minecraft.horn_coral": "Horn Coral", + "block.minecraft.dead_tube_coral": "Dead Tube Coral", + "block.minecraft.dead_brain_coral": "Dead Brain Coral", + "block.minecraft.dead_bubble_coral": "Dead Bubble Coral", + "block.minecraft.dead_fire_coral": "Dead Fire Coral", + "block.minecraft.dead_horn_coral": "Dead Horn Coral", + "block.minecraft.tube_coral_fan": "Tube Coral Fan", + "block.minecraft.brain_coral_fan": "Brain Coral Fan", + "block.minecraft.bubble_coral_fan": "Bubble Coral Fan", + "block.minecraft.fire_coral_fan": "Fire Coral Fan", + "block.minecraft.horn_coral_fan": "Horn Coral Fan", + "block.minecraft.dead_tube_coral_fan": "Dead Tube Coral Fan", + "block.minecraft.dead_brain_coral_fan": "Dead Brain Coral Fan", + "block.minecraft.dead_bubble_coral_fan": "Dead Bubble Coral Fan", + "block.minecraft.dead_fire_coral_fan": "Dead Fire Coral Fan", + "block.minecraft.dead_horn_coral_fan": "Dead Horn Coral Fan", + "block.minecraft.tube_coral_wall_fan": "Tube Coral Wall Fan", + "block.minecraft.brain_coral_wall_fan": "Brain Coral Wall Fan", + "block.minecraft.bubble_coral_wall_fan": "Bubble Coral Wall Fan", + "block.minecraft.fire_coral_wall_fan": "Fire Coral Wall Fan", + "block.minecraft.horn_coral_wall_fan": "Horn Coral Wall Fan", + "block.minecraft.dead_tube_coral_wall_fan": "Dead Tube Coral Wall Fan", + "block.minecraft.dead_brain_coral_wall_fan": "Dead Brain Coral Wall Fan", + "block.minecraft.dead_bubble_coral_wall_fan": "Dead Bubble Coral Wall Fan", + "block.minecraft.dead_fire_coral_wall_fan": "Dead Fire Coral Wall Fan", + "block.minecraft.dead_horn_coral_wall_fan": "Dead Horn Coral Wall Fan", + "block.minecraft.loom": "Loom", + "block.minecraft.conduit": "Conduit", + "block.minecraft.bamboo": "Bamboo", + "block.minecraft.bamboo_sapling": "Bamboo Shoot", + "block.minecraft.jigsaw": "Jigsaw Block", + "block.minecraft.composter": "Composter", + "block.minecraft.target": "Target", + "block.minecraft.polished_granite_stairs": "Polished Granite Stairs", + "block.minecraft.smooth_red_sandstone_stairs": "Smooth Red Sandstone Stairs", + "block.minecraft.mossy_stone_brick_stairs": "Mossy Stone Brick Stairs", + "block.minecraft.polished_diorite_stairs": "Polished Diorite Stairs", + "block.minecraft.mossy_cobblestone_stairs": "Mossy Cobblestone Stairs", + "block.minecraft.end_stone_brick_stairs": "End Stone Brick Stairs", + "block.minecraft.stone_stairs": "Stone Stairs", + "block.minecraft.smooth_sandstone_stairs": "Smooth Sandstone Stairs", + "block.minecraft.smooth_quartz_stairs": "Smooth Quartz Stairs", + "block.minecraft.granite_stairs": "Granite Stairs", + "block.minecraft.andesite_stairs": "Andesite Stairs", + "block.minecraft.red_nether_brick_stairs": "Red Nether Brick Stairs", + "block.minecraft.polished_andesite_stairs": "Polished Andesite Stairs", + "block.minecraft.diorite_stairs": "Diorite Stairs", + "block.minecraft.polished_granite_slab": "Polished Granite Slab", + "block.minecraft.smooth_red_sandstone_slab": "Smooth Red Sandstone Slab", + "block.minecraft.mossy_stone_brick_slab": "Mossy Stone Brick Slab", + "block.minecraft.polished_diorite_slab": "Polished Diorite Slab", + "block.minecraft.mossy_cobblestone_slab": "Mossy Cobblestone Slab", + "block.minecraft.end_stone_brick_slab": "End Stone Brick Slab", + "block.minecraft.smooth_sandstone_slab": "Smooth Sandstone Slab", + "block.minecraft.smooth_quartz_slab": "Smooth Quartz Slab", + "block.minecraft.granite_slab": "Granite Slab", + "block.minecraft.andesite_slab": "Andesite Slab", + "block.minecraft.red_nether_brick_slab": "Red Nether Brick Slab", + "block.minecraft.polished_andesite_slab": "Polished Andesite Slab", + "block.minecraft.diorite_slab": "Diorite Slab", + "block.minecraft.brick_wall": "Brick Wall", + "block.minecraft.prismarine_wall": "Prismarine Wall", + "block.minecraft.red_sandstone_wall": "Red Sandstone Wall", + "block.minecraft.mossy_stone_brick_wall": "Mossy Stone Brick Wall", + "block.minecraft.granite_wall": "Granite Wall", + "block.minecraft.stone_brick_wall": "Stone Brick Wall", + "block.minecraft.mud_brick_wall": "Mud Brick Wall", + "block.minecraft.nether_brick_wall": "Nether Brick Wall", + "block.minecraft.andesite_wall": "Andesite Wall", + "block.minecraft.red_nether_brick_wall": "Red Nether Brick Wall", + "block.minecraft.sandstone_wall": "Sandstone Wall", + "block.minecraft.end_stone_brick_wall": "End Stone Brick Wall", + "block.minecraft.diorite_wall": "Diorite Wall", + "block.minecraft.barrel": "Barrel", + "block.minecraft.smoker": "Smoker", + "block.minecraft.blast_furnace": "Blast Furnace", + "block.minecraft.cartography_table": "Cartography Table", + "block.minecraft.fletching_table": "Fletching Table", + "block.minecraft.smithing_table": "Smithing Table", + "block.minecraft.grindstone": "Grindstone", + "block.minecraft.lectern": "Lectern", + "block.minecraft.stonecutter": "Stonecutter", + "block.minecraft.bell": "Bell", + "block.minecraft.ominous_banner": "Ominous Banner", + "block.minecraft.lantern": "Lantern", + "block.minecraft.soul_lantern": "Soul Lantern", + "block.minecraft.sweet_berry_bush": "Sweet Berry Bush", + "block.minecraft.campfire": "Campfire", + "block.minecraft.soul_campfire": "Soul Campfire", + "block.minecraft.beehive": "Beehive", + "block.minecraft.bee_nest": "Bee Nest", + "block.minecraft.honey_block": "Honey Block", + "block.minecraft.honeycomb_block": "Honeycomb Block", + "block.minecraft.lodestone": "Lodestone", + "block.minecraft.netherite_block": "Block of Netherite", + "block.minecraft.ancient_debris": "Ancient Debris", + "block.minecraft.crying_obsidian": "Crying Obsidian", + "block.minecraft.blackstone": "Blackstone", + "block.minecraft.blackstone_slab": "Blackstone Slab", + "block.minecraft.blackstone_stairs": "Blackstone Stairs", + "block.minecraft.blackstone_wall": "Blackstone Wall", + "block.minecraft.polished_blackstone_bricks": "Polished Blackstone Bricks", + "block.minecraft.polished_blackstone_brick_slab": "Polished Blackstone Brick Slab", + "block.minecraft.polished_blackstone_brick_stairs": "Polished Blackstone Brick Stairs", + "block.minecraft.polished_blackstone_brick_wall": "Polished Blackstone Brick Wall", + "block.minecraft.chiseled_polished_blackstone": "Chiseled Polished Blackstone", + "block.minecraft.cracked_polished_blackstone_bricks": "Cracked Polished Blackstone Bricks", + "block.minecraft.gilded_blackstone": "Gilded Blackstone", + "block.minecraft.polished_blackstone": "Polished Blackstone", + "block.minecraft.polished_blackstone_wall": "Polished Blackstone Wall", + "block.minecraft.polished_blackstone_slab": "Polished Blackstone Slab", + "block.minecraft.polished_blackstone_stairs": "Polished Blackstone Stairs", + "block.minecraft.polished_blackstone_pressure_plate": "Polished Blackstone Pressure Plate", + "block.minecraft.polished_blackstone_button": "Polished Blackstone Button", + "block.minecraft.cracked_nether_bricks": "Cracked Nether Bricks", + "block.minecraft.chiseled_nether_bricks": "Chiseled Nether Bricks", + "block.minecraft.quartz_bricks": "Quartz Bricks", + "block.minecraft.chain": "Chain", + "block.minecraft.candle": "Candle", + "block.minecraft.white_candle": "White Candle", + "block.minecraft.orange_candle": "Orange Candle", + "block.minecraft.magenta_candle": "Magenta Candle", + "block.minecraft.light_blue_candle": "Light Blue Candle", + "block.minecraft.yellow_candle": "Yellow Candle", + "block.minecraft.lime_candle": "Lime Candle", + "block.minecraft.pink_candle": "Pink Candle", + "block.minecraft.gray_candle": "Gray Candle", + "block.minecraft.light_gray_candle": "Light Gray Candle", + "block.minecraft.cyan_candle": "Cyan Candle", + "block.minecraft.purple_candle": "Purple Candle", + "block.minecraft.blue_candle": "Blue Candle", + "block.minecraft.brown_candle": "Brown Candle", + "block.minecraft.green_candle": "Green Candle", + "block.minecraft.red_candle": "Red Candle", + "block.minecraft.black_candle": "Black Candle", + "block.minecraft.candle_cake": "Cake with Candle", + "block.minecraft.white_candle_cake": "Cake with White Candle", + "block.minecraft.orange_candle_cake": "Cake with Orange Candle", + "block.minecraft.magenta_candle_cake": "Cake with Magenta Candle", + "block.minecraft.light_blue_candle_cake": "Cake with Light Blue Candle", + "block.minecraft.yellow_candle_cake": "Cake with Yellow Candle", + "block.minecraft.lime_candle_cake": "Cake with Lime Candle", + "block.minecraft.pink_candle_cake": "Cake with Pink Candle", + "block.minecraft.gray_candle_cake": "Cake with Gray Candle", + "block.minecraft.light_gray_candle_cake": "Cake with Light Gray Candle", + "block.minecraft.cyan_candle_cake": "Cake with Cyan Candle", + "block.minecraft.purple_candle_cake": "Cake with Purple Candle", + "block.minecraft.blue_candle_cake": "Cake with Blue Candle", + "block.minecraft.brown_candle_cake": "Cake with Brown Candle", + "block.minecraft.green_candle_cake": "Cake with Green Candle", + "block.minecraft.red_candle_cake": "Cake with Red Candle", + "block.minecraft.black_candle_cake": "Cake with Black Candle", + "block.minecraft.amethyst_block": "Block of Amethyst", + "block.minecraft.small_amethyst_bud": "Small Amethyst Bud", + "block.minecraft.medium_amethyst_bud": "Medium Amethyst Bud", + "block.minecraft.large_amethyst_bud": "Large Amethyst Bud", + "block.minecraft.amethyst_cluster": "Amethyst Cluster", + "block.minecraft.budding_amethyst": "Budding Amethyst", + "block.minecraft.calcite": "Calcite", + "block.minecraft.tuff": "Tuff", + "block.minecraft.tinted_glass": "Tinted Glass", + "block.minecraft.dripstone_block": "Dripstone Block", + "block.minecraft.pointed_dripstone": "Pointed Dripstone", + "block.minecraft.copper_ore": "Copper Ore", + "block.minecraft.deepslate_copper_ore": "Deepslate Copper Ore", + "block.minecraft.copper_block": "Block of Copper", + "block.minecraft.exposed_copper": "Exposed Copper", + "block.minecraft.weathered_copper": "Weathered Copper", + "block.minecraft.oxidized_copper": "Oxidized Copper", + "block.minecraft.cut_copper": "Cut Copper", + "block.minecraft.exposed_cut_copper": "Exposed Cut Copper", + "block.minecraft.weathered_cut_copper": "Weathered Cut Copper", + "block.minecraft.oxidized_cut_copper": "Oxidized Cut Copper", + "block.minecraft.cut_copper_stairs": "Cut Copper Stairs", + "block.minecraft.exposed_cut_copper_stairs": "Exposed Cut Copper Stairs", + "block.minecraft.weathered_cut_copper_stairs": "Weathered Cut Copper Stairs", + "block.minecraft.oxidized_cut_copper_stairs": "Oxidized Cut Copper Stairs", + "block.minecraft.cut_copper_slab": "Cut Copper Slab", + "block.minecraft.exposed_cut_copper_slab": "Exposed Cut Copper Slab", + "block.minecraft.weathered_cut_copper_slab": "Weathered Cut Copper Slab", + "block.minecraft.oxidized_cut_copper_slab": "Oxidized Cut Copper Slab", + "block.minecraft.waxed_copper_block": "Waxed Block of Copper", + "block.minecraft.waxed_exposed_copper": "Waxed Exposed Copper", + "block.minecraft.waxed_weathered_copper": "Waxed Weathered Copper", + "block.minecraft.waxed_oxidized_copper": "Waxed Oxidized Copper", + "block.minecraft.waxed_cut_copper": "Waxed Cut Copper", + "block.minecraft.waxed_exposed_cut_copper": "Waxed Exposed Cut Copper", + "block.minecraft.waxed_weathered_cut_copper": "Waxed Weathered Cut Copper", + "block.minecraft.waxed_oxidized_cut_copper": "Waxed Oxidized Cut Copper", + "block.minecraft.waxed_cut_copper_stairs": "Waxed Cut Copper Stairs", + "block.minecraft.waxed_exposed_cut_copper_stairs": "Waxed Exposed Cut Copper Stairs", + "block.minecraft.waxed_weathered_cut_copper_stairs": "Waxed Weathered Cut Copper Stairs", + "block.minecraft.waxed_oxidized_cut_copper_stairs": "Waxed Oxidized Cut Copper Stairs", + "block.minecraft.waxed_cut_copper_slab": "Waxed Cut Copper Slab", + "block.minecraft.waxed_exposed_cut_copper_slab": "Waxed Exposed Cut Copper Slab", + "block.minecraft.waxed_weathered_cut_copper_slab": "Waxed Weathered Cut Copper Slab", + "block.minecraft.waxed_oxidized_cut_copper_slab": "Waxed Oxidized Cut Copper Slab", + "block.minecraft.lightning_rod": "Lightning Rod", + "block.minecraft.cave_vines": "Cave Vines", + "block.minecraft.cave_vines_plant": "Cave Vines Plant", + "block.minecraft.spore_blossom": "Spore Blossom", + "block.minecraft.azalea": "Azalea", + "block.minecraft.flowering_azalea": "Flowering Azalea", + "block.minecraft.azalea_leaves": "Azalea Leaves", + "block.minecraft.flowering_azalea_leaves": "Flowering Azalea Leaves", + "block.minecraft.moss_carpet": "Moss Carpet", + "block.minecraft.moss_block": "Moss Block", + "block.minecraft.big_dripleaf": "Big Dripleaf", + "block.minecraft.big_dripleaf_stem": "Big Dripleaf Stem", + "block.minecraft.small_dripleaf": "Small Dripleaf", + "block.minecraft.rooted_dirt": "Rooted Dirt", + "block.minecraft.mud": "Mud", + "block.minecraft.hanging_roots": "Hanging Roots", + "block.minecraft.powder_snow": "Powder Snow", + "block.minecraft.glow_lichen": "Glow Lichen", + "block.minecraft.sculk_sensor": "Sculk Sensor", + "block.minecraft.deepslate": "Deepslate", + "block.minecraft.cobbled_deepslate": "Cobbled Deepslate", + "block.minecraft.cobbled_deepslate_slab": "Cobbled Deepslate Slab", + "block.minecraft.cobbled_deepslate_stairs": "Cobbled Deepslate Stairs", + "block.minecraft.cobbled_deepslate_wall": "Cobbled Deepslate Wall", + "block.minecraft.chiseled_deepslate": "Chiseled Deepslate", + "block.minecraft.polished_deepslate": "Polished Deepslate", + "block.minecraft.polished_deepslate_slab": "Polished Deepslate Slab", + "block.minecraft.polished_deepslate_stairs": "Polished Deepslate Stairs", + "block.minecraft.polished_deepslate_wall": "Polished Deepslate Wall", + "block.minecraft.deepslate_bricks": "Deepslate Bricks", + "block.minecraft.deepslate_brick_slab": "Deepslate Brick Slab", + "block.minecraft.deepslate_brick_stairs": "Deepslate Brick Stairs", + "block.minecraft.deepslate_brick_wall": "Deepslate Brick Wall", + "block.minecraft.deepslate_tiles": "Deepslate Tiles", + "block.minecraft.deepslate_tile_slab": "Deepslate Tile Slab", + "block.minecraft.deepslate_tile_stairs": "Deepslate Tile Stairs", + "block.minecraft.deepslate_tile_wall": "Deepslate Tile Wall", + "block.minecraft.cracked_deepslate_bricks": "Cracked Deepslate Bricks", + "block.minecraft.cracked_deepslate_tiles": "Cracked Deepslate Tiles", + "block.minecraft.infested_deepslate": "Infested Deepslate", + "block.minecraft.smooth_basalt": "Smooth Basalt", + "block.minecraft.raw_iron_block": "Block of Raw Iron", + "block.minecraft.raw_copper_block": "Block of Raw Copper", + "block.minecraft.raw_gold_block": "Block of Raw Gold", + "block.minecraft.sculk": "Sculk", + "block.minecraft.sculk_catalyst": "Sculk Catalyst", + "block.minecraft.sculk_shrieker": "Sculk Shrieker", + "block.minecraft.sculk_vein": "Sculk Vein", + "block.minecraft.ochre_froglight": "Ochre Froglight", + "block.minecraft.verdant_froglight": "Verdant Froglight", + "block.minecraft.pearlescent_froglight": "Pearlescent Froglight", + "block.minecraft.frogspawn": "Frogspawn", + "block.minecraft.reinforced_deepslate": "Reinforced Deepslate", + "item.minecraft.name_tag": "Name Tag", + "item.minecraft.lead": "Lead", + "item.minecraft.iron_shovel": "Iron Shovel", + "item.minecraft.iron_pickaxe": "Iron Pickaxe", + "item.minecraft.iron_axe": "Iron Axe", + "item.minecraft.flint_and_steel": "Flint and Steel", + "item.minecraft.apple": "Apple", + "item.minecraft.cookie": "Cookie", + "item.minecraft.bow": "Bow", + "item.minecraft.bundle": "Bundle", + "item.minecraft.bundle.fullness": "%s/%s", + "item.minecraft.arrow": "Arrow", + "item.minecraft.spectral_arrow": "Spectral Arrow", + "item.minecraft.tipped_arrow": "Tipped Arrow", + "item.minecraft.dried_kelp": "Dried Kelp", + "item.minecraft.coal": "Coal", + "item.minecraft.charcoal": "Charcoal", + "item.minecraft.raw_copper": "Raw Copper", + "item.minecraft.raw_iron": "Raw Iron", + "item.minecraft.raw_gold": "Raw Gold", + "item.minecraft.diamond": "Diamond", + "item.minecraft.emerald": "Emerald", + "item.minecraft.iron_ingot": "Iron Ingot", + "item.minecraft.copper_ingot": "Copper Ingot", + "item.minecraft.gold_ingot": "Gold Ingot", + "item.minecraft.iron_sword": "Iron Sword", + "item.minecraft.wooden_sword": "Wooden Sword", + "item.minecraft.wooden_shovel": "Wooden Shovel", + "item.minecraft.wooden_pickaxe": "Wooden Pickaxe", + "item.minecraft.wooden_axe": "Wooden Axe", + "item.minecraft.stone_sword": "Stone Sword", + "item.minecraft.stone_shovel": "Stone Shovel", + "item.minecraft.stone_pickaxe": "Stone Pickaxe", + "item.minecraft.stone_axe": "Stone Axe", + "item.minecraft.diamond_sword": "Diamond Sword", + "item.minecraft.diamond_shovel": "Diamond Shovel", + "item.minecraft.diamond_pickaxe": "Diamond Pickaxe", + "item.minecraft.diamond_axe": "Diamond Axe", + "item.minecraft.stick": "Stick", + "item.minecraft.bowl": "Bowl", + "item.minecraft.mushroom_stew": "Mushroom Stew", + "item.minecraft.golden_sword": "Golden Sword", + "item.minecraft.golden_shovel": "Golden Shovel", + "item.minecraft.golden_pickaxe": "Golden Pickaxe", + "item.minecraft.golden_axe": "Golden Axe", + "item.minecraft.string": "String", + "item.minecraft.feather": "Feather", + "item.minecraft.gunpowder": "Gunpowder", + "item.minecraft.wooden_hoe": "Wooden Hoe", + "item.minecraft.stone_hoe": "Stone Hoe", + "item.minecraft.iron_hoe": "Iron Hoe", + "item.minecraft.diamond_hoe": "Diamond Hoe", + "item.minecraft.golden_hoe": "Golden Hoe", + "item.minecraft.wheat_seeds": "Wheat Seeds", + "item.minecraft.pumpkin_seeds": "Pumpkin Seeds", + "item.minecraft.melon_seeds": "Melon Seeds", + "item.minecraft.melon_slice": "Melon Slice", + "item.minecraft.wheat": "Wheat", + "item.minecraft.bread": "Bread", + "item.minecraft.leather_helmet": "Leather Cap", + "item.minecraft.leather_chestplate": "Leather Tunic", + "item.minecraft.leather_leggings": "Leather Pants", + "item.minecraft.leather_boots": "Leather Boots", + "item.minecraft.chainmail_helmet": "Chainmail Helmet", + "item.minecraft.chainmail_chestplate": "Chainmail Chestplate", + "item.minecraft.chainmail_leggings": "Chainmail Leggings", + "item.minecraft.chainmail_boots": "Chainmail Boots", + "item.minecraft.iron_helmet": "Iron Helmet", + "item.minecraft.iron_chestplate": "Iron Chestplate", + "item.minecraft.iron_leggings": "Iron Leggings", + "item.minecraft.iron_boots": "Iron Boots", + "item.minecraft.diamond_helmet": "Diamond Helmet", + "item.minecraft.diamond_chestplate": "Diamond Chestplate", + "item.minecraft.diamond_leggings": "Diamond Leggings", + "item.minecraft.diamond_boots": "Diamond Boots", + "item.minecraft.golden_helmet": "Golden Helmet", + "item.minecraft.golden_chestplate": "Golden Chestplate", + "item.minecraft.golden_leggings": "Golden Leggings", + "item.minecraft.golden_boots": "Golden Boots", + "item.minecraft.flint": "Flint", + "item.minecraft.porkchop": "Raw Porkchop", + "item.minecraft.cooked_porkchop": "Cooked Porkchop", + "item.minecraft.chicken": "Raw Chicken", + "item.minecraft.cooked_chicken": "Cooked Chicken", + "item.minecraft.mutton": "Raw Mutton", + "item.minecraft.cooked_mutton": "Cooked Mutton", + "item.minecraft.rabbit": "Raw Rabbit", + "item.minecraft.cooked_rabbit": "Cooked Rabbit", + "item.minecraft.rabbit_stew": "Rabbit Stew", + "item.minecraft.rabbit_foot": "Rabbit's Foot", + "item.minecraft.rabbit_hide": "Rabbit Hide", + "item.minecraft.beef": "Raw Beef", + "item.minecraft.cooked_beef": "Steak", + "item.minecraft.painting": "Painting", + "item.minecraft.item_frame": "Item Frame", + "item.minecraft.golden_apple": "Golden Apple", + "item.minecraft.enchanted_golden_apple": "Enchanted Golden Apple", + "item.minecraft.sign": "Sign", + "item.minecraft.bucket": "Bucket", + "item.minecraft.water_bucket": "Water Bucket", + "item.minecraft.lava_bucket": "Lava Bucket", + "item.minecraft.pufferfish_bucket": "Bucket of Pufferfish", + "item.minecraft.salmon_bucket": "Bucket of Salmon", + "item.minecraft.cod_bucket": "Bucket of Cod", + "item.minecraft.tropical_fish_bucket": "Bucket of Tropical Fish", + "item.minecraft.powder_snow_bucket": "Powder Snow Bucket", + "item.minecraft.axolotl_bucket": "Bucket of Axolotl", + "item.minecraft.tadpole_bucket": "Bucket of Tadpole", + "item.minecraft.minecart": "Minecart", + "item.minecraft.saddle": "Saddle", + "item.minecraft.redstone": "Redstone Dust", + "item.minecraft.snowball": "Snowball", + "item.minecraft.oak_boat": "Oak Boat", + "item.minecraft.oak_chest_boat": "Oak Boat with Chest", + "item.minecraft.spruce_boat": "Spruce Boat", + "item.minecraft.spruce_chest_boat": "Spruce Boat with Chest", + "item.minecraft.birch_boat": "Birch Boat", + "item.minecraft.birch_chest_boat": "Birch Boat with Chest", + "item.minecraft.jungle_boat": "Jungle Boat", + "item.minecraft.jungle_chest_boat": "Jungle Boat with Chest", + "item.minecraft.acacia_boat": "Acacia Boat", + "item.minecraft.acacia_chest_boat": "Acacia Boat with Chest", + "item.minecraft.dark_oak_boat": "Dark Oak Boat", + "item.minecraft.dark_oak_chest_boat": "Dark Oak Boat with Chest", + "item.minecraft.mangrove_boat": "Mangrove Boat", + "item.minecraft.mangrove_chest_boat": "Mangrove Boat with Chest", + "item.minecraft.bamboo_raft": "Bamboo Raft", + "item.minecraft.bamboo_chest_raft": "Bamboo Raft with Chest", + "item.minecraft.leather": "Leather", + "item.minecraft.milk_bucket": "Milk Bucket", + "item.minecraft.brick": "Brick", + "item.minecraft.clay_ball": "Clay Ball", + "item.minecraft.paper": "Paper", + "item.minecraft.book": "Book", + "item.minecraft.slime_ball": "Slimeball", + "item.minecraft.chest_minecart": "Minecart with Chest", + "item.minecraft.furnace_minecart": "Minecart with Furnace", + "item.minecraft.tnt_minecart": "Minecart with TNT", + "item.minecraft.hopper_minecart": "Minecart with Hopper", + "item.minecraft.command_block_minecart": "Minecart with Command Block", + "item.minecraft.egg": "Egg", + "item.minecraft.compass": "Compass", + "item.minecraft.recovery_compass": "Recovery Compass", + "item.minecraft.fishing_rod": "Fishing Rod", + "item.minecraft.clock": "Clock", + "item.minecraft.glowstone_dust": "Glowstone Dust", + "item.minecraft.cod": "Raw Cod", + "item.minecraft.salmon": "Raw Salmon", + "item.minecraft.pufferfish": "Pufferfish", + "item.minecraft.tropical_fish": "Tropical Fish", + "item.minecraft.cooked_cod": "Cooked Cod", + "item.minecraft.cooked_salmon": "Cooked Salmon", + "item.minecraft.music_disc_13": "Music Disc", + "item.minecraft.music_disc_cat": "Music Disc", + "item.minecraft.music_disc_blocks": "Music Disc", + "item.minecraft.music_disc_chirp": "Music Disc", + "item.minecraft.music_disc_far": "Music Disc", + "item.minecraft.music_disc_mall": "Music Disc", + "item.minecraft.music_disc_mellohi": "Music Disc", + "item.minecraft.music_disc_stal": "Music Disc", + "item.minecraft.music_disc_strad": "Music Disc", + "item.minecraft.music_disc_ward": "Music Disc", + "item.minecraft.music_disc_11": "Music Disc", + "item.minecraft.music_disc_wait": "Music Disc", + "item.minecraft.music_disc_pigstep": "Music Disc", + "item.minecraft.music_disc_otherside": "Music Disc", + "item.minecraft.music_disc_5": "Music Disc", + "item.minecraft.music_disc_13.desc": "C418 - 13", + "item.minecraft.music_disc_cat.desc": "C418 - cat", + "item.minecraft.music_disc_blocks.desc": "C418 - blocks", + "item.minecraft.music_disc_chirp.desc": "C418 - chirp", + "item.minecraft.music_disc_far.desc": "C418 - far", + "item.minecraft.music_disc_mall.desc": "C418 - mall", + "item.minecraft.music_disc_mellohi.desc": "C418 - mellohi", + "item.minecraft.music_disc_stal.desc": "C418 - stal", + "item.minecraft.music_disc_strad.desc": "C418 - strad", + "item.minecraft.music_disc_ward.desc": "C418 - ward", + "item.minecraft.music_disc_11.desc": "C418 - 11", + "item.minecraft.music_disc_wait.desc": "C418 - wait", + "item.minecraft.music_disc_pigstep.desc": "Lena Raine - Pigstep", + "item.minecraft.music_disc_otherside.desc": "Lena Raine - otherside", + "item.minecraft.music_disc_5.desc": "Samuel Åberg - 5", + "item.minecraft.bone": "Bone", + "item.minecraft.ink_sac": "Ink Sac", + "item.minecraft.red_dye": "Red Dye", + "item.minecraft.green_dye": "Green Dye", + "item.minecraft.cocoa_beans": "Cocoa Beans", + "item.minecraft.lapis_lazuli": "Lapis Lazuli", + "item.minecraft.purple_dye": "Purple Dye", + "item.minecraft.cyan_dye": "Cyan Dye", + "item.minecraft.light_gray_dye": "Light Gray Dye", + "item.minecraft.gray_dye": "Gray Dye", + "item.minecraft.pink_dye": "Pink Dye", + "item.minecraft.lime_dye": "Lime Dye", + "item.minecraft.yellow_dye": "Yellow Dye", + "item.minecraft.light_blue_dye": "Light Blue Dye", + "item.minecraft.magenta_dye": "Magenta Dye", + "item.minecraft.orange_dye": "Orange Dye", + "item.minecraft.bone_meal": "Bone Meal", + "item.minecraft.blue_dye": "Blue Dye", + "item.minecraft.black_dye": "Black Dye", + "item.minecraft.brown_dye": "Brown Dye", + "item.minecraft.white_dye": "White Dye", + "item.minecraft.sugar": "Sugar", + "item.minecraft.amethyst_shard": "Amethyst Shard", + "item.minecraft.spyglass": "Spyglass", + "item.minecraft.glow_berries": "Glow Berries", + "item.minecraft.disc_fragment_5": "Disc Fragment", + "item.minecraft.disc_fragment_5.desc": "Music Disc - 5", + "block.minecraft.black_bed": "Black Bed", + "block.minecraft.red_bed": "Red Bed", + "block.minecraft.green_bed": "Green Bed", + "block.minecraft.brown_bed": "Brown Bed", + "block.minecraft.blue_bed": "Blue Bed", + "block.minecraft.purple_bed": "Purple Bed", + "block.minecraft.cyan_bed": "Cyan Bed", + "block.minecraft.light_gray_bed": "Light Gray Bed", + "block.minecraft.gray_bed": "Gray Bed", + "block.minecraft.pink_bed": "Pink Bed", + "block.minecraft.lime_bed": "Lime Bed", + "block.minecraft.yellow_bed": "Yellow Bed", + "block.minecraft.light_blue_bed": "Light Blue Bed", + "block.minecraft.magenta_bed": "Magenta Bed", + "block.minecraft.orange_bed": "Orange Bed", + "block.minecraft.white_bed": "White Bed", + "block.minecraft.repeater": "Redstone Repeater", + "block.minecraft.comparator": "Redstone Comparator", + "item.minecraft.filled_map": "Map", + "item.minecraft.shears": "Shears", + "item.minecraft.rotten_flesh": "Rotten Flesh", + "item.minecraft.ender_pearl": "Ender Pearl", + "item.minecraft.blaze_rod": "Blaze Rod", + "item.minecraft.ghast_tear": "Ghast Tear", + "item.minecraft.nether_wart": "Nether Wart", + "item.minecraft.potion": "Potion", + "item.minecraft.splash_potion": "Splash Potion", + "item.minecraft.lingering_potion": "Lingering Potion", + "item.minecraft.end_crystal": "End Crystal", + "item.minecraft.gold_nugget": "Gold Nugget", + "item.minecraft.glass_bottle": "Glass Bottle", + "item.minecraft.spider_eye": "Spider Eye", + "item.minecraft.fermented_spider_eye": "Fermented Spider Eye", + "item.minecraft.blaze_powder": "Blaze Powder", + "item.minecraft.magma_cream": "Magma Cream", + "item.minecraft.cauldron": "Cauldron", + "item.minecraft.brewing_stand": "Brewing Stand", + "item.minecraft.ender_eye": "Eye of Ender", + "item.minecraft.glistering_melon_slice": "Glistering Melon Slice", + "item.minecraft.allay_spawn_egg": "Allay Spawn Egg", + "item.minecraft.axolotl_spawn_egg": "Axolotl Spawn Egg", + "item.minecraft.bat_spawn_egg": "Bat Spawn Egg", + "item.minecraft.bee_spawn_egg": "Bee Spawn Egg", + "item.minecraft.blaze_spawn_egg": "Blaze Spawn Egg", + "item.minecraft.cat_spawn_egg": "Cat Spawn Egg", + "item.minecraft.camel_spawn_egg": "Camel Spawn Egg", + "item.minecraft.cave_spider_spawn_egg": "Cave Spider Spawn Egg", + "item.minecraft.chicken_spawn_egg": "Chicken Spawn Egg", + "item.minecraft.cod_spawn_egg": "Cod Spawn Egg", + "item.minecraft.cow_spawn_egg": "Cow Spawn Egg", + "item.minecraft.creeper_spawn_egg": "Creeper Spawn Egg", + "item.minecraft.dolphin_spawn_egg": "Dolphin Spawn Egg", + "item.minecraft.donkey_spawn_egg": "Donkey Spawn Egg", + "item.minecraft.drowned_spawn_egg": "Drowned Spawn Egg", + "item.minecraft.elder_guardian_spawn_egg": "Elder Guardian Spawn Egg", + "item.minecraft.ender_dragon_spawn_egg": "Ender Dragon Spawn Egg", + "item.minecraft.enderman_spawn_egg": "Enderman Spawn Egg", + "item.minecraft.endermite_spawn_egg": "Endermite Spawn Egg", + "item.minecraft.evoker_spawn_egg": "Evoker Spawn Egg", + "item.minecraft.ghast_spawn_egg": "Ghast Spawn Egg", + "item.minecraft.glow_squid_spawn_egg": "Glow Squid Spawn Egg", + "item.minecraft.guardian_spawn_egg": "Guardian Spawn Egg", + "item.minecraft.hoglin_spawn_egg": "Hoglin Spawn Egg", + "item.minecraft.horse_spawn_egg": "Horse Spawn Egg", + "item.minecraft.husk_spawn_egg": "Husk Spawn Egg", + "item.minecraft.iron_golem_spawn_egg": "Iron Golem Spawn Egg", + "item.minecraft.ravager_spawn_egg": "Ravager Spawn Egg", + "item.minecraft.llama_spawn_egg": "Llama Spawn Egg", + "item.minecraft.magma_cube_spawn_egg": "Magma Cube Spawn Egg", + "item.minecraft.mooshroom_spawn_egg": "Mooshroom Spawn Egg", + "item.minecraft.mule_spawn_egg": "Mule Spawn Egg", + "item.minecraft.ocelot_spawn_egg": "Ocelot Spawn Egg", + "item.minecraft.panda_spawn_egg": "Panda Spawn Egg", + "item.minecraft.parrot_spawn_egg": "Parrot Spawn Egg", + "item.minecraft.pig_spawn_egg": "Pig Spawn Egg", + "item.minecraft.piglin_spawn_egg": "Piglin Spawn Egg", + "item.minecraft.piglin_brute_spawn_egg": "Piglin Brute Spawn Egg", + "item.minecraft.pillager_spawn_egg": "Pillager Spawn Egg", + "item.minecraft.phantom_spawn_egg": "Phantom Spawn Egg", + "item.minecraft.polar_bear_spawn_egg": "Polar Bear Spawn Egg", + "item.minecraft.pufferfish_spawn_egg": "Pufferfish Spawn Egg", + "item.minecraft.rabbit_spawn_egg": "Rabbit Spawn Egg", + "item.minecraft.fox_spawn_egg": "Fox Spawn Egg", + "item.minecraft.frog_spawn_egg": "Frog Spawn Egg", + "item.minecraft.salmon_spawn_egg": "Salmon Spawn Egg", + "item.minecraft.sheep_spawn_egg": "Sheep Spawn Egg", + "item.minecraft.shulker_spawn_egg": "Shulker Spawn Egg", + "item.minecraft.silverfish_spawn_egg": "Silverfish Spawn Egg", + "item.minecraft.skeleton_spawn_egg": "Skeleton Spawn Egg", + "item.minecraft.skeleton_horse_spawn_egg": "Skeleton Horse Spawn Egg", + "item.minecraft.slime_spawn_egg": "Slime Spawn Egg", + "item.minecraft.snow_golem_spawn_egg": "Snow Golem Spawn Egg", + "item.minecraft.spider_spawn_egg": "Spider Spawn Egg", + "item.minecraft.squid_spawn_egg": "Squid Spawn Egg", + "item.minecraft.stray_spawn_egg": "Stray Spawn Egg", + "item.minecraft.strider_spawn_egg": "Strider Spawn Egg", + "item.minecraft.tadpole_spawn_egg": "Tadpole Spawn Egg", + "item.minecraft.trader_llama_spawn_egg": "Trader Llama Spawn Egg", + "item.minecraft.tropical_fish_spawn_egg": "Tropical Fish Spawn Egg", + "item.minecraft.turtle_spawn_egg": "Turtle Spawn Egg", + "item.minecraft.vex_spawn_egg": "Vex Spawn Egg", + "item.minecraft.villager_spawn_egg": "Villager Spawn Egg", + "item.minecraft.wandering_trader_spawn_egg": "Wandering Trader Spawn Egg", + "item.minecraft.vindicator_spawn_egg": "Vindicator Spawn Egg", + "item.minecraft.warden_spawn_egg": "Warden Spawn Egg", + "item.minecraft.witch_spawn_egg": "Witch Spawn Egg", + "item.minecraft.wither_spawn_egg": "Wither Spawn Egg", + "item.minecraft.wither_skeleton_spawn_egg": "Wither Skeleton Spawn Egg", + "item.minecraft.wolf_spawn_egg": "Wolf Spawn Egg", + "item.minecraft.zoglin_spawn_egg": "Zoglin Spawn Egg", + "item.minecraft.zombie_spawn_egg": "Zombie Spawn Egg", + "item.minecraft.zombie_horse_spawn_egg": "Zombie Horse Spawn Egg", + "item.minecraft.zombified_piglin_spawn_egg": "Zombified Piglin Spawn Egg", + "item.minecraft.zombie_villager_spawn_egg": "Zombie Villager Spawn Egg", + "item.minecraft.goat_spawn_egg": "Goat Spawn Egg", + "item.minecraft.experience_bottle": "Bottle o' Enchanting", + "item.minecraft.fire_charge": "Fire Charge", + "item.minecraft.writable_book": "Book and Quill", + "item.minecraft.written_book": "Written Book", + "item.minecraft.flower_pot": "Flower Pot", + "item.minecraft.map": "Empty Map", + "item.minecraft.carrot": "Carrot", + "item.minecraft.golden_carrot": "Golden Carrot", + "item.minecraft.potato": "Potato", + "item.minecraft.baked_potato": "Baked Potato", + "item.minecraft.poisonous_potato": "Poisonous Potato", + "item.minecraft.carrot_on_a_stick": "Carrot on a Stick", + "item.minecraft.nether_star": "Nether Star", + "item.minecraft.pumpkin_pie": "Pumpkin Pie", + "item.minecraft.enchanted_book": "Enchanted Book", + "item.minecraft.firework_rocket": "Firework Rocket", + "item.minecraft.firework_rocket.flight": "Flight Duration:", + "item.minecraft.firework_star": "Firework Star", + "item.minecraft.firework_star.black": "Black", + "item.minecraft.firework_star.red": "Red", + "item.minecraft.firework_star.green": "Green", + "item.minecraft.firework_star.brown": "Brown", + "item.minecraft.firework_star.blue": "Blue", + "item.minecraft.firework_star.purple": "Purple", + "item.minecraft.firework_star.cyan": "Cyan", + "item.minecraft.firework_star.light_gray": "Light Gray", + "item.minecraft.firework_star.gray": "Gray", + "item.minecraft.firework_star.pink": "Pink", + "item.minecraft.firework_star.lime": "Lime", + "item.minecraft.firework_star.yellow": "Yellow", + "item.minecraft.firework_star.light_blue": "Light Blue", + "item.minecraft.firework_star.magenta": "Magenta", + "item.minecraft.firework_star.orange": "Orange", + "item.minecraft.firework_star.white": "White", + "item.minecraft.firework_star.custom_color": "Custom", + "item.minecraft.firework_star.fade_to": "Fade to", + "item.minecraft.firework_star.flicker": "Twinkle", + "item.minecraft.firework_star.trail": "Trail", + "item.minecraft.firework_star.shape.small_ball": "Small Ball", + "item.minecraft.firework_star.shape.large_ball": "Large Ball", + "item.minecraft.firework_star.shape.star": "Star-shaped", + "item.minecraft.firework_star.shape.creeper": "Creeper-shaped", + "item.minecraft.firework_star.shape.burst": "Burst", + "item.minecraft.firework_star.shape": "Unknown Shape", + "item.minecraft.nether_brick": "Nether Brick", + "item.minecraft.quartz": "Nether Quartz", + "item.minecraft.armor_stand": "Armor Stand", + "item.minecraft.iron_horse_armor": "Iron Horse Armor", + "item.minecraft.golden_horse_armor": "Golden Horse Armor", + "item.minecraft.diamond_horse_armor": "Diamond Horse Armor", + "item.minecraft.leather_horse_armor": "Leather Horse Armor", + "item.minecraft.prismarine_shard": "Prismarine Shard", + "item.minecraft.prismarine_crystals": "Prismarine Crystals", + "item.minecraft.chorus_fruit": "Chorus Fruit", + "item.minecraft.popped_chorus_fruit": "Popped Chorus Fruit", + "item.minecraft.beetroot": "Beetroot", + "item.minecraft.beetroot_seeds": "Beetroot Seeds", + "item.minecraft.beetroot_soup": "Beetroot Soup", + "item.minecraft.dragon_breath": "Dragon's Breath", + "item.minecraft.elytra": "Elytra", + "item.minecraft.totem_of_undying": "Totem of Undying", + "item.minecraft.shulker_shell": "Shulker Shell", + "item.minecraft.iron_nugget": "Iron Nugget", + "item.minecraft.knowledge_book": "Knowledge Book", + "item.minecraft.debug_stick": "Debug Stick", + "item.minecraft.debug_stick.empty": "%s has no properties", + "item.minecraft.debug_stick.update": "\"%s\" to %s", + "item.minecraft.debug_stick.select": "selected \"%s\" (%s)", + "item.minecraft.trident": "Trident", + "item.minecraft.scute": "Scute", + "item.minecraft.turtle_helmet": "Turtle Shell", + "item.minecraft.phantom_membrane": "Phantom Membrane", + "item.minecraft.nautilus_shell": "Nautilus Shell", + "item.minecraft.heart_of_the_sea": "Heart of the Sea", + "item.minecraft.crossbow": "Crossbow", + "item.minecraft.crossbow.projectile": "Projectile:", + "item.minecraft.suspicious_stew": "Suspicious Stew", + "item.minecraft.creeper_banner_pattern": "Banner Pattern", + "item.minecraft.skull_banner_pattern": "Banner Pattern", + "item.minecraft.flower_banner_pattern": "Banner Pattern", + "item.minecraft.mojang_banner_pattern": "Banner Pattern", + "item.minecraft.globe_banner_pattern": "Banner Pattern", + "item.minecraft.creeper_banner_pattern.desc": "Creeper Charge", + "item.minecraft.skull_banner_pattern.desc": "Skull Charge", + "item.minecraft.flower_banner_pattern.desc": "Flower Charge", + "item.minecraft.mojang_banner_pattern.desc": "Thing", + "item.minecraft.globe_banner_pattern.desc": "Globe", + "item.minecraft.piglin_banner_pattern": "Banner Pattern", + "item.minecraft.piglin_banner_pattern.desc": "Snout", + "item.minecraft.sweet_berries": "Sweet Berries", + "item.minecraft.honey_bottle": "Honey Bottle", + "item.minecraft.honeycomb": "Honeycomb", + "item.minecraft.lodestone_compass": "Lodestone Compass", + "item.minecraft.netherite_scrap": "Netherite Scrap", + "item.minecraft.netherite_ingot": "Netherite Ingot", + "item.minecraft.netherite_helmet": "Netherite Helmet", + "item.minecraft.netherite_chestplate": "Netherite Chestplate", + "item.minecraft.netherite_leggings": "Netherite Leggings", + "item.minecraft.netherite_boots": "Netherite Boots", + "item.minecraft.netherite_axe": "Netherite Axe", + "item.minecraft.netherite_pickaxe": "Netherite Pickaxe", + "item.minecraft.netherite_hoe": "Netherite Hoe", + "item.minecraft.netherite_shovel": "Netherite Shovel", + "item.minecraft.netherite_sword": "Netherite Sword", + "item.minecraft.warped_fungus_on_a_stick": "Warped Fungus on a Stick", + "item.minecraft.glow_ink_sac": "Glow Ink Sac", + "item.minecraft.glow_item_frame": "Glow Item Frame", + "item.minecraft.echo_shard": "Echo Shard", + "item.minecraft.goat_horn": "Goat Horn", + "instrument.minecraft.ponder_goat_horn": "Ponder", + "instrument.minecraft.sing_goat_horn": "Sing", + "instrument.minecraft.seek_goat_horn": "Seek", + "instrument.minecraft.feel_goat_horn": "Feel", + "instrument.minecraft.admire_goat_horn": "Admire", + "instrument.minecraft.call_goat_horn": "Call", + "instrument.minecraft.yearn_goat_horn": "Yearn", + "instrument.minecraft.dream_goat_horn": "Dream", + "container.inventory": "Inventory", + "container.hopper": "Item Hopper", + "container.crafting": "Crafting", + "container.dispenser": "Dispenser", + "container.dropper": "Dropper", + "container.furnace": "Furnace", + "container.enchant": "Enchant", + "container.smoker": "Smoker", + "container.lectern": "Lectern", + "container.blast_furnace": "Blast Furnace", + "container.enchant.lapis.one": "1 Lapis Lazuli", + "container.enchant.lapis.many": "%s Lapis Lazuli", + "container.enchant.level.one": "1 Enchantment Level", + "container.enchant.level.many": "%s Enchantment Levels", + "container.enchant.level.requirement": "Level Requirement: %s", + "container.enchant.clue": "%s . . . ?", + "container.repair": "Repair & Name", + "container.repair.cost": "Enchantment Cost: %1$s", + "container.repair.expensive": "Too Expensive!", + "container.creative": "Item Selection", + "container.brewing": "Brewing Stand", + "container.chest": "Chest", + "container.chestDouble": "Large Chest", + "container.enderchest": "Ender Chest", + "container.beacon": "Beacon", + "container.shulkerBox": "Shulker Box", + "container.shulkerBox.more": "and %s more...", + "container.barrel": "Barrel", + "container.spectatorCantOpen": "Unable to open. Loot not generated yet.", + "container.isLocked": "%s is locked!", + "container.loom": "Loom", + "container.grindstone_title": "Repair & Disenchant", + "container.cartography_table": "Cartography Table", + "container.stonecutter": "Stonecutter", + "container.upgrade": "Upgrade Gear", + "structure_block.invalid_structure_name": "Invalid structure name '%s'", + "structure_block.save_success": "Structure saved as '%s'", + "structure_block.save_failure": "Unable to save structure '%s'", + "structure_block.load_success": "Structure loaded from '%s'", + "structure_block.load_prepare": "Structure '%s' position prepared", + "structure_block.load_not_found": "Structure '%s' is not available", + "structure_block.size_success": "Size successfully detected for '%s'", + "structure_block.size_failure": "Unable to detect structure size. Add corners with matching structure names", + "structure_block.mode.save": "Save", + "structure_block.mode.load": "Load", + "structure_block.mode.data": "Data", + "structure_block.mode.corner": "Corner", + "structure_block.hover.save": "Save: %s", + "structure_block.hover.load": "Load: %s", + "structure_block.hover.data": "Data: %s", + "structure_block.hover.corner": "Corner: %s", + "structure_block.mode_info.save": "Save Mode - Write to File", + "structure_block.mode_info.load": "Load Mode - Load from File", + "structure_block.mode_info.data": "Data Mode - Game Logic Marker", + "structure_block.mode_info.corner": "Corner Mode - Placement and Size Marker", + "structure_block.structure_name": "Structure Name", + "structure_block.custom_data": "Custom Data Tag Name", + "structure_block.position": "Relative Position", + "structure_block.position.x": "relative Position x", + "structure_block.position.y": "relative position y", + "structure_block.position.z": "relative position z", + "structure_block.size": "Structure Size", + "structure_block.size.x": "structure size x", + "structure_block.size.y": "structure size y", + "structure_block.size.z": "structure size z", + "structure_block.integrity": "Structure Integrity and Seed", + "structure_block.integrity.integrity": "Structure Integrity", + "structure_block.integrity.seed": "Structure Seed", + "structure_block.include_entities": "Include entities:", + "structure_block.detect_size": "Detect structure size and position:", + "structure_block.button.detect_size": "DETECT", + "structure_block.button.save": "SAVE", + "structure_block.button.load": "LOAD", + "structure_block.show_air": "Show Invisible Blocks:", + "structure_block.show_boundingbox": "Show Bounding Box:", + "jigsaw_block.pool": "Target Pool:", + "jigsaw_block.name": "Name:", + "jigsaw_block.target": "Target Name:", + "jigsaw_block.final_state": "Turns into:", + "jigsaw_block.levels": "Levels: %s", + "jigsaw_block.keep_jigsaws": "Keep Jigsaws", + "jigsaw_block.generate": "Generate", + "jigsaw_block.joint_label": "Joint Type:", + "jigsaw_block.joint.rollable": "Rollable", + "jigsaw_block.joint.aligned": "Aligned", + "item.dyed": "Dyed", + "item.unbreakable": "Unbreakable", + "item.canBreak": "Can break:", + "item.canPlace": "Can be placed on:", + "item.color": "Color: %s", + "item.nbt_tags": "NBT: %s tag(s)", + "item.durability": "Durability: %s / %s", + "item.disabled": "Disabled item", + "filled_map.mansion": "Woodland Explorer Map", + "filled_map.monument": "Ocean Explorer Map", + "filled_map.buried_treasure": "Buried Treasure Map", + "filled_map.unknown": "Unknown Map", + "filled_map.id": "Id #%s", + "filled_map.level": "(Level %s/%s)", + "filled_map.scale": "Scaling at 1:%s", + "filled_map.locked": "Locked", + "entity.minecraft.allay": "Allay", + "entity.minecraft.area_effect_cloud": "Area Effect Cloud", + "entity.minecraft.armor_stand": "Armor Stand", + "entity.minecraft.arrow": "Arrow", + "entity.minecraft.axolotl": "Axolotl", + "entity.minecraft.bat": "Bat", + "entity.minecraft.bee": "Bee", + "entity.minecraft.blaze": "Blaze", + "entity.minecraft.boat": "Boat", + "entity.minecraft.chest_boat": "Boat with Chest", + "entity.minecraft.cat": "Cat", + "entity.minecraft.camel": "Camel", + "entity.minecraft.cave_spider": "Cave Spider", + "entity.minecraft.chest_minecart": "Minecart with Chest", + "entity.minecraft.chicken": "Chicken", + "entity.minecraft.command_block_minecart": "Minecart with Command Block", + "entity.minecraft.cod": "Cod", + "entity.minecraft.cow": "Cow", + "entity.minecraft.creeper": "Creeper", + "entity.minecraft.dolphin": "Dolphin", + "entity.minecraft.donkey": "Donkey", + "entity.minecraft.drowned": "Drowned", + "entity.minecraft.dragon_fireball": "Dragon Fireball", + "entity.minecraft.egg": "Thrown Egg", + "entity.minecraft.elder_guardian": "Elder Guardian", + "entity.minecraft.end_crystal": "End Crystal", + "entity.minecraft.ender_dragon": "Ender Dragon", + "entity.minecraft.ender_pearl": "Thrown Ender Pearl", + "entity.minecraft.enderman": "Enderman", + "entity.minecraft.endermite": "Endermite", + "entity.minecraft.evoker_fangs": "Evoker Fangs", + "entity.minecraft.evoker": "Evoker", + "entity.minecraft.eye_of_ender": "Eye of Ender", + "entity.minecraft.falling_block": "Falling Block", + "entity.minecraft.fireball": "Fireball", + "entity.minecraft.firework_rocket": "Firework Rocket", + "entity.minecraft.fishing_bobber": "Fishing Bobber", + "entity.minecraft.fox": "Fox", + "entity.minecraft.frog": "Frog", + "entity.minecraft.furnace_minecart": "Minecart with Furnace", + "entity.minecraft.ghast": "Ghast", + "entity.minecraft.giant": "Giant", + "entity.minecraft.glow_item_frame": "Glow Item Frame", + "entity.minecraft.glow_squid": "Glow Squid", + "entity.minecraft.goat": "Goat", + "entity.minecraft.guardian": "Guardian", + "entity.minecraft.hoglin": "Hoglin", + "entity.minecraft.hopper_minecart": "Minecart with Hopper", + "entity.minecraft.horse": "Horse", + "entity.minecraft.husk": "Husk", + "entity.minecraft.ravager": "Ravager", + "entity.minecraft.illusioner": "Illusioner", + "entity.minecraft.item": "Item", + "entity.minecraft.item_frame": "Item Frame", + "entity.minecraft.killer_bunny": "The Killer Bunny", + "entity.minecraft.leash_knot": "Leash Knot", + "entity.minecraft.lightning_bolt": "Lightning Bolt", + "entity.minecraft.llama": "Llama", + "entity.minecraft.llama_spit": "Llama Spit", + "entity.minecraft.magma_cube": "Magma Cube", + "entity.minecraft.marker": "Marker", + "entity.minecraft.minecart": "Minecart", + "entity.minecraft.mooshroom": "Mooshroom", + "entity.minecraft.mule": "Mule", + "entity.minecraft.ocelot": "Ocelot", + "entity.minecraft.painting": "Painting", + "entity.minecraft.panda": "Panda", + "entity.minecraft.parrot": "Parrot", + "entity.minecraft.phantom": "Phantom", + "entity.minecraft.pig": "Pig", + "entity.minecraft.piglin": "Piglin", + "entity.minecraft.piglin_brute": "Piglin Brute", + "entity.minecraft.pillager": "Pillager", + "entity.minecraft.player": "Player", + "entity.minecraft.polar_bear": "Polar Bear", + "entity.minecraft.potion": "Potion", + "entity.minecraft.pufferfish": "Pufferfish", + "entity.minecraft.rabbit": "Rabbit", + "entity.minecraft.salmon": "Salmon", + "entity.minecraft.sheep": "Sheep", + "entity.minecraft.shulker": "Shulker", + "entity.minecraft.shulker_bullet": "Shulker Bullet", + "entity.minecraft.silverfish": "Silverfish", + "entity.minecraft.skeleton": "Skeleton", + "entity.minecraft.skeleton_horse": "Skeleton Horse", + "entity.minecraft.slime": "Slime", + "entity.minecraft.small_fireball": "Small Fireball", + "entity.minecraft.snowball": "Snowball", + "entity.minecraft.snow_golem": "Snow Golem", + "entity.minecraft.spawner_minecart": "Minecart with Monster Spawner", + "entity.minecraft.spectral_arrow": "Spectral Arrow", + "entity.minecraft.spider": "Spider", + "entity.minecraft.squid": "Squid", + "entity.minecraft.stray": "Stray", + "entity.minecraft.strider": "Strider", + "entity.minecraft.tadpole": "Tadpole", + "entity.minecraft.tnt": "Primed TNT", + "entity.minecraft.tnt_minecart": "Minecart with TNT", + "entity.minecraft.trader_llama": "Trader Llama", + "entity.minecraft.trident": "Trident", + "entity.minecraft.tropical_fish": "Tropical Fish", + "entity.minecraft.tropical_fish.predefined.0": "Anemone", + "entity.minecraft.tropical_fish.predefined.1": "Black Tang", + "entity.minecraft.tropical_fish.predefined.2": "Blue Tang", + "entity.minecraft.tropical_fish.predefined.3": "Butterflyfish", + "entity.minecraft.tropical_fish.predefined.4": "Cichlid", + "entity.minecraft.tropical_fish.predefined.5": "Clownfish", + "entity.minecraft.tropical_fish.predefined.6": "Cotton Candy Betta", + "entity.minecraft.tropical_fish.predefined.7": "Dottyback", + "entity.minecraft.tropical_fish.predefined.8": "Emperor Red Snapper", + "entity.minecraft.tropical_fish.predefined.9": "Goatfish", + "entity.minecraft.tropical_fish.predefined.10": "Moorish Idol", + "entity.minecraft.tropical_fish.predefined.11": "Ornate Butterflyfish", + "entity.minecraft.tropical_fish.predefined.12": "Parrotfish", + "entity.minecraft.tropical_fish.predefined.13": "Queen Angelfish", + "entity.minecraft.tropical_fish.predefined.14": "Red Cichlid", + "entity.minecraft.tropical_fish.predefined.15": "Red Lipped Blenny", + "entity.minecraft.tropical_fish.predefined.16": "Red Snapper", + "entity.minecraft.tropical_fish.predefined.17": "Threadfin", + "entity.minecraft.tropical_fish.predefined.18": "Tomato Clownfish", + "entity.minecraft.tropical_fish.predefined.19": "Triggerfish", + "entity.minecraft.tropical_fish.predefined.20": "Yellowtail Parrotfish", + "entity.minecraft.tropical_fish.predefined.21": "Yellow Tang", + "entity.minecraft.tropical_fish.type.flopper": "Flopper", + "entity.minecraft.tropical_fish.type.stripey": "Stripey", + "entity.minecraft.tropical_fish.type.glitter": "Glitter", + "entity.minecraft.tropical_fish.type.blockfish": "Blockfish", + "entity.minecraft.tropical_fish.type.betty": "Betty", + "entity.minecraft.tropical_fish.type.clayfish": "Clayfish", + "entity.minecraft.tropical_fish.type.kob": "Kob", + "entity.minecraft.tropical_fish.type.sunstreak": "Sunstreak", + "entity.minecraft.tropical_fish.type.snooper": "Snooper", + "entity.minecraft.tropical_fish.type.dasher": "Dasher", + "entity.minecraft.tropical_fish.type.brinely": "Brinely", + "entity.minecraft.tropical_fish.type.spotty": "Spotty", + "entity.minecraft.turtle": "Turtle", + "entity.minecraft.vex": "Vex", + "entity.minecraft.villager.armorer": "Armorer", + "entity.minecraft.villager.butcher": "Butcher", + "entity.minecraft.villager.cartographer": "Cartographer", + "entity.minecraft.villager.cleric": "Cleric", + "entity.minecraft.villager.farmer": "Farmer", + "entity.minecraft.villager.fisherman": "Fisherman", + "entity.minecraft.villager.fletcher": "Fletcher", + "entity.minecraft.villager.leatherworker": "Leatherworker", + "entity.minecraft.villager.librarian": "Librarian", + "entity.minecraft.villager.mason": "Mason", + "entity.minecraft.villager.none": "Villager", + "entity.minecraft.villager.nitwit": "Nitwit", + "entity.minecraft.villager.shepherd": "Shepherd", + "entity.minecraft.villager.toolsmith": "Toolsmith", + "entity.minecraft.villager.weaponsmith": "Weaponsmith", + "entity.minecraft.villager": "Villager", + "entity.minecraft.wandering_trader": "Wandering Trader", + "entity.minecraft.iron_golem": "Iron Golem", + "entity.minecraft.vindicator": "Vindicator", + "entity.minecraft.warden": "Warden", + "entity.minecraft.witch": "Witch", + "entity.minecraft.wither": "Wither", + "entity.minecraft.wither_skeleton": "Wither Skeleton", + "entity.minecraft.wither_skull": "Wither Skull", + "entity.minecraft.wolf": "Wolf", + "entity.minecraft.experience_bottle": "Thrown Bottle o' Enchanting", + "entity.minecraft.experience_orb": "Experience Orb", + "entity.minecraft.zoglin": "Zoglin", + "entity.minecraft.zombie": "Zombie", + "entity.minecraft.zombie_horse": "Zombie Horse", + "entity.minecraft.zombified_piglin": "Zombified Piglin", + "entity.minecraft.zombie_villager": "Zombie Villager", + "death.fell.accident.ladder": "%1$s fell off a ladder", + "death.fell.accident.vines": "%1$s fell off some vines", + "death.fell.accident.weeping_vines": "%1$s fell off some weeping vines", + "death.fell.accident.twisting_vines": "%1$s fell off some twisting vines", + "death.fell.accident.scaffolding": "%1$s fell off scaffolding", + "death.fell.accident.other_climbable": "%1$s fell while climbing", + "death.fell.accident.generic": "%1$s fell from a high place", + "death.fell.killer": "%1$s was doomed to fall", + "death.fell.assist": "%1$s was doomed to fall by %2$s", + "death.fell.assist.item": "%1$s was doomed to fall by %2$s using %3$s", + "death.fell.finish": "%1$s fell too far and was finished by %2$s", + "death.fell.finish.item": "%1$s fell too far and was finished by %2$s using %3$s", + "death.attack.lightningBolt": "%1$s was struck by lightning", + "death.attack.lightningBolt.player": "%1$s was struck by lightning whilst fighting %2$s", + "death.attack.inFire": "%1$s went up in flames", + "death.attack.inFire.player": "%1$s walked into fire whilst fighting %2$s", + "death.attack.onFire": "%1$s burned to death", + "death.attack.onFire.item": "%1$s was burnt to a crisp whilst fighting %2$s wielding %3$s", + "death.attack.onFire.player": "%1$s was burnt to a crisp whilst fighting %2$s", + "death.attack.lava": "%1$s tried to swim in lava", + "death.attack.lava.player": "%1$s tried to swim in lava to escape %2$s", + "death.attack.hotFloor": "%1$s discovered the floor was lava", + "death.attack.hotFloor.player": "%1$s walked into danger zone due to %2$s", + "death.attack.inWall": "%1$s suffocated in a wall", + "death.attack.inWall.player": "%1$s suffocated in a wall whilst fighting %2$s", + "death.attack.cramming": "%1$s was squished too much", + "death.attack.cramming.player": "%1$s was squashed by %2$s", + "death.attack.drown": "%1$s drowned", + "death.attack.drown.player": "%1$s drowned whilst trying to escape %2$s", + "death.attack.dryout": "%1$s died from dehydration", + "death.attack.dryout.player": "%1$s died from dehydration whilst trying to escape %2$s", + "death.attack.starve": "%1$s starved to death", + "death.attack.starve.player": "%1$s starved to death whilst fighting %2$s", + "death.attack.cactus": "%1$s was pricked to death", + "death.attack.cactus.player": "%1$s walked into a cactus whilst trying to escape %2$s", + "death.attack.generic": "%1$s died", + "death.attack.generic.player": "%1$s died because of %2$s", + "death.attack.explosion": "%1$s blew up", + "death.attack.explosion.player": "%1$s was blown up by %2$s", + "death.attack.explosion.player.item": "%1$s was blown up by %2$s using %3$s", + "death.attack.magic": "%1$s was killed by magic", + "death.attack.magic.player": "%1$s was killed by magic whilst trying to escape %2$s", + "death.attack.even_more_magic": "%1$s was killed by even more magic", + "death.attack.message_too_long": "Actually, message was too long to deliver fully. Sorry! Here's stripped version: %s", + "death.attack.wither": "%1$s withered away", + "death.attack.wither.player": "%1$s withered away whilst fighting %2$s", + "death.attack.witherSkull": "%1$s was shot by a skull from %2$s", + "death.attack.witherSkull.item": "%1$s was shot by a skull from %2$s using %3$s", + "death.attack.anvil": "%1$s was squashed by a falling anvil", + "death.attack.anvil.player": "%1$s was squashed by a falling anvil whilst fighting %2$s", + "death.attack.fallingBlock": "%1$s was squashed by a falling block", + "death.attack.fallingBlock.player": "%1$s was squashed by a falling block whilst fighting %2$s", + "death.attack.stalagmite": "%1$s was impaled on a stalagmite", + "death.attack.stalagmite.player": "%1$s was impaled on a stalagmite whilst fighting %2$s", + "death.attack.fallingStalactite": "%1$s was skewered by a falling stalactite", + "death.attack.fallingStalactite.player": "%1$s was skewered by a falling stalactite whilst fighting %2$s", + "death.attack.sonic_boom": "%1$s was obliterated by a sonically-charged shriek", + "death.attack.sonic_boom.item": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s wielding %3$s", + "death.attack.sonic_boom.player": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s", + "death.attack.mob": "%1$s was slain by %2$s", + "death.attack.mob.item": "%1$s was slain by %2$s using %3$s", + "death.attack.player": "%1$s was slain by %2$s", + "death.attack.player.item": "%1$s was slain by %2$s using %3$s", + "death.attack.arrow": "%1$s was shot by %2$s", + "death.attack.arrow.item": "%1$s was shot by %2$s using %3$s", + "death.attack.fireball": "%1$s was fireballed by %2$s", + "death.attack.fireball.item": "%1$s was fireballed by %2$s using %3$s", + "death.attack.thrown": "%1$s was pummeled by %2$s", + "death.attack.thrown.item": "%1$s was pummeled by %2$s using %3$s", + "death.attack.indirectMagic": "%1$s was killed by %2$s using magic", + "death.attack.indirectMagic.item": "%1$s was killed by %2$s using %3$s", + "death.attack.thorns": "%1$s was killed trying to hurt %2$s", + "death.attack.thorns.item": "%1$s was killed by %3$s trying to hurt %2$s", + "death.attack.trident": "%1$s was impaled by %2$s", + "death.attack.trident.item": "%1$s was impaled by %2$s with %3$s", + "death.attack.fall": "%1$s hit the ground too hard", + "death.attack.fall.player": "%1$s hit the ground too hard whilst trying to escape %2$s", + "death.attack.outOfWorld": "%1$s fell out of the world", + "death.attack.outOfWorld.player": "%1$s didn't want to live in the same world as %2$s", + "death.attack.dragonBreath": "%1$s was roasted in dragon breath", + "death.attack.dragonBreath.player": "%1$s was roasted in dragon breath by %2$s", + "death.attack.flyIntoWall": "%1$s experienced kinetic energy", + "death.attack.flyIntoWall.player": "%1$s experienced kinetic energy whilst trying to escape %2$s", + "death.attack.fireworks": "%1$s went off with a bang", + "death.attack.fireworks.player": "%1$s went off with a bang whilst fighting %2$s", + "death.attack.fireworks.item": "%1$s went off with a bang due to a firework fired from %3$s by %2$s", + "death.attack.badRespawnPoint.message": "%1$s was killed by %2$s", + "death.attack.badRespawnPoint.link": "Intentional Game Design", + "death.attack.sweetBerryBush": "%1$s was poked to death by a sweet berry bush", + "death.attack.sweetBerryBush.player": "%1$s was poked to death by a sweet berry bush whilst trying to escape %2$s", + "death.attack.sting": "%1$s was stung to death", + "death.attack.sting.item": "%1$s was stung to death by %2$s using %3$s", + "death.attack.sting.player": "%1$s was stung to death by %2$s", + "death.attack.freeze": "%1$s froze to death", + "death.attack.freeze.player": "%1$s was frozen to death by %2$s", + "deathScreen.respawn": "Respawn", + "deathScreen.spectate": "Spectate World", + "deathScreen.titleScreen": "Title Screen", + "deathScreen.score": "Score", + "deathScreen.title.hardcore": "Game Over!", + "deathScreen.title": "You Died!", + "deathScreen.quit.confirm": "Are you sure you want to quit?", + "effect.none": "No Effects", + "effect.minecraft.speed": "Speed", + "effect.minecraft.slowness": "Slowness", + "effect.minecraft.haste": "Haste", + "effect.minecraft.mining_fatigue": "Mining Fatigue", + "effect.minecraft.strength": "Strength", + "effect.minecraft.instant_health": "Instant Health", + "effect.minecraft.instant_damage": "Instant Damage", + "effect.minecraft.jump_boost": "Jump Boost", + "effect.minecraft.nausea": "Nausea", + "effect.minecraft.regeneration": "Regeneration", + "effect.minecraft.resistance": "Resistance", + "effect.minecraft.fire_resistance": "Fire Resistance", + "effect.minecraft.water_breathing": "Water Breathing", + "effect.minecraft.invisibility": "Invisibility", + "effect.minecraft.blindness": "Blindness", + "effect.minecraft.night_vision": "Night Vision", + "effect.minecraft.hunger": "Hunger", + "effect.minecraft.weakness": "Weakness", + "effect.minecraft.poison": "Poison", + "effect.minecraft.wither": "Wither", + "effect.minecraft.health_boost": "Health Boost", + "effect.minecraft.absorption": "Absorption", + "effect.minecraft.saturation": "Saturation", + "effect.minecraft.glowing": "Glowing", + "effect.minecraft.luck": "Luck", + "effect.minecraft.unluck": "Bad Luck", + "effect.minecraft.levitation": "Levitation", + "effect.minecraft.slow_falling": "Slow Falling", + "effect.minecraft.conduit_power": "Conduit Power", + "effect.minecraft.dolphins_grace": "Dolphin's Grace", + "effect.minecraft.bad_omen": "Bad Omen", + "effect.minecraft.hero_of_the_village": "Hero of the Village", + "effect.minecraft.darkness": "Darkness", + "event.minecraft.raid": "Raid", + "event.minecraft.raid.raiders_remaining": "Raiders Remaining: %s", + "event.minecraft.raid.victory": "Victory", + "event.minecraft.raid.defeat": "Defeat", + "item.minecraft.tipped_arrow.effect.empty": "Uncraftable Tipped Arrow", + "item.minecraft.tipped_arrow.effect.water": "Arrow of Splashing", + "item.minecraft.tipped_arrow.effect.mundane": "Tipped Arrow", + "item.minecraft.tipped_arrow.effect.thick": "Tipped Arrow", + "item.minecraft.tipped_arrow.effect.awkward": "Tipped Arrow", + "item.minecraft.tipped_arrow.effect.night_vision": "Arrow of Night Vision", + "item.minecraft.tipped_arrow.effect.invisibility": "Arrow of Invisibility", + "item.minecraft.tipped_arrow.effect.leaping": "Arrow of Leaping", + "item.minecraft.tipped_arrow.effect.fire_resistance": "Arrow of Fire Resistance", + "item.minecraft.tipped_arrow.effect.swiftness": "Arrow of Swiftness", + "item.minecraft.tipped_arrow.effect.slowness": "Arrow of Slowness", + "item.minecraft.tipped_arrow.effect.water_breathing": "Arrow of Water Breathing", + "item.minecraft.tipped_arrow.effect.healing": "Arrow of Healing", + "item.minecraft.tipped_arrow.effect.harming": "Arrow of Harming", + "item.minecraft.tipped_arrow.effect.poison": "Arrow of Poison", + "item.minecraft.tipped_arrow.effect.regeneration": "Arrow of Regeneration", + "item.minecraft.tipped_arrow.effect.strength": "Arrow of Strength", + "item.minecraft.tipped_arrow.effect.weakness": "Arrow of Weakness", + "item.minecraft.tipped_arrow.effect.levitation": "Arrow of Levitation", + "item.minecraft.tipped_arrow.effect.luck": "Arrow of Luck", + "item.minecraft.tipped_arrow.effect.turtle_master": "Arrow of the Turtle Master", + "item.minecraft.tipped_arrow.effect.slow_falling": "Arrow of Slow Falling", + "potion.whenDrank": "When Applied:", + "potion.withAmplifier": "%s %s", + "potion.withDuration": "%s (%s)", + "item.minecraft.potion.effect.empty": "Uncraftable Potion", + "item.minecraft.potion.effect.water": "Water Bottle", + "item.minecraft.potion.effect.mundane": "Mundane Potion", + "item.minecraft.potion.effect.thick": "Thick Potion", + "item.minecraft.potion.effect.awkward": "Awkward Potion", + "item.minecraft.potion.effect.night_vision": "Potion of Night Vision", + "item.minecraft.potion.effect.invisibility": "Potion of Invisibility", + "item.minecraft.potion.effect.leaping": "Potion of Leaping", + "item.minecraft.potion.effect.fire_resistance": "Potion of Fire Resistance", + "item.minecraft.potion.effect.swiftness": "Potion of Swiftness", + "item.minecraft.potion.effect.slowness": "Potion of Slowness", + "item.minecraft.potion.effect.water_breathing": "Potion of Water Breathing", + "item.minecraft.potion.effect.healing": "Potion of Healing", + "item.minecraft.potion.effect.harming": "Potion of Harming", + "item.minecraft.potion.effect.poison": "Potion of Poison", + "item.minecraft.potion.effect.regeneration": "Potion of Regeneration", + "item.minecraft.potion.effect.strength": "Potion of Strength", + "item.minecraft.potion.effect.weakness": "Potion of Weakness", + "item.minecraft.potion.effect.levitation": "Potion of Levitation", + "item.minecraft.potion.effect.luck": "Potion of Luck", + "item.minecraft.potion.effect.turtle_master": "Potion of the Turtle Master", + "item.minecraft.potion.effect.slow_falling": "Potion of Slow Falling", + "item.minecraft.splash_potion.effect.empty": "Splash Uncraftable Potion", + "item.minecraft.splash_potion.effect.water": "Splash Water Bottle", + "item.minecraft.splash_potion.effect.mundane": "Mundane Splash Potion", + "item.minecraft.splash_potion.effect.thick": "Thick Splash Potion", + "item.minecraft.splash_potion.effect.awkward": "Awkward Splash Potion", + "item.minecraft.splash_potion.effect.night_vision": "Splash Potion of Night Vision", + "item.minecraft.splash_potion.effect.invisibility": "Splash Potion of Invisibility", + "item.minecraft.splash_potion.effect.leaping": "Splash Potion of Leaping", + "item.minecraft.splash_potion.effect.fire_resistance": "Splash Potion of Fire Resistance", + "item.minecraft.splash_potion.effect.swiftness": "Splash Potion of Swiftness", + "item.minecraft.splash_potion.effect.slowness": "Splash Potion of Slowness", + "item.minecraft.splash_potion.effect.water_breathing": "Splash Potion of Water Breathing", + "item.minecraft.splash_potion.effect.healing": "Splash Potion of Healing", + "item.minecraft.splash_potion.effect.harming": "Splash Potion of Harming", + "item.minecraft.splash_potion.effect.poison": "Splash Potion of Poison", + "item.minecraft.splash_potion.effect.regeneration": "Splash Potion of Regeneration", + "item.minecraft.splash_potion.effect.strength": "Splash Potion of Strength", + "item.minecraft.splash_potion.effect.weakness": "Splash Potion of Weakness", + "item.minecraft.splash_potion.effect.levitation": "Splash Potion of Levitation", + "item.minecraft.splash_potion.effect.luck": "Splash Potion of Luck", + "item.minecraft.splash_potion.effect.turtle_master": "Splash Potion of the Turtle Master", + "item.minecraft.splash_potion.effect.slow_falling": "Splash Potion of Slow Falling", + "item.minecraft.lingering_potion.effect.empty": "Lingering Uncraftable Potion", + "item.minecraft.lingering_potion.effect.water": "Lingering Water Bottle", + "item.minecraft.lingering_potion.effect.mundane": "Mundane Lingering Potion", + "item.minecraft.lingering_potion.effect.thick": "Thick Lingering Potion", + "item.minecraft.lingering_potion.effect.awkward": "Awkward Lingering Potion", + "item.minecraft.lingering_potion.effect.night_vision": "Lingering Potion of Night Vision", + "item.minecraft.lingering_potion.effect.invisibility": "Lingering Potion of Invisibility", + "item.minecraft.lingering_potion.effect.leaping": "Lingering Potion of Leaping", + "item.minecraft.lingering_potion.effect.fire_resistance": "Lingering Potion of Fire Resistance", + "item.minecraft.lingering_potion.effect.swiftness": "Lingering Potion of Swiftness", + "item.minecraft.lingering_potion.effect.slowness": "Lingering Potion of Slowness", + "item.minecraft.lingering_potion.effect.water_breathing": "Lingering Potion of Water Breathing", + "item.minecraft.lingering_potion.effect.healing": "Lingering Potion of Healing", + "item.minecraft.lingering_potion.effect.harming": "Lingering Potion of Harming", + "item.minecraft.lingering_potion.effect.poison": "Lingering Potion of Poison", + "item.minecraft.lingering_potion.effect.regeneration": "Lingering Potion of Regeneration", + "item.minecraft.lingering_potion.effect.strength": "Lingering Potion of Strength", + "item.minecraft.lingering_potion.effect.weakness": "Lingering Potion of Weakness", + "item.minecraft.lingering_potion.effect.levitation": "Lingering Potion of Levitation", + "item.minecraft.lingering_potion.effect.luck": "Lingering Potion of Luck", + "item.minecraft.lingering_potion.effect.turtle_master": "Lingering Potion of the Turtle Master", + "item.minecraft.lingering_potion.effect.slow_falling": "Lingering Potion of Slow Falling", + "potion.potency.0": "", + "potion.potency.1": "II", + "potion.potency.2": "III", + "potion.potency.3": "IV", + "potion.potency.4": "V", + "potion.potency.5": "VI", + "enchantment.minecraft.sharpness": "Sharpness", + "enchantment.minecraft.smite": "Smite", + "enchantment.minecraft.bane_of_arthropods": "Bane of Arthropods", + "enchantment.minecraft.knockback": "Knockback", + "enchantment.minecraft.fire_aspect": "Fire Aspect", + "enchantment.minecraft.sweeping": "Sweeping Edge", + "enchantment.minecraft.protection": "Protection", + "enchantment.minecraft.fire_protection": "Fire Protection", + "enchantment.minecraft.feather_falling": "Feather Falling", + "enchantment.minecraft.blast_protection": "Blast Protection", + "enchantment.minecraft.projectile_protection": "Projectile Protection", + "enchantment.minecraft.respiration": "Respiration", + "enchantment.minecraft.aqua_affinity": "Aqua Affinity", + "enchantment.minecraft.depth_strider": "Depth Strider", + "enchantment.minecraft.frost_walker": "Frost Walker", + "enchantment.minecraft.soul_speed": "Soul Speed", + "enchantment.minecraft.swift_sneak": "Swift Sneak", + "enchantment.minecraft.efficiency": "Efficiency", + "enchantment.minecraft.silk_touch": "Silk Touch", + "enchantment.minecraft.unbreaking": "Unbreaking", + "enchantment.minecraft.looting": "Looting", + "enchantment.minecraft.fortune": "Fortune", + "enchantment.minecraft.luck_of_the_sea": "Luck of the Sea", + "enchantment.minecraft.lure": "Lure", + "enchantment.minecraft.power": "Power", + "enchantment.minecraft.flame": "Flame", + "enchantment.minecraft.punch": "Punch", + "enchantment.minecraft.infinity": "Infinity", + "enchantment.minecraft.thorns": "Thorns", + "enchantment.minecraft.mending": "Mending", + "enchantment.minecraft.binding_curse": "Curse of Binding", + "enchantment.minecraft.vanishing_curse": "Curse of Vanishing", + "enchantment.minecraft.loyalty": "Loyalty", + "enchantment.minecraft.impaling": "Impaling", + "enchantment.minecraft.riptide": "Riptide", + "enchantment.minecraft.channeling": "Channeling", + "enchantment.minecraft.multishot": "Multishot", + "enchantment.minecraft.quick_charge": "Quick Charge", + "enchantment.minecraft.piercing": "Piercing", + "enchantment.level.1": "I", + "enchantment.level.2": "II", + "enchantment.level.3": "III", + "enchantment.level.4": "IV", + "enchantment.level.5": "V", + "enchantment.level.6": "VI", + "enchantment.level.7": "VII", + "enchantment.level.8": "VIII", + "enchantment.level.9": "IX", + "enchantment.level.10": "X", + "gui.minutes": "%s minute(s)", + "gui.hours": "%s hour(s)", + "gui.days": "%s day(s)", + "gui.advancements": "Advancements", + "gui.stats": "Statistics", + "gui.entity_tooltip.type": "Type: %s", + "advancements.empty": "There doesn't seem to be anything here...", + "advancements.sad_label": ":(", + "advancements.toast.task": "Advancement Made!", + "advancements.toast.challenge": "Challenge Complete!", + "advancements.toast.goal": "Goal Reached!", + "stats.tooltip.type.statistic": "Statistic", + "stat.generalButton": "General", + "stat.itemsButton": "Items", + "stat.mobsButton": "Mobs", + "stat_type.minecraft.mined": "Times Mined", + "stat_type.minecraft.crafted": "Times Crafted", + "stat_type.minecraft.used": "Times Used", + "stat_type.minecraft.broken": "Times Broken", + "stat_type.minecraft.picked_up": "Picked Up", + "stat_type.minecraft.dropped": "Dropped", + "stat_type.minecraft.killed": "You killed %s %s", + "stat_type.minecraft.killed.none": "You have never killed %s", + "stat_type.minecraft.killed_by": "%s killed you %s time(s)", + "stat_type.minecraft.killed_by.none": "You have never been killed by %s", + "stat.minecraft.animals_bred": "Animals Bred", + "stat.minecraft.aviate_one_cm": "Distance by Elytra", + "stat.minecraft.clean_armor": "Armor Pieces Cleaned", + "stat.minecraft.clean_banner": "Banners Cleaned", + "stat.minecraft.clean_shulker_box": "Shulker Boxes Cleaned", + "stat.minecraft.climb_one_cm": "Distance Climbed", + "stat.minecraft.bell_ring": "Bells Rung", + "stat.minecraft.target_hit": "Targets Hit", + "stat.minecraft.boat_one_cm": "Distance by Boat", + "stat.minecraft.crouch_one_cm": "Distance Crouched", + "stat.minecraft.damage_dealt": "Damage Dealt", + "stat.minecraft.damage_dealt_absorbed": "Damage Dealt (Absorbed)", + "stat.minecraft.damage_dealt_resisted": "Damage Dealt (Resisted)", + "stat.minecraft.damage_taken": "Damage Taken", + "stat.minecraft.damage_blocked_by_shield": "Damage Blocked by Shield", + "stat.minecraft.damage_absorbed": "Damage Absorbed", + "stat.minecraft.damage_resisted": "Damage Resisted", + "stat.minecraft.deaths": "Number of Deaths", + "stat.minecraft.walk_under_water_one_cm": "Distance Walked under Water", + "stat.minecraft.drop": "Items Dropped", + "stat.minecraft.eat_cake_slice": "Cake Slices Eaten", + "stat.minecraft.enchant_item": "Items Enchanted", + "stat.minecraft.fall_one_cm": "Distance Fallen", + "stat.minecraft.fill_cauldron": "Cauldrons Filled", + "stat.minecraft.fish_caught": "Fish Caught", + "stat.minecraft.fly_one_cm": "Distance Flown", + "stat.minecraft.horse_one_cm": "Distance by Horse", + "stat.minecraft.inspect_dispenser": "Dispensers Searched", + "stat.minecraft.inspect_dropper": "Droppers Searched", + "stat.minecraft.inspect_hopper": "Hoppers Searched", + "stat.minecraft.interact_with_anvil": "Interactions with Anvil", + "stat.minecraft.interact_with_beacon": "Interactions with Beacon", + "stat.minecraft.interact_with_brewingstand": "Interactions with Brewing Stand", + "stat.minecraft.interact_with_campfire": "Interactions with Campfire", + "stat.minecraft.interact_with_cartography_table": "Interactions with Cartography Table", + "stat.minecraft.interact_with_crafting_table": "Interactions with Crafting Table", + "stat.minecraft.interact_with_furnace": "Interactions with Furnace", + "stat.minecraft.interact_with_grindstone": "Interactions with Grindstone", + "stat.minecraft.interact_with_lectern": "Interactions with Lectern", + "stat.minecraft.interact_with_loom": "Interactions with Loom", + "stat.minecraft.interact_with_blast_furnace": "Interactions with Blast Furnace", + "stat.minecraft.interact_with_smithing_table": "Interactions with Smithing Table", + "stat.minecraft.interact_with_smoker": "Interactions with Smoker", + "stat.minecraft.interact_with_stonecutter": "Interactions with Stonecutter", + "stat.minecraft.jump": "Jumps", + "stat.minecraft.junk_fished": "Junk Fished", + "stat.minecraft.leave_game": "Games Quit", + "stat.minecraft.minecart_one_cm": "Distance by Minecart", + "stat.minecraft.mob_kills": "Mob Kills", + "stat.minecraft.open_barrel": "Barrels Opened", + "stat.minecraft.open_chest": "Chests Opened", + "stat.minecraft.open_enderchest": "Ender Chests Opened", + "stat.minecraft.open_shulker_box": "Shulker Boxes Opened", + "stat.minecraft.pig_one_cm": "Distance by Pig", + "stat.minecraft.strider_one_cm": "Distance by Strider", + "stat.minecraft.player_kills": "Player Kills", + "stat.minecraft.play_noteblock": "Note Blocks Played", + "stat.minecraft.play_time": "Time Played", + "stat.minecraft.play_record": "Music Discs Played", + "stat.minecraft.pot_flower": "Plants Potted", + "stat.minecraft.raid_trigger": "Raids Triggered", + "stat.minecraft.raid_win": "Raids Won", + "stat.minecraft.ring_bell": "Bells Rung", + "stat.minecraft.sleep_in_bed": "Times Slept in a Bed", + "stat.minecraft.sneak_time": "Sneak Time", + "stat.minecraft.sprint_one_cm": "Distance Sprinted", + "stat.minecraft.walk_on_water_one_cm": "Distance Walked on Water", + "stat.minecraft.swim_one_cm": "Distance Swum", + "stat.minecraft.talked_to_villager": "Talked to Villagers", + "stat.minecraft.time_since_rest": "Time Since Last Rest", + "stat.minecraft.time_since_death": "Time Since Last Death", + "stat.minecraft.total_world_time": "Time with World Open", + "stat.minecraft.traded_with_villager": "Traded with Villagers", + "stat.minecraft.treasure_fished": "Treasure Fished", + "stat.minecraft.trigger_trapped_chest": "Trapped Chests Triggered", + "stat.minecraft.tune_noteblock": "Note Blocks Tuned", + "stat.minecraft.use_cauldron": "Water Taken from Cauldron", + "stat.minecraft.walk_one_cm": "Distance Walked", + "recipe.toast.title": "New Recipes Unlocked!", + "recipe.toast.description": "Check your recipe book", + "itemGroup.buildingBlocks": "Building Blocks", + "itemGroup.coloredBlocks": "Colored Blocks", + "itemGroup.natural": "Natural Blocks", + "itemGroup.functional": "Functional Blocks", + "itemGroup.redstone": "Redstone Blocks", + "itemGroup.op": "Operator Utilities", + "itemGroup.spawnEggs": "Spawn Eggs", + "itemGroup.search": "Search Items", + "itemGroup.consumables": "Consumables", + "itemGroup.foodAndDrink": "Food & Drinks", + "itemGroup.tools": "Tools & Utilities", + "itemGroup.combat": "Combat", + "itemGroup.crafting": "Crafting", + "itemGroup.ingredients": "Ingredients", + "itemGroup.inventory": "Survival Inventory", + "itemGroup.hotbar": "Saved Hotbars", + "inventory.binSlot": "Destroy Item", + "inventory.hotbarSaved": "Item hotbar saved (restore with %1$s+%2$s)", + "inventory.hotbarInfo": "Save hotbar with %1$s+%2$s", + "advMode.setCommand": "Set Console Command for Block", + "advMode.setCommand.success": "Command set: %s", + "advMode.command": "Console Command", + "advMode.nearestPlayer": "Use \"@p\" to target nearest player", + "advMode.randomPlayer": "Use \"@r\" to target random player", + "advMode.allPlayers": "Use \"@a\" to target all players", + "advMode.allEntities": "Use \"@e\" to target all entities", + "advMode.self": "Use \"@s\" to target the executing entity", + "advMode.previousOutput": "Previous Output", + "advMode.mode": "Mode", + "advMode.mode.sequence": "Chain", + "advMode.mode.auto": "Repeat", + "advMode.mode.redstone": "Impulse", + "advMode.type": "Type", + "advMode.mode.conditional": "Conditional", + "advMode.mode.unconditional": "Unconditional", + "advMode.triggering": "Triggering", + "advMode.mode.redstoneTriggered": "Needs Redstone", + "advMode.mode.autoexec.bat": "Always Active", + "advMode.notEnabled": "Command blocks are not enabled on this server", + "advMode.notAllowed": "Must be an opped player in creative mode", + "advMode.trackOutput": "Track output", + "mount.onboard": "Press %1$s to Dismount", + "build.tooHigh": "Height limit for building is %s", + "item.modifiers.mainhand": "When in Main Hand:", + "item.modifiers.offhand": "When in Off Hand:", + "item.modifiers.feet": "When on Feet:", + "item.modifiers.legs": "When on Legs:", + "item.modifiers.chest": "When on Body:", + "item.modifiers.head": "When on Head:", + "attribute.modifier.plus.0": "+%s %s", + "attribute.modifier.plus.1": "+%s%% %s", + "attribute.modifier.plus.2": "+%s%% %s", + "attribute.modifier.take.0": "-%s %s", + "attribute.modifier.take.1": "-%s%% %s", + "attribute.modifier.take.2": "-%s%% %s", + "attribute.modifier.equals.0": "%s %s", + "attribute.modifier.equals.1": "%s%% %s", + "attribute.modifier.equals.2": "%s%% %s", + "attribute.name.horse.jump_strength": "Horse Jump Strength", + "attribute.name.zombie.spawn_reinforcements": "Zombie Reinforcements", + "attribute.name.generic.max_health": "Max Health", + "attribute.name.generic.follow_range": "Mob Follow Range", + "attribute.name.generic.knockback_resistance": "Knockback Resistance", + "attribute.name.generic.movement_speed": "Speed", + "attribute.name.generic.flying_speed": "Flying Speed", + "attribute.name.generic.attack_damage": "Attack Damage", + "attribute.name.generic.attack_knockback": "Attack Knockback", + "attribute.name.generic.attack_speed": "Attack Speed", + "attribute.name.generic.luck": "Luck", + "attribute.name.generic.armor": "Armor", + "attribute.name.generic.armor_toughness": "Armor Toughness", + "screenshot.success": "Saved screenshot as %s", + "screenshot.failure": "Couldn't save screenshot: %s", + "block.minecraft.black_banner": "Black Banner", + "block.minecraft.red_banner": "Red Banner", + "block.minecraft.green_banner": "Green Banner", + "block.minecraft.brown_banner": "Brown Banner", + "block.minecraft.blue_banner": "Blue Banner", + "block.minecraft.purple_banner": "Purple Banner", + "block.minecraft.cyan_banner": "Cyan Banner", + "block.minecraft.light_gray_banner": "Light Gray Banner", + "block.minecraft.gray_banner": "Gray Banner", + "block.minecraft.pink_banner": "Pink Banner", + "block.minecraft.lime_banner": "Lime Banner", + "block.minecraft.yellow_banner": "Yellow Banner", + "block.minecraft.light_blue_banner": "Light Blue Banner", + "block.minecraft.magenta_banner": "Magenta Banner", + "block.minecraft.orange_banner": "Orange Banner", + "block.minecraft.white_banner": "White Banner", + "item.minecraft.shield": "Shield", + "item.minecraft.shield.black": "Black Shield", + "item.minecraft.shield.red": "Red Shield", + "item.minecraft.shield.green": "Green Shield", + "item.minecraft.shield.brown": "Brown Shield", + "item.minecraft.shield.blue": "Blue Shield", + "item.minecraft.shield.purple": "Purple Shield", + "item.minecraft.shield.cyan": "Cyan Shield", + "item.minecraft.shield.light_gray": "Light Gray Shield", + "item.minecraft.shield.gray": "Gray Shield", + "item.minecraft.shield.pink": "Pink Shield", + "item.minecraft.shield.lime": "Lime Shield", + "item.minecraft.shield.yellow": "Yellow Shield", + "item.minecraft.shield.light_blue": "Light Blue Shield", + "item.minecraft.shield.magenta": "Magenta Shield", + "item.minecraft.shield.orange": "Orange Shield", + "item.minecraft.shield.white": "White Shield", + "block.minecraft.banner.base.black": "Fully Black Field", + "block.minecraft.banner.base.red": "Fully Red Field", + "block.minecraft.banner.base.green": "Fully Green Field", + "block.minecraft.banner.base.brown": "Fully Brown Field", + "block.minecraft.banner.base.blue": "Fully Blue Field", + "block.minecraft.banner.base.purple": "Fully Purple Field", + "block.minecraft.banner.base.cyan": "Fully Cyan Field", + "block.minecraft.banner.base.light_gray": "Fully Light Gray Field", + "block.minecraft.banner.base.gray": "Fully Gray Field", + "block.minecraft.banner.base.pink": "Fully Pink Field", + "block.minecraft.banner.base.lime": "Fully Lime Field", + "block.minecraft.banner.base.yellow": "Fully Yellow Field", + "block.minecraft.banner.base.light_blue": "Fully Light Blue Field", + "block.minecraft.banner.base.magenta": "Fully Magenta Field", + "block.minecraft.banner.base.orange": "Fully Orange Field", + "block.minecraft.banner.base.white": "Fully White Field", + "block.minecraft.banner.square_bottom_left.black": "Black Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.red": "Red Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.green": "Green Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.brown": "Brown Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.blue": "Blue Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.purple": "Purple Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.cyan": "Cyan Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.light_gray": "Light Gray Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.gray": "Gray Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.pink": "Pink Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.lime": "Lime Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.yellow": "Yellow Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.light_blue": "Light Blue Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.magenta": "Magenta Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.orange": "Orange Base Dexter Canton", + "block.minecraft.banner.square_bottom_left.white": "White Base Dexter Canton", + "block.minecraft.banner.square_bottom_right.black": "Black Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.red": "Red Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.green": "Green Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.brown": "Brown Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.blue": "Blue Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.purple": "Purple Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.cyan": "Cyan Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.light_gray": "Light Gray Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.gray": "Gray Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.pink": "Pink Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.lime": "Lime Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.yellow": "Yellow Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.light_blue": "Light Blue Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.magenta": "Magenta Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.orange": "Orange Base Sinister Canton", + "block.minecraft.banner.square_bottom_right.white": "White Base Sinister Canton", + "block.minecraft.banner.square_top_left.black": "Black Chief Dexter Canton", + "block.minecraft.banner.square_top_left.red": "Red Chief Dexter Canton", + "block.minecraft.banner.square_top_left.green": "Green Chief Dexter Canton", + "block.minecraft.banner.square_top_left.brown": "Brown Chief Dexter Canton", + "block.minecraft.banner.square_top_left.blue": "Blue Chief Dexter Canton", + "block.minecraft.banner.square_top_left.purple": "Purple Chief Dexter Canton", + "block.minecraft.banner.square_top_left.cyan": "Cyan Chief Dexter Canton", + "block.minecraft.banner.square_top_left.light_gray": "Light Gray Chief Dexter Canton", + "block.minecraft.banner.square_top_left.gray": "Gray Chief Dexter Canton", + "block.minecraft.banner.square_top_left.pink": "Pink Chief Dexter Canton", + "block.minecraft.banner.square_top_left.lime": "Lime Chief Dexter Canton", + "block.minecraft.banner.square_top_left.yellow": "Yellow Chief Dexter Canton", + "block.minecraft.banner.square_top_left.light_blue": "Light Blue Chief Dexter Canton", + "block.minecraft.banner.square_top_left.magenta": "Magenta Chief Dexter Canton", + "block.minecraft.banner.square_top_left.orange": "Orange Chief Dexter Canton", + "block.minecraft.banner.square_top_left.white": "White Chief Dexter Canton", + "block.minecraft.banner.square_top_right.black": "Black Chief Sinister Canton", + "block.minecraft.banner.square_top_right.red": "Red Chief Sinister Canton", + "block.minecraft.banner.square_top_right.green": "Green Chief Sinister Canton", + "block.minecraft.banner.square_top_right.brown": "Brown Chief Sinister Canton", + "block.minecraft.banner.square_top_right.blue": "Blue Chief Sinister Canton", + "block.minecraft.banner.square_top_right.purple": "Purple Chief Sinister Canton", + "block.minecraft.banner.square_top_right.cyan": "Cyan Chief Sinister Canton", + "block.minecraft.banner.square_top_right.light_gray": "Light Gray Chief Sinister Canton", + "block.minecraft.banner.square_top_right.gray": "Gray Chief Sinister Canton", + "block.minecraft.banner.square_top_right.pink": "Pink Chief Sinister Canton", + "block.minecraft.banner.square_top_right.lime": "Lime Chief Sinister Canton", + "block.minecraft.banner.square_top_right.yellow": "Yellow Chief Sinister Canton", + "block.minecraft.banner.square_top_right.light_blue": "Light Blue Chief Sinister Canton", + "block.minecraft.banner.square_top_right.magenta": "Magenta Chief Sinister Canton", + "block.minecraft.banner.square_top_right.orange": "Orange Chief Sinister Canton", + "block.minecraft.banner.square_top_right.white": "White Chief Sinister Canton", + "block.minecraft.banner.stripe_bottom.black": "Black Base", + "block.minecraft.banner.stripe_bottom.red": "Red Base", + "block.minecraft.banner.stripe_bottom.green": "Green Base", + "block.minecraft.banner.stripe_bottom.brown": "Brown Base", + "block.minecraft.banner.stripe_bottom.blue": "Blue Base", + "block.minecraft.banner.stripe_bottom.purple": "Purple Base", + "block.minecraft.banner.stripe_bottom.cyan": "Cyan Base", + "block.minecraft.banner.stripe_bottom.light_gray": "Light Gray Base", + "block.minecraft.banner.stripe_bottom.gray": "Gray Base", + "block.minecraft.banner.stripe_bottom.pink": "Pink Base", + "block.minecraft.banner.stripe_bottom.lime": "Lime Base", + "block.minecraft.banner.stripe_bottom.yellow": "Yellow Base", + "block.minecraft.banner.stripe_bottom.light_blue": "Light Blue Base", + "block.minecraft.banner.stripe_bottom.magenta": "Magenta Base", + "block.minecraft.banner.stripe_bottom.orange": "Orange Base", + "block.minecraft.banner.stripe_bottom.white": "White Base", + "block.minecraft.banner.stripe_top.black": "Black Chief", + "block.minecraft.banner.stripe_top.red": "Red Chief", + "block.minecraft.banner.stripe_top.green": "Green Chief", + "block.minecraft.banner.stripe_top.brown": "Brown Chief", + "block.minecraft.banner.stripe_top.blue": "Blue Chief", + "block.minecraft.banner.stripe_top.purple": "Purple Chief", + "block.minecraft.banner.stripe_top.cyan": "Cyan Chief", + "block.minecraft.banner.stripe_top.light_gray": "Light Gray Chief", + "block.minecraft.banner.stripe_top.gray": "Gray Chief", + "block.minecraft.banner.stripe_top.pink": "Pink Chief", + "block.minecraft.banner.stripe_top.lime": "Lime Chief", + "block.minecraft.banner.stripe_top.yellow": "Yellow Chief", + "block.minecraft.banner.stripe_top.light_blue": "Light Blue Chief", + "block.minecraft.banner.stripe_top.magenta": "Magenta Chief", + "block.minecraft.banner.stripe_top.orange": "Orange Chief", + "block.minecraft.banner.stripe_top.white": "White Chief", + "block.minecraft.banner.stripe_left.black": "Black Pale Dexter", + "block.minecraft.banner.stripe_left.red": "Red Pale Dexter", + "block.minecraft.banner.stripe_left.green": "Green Pale Dexter", + "block.minecraft.banner.stripe_left.brown": "Brown Pale Dexter", + "block.minecraft.banner.stripe_left.blue": "Blue Pale Dexter", + "block.minecraft.banner.stripe_left.purple": "Purple Pale Dexter", + "block.minecraft.banner.stripe_left.cyan": "Cyan Pale Dexter", + "block.minecraft.banner.stripe_left.light_gray": "Light Gray Pale Dexter", + "block.minecraft.banner.stripe_left.gray": "Gray Pale Dexter", + "block.minecraft.banner.stripe_left.pink": "Pink Pale Dexter", + "block.minecraft.banner.stripe_left.lime": "Lime Pale Dexter", + "block.minecraft.banner.stripe_left.yellow": "Yellow Pale Dexter", + "block.minecraft.banner.stripe_left.light_blue": "Light Blue Pale Dexter", + "block.minecraft.banner.stripe_left.magenta": "Magenta Pale Dexter", + "block.minecraft.banner.stripe_left.orange": "Orange Pale Dexter", + "block.minecraft.banner.stripe_left.white": "White Pale Dexter", + "block.minecraft.banner.stripe_right.black": "Black Pale Sinister", + "block.minecraft.banner.stripe_right.red": "Red Pale Sinister", + "block.minecraft.banner.stripe_right.green": "Green Pale Sinister", + "block.minecraft.banner.stripe_right.brown": "Brown Pale Sinister", + "block.minecraft.banner.stripe_right.blue": "Blue Pale Sinister", + "block.minecraft.banner.stripe_right.purple": "Purple Pale Sinister", + "block.minecraft.banner.stripe_right.cyan": "Cyan Pale Sinister", + "block.minecraft.banner.stripe_right.light_gray": "Light Gray Pale Sinister", + "block.minecraft.banner.stripe_right.gray": "Gray Pale Sinister", + "block.minecraft.banner.stripe_right.pink": "Pink Pale Sinister", + "block.minecraft.banner.stripe_right.lime": "Lime Pale Sinister", + "block.minecraft.banner.stripe_right.yellow": "Yellow Pale Sinister", + "block.minecraft.banner.stripe_right.light_blue": "Light Blue Pale Sinister", + "block.minecraft.banner.stripe_right.magenta": "Magenta Pale Sinister", + "block.minecraft.banner.stripe_right.orange": "Orange Pale Sinister", + "block.minecraft.banner.stripe_right.white": "White Pale Sinister", + "block.minecraft.banner.stripe_center.black": "Black Pale", + "block.minecraft.banner.stripe_center.red": "Red Pale", + "block.minecraft.banner.stripe_center.green": "Green Pale", + "block.minecraft.banner.stripe_center.brown": "Brown Pale", + "block.minecraft.banner.stripe_center.blue": "Blue Pale", + "block.minecraft.banner.stripe_center.purple": "Purple Pale", + "block.minecraft.banner.stripe_center.cyan": "Cyan Pale", + "block.minecraft.banner.stripe_center.light_gray": "Light Gray Pale", + "block.minecraft.banner.stripe_center.gray": "Gray Pale", + "block.minecraft.banner.stripe_center.pink": "Pink Pale", + "block.minecraft.banner.stripe_center.lime": "Lime Pale", + "block.minecraft.banner.stripe_center.yellow": "Yellow Pale", + "block.minecraft.banner.stripe_center.light_blue": "Light Blue Pale", + "block.minecraft.banner.stripe_center.magenta": "Magenta Pale", + "block.minecraft.banner.stripe_center.orange": "Orange Pale", + "block.minecraft.banner.stripe_center.white": "White Pale", + "block.minecraft.banner.stripe_middle.black": "Black Fess", + "block.minecraft.banner.stripe_middle.red": "Red Fess", + "block.minecraft.banner.stripe_middle.green": "Green Fess", + "block.minecraft.banner.stripe_middle.brown": "Brown Fess", + "block.minecraft.banner.stripe_middle.blue": "Blue Fess", + "block.minecraft.banner.stripe_middle.purple": "Purple Fess", + "block.minecraft.banner.stripe_middle.cyan": "Cyan Fess", + "block.minecraft.banner.stripe_middle.light_gray": "Light Gray Fess", + "block.minecraft.banner.stripe_middle.gray": "Gray Fess", + "block.minecraft.banner.stripe_middle.pink": "Pink Fess", + "block.minecraft.banner.stripe_middle.lime": "Lime Fess", + "block.minecraft.banner.stripe_middle.yellow": "Yellow Fess", + "block.minecraft.banner.stripe_middle.light_blue": "Light Blue Fess", + "block.minecraft.banner.stripe_middle.magenta": "Magenta Fess", + "block.minecraft.banner.stripe_middle.orange": "Orange Fess", + "block.minecraft.banner.stripe_middle.white": "White Fess", + "block.minecraft.banner.stripe_downright.black": "Black Bend", + "block.minecraft.banner.stripe_downright.red": "Red Bend", + "block.minecraft.banner.stripe_downright.green": "Green Bend", + "block.minecraft.banner.stripe_downright.brown": "Brown Bend", + "block.minecraft.banner.stripe_downright.blue": "Blue Bend", + "block.minecraft.banner.stripe_downright.purple": "Purple Bend", + "block.minecraft.banner.stripe_downright.cyan": "Cyan Bend", + "block.minecraft.banner.stripe_downright.light_gray": "Light Gray Bend", + "block.minecraft.banner.stripe_downright.gray": "Gray Bend", + "block.minecraft.banner.stripe_downright.pink": "Pink Bend", + "block.minecraft.banner.stripe_downright.lime": "Lime Bend", + "block.minecraft.banner.stripe_downright.yellow": "Yellow Bend", + "block.minecraft.banner.stripe_downright.light_blue": "Light Blue Bend", + "block.minecraft.banner.stripe_downright.magenta": "Magenta Bend", + "block.minecraft.banner.stripe_downright.orange": "Orange Bend", + "block.minecraft.banner.stripe_downright.white": "White Bend", + "block.minecraft.banner.stripe_downleft.black": "Black Bend Sinister", + "block.minecraft.banner.stripe_downleft.red": "Red Bend Sinister", + "block.minecraft.banner.stripe_downleft.green": "Green Bend Sinister", + "block.minecraft.banner.stripe_downleft.brown": "Brown Bend Sinister", + "block.minecraft.banner.stripe_downleft.blue": "Blue Bend Sinister", + "block.minecraft.banner.stripe_downleft.purple": "Purple Bend Sinister", + "block.minecraft.banner.stripe_downleft.cyan": "Cyan Bend Sinister", + "block.minecraft.banner.stripe_downleft.light_gray": "Light Gray Bend Sinister", + "block.minecraft.banner.stripe_downleft.gray": "Gray Bend Sinister", + "block.minecraft.banner.stripe_downleft.pink": "Pink Bend Sinister", + "block.minecraft.banner.stripe_downleft.lime": "Lime Bend Sinister", + "block.minecraft.banner.stripe_downleft.yellow": "Yellow Bend Sinister", + "block.minecraft.banner.stripe_downleft.light_blue": "Light Blue Bend Sinister", + "block.minecraft.banner.stripe_downleft.magenta": "Magenta Bend Sinister", + "block.minecraft.banner.stripe_downleft.orange": "Orange Bend Sinister", + "block.minecraft.banner.stripe_downleft.white": "White Bend Sinister", + "block.minecraft.banner.small_stripes.black": "Black Paly", + "block.minecraft.banner.small_stripes.red": "Red Paly", + "block.minecraft.banner.small_stripes.green": "Green Paly", + "block.minecraft.banner.small_stripes.brown": "Brown Paly", + "block.minecraft.banner.small_stripes.blue": "Blue Paly", + "block.minecraft.banner.small_stripes.purple": "Purple Paly", + "block.minecraft.banner.small_stripes.cyan": "Cyan Paly", + "block.minecraft.banner.small_stripes.light_gray": "Light Gray Paly", + "block.minecraft.banner.small_stripes.gray": "Gray Paly", + "block.minecraft.banner.small_stripes.pink": "Pink Paly", + "block.minecraft.banner.small_stripes.lime": "Lime Paly", + "block.minecraft.banner.small_stripes.yellow": "Yellow Paly", + "block.minecraft.banner.small_stripes.light_blue": "Light Blue Paly", + "block.minecraft.banner.small_stripes.magenta": "Magenta Paly", + "block.minecraft.banner.small_stripes.orange": "Orange Paly", + "block.minecraft.banner.small_stripes.white": "White Paly", + "block.minecraft.banner.cross.black": "Black Saltire", + "block.minecraft.banner.cross.red": "Red Saltire", + "block.minecraft.banner.cross.green": "Green Saltire", + "block.minecraft.banner.cross.brown": "Brown Saltire", + "block.minecraft.banner.cross.blue": "Blue Saltire", + "block.minecraft.banner.cross.purple": "Purple Saltire", + "block.minecraft.banner.cross.cyan": "Cyan Saltire", + "block.minecraft.banner.cross.light_gray": "Light Gray Saltire", + "block.minecraft.banner.cross.gray": "Gray Saltire", + "block.minecraft.banner.cross.pink": "Pink Saltire", + "block.minecraft.banner.cross.lime": "Lime Saltire", + "block.minecraft.banner.cross.yellow": "Yellow Saltire", + "block.minecraft.banner.cross.light_blue": "Light Blue Saltire", + "block.minecraft.banner.cross.magenta": "Magenta Saltire", + "block.minecraft.banner.cross.orange": "Orange Saltire", + "block.minecraft.banner.cross.white": "White Saltire", + "block.minecraft.banner.triangle_bottom.black": "Black Chevron", + "block.minecraft.banner.triangle_bottom.red": "Red Chevron", + "block.minecraft.banner.triangle_bottom.green": "Green Chevron", + "block.minecraft.banner.triangle_bottom.brown": "Brown Chevron", + "block.minecraft.banner.triangle_bottom.blue": "Blue Chevron", + "block.minecraft.banner.triangle_bottom.purple": "Purple Chevron", + "block.minecraft.banner.triangle_bottom.cyan": "Cyan Chevron", + "block.minecraft.banner.triangle_bottom.light_gray": "Light Gray Chevron", + "block.minecraft.banner.triangle_bottom.gray": "Gray Chevron", + "block.minecraft.banner.triangle_bottom.pink": "Pink Chevron", + "block.minecraft.banner.triangle_bottom.lime": "Lime Chevron", + "block.minecraft.banner.triangle_bottom.yellow": "Yellow Chevron", + "block.minecraft.banner.triangle_bottom.light_blue": "Light Blue Chevron", + "block.minecraft.banner.triangle_bottom.magenta": "Magenta Chevron", + "block.minecraft.banner.triangle_bottom.orange": "Orange Chevron", + "block.minecraft.banner.triangle_bottom.white": "White Chevron", + "block.minecraft.banner.triangle_top.black": "Black Inverted Chevron", + "block.minecraft.banner.triangle_top.red": "Red Inverted Chevron", + "block.minecraft.banner.triangle_top.green": "Green Inverted Chevron", + "block.minecraft.banner.triangle_top.brown": "Brown Inverted Chevron", + "block.minecraft.banner.triangle_top.blue": "Blue Inverted Chevron", + "block.minecraft.banner.triangle_top.purple": "Purple Inverted Chevron", + "block.minecraft.banner.triangle_top.cyan": "Cyan Inverted Chevron", + "block.minecraft.banner.triangle_top.light_gray": "Light Gray Inverted Chevron", + "block.minecraft.banner.triangle_top.gray": "Gray Inverted Chevron", + "block.minecraft.banner.triangle_top.pink": "Pink Inverted Chevron", + "block.minecraft.banner.triangle_top.lime": "Lime Inverted Chevron", + "block.minecraft.banner.triangle_top.yellow": "Yellow Inverted Chevron", + "block.minecraft.banner.triangle_top.light_blue": "Light Blue Inverted Chevron", + "block.minecraft.banner.triangle_top.magenta": "Magenta Inverted Chevron", + "block.minecraft.banner.triangle_top.orange": "Orange Inverted Chevron", + "block.minecraft.banner.triangle_top.white": "White Inverted Chevron", + "block.minecraft.banner.triangles_bottom.black": "Black Base Indented", + "block.minecraft.banner.triangles_bottom.red": "Red Base Indented", + "block.minecraft.banner.triangles_bottom.green": "Green Base Indented", + "block.minecraft.banner.triangles_bottom.brown": "Brown Base Indented", + "block.minecraft.banner.triangles_bottom.blue": "Blue Base Indented", + "block.minecraft.banner.triangles_bottom.purple": "Purple Base Indented", + "block.minecraft.banner.triangles_bottom.cyan": "Cyan Base Indented", + "block.minecraft.banner.triangles_bottom.light_gray": "Light Gray Base Indented", + "block.minecraft.banner.triangles_bottom.gray": "Gray Base Indented", + "block.minecraft.banner.triangles_bottom.pink": "Pink Base Indented", + "block.minecraft.banner.triangles_bottom.lime": "Lime Base Indented", + "block.minecraft.banner.triangles_bottom.yellow": "Yellow Base Indented", + "block.minecraft.banner.triangles_bottom.light_blue": "Light Blue Base Indented", + "block.minecraft.banner.triangles_bottom.magenta": "Magenta Base Indented", + "block.minecraft.banner.triangles_bottom.orange": "Orange Base Indented", + "block.minecraft.banner.triangles_bottom.white": "White Base Indented", + "block.minecraft.banner.triangles_top.black": "Black Chief Indented", + "block.minecraft.banner.triangles_top.red": "Red Chief Indented", + "block.minecraft.banner.triangles_top.green": "Green Chief Indented", + "block.minecraft.banner.triangles_top.brown": "Brown Chief Indented", + "block.minecraft.banner.triangles_top.blue": "Blue Chief Indented", + "block.minecraft.banner.triangles_top.purple": "Purple Chief Indented", + "block.minecraft.banner.triangles_top.cyan": "Cyan Chief Indented", + "block.minecraft.banner.triangles_top.light_gray": "Light Gray Chief Indented", + "block.minecraft.banner.triangles_top.gray": "Gray Chief Indented", + "block.minecraft.banner.triangles_top.pink": "Pink Chief Indented", + "block.minecraft.banner.triangles_top.lime": "Lime Chief Indented", + "block.minecraft.banner.triangles_top.yellow": "Yellow Chief Indented", + "block.minecraft.banner.triangles_top.light_blue": "Light Blue Chief Indented", + "block.minecraft.banner.triangles_top.magenta": "Magenta Chief Indented", + "block.minecraft.banner.triangles_top.orange": "Orange Chief Indented", + "block.minecraft.banner.triangles_top.white": "White Chief Indented", + "block.minecraft.banner.diagonal_left.black": "Black Per Bend Sinister", + "block.minecraft.banner.diagonal_left.red": "Red Per Bend Sinister", + "block.minecraft.banner.diagonal_left.green": "Green Per Bend Sinister", + "block.minecraft.banner.diagonal_left.brown": "Brown Per Bend Sinister", + "block.minecraft.banner.diagonal_left.blue": "Blue Per Bend Sinister", + "block.minecraft.banner.diagonal_left.purple": "Purple Per Bend Sinister", + "block.minecraft.banner.diagonal_left.cyan": "Cyan Per Bend Sinister", + "block.minecraft.banner.diagonal_left.light_gray": "Light Gray Per Bend Sinister", + "block.minecraft.banner.diagonal_left.gray": "Gray Per Bend Sinister", + "block.minecraft.banner.diagonal_left.pink": "Pink Per Bend Sinister", + "block.minecraft.banner.diagonal_left.lime": "Lime Per Bend Sinister", + "block.minecraft.banner.diagonal_left.yellow": "Yellow Per Bend Sinister", + "block.minecraft.banner.diagonal_left.light_blue": "Light Blue Per Bend Sinister", + "block.minecraft.banner.diagonal_left.magenta": "Magenta Per Bend Sinister", + "block.minecraft.banner.diagonal_left.orange": "Orange Per Bend Sinister", + "block.minecraft.banner.diagonal_left.white": "White Per Bend Sinister", + "block.minecraft.banner.diagonal_right.black": "Black Per Bend", + "block.minecraft.banner.diagonal_right.red": "Red Per Bend", + "block.minecraft.banner.diagonal_right.green": "Green Per Bend", + "block.minecraft.banner.diagonal_right.brown": "Brown Per Bend", + "block.minecraft.banner.diagonal_right.blue": "Blue Per Bend", + "block.minecraft.banner.diagonal_right.purple": "Purple Per Bend", + "block.minecraft.banner.diagonal_right.cyan": "Cyan Per Bend", + "block.minecraft.banner.diagonal_right.light_gray": "Light Gray Per Bend", + "block.minecraft.banner.diagonal_right.gray": "Gray Per Bend", + "block.minecraft.banner.diagonal_right.pink": "Pink Per Bend", + "block.minecraft.banner.diagonal_right.lime": "Lime Per Bend", + "block.minecraft.banner.diagonal_right.yellow": "Yellow Per Bend", + "block.minecraft.banner.diagonal_right.light_blue": "Light Blue Per Bend", + "block.minecraft.banner.diagonal_right.magenta": "Magenta Per Bend", + "block.minecraft.banner.diagonal_right.orange": "Orange Per Bend", + "block.minecraft.banner.diagonal_right.white": "White Per Bend", + "block.minecraft.banner.diagonal_up_left.black": "Black Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.red": "Red Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.green": "Green Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.brown": "Brown Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.blue": "Blue Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.purple": "Purple Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.cyan": "Cyan Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.light_gray": "Light Gray Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.gray": "Gray Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.pink": "Pink Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.lime": "Lime Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.yellow": "Yellow Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.light_blue": "Light Blue Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.magenta": "Magenta Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.orange": "Orange Per Bend Inverted", + "block.minecraft.banner.diagonal_up_left.white": "White Per Bend Inverted", + "block.minecraft.banner.diagonal_up_right.black": "Black Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.red": "Red Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.green": "Green Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.brown": "Brown Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.blue": "Blue Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.purple": "Purple Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.cyan": "Cyan Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.light_gray": "Light Gray Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.gray": "Gray Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.pink": "Pink Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.lime": "Lime Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.yellow": "Yellow Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.light_blue": "Light Blue Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.magenta": "Magenta Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.orange": "Orange Per Bend Sinister Inverted", + "block.minecraft.banner.diagonal_up_right.white": "White Per Bend Sinister Inverted", + "block.minecraft.banner.circle.black": "Black Roundel", + "block.minecraft.banner.circle.red": "Red Roundel", + "block.minecraft.banner.circle.green": "Green Roundel", + "block.minecraft.banner.circle.brown": "Brown Roundel", + "block.minecraft.banner.circle.blue": "Blue Roundel", + "block.minecraft.banner.circle.purple": "Purple Roundel", + "block.minecraft.banner.circle.cyan": "Cyan Roundel", + "block.minecraft.banner.circle.light_gray": "Light Gray Roundel", + "block.minecraft.banner.circle.gray": "Gray Roundel", + "block.minecraft.banner.circle.pink": "Pink Roundel", + "block.minecraft.banner.circle.lime": "Lime Roundel", + "block.minecraft.banner.circle.yellow": "Yellow Roundel", + "block.minecraft.banner.circle.light_blue": "Light Blue Roundel", + "block.minecraft.banner.circle.magenta": "Magenta Roundel", + "block.minecraft.banner.circle.orange": "Orange Roundel", + "block.minecraft.banner.circle.white": "White Roundel", + "block.minecraft.banner.rhombus.black": "Black Lozenge", + "block.minecraft.banner.rhombus.red": "Red Lozenge", + "block.minecraft.banner.rhombus.green": "Green Lozenge", + "block.minecraft.banner.rhombus.brown": "Brown Lozenge", + "block.minecraft.banner.rhombus.blue": "Blue Lozenge", + "block.minecraft.banner.rhombus.purple": "Purple Lozenge", + "block.minecraft.banner.rhombus.cyan": "Cyan Lozenge", + "block.minecraft.banner.rhombus.light_gray": "Light Gray Lozenge", + "block.minecraft.banner.rhombus.gray": "Gray Lozenge", + "block.minecraft.banner.rhombus.pink": "Pink Lozenge", + "block.minecraft.banner.rhombus.lime": "Lime Lozenge", + "block.minecraft.banner.rhombus.yellow": "Yellow Lozenge", + "block.minecraft.banner.rhombus.light_blue": "Light Blue Lozenge", + "block.minecraft.banner.rhombus.magenta": "Magenta Lozenge", + "block.minecraft.banner.rhombus.orange": "Orange Lozenge", + "block.minecraft.banner.rhombus.white": "White Lozenge", + "block.minecraft.banner.half_vertical.black": "Black Per Pale", + "block.minecraft.banner.half_vertical.red": "Red Per Pale", + "block.minecraft.banner.half_vertical.green": "Green Per Pale", + "block.minecraft.banner.half_vertical.brown": "Brown Per Pale", + "block.minecraft.banner.half_vertical.blue": "Blue Per Pale", + "block.minecraft.banner.half_vertical.purple": "Purple Per Pale", + "block.minecraft.banner.half_vertical.cyan": "Cyan Per Pale", + "block.minecraft.banner.half_vertical.light_gray": "Light Gray Per Pale", + "block.minecraft.banner.half_vertical.gray": "Gray Per Pale", + "block.minecraft.banner.half_vertical.pink": "Pink Per Pale", + "block.minecraft.banner.half_vertical.lime": "Lime Per Pale", + "block.minecraft.banner.half_vertical.yellow": "Yellow Per Pale", + "block.minecraft.banner.half_vertical.light_blue": "Light Blue Per Pale", + "block.minecraft.banner.half_vertical.magenta": "Magenta Per Pale", + "block.minecraft.banner.half_vertical.orange": "Orange Per Pale", + "block.minecraft.banner.half_vertical.white": "White Per Pale", + "block.minecraft.banner.half_horizontal.black": "Black Per Fess", + "block.minecraft.banner.half_horizontal.red": "Red Per Fess", + "block.minecraft.banner.half_horizontal.green": "Green Per Fess", + "block.minecraft.banner.half_horizontal.brown": "Brown Per Fess", + "block.minecraft.banner.half_horizontal.blue": "Blue Per Fess", + "block.minecraft.banner.half_horizontal.purple": "Purple Per Fess", + "block.minecraft.banner.half_horizontal.cyan": "Cyan Per Fess", + "block.minecraft.banner.half_horizontal.light_gray": "Light Gray Per Fess", + "block.minecraft.banner.half_horizontal.gray": "Gray Per Fess", + "block.minecraft.banner.half_horizontal.pink": "Pink Per Fess", + "block.minecraft.banner.half_horizontal.lime": "Lime Per Fess", + "block.minecraft.banner.half_horizontal.yellow": "Yellow Per Fess", + "block.minecraft.banner.half_horizontal.light_blue": "Light Blue Per Fess", + "block.minecraft.banner.half_horizontal.magenta": "Magenta Per Fess", + "block.minecraft.banner.half_horizontal.orange": "Orange Per Fess", + "block.minecraft.banner.half_horizontal.white": "White Per Fess", + "block.minecraft.banner.half_vertical_right.black": "Black Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.red": "Red Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.green": "Green Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.brown": "Brown Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.blue": "Blue Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.purple": "Purple Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.cyan": "Cyan Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.light_gray": "Light Gray Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.gray": "Gray Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.pink": "Pink Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.lime": "Lime Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.yellow": "Yellow Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.light_blue": "Light Blue Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.magenta": "Magenta Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.orange": "Orange Per Pale Inverted", + "block.minecraft.banner.half_vertical_right.white": "White Per Pale Inverted", + "block.minecraft.banner.half_horizontal_bottom.black": "Black Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.red": "Red Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.green": "Green Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.brown": "Brown Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.blue": "Blue Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.purple": "Purple Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.cyan": "Cyan Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.light_gray": "Light Gray Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.gray": "Gray Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.pink": "Pink Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.lime": "Lime Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.yellow": "Yellow Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.light_blue": "Light Blue Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.magenta": "Magenta Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.orange": "Orange Per Fess Inverted", + "block.minecraft.banner.half_horizontal_bottom.white": "White Per Fess Inverted", + "block.minecraft.banner.creeper.black": "Black Creeper Charge", + "block.minecraft.banner.creeper.red": "Red Creeper Charge", + "block.minecraft.banner.creeper.green": "Green Creeper Charge", + "block.minecraft.banner.creeper.brown": "Brown Creeper Charge", + "block.minecraft.banner.creeper.blue": "Blue Creeper Charge", + "block.minecraft.banner.creeper.purple": "Purple Creeper Charge", + "block.minecraft.banner.creeper.cyan": "Cyan Creeper Charge", + "block.minecraft.banner.creeper.light_gray": "Light Gray Creeper Charge", + "block.minecraft.banner.creeper.gray": "Gray Creeper Charge", + "block.minecraft.banner.creeper.pink": "Pink Creeper Charge", + "block.minecraft.banner.creeper.lime": "Lime Creeper Charge", + "block.minecraft.banner.creeper.yellow": "Yellow Creeper Charge", + "block.minecraft.banner.creeper.light_blue": "Light Blue Creeper Charge", + "block.minecraft.banner.creeper.magenta": "Magenta Creeper Charge", + "block.minecraft.banner.creeper.orange": "Orange Creeper Charge", + "block.minecraft.banner.creeper.white": "White Creeper Charge", + "block.minecraft.banner.bricks.black": "Black Field Masoned", + "block.minecraft.banner.bricks.red": "Red Field Masoned", + "block.minecraft.banner.bricks.green": "Green Field Masoned", + "block.minecraft.banner.bricks.brown": "Brown Field Masoned", + "block.minecraft.banner.bricks.blue": "Blue Field Masoned", + "block.minecraft.banner.bricks.purple": "Purple Field Masoned", + "block.minecraft.banner.bricks.cyan": "Cyan Field Masoned", + "block.minecraft.banner.bricks.light_gray": "Light Gray Field Masoned", + "block.minecraft.banner.bricks.gray": "Gray Field Masoned", + "block.minecraft.banner.bricks.pink": "Pink Field Masoned", + "block.minecraft.banner.bricks.lime": "Lime Field Masoned", + "block.minecraft.banner.bricks.yellow": "Yellow Field Masoned", + "block.minecraft.banner.bricks.light_blue": "Light Blue Field Masoned", + "block.minecraft.banner.bricks.magenta": "Magenta Field Masoned", + "block.minecraft.banner.bricks.orange": "Orange Field Masoned", + "block.minecraft.banner.bricks.white": "White Field Masoned", + "block.minecraft.banner.gradient.black": "Black Gradient", + "block.minecraft.banner.gradient.red": "Red Gradient", + "block.minecraft.banner.gradient.green": "Green Gradient", + "block.minecraft.banner.gradient.brown": "Brown Gradient", + "block.minecraft.banner.gradient.blue": "Blue Gradient", + "block.minecraft.banner.gradient.purple": "Purple Gradient", + "block.minecraft.banner.gradient.cyan": "Cyan Gradient", + "block.minecraft.banner.gradient.light_gray": "Light Gray Gradient", + "block.minecraft.banner.gradient.gray": "Gray Gradient", + "block.minecraft.banner.gradient.pink": "Pink Gradient", + "block.minecraft.banner.gradient.lime": "Lime Gradient", + "block.minecraft.banner.gradient.yellow": "Yellow Gradient", + "block.minecraft.banner.gradient.light_blue": "Light Blue Gradient", + "block.minecraft.banner.gradient.magenta": "Magenta Gradient", + "block.minecraft.banner.gradient.orange": "Orange Gradient", + "block.minecraft.banner.gradient.white": "White Gradient", + "block.minecraft.banner.gradient_up.black": "Black Base Gradient", + "block.minecraft.banner.gradient_up.red": "Red Base Gradient", + "block.minecraft.banner.gradient_up.green": "Green Base Gradient", + "block.minecraft.banner.gradient_up.brown": "Brown Base Gradient", + "block.minecraft.banner.gradient_up.blue": "Blue Base Gradient", + "block.minecraft.banner.gradient_up.purple": "Purple Base Gradient", + "block.minecraft.banner.gradient_up.cyan": "Cyan Base Gradient", + "block.minecraft.banner.gradient_up.light_gray": "Light Gray Base Gradient", + "block.minecraft.banner.gradient_up.gray": "Gray Base Gradient", + "block.minecraft.banner.gradient_up.pink": "Pink Base Gradient", + "block.minecraft.banner.gradient_up.lime": "Lime Base Gradient", + "block.minecraft.banner.gradient_up.yellow": "Yellow Base Gradient", + "block.minecraft.banner.gradient_up.light_blue": "Light Blue Base Gradient", + "block.minecraft.banner.gradient_up.magenta": "Magenta Base Gradient", + "block.minecraft.banner.gradient_up.orange": "Orange Base Gradient", + "block.minecraft.banner.gradient_up.white": "White Base Gradient", + "block.minecraft.banner.skull.black": "Black Skull Charge", + "block.minecraft.banner.skull.red": "Red Skull Charge", + "block.minecraft.banner.skull.green": "Green Skull Charge", + "block.minecraft.banner.skull.brown": "Brown Skull Charge", + "block.minecraft.banner.skull.blue": "Blue Skull Charge", + "block.minecraft.banner.skull.purple": "Purple Skull Charge", + "block.minecraft.banner.skull.cyan": "Cyan Skull Charge", + "block.minecraft.banner.skull.light_gray": "Light Gray Skull Charge", + "block.minecraft.banner.skull.gray": "Gray Skull Charge", + "block.minecraft.banner.skull.pink": "Pink Skull Charge", + "block.minecraft.banner.skull.lime": "Lime Skull Charge", + "block.minecraft.banner.skull.yellow": "Yellow Skull Charge", + "block.minecraft.banner.skull.light_blue": "Light Blue Skull Charge", + "block.minecraft.banner.skull.magenta": "Magenta Skull Charge", + "block.minecraft.banner.skull.orange": "Orange Skull Charge", + "block.minecraft.banner.skull.white": "White Skull Charge", + "block.minecraft.banner.flower.black": "Black Flower Charge", + "block.minecraft.banner.flower.red": "Red Flower Charge", + "block.minecraft.banner.flower.green": "Green Flower Charge", + "block.minecraft.banner.flower.brown": "Brown Flower Charge", + "block.minecraft.banner.flower.blue": "Blue Flower Charge", + "block.minecraft.banner.flower.purple": "Purple Flower Charge", + "block.minecraft.banner.flower.cyan": "Cyan Flower Charge", + "block.minecraft.banner.flower.light_gray": "Light Gray Flower Charge", + "block.minecraft.banner.flower.gray": "Gray Flower Charge", + "block.minecraft.banner.flower.pink": "Pink Flower Charge", + "block.minecraft.banner.flower.lime": "Lime Flower Charge", + "block.minecraft.banner.flower.yellow": "Yellow Flower Charge", + "block.minecraft.banner.flower.light_blue": "Light Blue Flower Charge", + "block.minecraft.banner.flower.magenta": "Magenta Flower Charge", + "block.minecraft.banner.flower.orange": "Orange Flower Charge", + "block.minecraft.banner.flower.white": "White Flower Charge", + "block.minecraft.banner.border.black": "Black Bordure", + "block.minecraft.banner.border.red": "Red Bordure", + "block.minecraft.banner.border.green": "Green Bordure", + "block.minecraft.banner.border.brown": "Brown Bordure", + "block.minecraft.banner.border.blue": "Blue Bordure", + "block.minecraft.banner.border.purple": "Purple Bordure", + "block.minecraft.banner.border.cyan": "Cyan Bordure", + "block.minecraft.banner.border.light_gray": "Light Gray Bordure", + "block.minecraft.banner.border.gray": "Gray Bordure", + "block.minecraft.banner.border.pink": "Pink Bordure", + "block.minecraft.banner.border.lime": "Lime Bordure", + "block.minecraft.banner.border.yellow": "Yellow Bordure", + "block.minecraft.banner.border.light_blue": "Light Blue Bordure", + "block.minecraft.banner.border.magenta": "Magenta Bordure", + "block.minecraft.banner.border.orange": "Orange Bordure", + "block.minecraft.banner.border.white": "White Bordure", + "block.minecraft.banner.curly_border.black": "Black Bordure Indented", + "block.minecraft.banner.curly_border.red": "Red Bordure Indented", + "block.minecraft.banner.curly_border.green": "Green Bordure Indented", + "block.minecraft.banner.curly_border.brown": "Brown Bordure Indented", + "block.minecraft.banner.curly_border.blue": "Blue Bordure Indented", + "block.minecraft.banner.curly_border.purple": "Purple Bordure Indented", + "block.minecraft.banner.curly_border.cyan": "Cyan Bordure Indented", + "block.minecraft.banner.curly_border.light_gray": "Light Gray Bordure Indented", + "block.minecraft.banner.curly_border.gray": "Gray Bordure Indented", + "block.minecraft.banner.curly_border.pink": "Pink Bordure Indented", + "block.minecraft.banner.curly_border.lime": "Lime Bordure Indented", + "block.minecraft.banner.curly_border.yellow": "Yellow Bordure Indented", + "block.minecraft.banner.curly_border.light_blue": "Light Blue Bordure Indented", + "block.minecraft.banner.curly_border.magenta": "Magenta Bordure Indented", + "block.minecraft.banner.curly_border.orange": "Orange Bordure Indented", + "block.minecraft.banner.curly_border.white": "White Bordure Indented", + "block.minecraft.banner.mojang.black": "Black Thing", + "block.minecraft.banner.mojang.red": "Red Thing", + "block.minecraft.banner.mojang.green": "Green Thing", + "block.minecraft.banner.mojang.brown": "Brown Thing", + "block.minecraft.banner.mojang.blue": "Blue Thing", + "block.minecraft.banner.mojang.purple": "Purple Thing", + "block.minecraft.banner.mojang.cyan": "Cyan Thing", + "block.minecraft.banner.mojang.light_gray": "Light Gray Thing", + "block.minecraft.banner.mojang.gray": "Gray Thing", + "block.minecraft.banner.mojang.pink": "Pink Thing", + "block.minecraft.banner.mojang.lime": "Lime Thing", + "block.minecraft.banner.mojang.yellow": "Yellow Thing", + "block.minecraft.banner.mojang.light_blue": "Light Blue Thing", + "block.minecraft.banner.mojang.magenta": "Magenta Thing", + "block.minecraft.banner.mojang.orange": "Orange Thing", + "block.minecraft.banner.mojang.white": "White Thing", + "block.minecraft.banner.straight_cross.black": "Black Cross", + "block.minecraft.banner.straight_cross.red": "Red Cross", + "block.minecraft.banner.straight_cross.green": "Green Cross", + "block.minecraft.banner.straight_cross.brown": "Brown Cross", + "block.minecraft.banner.straight_cross.blue": "Blue Cross", + "block.minecraft.banner.straight_cross.purple": "Purple Cross", + "block.minecraft.banner.straight_cross.cyan": "Cyan Cross", + "block.minecraft.banner.straight_cross.light_gray": "Light Gray Cross", + "block.minecraft.banner.straight_cross.gray": "Gray Cross", + "block.minecraft.banner.straight_cross.pink": "Pink Cross", + "block.minecraft.banner.straight_cross.lime": "Lime Cross", + "block.minecraft.banner.straight_cross.yellow": "Yellow Cross", + "block.minecraft.banner.straight_cross.light_blue": "Light Blue Cross", + "block.minecraft.banner.straight_cross.magenta": "Magenta Cross", + "block.minecraft.banner.straight_cross.orange": "Orange Cross", + "block.minecraft.banner.straight_cross.white": "White Cross", + "block.minecraft.banner.globe.black": "Black Globe", + "block.minecraft.banner.globe.red": "Red Globe", + "block.minecraft.banner.globe.green": "Green Globe", + "block.minecraft.banner.globe.brown": "Brown Globe", + "block.minecraft.banner.globe.blue": "Blue Globe", + "block.minecraft.banner.globe.purple": "Purple Globe", + "block.minecraft.banner.globe.cyan": "Cyan Globe", + "block.minecraft.banner.globe.light_gray": "Light Gray Globe", + "block.minecraft.banner.globe.gray": "Gray Globe", + "block.minecraft.banner.globe.pink": "Pink Globe", + "block.minecraft.banner.globe.lime": "Lime Globe", + "block.minecraft.banner.globe.yellow": "Yellow Globe", + "block.minecraft.banner.globe.light_blue": "Light Blue Globe", + "block.minecraft.banner.globe.magenta": "Magenta Globe", + "block.minecraft.banner.globe.orange": "Orange Globe", + "block.minecraft.banner.globe.white": "White Globe", + "block.minecraft.banner.piglin.black": "Black Snout", + "block.minecraft.banner.piglin.red": "Red Snout", + "block.minecraft.banner.piglin.green": "Green Snout", + "block.minecraft.banner.piglin.brown": "Brown Snout", + "block.minecraft.banner.piglin.blue": "Blue Snout", + "block.minecraft.banner.piglin.purple": "Purple Snout", + "block.minecraft.banner.piglin.cyan": "Cyan Snout", + "block.minecraft.banner.piglin.light_gray": "Light Gray Snout", + "block.minecraft.banner.piglin.gray": "Gray Snout", + "block.minecraft.banner.piglin.pink": "Pink Snout", + "block.minecraft.banner.piglin.lime": "Lime Snout", + "block.minecraft.banner.piglin.yellow": "Yellow Snout", + "block.minecraft.banner.piglin.light_blue": "Light Blue Snout", + "block.minecraft.banner.piglin.magenta": "Magenta Snout", + "block.minecraft.banner.piglin.orange": "Orange Snout", + "block.minecraft.banner.piglin.white": "White Snout", + "subtitles.ambient.cave": "Eerie noise", + "subtitles.block.amethyst_block.chime": "Amethyst chimes", + "subtitles.block.anvil.destroy": "Anvil destroyed", + "subtitles.block.anvil.land": "Anvil landed", + "subtitles.block.anvil.use": "Anvil used", + "subtitles.block.barrel.close": "Barrel closes", + "subtitles.block.barrel.open": "Barrel opens", + "subtitles.block.beacon.activate": "Beacon activates", + "subtitles.block.beacon.ambient": "Beacon hums", + "subtitles.block.beacon.deactivate": "Beacon deactivates", + "subtitles.block.beacon.power_select": "Beacon power selected", + "subtitles.block.beehive.drip": "Honey drips", + "subtitles.block.beehive.enter": "Bee enters hive", + "subtitles.block.beehive.exit": "Bee leaves hive", + "subtitles.block.beehive.shear": "Shears scrape", + "subtitles.block.beehive.work": "Bees work", + "subtitles.block.bell.resonate": "Bell resonates", + "subtitles.block.bell.use": "Bell rings", + "subtitles.block.big_dripleaf.tilt_down": "Dripleaf tilts down", + "subtitles.block.big_dripleaf.tilt_up": "Dripleaf tilts up", + "subtitles.block.blastfurnace.fire_crackle": "Blast Furnace crackles", + "subtitles.block.brewing_stand.brew": "Brewing Stand bubbles", + "subtitles.block.bubble_column.bubble_pop": "Bubbles pop", + "subtitles.block.bubble_column.upwards_ambient": "Bubbles flow", + "subtitles.block.bubble_column.upwards_inside": "Bubbles woosh", + "subtitles.block.bubble_column.whirlpool_ambient": "Bubbles whirl", + "subtitles.block.bubble_column.whirlpool_inside": "Bubbles zoom", + "subtitles.block.button.click": "Button clicks", + "subtitles.block.campfire.crackle": "Campfire crackles", + "subtitles.block.candle.crackle": "Candle crackles", + "subtitles.block.cake.add_candle": "Cake squishes", + "subtitles.block.chest.close": "Chest closes", + "subtitles.block.chest.locked": "Chest locked", + "subtitles.block.chest.open": "Chest opens", + "subtitles.chiseled_bookshelf.insert": "Book placed", + "subtitles.chiseled_bookshelf.insert_enchanted": "Enchanted book placed", + "subtitles.chiseled_bookshelf.take": "Book taken", + "subtitles.chiseled_bookshelf.take_enchanted": "Enchanted book taken", + "subtitles.block.chorus_flower.death": "Chorus Flower withers", + "subtitles.block.chorus_flower.grow": "Chorus Flower grows", + "subtitles.block.comparator.click": "Comparator clicks", + "subtitles.block.composter.empty": "Composter emptied", + "subtitles.block.composter.fill": "Composter filled", + "subtitles.block.composter.ready": "Composter composts", + "subtitles.block.conduit.activate": "Conduit activates", + "subtitles.block.conduit.ambient": "Conduit pulses", + "subtitles.block.conduit.attack.target": "Conduit attacks", + "subtitles.block.conduit.deactivate": "Conduit deactivates", + "subtitles.block.dispenser.dispense": "Dispensed item", + "subtitles.block.dispenser.fail": "Dispenser failed", + "subtitles.block.door.toggle": "Door creaks", + "subtitles.block.enchantment_table.use": "Enchanting Table used", + "subtitles.block.end_portal.spawn": "End Portal opens", + "subtitles.block.end_portal_frame.fill": "Eye of Ender attaches", + "subtitles.block.fence_gate.toggle": "Fence Gate creaks", + "subtitles.block.fire.ambient": "Fire crackles", + "subtitles.block.fire.extinguish": "Fire extinguished", + "subtitles.block.frogspawn.hatch": "Tadpole hatches", + "subtitles.block.furnace.fire_crackle": "Furnace crackles", + "subtitles.block.generic.break": "Block broken", + "subtitles.block.generic.footsteps": "Footsteps", + "subtitles.block.generic.hit": "Block breaking", + "subtitles.block.generic.place": "Block placed", + "subtitles.block.grindstone.use": "Grindstone used", + "subtitles.block.growing_plant.crop": "Plant cropped", + "subtitles.block.honey_block.slide": "Sliding down a honey block", + "subtitles.item.honeycomb.wax_on": "Wax on", + "subtitles.block.iron_trapdoor.close": "Trapdoor closes", + "subtitles.block.iron_trapdoor.open": "Trapdoor opens", + "subtitles.block.lava.ambient": "Lava pops", + "subtitles.block.lava.extinguish": "Lava hisses", + "subtitles.block.lever.click": "Lever clicks", + "subtitles.block.note_block.note": "Note Block plays", + "subtitles.block.piston.move": "Piston moves", + "subtitles.block.pointed_dripstone.land": "Stalactite crashes down", + "subtitles.block.pointed_dripstone.drip_lava": "Lava drips", + "subtitles.block.pointed_dripstone.drip_water": "Water drips", + "subtitles.block.pointed_dripstone.drip_lava_into_cauldron": "Lava drips into Cauldron", + "subtitles.block.pointed_dripstone.drip_water_into_cauldron": "Water drips into Cauldron", + "subtitles.block.portal.ambient": "Portal whooshes", + "subtitles.block.portal.travel": "Portal noise fades", + "subtitles.block.portal.trigger": "Portal noise intensifies", + "subtitles.block.pressure_plate.click": "Pressure Plate clicks", + "subtitles.block.pumpkin.carve": "Shears carve", + "subtitles.block.redstone_torch.burnout": "Torch fizzes", + "subtitles.block.respawn_anchor.ambient": "Portal whooshes", + "subtitles.block.respawn_anchor.charge": "Respawn Anchor is charged", + "subtitles.block.respawn_anchor.deplete": "Respawn Anchor depletes", + "subtitles.block.respawn_anchor.set_spawn": "Respawn Anchor sets spawn", + "subtitles.block.sculk.charge": "Sculk bubbles", + "subtitles.block.sculk.spread": "Sculk spreads", + "subtitles.block.sculk_catalyst.bloom": "Sculk Catalyst blooms", + "subtitles.block.sculk_sensor.clicking": "Sculk Sensor starts clicking", + "subtitles.block.sculk_sensor.clicking_stop": "Sculk Sensor stops clicking", + "subtitles.block.sculk_shrieker.shriek": "Sculk Shrieker shrieks", + "subtitles.block.shulker_box.close": "Shulker closes", + "subtitles.block.shulker_box.open": "Shulker opens", + "subtitles.block.smithing_table.use": "Smithing Table used", + "subtitles.block.smoker.smoke": "Smoker smokes", + "subtitles.block.sweet_berry_bush.pick_berries": "Berries pop", + "subtitles.block.trapdoor.toggle": "Trapdoor creaks", + "subtitles.block.tripwire.attach": "Tripwire attaches", + "subtitles.block.tripwire.click": "Tripwire clicks", + "subtitles.block.tripwire.detach": "Tripwire detaches", + "subtitles.block.water.ambient": "Water flows", + "subtitles.enchant.thorns.hit": "Thorns prick", + "subtitles.entity.allay.death": "Allay dies", + "subtitles.entity.allay.hurt": "Allay hurts", + "subtitles.entity.allay.ambient_with_item": "Allay seeks", + "subtitles.entity.allay.ambient_without_item": "Allay yearns", + "subtitles.entity.allay.item_given": "Allay chortles", + "subtitles.entity.allay.item_taken": "Allay allays", + "subtitles.entity.allay.item_thrown": "Allay tosses", + "subtitles.entity.armor_stand.fall": "Something fell", + "subtitles.entity.arrow.hit": "Arrow hits", + "subtitles.entity.arrow.hit_player": "Player hit", + "subtitles.entity.arrow.shoot": "Arrow fired", + "subtitles.entity.axolotl.attack": "Axolotl attacks", + "subtitles.entity.axolotl.death": "Axolotl dies", + "subtitles.entity.axolotl.hurt": "Axolotl hurts", + "subtitles.entity.axolotl.idle_air": "Axolotl chirps", + "subtitles.entity.axolotl.idle_water": "Axolotl chirps", + "subtitles.entity.axolotl.splash": "Axolotl splashes", + "subtitles.entity.axolotl.swim": "Axolotl swims", + "subtitles.entity.bat.ambient": "Bat screeches", + "subtitles.entity.bat.death": "Bat dies", + "subtitles.entity.bat.hurt": "Bat hurts", + "subtitles.entity.bat.takeoff": "Bat takes off", + "subtitles.entity.bee.ambient": "Bee buzzes", + "subtitles.entity.bee.death": "Bee dies", + "subtitles.entity.bee.hurt": "Bee hurts", + "subtitles.entity.bee.loop": "Bee buzzes", + "subtitles.entity.bee.loop_aggressive": "Bee buzzes angrily", + "subtitles.entity.bee.pollinate": "Bee buzzes happily", + "subtitles.entity.bee.sting": "Bee stings", + "subtitles.entity.blaze.ambient": "Blaze breathes", + "subtitles.entity.blaze.burn": "Blaze crackles", + "subtitles.entity.blaze.death": "Blaze dies", + "subtitles.entity.blaze.hurt": "Blaze hurts", + "subtitles.entity.blaze.shoot": "Blaze shoots", + "subtitles.entity.boat.paddle_land": "Rowing", + "subtitles.entity.boat.paddle_water": "Rowing", + "subtitles.entity.camel.ambient": "Camel grunts", + "subtitles.entity.camel.dash": "Camel yeets", + "subtitles.entity.camel.dash_ready": "Camel recovers", + "subtitles.entity.camel.death": "Camel dies", + "subtitles.entity.camel.eat": "Camel eats", + "subtitles.entity.camel.hurt": "Camel hurts", + "subtitles.entity.camel.saddle": "Saddle equips", + "subtitles.entity.camel.sit": "Camel sits down", + "subtitles.entity.camel.stand": "Camel stands up", + "subtitles.entity.camel.step": "Camel steps", + "subtitles.entity.camel.step_sand": "Camel sands", + "subtitles.entity.cat.ambient": "Cat meows", + "subtitles.entity.cat.beg_for_food": "Cat begs", + "subtitles.entity.cat.death": "Cat dies", + "subtitles.entity.cat.eat": "Cat eats", + "subtitles.entity.cat.hiss": "Cat hisses", + "subtitles.entity.cat.hurt": "Cat hurts", + "subtitles.entity.cat.purr": "Cat purrs", + "subtitles.entity.chicken.ambient": "Chicken clucks", + "subtitles.entity.chicken.death": "Chicken dies", + "subtitles.entity.chicken.egg": "Chicken plops", + "subtitles.entity.chicken.hurt": "Chicken hurts", + "subtitles.entity.cod.death": "Cod dies", + "subtitles.entity.cod.flop": "Cod flops", + "subtitles.entity.cod.hurt": "Cod hurts", + "subtitles.entity.cow.ambient": "Cow moos", + "subtitles.entity.cow.death": "Cow dies", + "subtitles.entity.cow.hurt": "Cow hurts", + "subtitles.entity.cow.milk": "Cow gets milked", + "subtitles.entity.creeper.death": "Creeper dies", + "subtitles.entity.creeper.hurt": "Creeper hurts", + "subtitles.entity.creeper.primed": "Creeper hisses", + "subtitles.entity.dolphin.ambient": "Dolphin chirps", + "subtitles.entity.dolphin.ambient_water": "Dolphin whistles", + "subtitles.entity.dolphin.attack": "Dolphin attacks", + "subtitles.entity.dolphin.death": "Dolphin dies", + "subtitles.entity.dolphin.eat": "Dolphin eats", + "subtitles.entity.dolphin.hurt": "Dolphin hurts", + "subtitles.entity.dolphin.jump": "Dolphin jumps", + "subtitles.entity.dolphin.play": "Dolphin plays", + "subtitles.entity.dolphin.splash": "Dolphin splashes", + "subtitles.entity.dolphin.swim": "Dolphin swims", + "subtitles.entity.donkey.ambient": "Donkey hee-haws", + "subtitles.entity.donkey.angry": "Donkey neighs", + "subtitles.entity.donkey.chest": "Donkey Chest equips", + "subtitles.entity.donkey.death": "Donkey dies", + "subtitles.entity.donkey.eat": "Donkey eats", + "subtitles.entity.donkey.hurt": "Donkey hurts", + "subtitles.entity.drowned.ambient": "Drowned gurgles", + "subtitles.entity.drowned.ambient_water": "Drowned gurgles", + "subtitles.entity.drowned.death": "Drowned dies", + "subtitles.entity.drowned.hurt": "Drowned hurts", + "subtitles.entity.drowned.shoot": "Drowned throws Trident", + "subtitles.entity.drowned.step": "Drowned steps", + "subtitles.entity.drowned.swim": "Drowned swims", + "subtitles.entity.egg.throw": "Egg flies", + "subtitles.entity.elder_guardian.ambient": "Elder Guardian moans", + "subtitles.entity.elder_guardian.ambient_land": "Elder Guardian flaps", + "subtitles.entity.elder_guardian.curse": "Elder Guardian curses", + "subtitles.entity.elder_guardian.death": "Elder Guardian dies", + "subtitles.entity.elder_guardian.flop": "Elder Guardian flops", + "subtitles.entity.elder_guardian.hurt": "Elder Guardian hurts", + "subtitles.entity.ender_dragon.ambient": "Dragon roars", + "subtitles.entity.ender_dragon.death": "Dragon dies", + "subtitles.entity.ender_dragon.flap": "Dragon flaps", + "subtitles.entity.ender_dragon.growl": "Dragon growls", + "subtitles.entity.ender_dragon.hurt": "Dragon hurts", + "subtitles.entity.ender_dragon.shoot": "Dragon shoots", + "subtitles.entity.ender_eye.death": "Eye of Ender falls", + "subtitles.entity.ender_eye.launch": "Eye of Ender shoots", + "subtitles.entity.ender_pearl.throw": "Ender Pearl flies", + "subtitles.entity.enderman.ambient": "Enderman vwoops", + "subtitles.entity.enderman.death": "Enderman dies", + "subtitles.entity.enderman.hurt": "Enderman hurts", + "subtitles.entity.enderman.scream": "Enderman screams", + "subtitles.entity.enderman.stare": "Enderman cries out", + "subtitles.entity.enderman.teleport": "Enderman teleports", + "subtitles.entity.endermite.ambient": "Endermite scuttles", + "subtitles.entity.endermite.death": "Endermite dies", + "subtitles.entity.endermite.hurt": "Endermite hurts", + "subtitles.entity.evoker.ambient": "Evoker murmurs", + "subtitles.entity.evoker.cast_spell": "Evoker casts spell", + "subtitles.entity.evoker.celebrate": "Evoker cheers", + "subtitles.entity.evoker.death": "Evoker dies", + "subtitles.entity.evoker.hurt": "Evoker hurts", + "subtitles.entity.evoker.prepare_attack": "Evoker prepares attack", + "subtitles.entity.evoker.prepare_summon": "Evoker prepares summoning", + "subtitles.entity.evoker.prepare_wololo": "Evoker prepares charming", + "subtitles.entity.evoker_fangs.attack": "Fangs snap", + "subtitles.entity.experience_orb.pickup": "Experience gained", + "subtitles.entity.firework_rocket.blast": "Firework blasts", + "subtitles.entity.firework_rocket.launch": "Firework launches", + "subtitles.entity.firework_rocket.twinkle": "Firework twinkles", + "subtitles.entity.fishing_bobber.retrieve": "Bobber retrieved", + "subtitles.entity.fishing_bobber.splash": "Fishing Bobber splashes", + "subtitles.entity.fishing_bobber.throw": "Bobber thrown", + "subtitles.entity.fox.aggro": "Fox angers", + "subtitles.entity.fox.ambient": "Fox squeaks", + "subtitles.entity.fox.bite": "Fox bites", + "subtitles.entity.fox.death": "Fox dies", + "subtitles.entity.fox.eat": "Fox eats", + "subtitles.entity.fox.hurt": "Fox hurts", + "subtitles.entity.fox.screech": "Fox screeches", + "subtitles.entity.fox.sleep": "Fox snores", + "subtitles.entity.fox.sniff": "Fox sniffs", + "subtitles.entity.fox.spit": "Fox spits", + "subtitles.entity.fox.teleport": "Fox teleports", + "subtitles.entity.frog.ambient": "Frog croaks", + "subtitles.entity.frog.death": "Frog dies", + "subtitles.entity.frog.eat": "Frog eats", + "subtitles.entity.frog.hurt": "Frog hurts", + "subtitles.entity.frog.lay_spawn": "Frog lays spawn", + "subtitles.entity.frog.long_jump": "Frog jumps", + "subtitles.entity.generic.big_fall": "Something fell", + "subtitles.entity.generic.burn": "Burning", + "subtitles.entity.generic.death": "Dying", + "subtitles.entity.generic.drink": "Sipping", + "subtitles.entity.generic.eat": "Eating", + "subtitles.entity.generic.explode": "Explosion", + "subtitles.entity.generic.extinguish_fire": "Fire extinguishes", + "subtitles.entity.generic.hurt": "Something hurts", + "subtitles.entity.generic.small_fall": "Something trips", + "subtitles.entity.generic.splash": "Splashing", + "subtitles.entity.generic.swim": "Swimming", + "subtitles.entity.ghast.ambient": "Ghast cries", + "subtitles.entity.ghast.death": "Ghast dies", + "subtitles.entity.ghast.hurt": "Ghast hurts", + "subtitles.entity.ghast.shoot": "Ghast shoots", + "subtitles.entity.glow_item_frame.add_item": "Glow Item Frame fills", + "subtitles.entity.glow_item_frame.break": "Glow Item Frame breaks", + "subtitles.entity.glow_item_frame.place": "Glow Item Frame placed", + "subtitles.entity.glow_item_frame.remove_item": "Glow Item Frame empties", + "subtitles.entity.glow_item_frame.rotate_item": "Glow Item Frame clicks", + "subtitles.entity.glow_squid.ambient": "Glow Squid swims", + "subtitles.entity.glow_squid.death": "Glow Squid dies", + "subtitles.entity.glow_squid.hurt": "Glow Squid hurts", + "subtitles.entity.glow_squid.squirt": "Glow Squid shoots ink", + "subtitles.entity.goat.ambient": "Goat bleats", + "subtitles.entity.goat.screaming.ambient": "Goat bellows", + "subtitles.entity.goat.death": "Goat dies", + "subtitles.entity.goat.eat": "Goat eats", + "subtitles.entity.goat.horn_break": "Goat Horn breaks off", + "subtitles.entity.goat.hurt": "Goat hurts", + "subtitles.entity.goat.long_jump": "Goat leaps", + "subtitles.entity.goat.milk": "Goat gets milked", + "subtitles.entity.goat.prepare_ram": "Goat stomps", + "subtitles.entity.goat.ram_impact": "Goat rams", + "subtitles.entity.goat.step": "Goat steps", + "subtitles.entity.guardian.ambient": "Guardian moans", + "subtitles.entity.guardian.ambient_land": "Guardian flaps", + "subtitles.entity.guardian.attack": "Guardian shoots", + "subtitles.entity.guardian.death": "Guardian dies", + "subtitles.entity.guardian.flop": "Guardian flops", + "subtitles.entity.guardian.hurt": "Guardian hurts", + "subtitles.entity.hoglin.ambient": "Hoglin growls", + "subtitles.entity.hoglin.angry": "Hoglin growls angrily", + "subtitles.entity.hoglin.attack": "Hoglin attacks", + "subtitles.entity.hoglin.converted_to_zombified": "Hoglin converts to Zoglin", + "subtitles.entity.hoglin.death": "Hoglin dies", + "subtitles.entity.hoglin.hurt": "Hoglin hurts", + "subtitles.entity.hoglin.retreat": "Hoglin retreats", + "subtitles.entity.hoglin.step": "Hoglin steps", + "subtitles.entity.horse.ambient": "Horse neighs", + "subtitles.entity.horse.angry": "Horse neighs", + "subtitles.entity.horse.armor": "Horse armor equips", + "subtitles.entity.horse.breathe": "Horse breathes", + "subtitles.entity.horse.death": "Horse dies", + "subtitles.entity.horse.eat": "Horse eats", + "subtitles.entity.horse.gallop": "Horse gallops", + "subtitles.entity.horse.hurt": "Horse hurts", + "subtitles.entity.horse.jump": "Horse jumps", + "subtitles.entity.horse.saddle": "Saddle equips", + "subtitles.entity.husk.ambient": "Husk groans", + "subtitles.entity.husk.converted_to_zombie": "Husk converts to Zombie", + "subtitles.entity.husk.death": "Husk dies", + "subtitles.entity.husk.hurt": "Husk hurts", + "subtitles.entity.illusioner.ambient": "Illusioner murmurs", + "subtitles.entity.illusioner.cast_spell": "Illusioner casts spell", + "subtitles.entity.illusioner.death": "Illusioner dies", + "subtitles.entity.illusioner.hurt": "Illusioner hurts", + "subtitles.entity.illusioner.mirror_move": "Illusioner displaces", + "subtitles.entity.illusioner.prepare_blindness": "Illusioner prepares blindness", + "subtitles.entity.illusioner.prepare_mirror": "Illusioner prepares mirror image", + "subtitles.entity.iron_golem.attack": "Iron Golem attacks", + "subtitles.entity.iron_golem.damage": "Iron Golem breaks", + "subtitles.entity.iron_golem.death": "Iron Golem dies", + "subtitles.entity.iron_golem.hurt": "Iron Golem hurts", + "subtitles.entity.iron_golem.repair": "Iron Golem repaired", + "subtitles.entity.item.break": "Item breaks", + "subtitles.entity.item.pickup": "Item plops", + "subtitles.entity.item_frame.add_item": "Item Frame fills", + "subtitles.entity.item_frame.break": "Item Frame breaks", + "subtitles.entity.item_frame.place": "Item Frame placed", + "subtitles.entity.item_frame.remove_item": "Item Frame empties", + "subtitles.entity.item_frame.rotate_item": "Item Frame clicks", + "subtitles.entity.leash_knot.break": "Leash knot breaks", + "subtitles.entity.leash_knot.place": "Leash knot tied", + "subtitles.entity.lightning_bolt.impact": "Lightning strikes", + "subtitles.entity.lightning_bolt.thunder": "Thunder roars", + "subtitles.entity.llama.ambient": "Llama bleats", + "subtitles.entity.llama.angry": "Llama bleats angrily", + "subtitles.entity.llama.chest": "Llama Chest equips", + "subtitles.entity.llama.death": "Llama dies", + "subtitles.entity.llama.eat": "Llama eats", + "subtitles.entity.llama.hurt": "Llama hurts", + "subtitles.entity.llama.spit": "Llama spits", + "subtitles.entity.llama.step": "Llama steps", + "subtitles.entity.llama.swag": "Llama is decorated", + "subtitles.entity.magma_cube.death": "Magma Cube dies", + "subtitles.entity.magma_cube.hurt": "Magma Cube hurts", + "subtitles.entity.magma_cube.squish": "Magma Cube squishes", + "subtitles.entity.minecart.riding": "Minecart rolls", + "subtitles.entity.mooshroom.convert": "Mooshroom transforms", + "subtitles.entity.mooshroom.eat": "Mooshroom eats", + "subtitles.entity.mooshroom.milk": "Mooshroom gets milked", + "subtitles.entity.mooshroom.suspicious_milk": "Mooshroom gets milked suspiciously", + "subtitles.entity.mule.ambient": "Mule hee-haws", + "subtitles.entity.mule.angry": "Mule neighs", + "subtitles.entity.mule.chest": "Mule Chest equips", + "subtitles.entity.mule.death": "Mule dies", + "subtitles.entity.mule.eat": "Mule eats", + "subtitles.entity.mule.hurt": "Mule hurts", + "subtitles.entity.painting.break": "Painting breaks", + "subtitles.entity.painting.place": "Painting placed", + "subtitles.entity.panda.aggressive_ambient": "Panda huffs", + "subtitles.entity.panda.ambient": "Panda pants", + "subtitles.entity.panda.bite": "Panda bites", + "subtitles.entity.panda.cant_breed": "Panda bleats", + "subtitles.entity.panda.death": "Panda dies", + "subtitles.entity.panda.eat": "Panda eats", + "subtitles.entity.panda.hurt": "Panda hurts", + "subtitles.entity.panda.pre_sneeze": "Panda's nose tickles", + "subtitles.entity.panda.sneeze": "Panda sneezes", + "subtitles.entity.panda.step": "Panda steps", + "subtitles.entity.panda.worried_ambient": "Panda whimpers", + "subtitles.entity.parrot.ambient": "Parrot talks", + "subtitles.entity.parrot.death": "Parrot dies", + "subtitles.entity.parrot.eats": "Parrot eats", + "subtitles.entity.parrot.fly": "Parrot flutters", + "subtitles.entity.parrot.hurts": "Parrot hurts", + "subtitles.entity.parrot.imitate.blaze": "Parrot breathes", + "subtitles.entity.parrot.imitate.creeper": "Parrot hisses", + "subtitles.entity.parrot.imitate.drowned": "Parrot gurgles", + "subtitles.entity.parrot.imitate.elder_guardian": "Parrot moans", + "subtitles.entity.parrot.imitate.ender_dragon": "Parrot roars", + "subtitles.entity.parrot.imitate.endermite": "Parrot scuttles", + "subtitles.entity.parrot.imitate.evoker": "Parrot murmurs", + "subtitles.entity.parrot.imitate.ghast": "Parrot cries", + "subtitles.entity.parrot.imitate.guardian": "Parrot moans", + "subtitles.entity.parrot.imitate.hoglin": "Parrot growls", + "subtitles.entity.parrot.imitate.husk": "Parrot groans", + "subtitles.entity.parrot.imitate.illusioner": "Parrot murmurs", + "subtitles.entity.parrot.imitate.magma_cube": "Parrot squishes", + "subtitles.entity.parrot.imitate.phantom": "Parrot screeches", + "subtitles.entity.parrot.imitate.piglin": "Parrot snorts", + "subtitles.entity.parrot.imitate.piglin_brute": "Parrot snorts", + "subtitles.entity.parrot.imitate.pillager": "Parrot murmurs", + "subtitles.entity.parrot.imitate.ravager": "Parrot grunts", + "subtitles.entity.parrot.imitate.shulker": "Parrot lurks", + "subtitles.entity.parrot.imitate.silverfish": "Parrot hisses", + "subtitles.entity.parrot.imitate.skeleton": "Parrot rattles", + "subtitles.entity.parrot.imitate.slime": "Parrot squishes", + "subtitles.entity.parrot.imitate.spider": "Parrot hisses", + "subtitles.entity.parrot.imitate.stray": "Parrot rattles", + "subtitles.entity.parrot.imitate.vex": "Parrot vexes", + "subtitles.entity.parrot.imitate.vindicator": "Parrot mutters", + "subtitles.entity.parrot.imitate.warden": "Parrot whines", + "subtitles.entity.parrot.imitate.witch": "Parrot giggles", + "subtitles.entity.parrot.imitate.wither": "Parrot angers", + "subtitles.entity.parrot.imitate.wither_skeleton": "Parrot rattles", + "subtitles.entity.parrot.imitate.zoglin": "Parrot growls", + "subtitles.entity.parrot.imitate.zombie": "Parrot groans", + "subtitles.entity.parrot.imitate.zombie_villager": "Parrot groans", + "subtitles.entity.phantom.ambient": "Phantom screeches", + "subtitles.entity.phantom.bite": "Phantom bites", + "subtitles.entity.phantom.death": "Phantom dies", + "subtitles.entity.phantom.flap": "Phantom flaps", + "subtitles.entity.phantom.hurt": "Phantom hurts", + "subtitles.entity.phantom.swoop": "Phantom swoops", + "subtitles.entity.pig.ambient": "Pig oinks", + "subtitles.entity.pig.death": "Pig dies", + "subtitles.entity.pig.hurt": "Pig hurts", + "subtitles.entity.pig.saddle": "Saddle equips", + "subtitles.entity.piglin.admiring_item": "Piglin admires item", + "subtitles.entity.piglin.ambient": "Piglin snorts", + "subtitles.entity.piglin.angry": "Piglin snorts angrily", + "subtitles.entity.piglin.celebrate": "Piglin celebrates", + "subtitles.entity.piglin.converted_to_zombified": "Piglin converts to Zombified Piglin", + "subtitles.entity.piglin.death": "Piglin dies", + "subtitles.entity.piglin.hurt": "Piglin hurts", + "subtitles.entity.piglin.jealous": "Piglin snorts enviously", + "subtitles.entity.piglin.retreat": "Piglin retreats", + "subtitles.entity.piglin.step": "Piglin steps", + "subtitles.entity.piglin_brute.ambient": "Piglin Brute snorts", + "subtitles.entity.piglin_brute.angry": "Piglin Brute snorts angrily", + "subtitles.entity.piglin_brute.death": "Piglin Brute dies", + "subtitles.entity.piglin_brute.hurt": "Piglin Brute hurts", + "subtitles.entity.piglin_brute.step": "Piglin Brute steps", + "subtitles.entity.piglin_brute.converted_to_zombified": "Piglin Brute converts to Zombified Piglin", + "subtitles.entity.pillager.ambient": "Pillager murmurs", + "subtitles.entity.pillager.celebrate": "Pillager cheers", + "subtitles.entity.pillager.death": "Pillager dies", + "subtitles.entity.pillager.hurt": "Pillager hurts", + "subtitles.entity.player.attack.crit": "Critical attack", + "subtitles.entity.player.attack.knockback": "Knockback attack", + "subtitles.entity.player.attack.strong": "Strong attack", + "subtitles.entity.player.attack.sweep": "Sweeping attack", + "subtitles.entity.player.attack.weak": "Weak attack", + "subtitles.entity.player.burp": "Burp", + "subtitles.entity.player.death": "Player dies", + "subtitles.entity.player.hurt": "Player hurts", + "subtitles.entity.player.hurt_drown": "Player drowning", + "subtitles.entity.player.hurt_on_fire": "Player burns", + "subtitles.entity.player.levelup": "Player dings", + "subtitles.entity.player.freeze_hurt": "Player freezes", + "subtitles.entity.polar_bear.ambient": "Polar Bear groans", + "subtitles.entity.polar_bear.ambient_baby": "Polar Bear hums", + "subtitles.entity.polar_bear.death": "Polar Bear dies", + "subtitles.entity.polar_bear.hurt": "Polar Bear hurts", + "subtitles.entity.polar_bear.warning": "Polar Bear roars", + "subtitles.entity.potion.splash": "Bottle smashes", + "subtitles.entity.potion.throw": "Bottle thrown", + "subtitles.entity.puffer_fish.blow_out": "Pufferfish deflates", + "subtitles.entity.puffer_fish.blow_up": "Pufferfish inflates", + "subtitles.entity.puffer_fish.death": "Pufferfish dies", + "subtitles.entity.puffer_fish.flop": "Pufferfish flops", + "subtitles.entity.puffer_fish.hurt": "Pufferfish hurts", + "subtitles.entity.puffer_fish.sting": "Pufferfish stings", + "subtitles.entity.rabbit.ambient": "Rabbit squeaks", + "subtitles.entity.rabbit.attack": "Rabbit attacks", + "subtitles.entity.rabbit.death": "Rabbit dies", + "subtitles.entity.rabbit.hurt": "Rabbit hurts", + "subtitles.entity.rabbit.jump": "Rabbit hops", + "subtitles.entity.ravager.ambient": "Ravager grunts", + "subtitles.entity.ravager.attack": "Ravager bites", + "subtitles.entity.ravager.celebrate": "Ravager cheers", + "subtitles.entity.ravager.death": "Ravager dies", + "subtitles.entity.ravager.hurt": "Ravager hurts", + "subtitles.entity.ravager.roar": "Ravager roars", + "subtitles.entity.ravager.step": "Ravager steps", + "subtitles.entity.ravager.stunned": "Ravager stunned", + "subtitles.entity.salmon.death": "Salmon dies", + "subtitles.entity.salmon.flop": "Salmon flops", + "subtitles.entity.salmon.hurt": "Salmon hurts", + "subtitles.entity.sheep.ambient": "Sheep baahs", + "subtitles.entity.sheep.death": "Sheep dies", + "subtitles.entity.sheep.hurt": "Sheep hurts", + "subtitles.entity.shulker.ambient": "Shulker lurks", + "subtitles.entity.shulker.close": "Shulker closes", + "subtitles.entity.shulker.death": "Shulker dies", + "subtitles.entity.shulker.hurt": "Shulker hurts", + "subtitles.entity.shulker.open": "Shulker opens", + "subtitles.entity.shulker.shoot": "Shulker shoots", + "subtitles.entity.shulker.teleport": "Shulker teleports", + "subtitles.entity.shulker_bullet.hit": "Shulker Bullet explodes", + "subtitles.entity.shulker_bullet.hurt": "Shulker Bullet breaks", + "subtitles.entity.silverfish.ambient": "Silverfish hisses", + "subtitles.entity.silverfish.death": "Silverfish dies", + "subtitles.entity.silverfish.hurt": "Silverfish hurts", + "subtitles.entity.skeleton.ambient": "Skeleton rattles", + "subtitles.entity.skeleton.converted_to_stray": "Skeleton converts to Stray", + "subtitles.entity.skeleton.death": "Skeleton dies", + "subtitles.entity.skeleton.hurt": "Skeleton hurts", + "subtitles.entity.skeleton.shoot": "Skeleton shoots", + "subtitles.entity.skeleton_horse.ambient": "Skeleton Horse cries", + "subtitles.entity.skeleton_horse.death": "Skeleton Horse dies", + "subtitles.entity.skeleton_horse.hurt": "Skeleton Horse hurts", + "subtitles.entity.skeleton_horse.swim": "Skeleton Horse swims", + "subtitles.entity.slime.attack": "Slime attacks", + "subtitles.entity.slime.death": "Slime dies", + "subtitles.entity.slime.hurt": "Slime hurts", + "subtitles.entity.slime.squish": "Slime squishes", + "subtitles.entity.snow_golem.death": "Snow Golem dies", + "subtitles.entity.snow_golem.hurt": "Snow Golem hurts", + "subtitles.entity.snowball.throw": "Snowball flies", + "subtitles.entity.spider.ambient": "Spider hisses", + "subtitles.entity.spider.death": "Spider dies", + "subtitles.entity.spider.hurt": "Spider hurts", + "subtitles.entity.squid.ambient": "Squid swims", + "subtitles.entity.squid.death": "Squid dies", + "subtitles.entity.squid.hurt": "Squid hurts", + "subtitles.entity.squid.squirt": "Squid shoots ink", + "subtitles.entity.stray.ambient": "Stray rattles", + "subtitles.entity.stray.death": "Stray dies", + "subtitles.entity.stray.hurt": "Stray hurts", + "subtitles.entity.strider.death": "Strider dies", + "subtitles.entity.strider.eat": "Strider eats", + "subtitles.entity.strider.happy": "Strider warbles", + "subtitles.entity.strider.hurt": "Strider hurts", + "subtitles.entity.strider.idle": "Strider chirps", + "subtitles.entity.strider.retreat": "Strider retreats", + "subtitles.entity.tadpole.death": "Tadpole dies", + "subtitles.entity.tadpole.flop": "Tadpole flops", + "subtitles.entity.tadpole.grow_up": "Tadpole grows up", + "subtitles.entity.tadpole.hurt": "Tadpole hurts", + "subtitles.entity.tnt.primed": "TNT fizzes", + "subtitles.entity.tropical_fish.death": "Tropical Fish dies", + "subtitles.entity.tropical_fish.flop": "Tropical Fish flops", + "subtitles.entity.tropical_fish.hurt": "Tropical Fish hurts", + "subtitles.entity.turtle.ambient_land": "Turtle chirps", + "subtitles.entity.turtle.death": "Turtle dies", + "subtitles.entity.turtle.death_baby": "Turtle baby dies", + "subtitles.entity.turtle.egg_break": "Turtle Egg breaks", + "subtitles.entity.turtle.egg_crack": "Turtle Egg cracks", + "subtitles.entity.turtle.egg_hatch": "Turtle Egg hatches", + "subtitles.entity.turtle.hurt": "Turtle hurts", + "subtitles.entity.turtle.hurt_baby": "Turtle baby hurts", + "subtitles.entity.turtle.lay_egg": "Turtle lays egg", + "subtitles.entity.turtle.shamble": "Turtle shambles", + "subtitles.entity.turtle.shamble_baby": "Turtle baby shambles", + "subtitles.entity.turtle.swim": "Turtle swims", + "subtitles.entity.vex.ambient": "Vex vexes", + "subtitles.entity.vex.charge": "Vex shrieks", + "subtitles.entity.vex.death": "Vex dies", + "subtitles.entity.vex.hurt": "Vex hurts", + "subtitles.entity.villager.ambient": "Villager mumbles", + "subtitles.entity.villager.celebrate": "Villager cheers", + "subtitles.entity.villager.death": "Villager dies", + "subtitles.entity.villager.hurt": "Villager hurts", + "subtitles.entity.villager.no": "Villager disagrees", + "subtitles.entity.villager.trade": "Villager trades", + "subtitles.entity.villager.work_armorer": "Armorer works", + "subtitles.entity.villager.work_butcher": "Butcher works", + "subtitles.entity.villager.work_cartographer": "Cartographer works", + "subtitles.entity.villager.work_cleric": "Cleric works", + "subtitles.entity.villager.work_farmer": "Farmer works", + "subtitles.entity.villager.work_fisherman": "Fisherman works", + "subtitles.entity.villager.work_fletcher": "Fletcher works", + "subtitles.entity.villager.work_leatherworker": "Leatherworker works", + "subtitles.entity.villager.work_librarian": "Librarian works", + "subtitles.entity.villager.work_mason": "Mason works", + "subtitles.entity.villager.work_shepherd": "Shepherd works", + "subtitles.entity.villager.work_toolsmith": "Toolsmith works", + "subtitles.entity.villager.work_weaponsmith": "Weaponsmith works", + "subtitles.entity.villager.yes": "Villager agrees", + "subtitles.entity.vindicator.ambient": "Vindicator mutters", + "subtitles.entity.vindicator.celebrate": "Vindicator cheers", + "subtitles.entity.vindicator.death": "Vindicator dies", + "subtitles.entity.vindicator.hurt": "Vindicator hurts", + "subtitles.entity.wandering_trader.ambient": "Wandering Trader mumbles", + "subtitles.entity.wandering_trader.death": "Wandering Trader dies", + "subtitles.entity.wandering_trader.disappeared": "Wandering Trader disappears", + "subtitles.entity.wandering_trader.drink_milk": "Wandering Trader drinks milk", + "subtitles.entity.wandering_trader.drink_potion": "Wandering Trader drinks potion", + "subtitles.entity.wandering_trader.hurt": "Wandering Trader hurts", + "subtitles.entity.wandering_trader.no": "Wandering Trader disagrees", + "subtitles.entity.wandering_trader.reappeared": "Wandering Trader appears", + "subtitles.entity.wandering_trader.trade": "Wandering Trader trades", + "subtitles.entity.wandering_trader.yes": "Wandering Trader agrees", + "subtitles.entity.warden.roar": "Warden roars", + "subtitles.entity.warden.sniff": "Warden sniffs", + "subtitles.entity.warden.emerge": "Warden emerges", + "subtitles.entity.warden.dig": "Warden digs", + "subtitles.entity.warden.hurt": "Warden hurts", + "subtitles.entity.warden.death": "Warden dies", + "subtitles.entity.warden.step": "Warden steps", + "subtitles.entity.warden.listening": "Warden takes notice", + "subtitles.entity.warden.listening_angry": "Warden takes notice angrily", + "subtitles.entity.warden.heartbeat": "Warden's heart beats", + "subtitles.entity.warden.attack_impact": "Warden lands hit", + "subtitles.entity.warden.tendril_clicks": "Warden's tendrils click", + "subtitles.entity.warden.angry": "Warden rages", + "subtitles.entity.warden.agitated": "Warden groans angrily", + "subtitles.entity.warden.ambient": "Warden whines", + "subtitles.entity.warden.nearby_close": "Warden approaches", + "subtitles.entity.warden.nearby_closer": "Warden advances", + "subtitles.entity.warden.nearby_closest": "Warden draws close", + "subtitles.entity.warden.sonic_charge": "Warden charges", + "subtitles.entity.warden.sonic_boom": "Warden booms", + "subtitles.entity.witch.ambient": "Witch giggles", + "subtitles.entity.witch.celebrate": "Witch cheers", + "subtitles.entity.witch.death": "Witch dies", + "subtitles.entity.witch.drink": "Witch drinks", + "subtitles.entity.witch.hurt": "Witch hurts", + "subtitles.entity.witch.throw": "Witch throws", + "subtitles.entity.wither.ambient": "Wither angers", + "subtitles.entity.wither.death": "Wither dies", + "subtitles.entity.wither.hurt": "Wither hurts", + "subtitles.entity.wither.shoot": "Wither attacks", + "subtitles.entity.wither.spawn": "Wither released", + "subtitles.entity.wither_skeleton.ambient": "Wither Skeleton rattles", + "subtitles.entity.wither_skeleton.death": "Wither Skeleton dies", + "subtitles.entity.wither_skeleton.hurt": "Wither Skeleton hurts", + "subtitles.entity.wolf.ambient": "Wolf pants", + "subtitles.entity.wolf.death": "Wolf dies", + "subtitles.entity.wolf.growl": "Wolf growls", + "subtitles.entity.wolf.hurt": "Wolf hurts", + "subtitles.entity.wolf.shake": "Wolf shakes", + "subtitles.entity.zoglin.ambient": "Zoglin growls", + "subtitles.entity.zoglin.angry": "Zoglin growls angrily", + "subtitles.entity.zoglin.attack": "Zoglin attacks", + "subtitles.entity.zoglin.death": "Zoglin dies", + "subtitles.entity.zoglin.hurt": "Zoglin hurts", + "subtitles.entity.zoglin.step": "Zoglin steps", + "subtitles.entity.zombie.ambient": "Zombie groans", + "subtitles.entity.zombie.attack_wooden_door": "Door shakes", + "subtitles.entity.zombie.converted_to_drowned": "Zombie converts to Drowned", + "subtitles.entity.zombie.break_wooden_door": "Door breaks", + "subtitles.entity.zombie.death": "Zombie dies", + "subtitles.entity.zombie.destroy_egg": "Turtle Egg stomped", + "subtitles.entity.zombie.hurt": "Zombie hurts", + "subtitles.entity.zombie.infect": "Zombie infects", + "subtitles.entity.zombie_horse.ambient": "Zombie Horse cries", + "subtitles.entity.zombie_horse.death": "Zombie Horse dies", + "subtitles.entity.zombie_horse.hurt": "Zombie Horse hurts", + "subtitles.entity.zombie_villager.ambient": "Zombie Villager groans", + "subtitles.entity.zombie_villager.converted": "Zombie Villager vociferates", + "subtitles.entity.zombie_villager.cure": "Zombie Villager snuffles", + "subtitles.entity.zombie_villager.death": "Zombie Villager dies", + "subtitles.entity.zombie_villager.hurt": "Zombie Villager hurts", + "subtitles.entity.zombified_piglin.ambient": "Zombified Piglin grunts", + "subtitles.entity.zombified_piglin.angry": "Zombified Piglin grunts angrily", + "subtitles.entity.zombified_piglin.death": "Zombified Piglin dies", + "subtitles.entity.zombified_piglin.hurt": "Zombified Piglin hurts", + "subtitles.event.raid.horn": "Ominous horn blares", + "subtitles.item.armor.equip": "Gear equips", + "subtitles.item.armor.equip_chain": "Chain armor jingles", + "subtitles.item.armor.equip_diamond": "Diamond armor clangs", + "subtitles.item.armor.equip_elytra": "Elytra rustle", + "subtitles.item.armor.equip_gold": "Gold armor clinks", + "subtitles.item.armor.equip_iron": "Iron armor clanks", + "subtitles.item.armor.equip_leather": "Leather armor rustles", + "subtitles.item.armor.equip_netherite": "Netherite armor clanks", + "subtitles.item.armor.equip_turtle": "Turtle Shell thunks", + "subtitles.item.axe.strip": "Axe strips", + "subtitles.item.axe.scrape": "Axe scrapes", + "subtitles.item.axe.wax_off": "Wax off", + "subtitles.item.bone_meal.use": "Bone Meal crinkles", + "subtitles.item.book.page_turn": "Page rustles", + "subtitles.item.book.put": "Book thumps", + "subtitles.item.bottle.empty": "Bottle empties", + "subtitles.item.bottle.fill": "Bottle fills", + "subtitles.item.bucket.empty": "Bucket empties", + "subtitles.item.bucket.fill": "Bucket fills", + "subtitles.item.bucket.fill_axolotl": "Axolotl scooped", + "subtitles.item.bucket.fill_fish": "Fish captured", + "subtitles.item.bucket.fill_tadpole": "Tadpole captured", + "subtitles.item.bundle.drop_contents": "Bundle empties", + "subtitles.item.bundle.insert": "Item packed", + "subtitles.item.bundle.remove_one": "Item unpacked", + "subtitles.item.chorus_fruit.teleport": "Player teleports", + "subtitles.item.crop.plant": "Crop planted", + "subtitles.item.crossbow.charge": "Crossbow charges up", + "subtitles.item.crossbow.hit": "Arrow hits", + "subtitles.item.crossbow.load": "Crossbow loads", + "subtitles.item.crossbow.shoot": "Crossbow fires", + "subtitles.item.firecharge.use": "Fireball whooshes", + "subtitles.item.flintandsteel.use": "Flint and Steel click", + "subtitles.item.goat_horn.play": "Goat Horn plays", + "subtitles.item.hoe.till": "Hoe tills", + "subtitles.item.honey_bottle.drink": "Gulping", + "subtitles.item.lodestone_compass.lock": "Lodestone Compass locks onto Lodestone", + "subtitles.item.nether_wart.plant": "Crop planted", + "subtitles.item.shears.shear": "Shears click", + "subtitles.item.shield.block": "Shield blocks", + "subtitles.item.shovel.flatten": "Shovel flattens", + "subtitles.item.totem.use": "Totem activates", + "subtitles.item.trident.hit": "Trident stabs", + "subtitles.item.trident.hit_ground": "Trident vibrates", + "subtitles.item.trident.return": "Trident returns", + "subtitles.item.trident.riptide": "Trident zooms", + "subtitles.item.trident.throw": "Trident clangs", + "subtitles.item.trident.thunder": "Trident thunder cracks", + "subtitles.item.spyglass.use": "Spyglass expands", + "subtitles.item.spyglass.stop_using": "Spyglass retracts", + "subtitles.item.ink_sac.use": "Ink Sac splotches", + "subtitles.item.glow_ink_sac.use": "Glow Ink Sac splotches", + "subtitles.item.dye.use": "Dye stains", + "subtitles.particle.soul_escape": "Soul escapes", + "subtitles.ui.cartography_table.take_result": "Map drawn", + "subtitles.ui.loom.take_result": "Loom used", + "subtitles.ui.stonecutter.take_result": "Stonecutter used", + "subtitles.weather.rain": "Rain falls", + "telemetry_info.screen.title": "Telemetry Data Collection", + "telemetry_info.screen.description": "Collecting this data helps us improve Minecraft by guiding us in directions that are relevant to our players.\nYou can also send in additional feedback to help us keep improving Minecraft.", + "telemetry_info.button.show_data": "Open My Data", + "telemetry_info.button.give_feedback": "Give Feedback", + "telemetry_info.property_title": "Included Data", + "telemetry.property.user_id.title": "User ID", + "telemetry.property.client_id.title": "Client ID", + "telemetry.property.minecraft_session_id.title": "Minecraft Session ID", + "telemetry.property.game_version.title": "Game Version", + "telemetry.property.operating_system.title": "Operating System", + "telemetry.property.platform.title": "Platform", + "telemetry.property.client_modded.title": "Client Modded", + "telemetry.property.event_timestamp_utc.title": "Event Timestamp (UTC)", + "telemetry.property.opt_in.title": "Opt-In", + "telemetry.property.world_session_id.title": "World Session ID", + "telemetry.property.server_modded.title": "Server Modded", + "telemetry.property.server_type.title": "Server Type", + "telemetry.property.frame_rate_samples.title": "Frame Rate Samples (FPS)", + "telemetry.property.render_time_samples.title": "Render Time Samples", + "telemetry.property.used_memory_samples.title": "Used Random Access Memory", + "telemetry.property.number_of_samples.title": "Sample Count", + "telemetry.property.render_distance.title": "Render Distance", + "telemetry.property.dedicated_memory_kb.title": "Dedicated Memory (kB)", + "telemetry.property.game_mode.title": "Game Mode", + "telemetry.property.seconds_since_load.title": "Time Since Load (Seconds)", + "telemetry.property.ticks_since_load.title": "Time Since Load (Ticks)", + "telemetry.property.world_load_time_ms.title": "World Load Time (Milliseconds)", + "telemetry.property.new_world.title": "New World", + "telemetry.event.required": "%s (Required)", + "telemetry.event.optional": "%s (Optional)", + "telemetry.event.world_loaded.title": "World Loaded", + "telemetry.event.world_loaded.description": "Knowing how players play Minecraft (such as Game Mode, client or server modded, and game version) allows us to focus game updates to improve the areas that players care about most.\nThe World Loaded event is paired with the World Unloaded event to calculate how long the play session has lasted.", + "telemetry.event.world_unloaded.title": "World Unloaded", + "telemetry.event.world_unloaded.description": "This event is paired with the World Loaded event to calculate how long the world session has lasted.\nThe duration (in seconds and ticks) is measured when a world session has ended (quitting to title, disconnecting from a server).", + "telemetry.event.performance_metrics.title": "Performance Metrics", + "telemetry.event.performance_metrics.description": "Knowing the overall performance profile of Minecraft helps us tune and optimize the game for a wide range of machine specifications and operating systems. \nGame version is included to help us compare the performance profile for new versions of Minecraft.", + "telemetry.event.world_load_times.title": "World Load Times", + "telemetry.event.world_load_times.description": "It’s important for us to understand how long it takes to join a world, and how that changes over time. For example, when we add new features or do larger technical changes, we need to see what impact that had on load times.", + "debug.prefix": "[Debug]:", + "debug.reload_chunks.help": "F3 + A = Reload chunks", + "debug.show_hitboxes.help": "F3 + B = Show hitboxes", + "debug.clear_chat.help": "F3 + D = Clear chat", + "debug.chunk_boundaries.help": "F3 + G = Show chunk boundaries", + "debug.advanced_tooltips.help": "F3 + H = Advanced tooltips", + "debug.creative_spectator.help": "F3 + N = Cycle previous gamemode <-> spectator", + "debug.pause_focus.help": "F3 + P = Pause on lost focus", + "debug.help.help": "F3 + Q = Show this list", + "debug.reload_resourcepacks.help": "F3 + T = Reload resource packs", + "debug.pause.help": "F3 + Esc = Pause without pause menu (if pausing is possible)", + "debug.copy_location.help": "F3 + C = Copy location as /tp command, hold F3 + C to crash the game", + "debug.inspect.help": "F3 + I = Copy entity or block data to clipboard", + "debug.gamemodes.help": "F3 + F4 = Open game mode switcher", + "debug.profiling.help": "F3 + L = Start/stop profiling", + "debug.copy_location.message": "Copied location to clipboard", + "debug.inspect.server.block": "Copied server-side block data to clipboard", + "debug.inspect.server.entity": "Copied server-side entity data to clipboard", + "debug.inspect.client.block": "Copied client-side block data to clipboard", + "debug.inspect.client.entity": "Copied client-side entity data to clipboard", + "debug.reload_chunks.message": "Reloading all chunks", + "debug.show_hitboxes.on": "Hitboxes: shown", + "debug.show_hitboxes.off": "Hitboxes: hidden", + "debug.chunk_boundaries.on": "Chunk borders: shown", + "debug.chunk_boundaries.off": "Chunk borders: hidden", + "debug.advanced_tooltips.on": "Advanced tooltips: shown", + "debug.advanced_tooltips.off": "Advanced tooltips: hidden", + "debug.creative_spectator.error": "Unable to switch gamemode; no permission", + "debug.gamemodes.error": "Unable to open game mode switcher; no permission", + "debug.pause_focus.on": "Pause on lost focus: enabled", + "debug.pause_focus.off": "Pause on lost focus: disabled", + "debug.help.message": "Key bindings:", + "debug.reload_resourcepacks.message": "Reloaded resource packs", + "debug.crash.message": "F3 + C is held down. This will crash the game unless released.", + "debug.crash.warning": "Crashing in %s...", + "debug.gamemodes.press_f4": "[ F4 ]", + "debug.gamemodes.select_next": "%s Next", + "debug.profiling.start": "Profiling started for %s seconds. Use F3 + L to stop early", + "debug.profiling.stop": "Profiling ended. Saved results to %s", + "resourcepack.downloading": "Downloading Resource Pack", + "resourcepack.requesting": "Making Request...", + "resourcepack.progress": "Downloading file (%s MB)...", + "tutorial.bundleInsert.title": "Use a Bundle", + "tutorial.bundleInsert.description": "Right Click to add items", + "tutorial.move.title": "Move with %s, %s, %s and %s", + "tutorial.move.description": "Jump with %s", + "tutorial.look.title": "Look around", + "tutorial.look.description": "Use your mouse to turn", + "tutorial.find_tree.title": "Find a tree", + "tutorial.find_tree.description": "Punch it to collect wood", + "tutorial.punch_tree.title": "Destroy the tree", + "tutorial.punch_tree.description": "Hold down %s", + "tutorial.open_inventory.title": "Open your inventory", + "tutorial.open_inventory.description": "Press %s", + "tutorial.craft_planks.title": "Craft wooden planks", + "tutorial.craft_planks.description": "The recipe book can help", + "tutorial.socialInteractions.title": "Social Interactions", + "tutorial.socialInteractions.description": "Press %s to open", + "advancements.adventure.adventuring_time.title": "Adventuring Time", + "advancements.adventure.adventuring_time.description": "Discover every biome", + "advancements.adventure.arbalistic.title": "Arbalistic", + "advancements.adventure.arbalistic.description": "Kill five unique mobs with one crossbow shot", + "advancements.adventure.avoid_vibration.title": "Sneak 100", + "advancements.adventure.avoid_vibration.description": "Sneak near a Sculk Sensor or Warden to prevent it from detecting you", + "advancements.adventure.bullseye.title": "Bullseye", + "advancements.adventure.bullseye.description": "Hit the bullseye of a Target block from at least 30 meters away", + "advancements.adventure.fall_from_world_height.title": "Caves & Cliffs", + "advancements.adventure.fall_from_world_height.description": "Free fall from the top of the world (build limit) to the bottom of the world and survive", + "advancements.adventure.kill_mob_near_sculk_catalyst.title": "It Spreads", + "advancements.adventure.kill_mob_near_sculk_catalyst.description": "Kill a mob near a Sculk Catalyst", + "advancements.adventure.walk_on_powder_snow_with_leather_boots.title": "Light as a Rabbit", + "advancements.adventure.walk_on_powder_snow_with_leather_boots.description": "Walk on Powder Snow...without sinking in it", + "advancements.adventure.lightning_rod_with_villager_no_fire.title": "Surge Protector", + "advancements.adventure.lightning_rod_with_villager_no_fire.description": "Protect a Villager from an undesired shock without starting a fire", + "advancements.adventure.spyglass_at_parrot.title": "Is It a Bird?", + "advancements.adventure.spyglass_at_parrot.description": "Look at a Parrot through a Spyglass", + "advancements.adventure.spyglass_at_ghast.title": "Is It a Balloon?", + "advancements.adventure.spyglass_at_ghast.description": "Look at a Ghast through a Spyglass", + "advancements.adventure.spyglass_at_dragon.title": "Is It a Plane?", + "advancements.adventure.spyglass_at_dragon.description": "Look at the Ender Dragon through a Spyglass", + "advancements.adventure.hero_of_the_village.title": "Hero of the Village", + "advancements.adventure.hero_of_the_village.description": "Successfully defend a village from a raid", + "advancements.adventure.honey_block_slide.title": "Sticky Situation", + "advancements.adventure.honey_block_slide.description": "Jump into a Honey Block to break your fall", + "advancements.adventure.kill_all_mobs.title": "Monsters Hunted", + "advancements.adventure.kill_all_mobs.description": "Kill one of every hostile monster", + "advancements.adventure.kill_a_mob.title": "Monster Hunter", + "advancements.adventure.kill_a_mob.description": "Kill any hostile monster", + "advancements.adventure.ol_betsy.title": "Ol' Betsy", + "advancements.adventure.ol_betsy.description": "Shoot a Crossbow", + "advancements.adventure.play_jukebox_in_meadows.title": "Sound of Music", + "advancements.adventure.play_jukebox_in_meadows.description": "Make the Meadows come alive with the sound of music from a Jukebox", + "advancements.adventure.root.title": "Adventure", + "advancements.adventure.root.description": "Adventure, exploration and combat", + "advancements.adventure.shoot_arrow.title": "Take Aim", + "advancements.adventure.shoot_arrow.description": "Shoot something with an Arrow", + "advancements.adventure.sleep_in_bed.title": "Sweet Dreams", + "advancements.adventure.sleep_in_bed.description": "Sleep in a Bed to change your respawn point", + "advancements.adventure.sniper_duel.title": "Sniper Duel", + "advancements.adventure.sniper_duel.description": "Kill a Skeleton from at least 50 meters away", + "advancements.adventure.summon_iron_golem.title": "Hired Help", + "advancements.adventure.summon_iron_golem.description": "Summon an Iron Golem to help defend a village", + "advancements.adventure.totem_of_undying.title": "Postmortal", + "advancements.adventure.totem_of_undying.description": "Use a Totem of Undying to cheat death", + "advancements.adventure.trade.title": "What a Deal!", + "advancements.adventure.trade.description": "Successfully trade with a Villager", + "advancements.adventure.trade_at_world_height.title": "Star Trader", + "advancements.adventure.trade_at_world_height.description": "Trade with a Villager at the build height limit", + "advancements.adventure.throw_trident.title": "A Throwaway Joke", + "advancements.adventure.throw_trident.description": "Throw a Trident at something.\nNote: Throwing away your only weapon is not a good idea.", + "advancements.adventure.two_birds_one_arrow.title": "Two Birds, One Arrow", + "advancements.adventure.two_birds_one_arrow.description": "Kill two Phantoms with a piercing Arrow", + "advancements.adventure.very_very_frightening.title": "Very Very Frightening", + "advancements.adventure.very_very_frightening.description": "Strike a Villager with lightning", + "advancements.adventure.voluntary_exile.title": "Voluntary Exile", + "advancements.adventure.voluntary_exile.description": "Kill a raid captain.\nMaybe consider staying away from villages for the time being...", + "advancements.adventure.whos_the_pillager_now.title": "Who's the Pillager Now?", + "advancements.adventure.whos_the_pillager_now.description": "Give a Pillager a taste of their own medicine", + "advancements.husbandry.root.title": "Husbandry", + "advancements.husbandry.root.description": "The world is full of friends and food", + "advancements.husbandry.breed_an_animal.title": "The Parrots and the Bats", + "advancements.husbandry.breed_an_animal.description": "Breed two animals together", + "advancements.husbandry.fishy_business.title": "Fishy Business", + "advancements.husbandry.fishy_business.description": "Catch a fish", + "advancements.husbandry.make_a_sign_glow.title": "Glow and Behold!", + "advancements.husbandry.make_a_sign_glow.description": "Make the text of any kind of sign glow", + "advancements.husbandry.ride_a_boat_with_a_goat.title": "Whatever Floats Your Goat!", + "advancements.husbandry.ride_a_boat_with_a_goat.description": "Get in a Boat and float with a Goat", + "advancements.husbandry.tactical_fishing.title": "Tactical Fishing", + "advancements.husbandry.tactical_fishing.description": "Catch a Fish... without a Fishing Rod!", + "advancements.husbandry.axolotl_in_a_bucket.title": "The Cutest Predator", + "advancements.husbandry.axolotl_in_a_bucket.description": "Catch an Axolotl in a Bucket", + "advancements.husbandry.froglights.title": "With Our Powers Combined!", + "advancements.husbandry.froglights.description": "Have all Froglights in your inventory", + "advancements.husbandry.tadpole_in_a_bucket.title": "Bukkit Bukkit", + "advancements.husbandry.tadpole_in_a_bucket.description": "Catch a Tadpole in a Bucket", + "advancements.husbandry.leash_all_frog_variants.title": "When the Squad Hops into Town", + "advancements.husbandry.leash_all_frog_variants.description": "Get each Frog variant on a Lead", + "advancements.husbandry.kill_axolotl_target.title": "The Healing Power of Friendship!", + "advancements.husbandry.kill_axolotl_target.description": "Team up with an Axolotl and win a fight", + "advancements.husbandry.breed_all_animals.title": "Two by Two", + "advancements.husbandry.breed_all_animals.description": "Breed all the animals!", + "advancements.husbandry.tame_an_animal.title": "Best Friends Forever", + "advancements.husbandry.tame_an_animal.description": "Tame an animal", + "advancements.husbandry.plant_seed.title": "A Seedy Place", + "advancements.husbandry.plant_seed.description": "Plant a seed and watch it grow", + "advancements.husbandry.netherite_hoe.title": "Serious Dedication", + "advancements.husbandry.netherite_hoe.description": "Use a Netherite Ingot to upgrade a Hoe, and then reevaluate your life choices", + "advancements.husbandry.balanced_diet.title": "A Balanced Diet", + "advancements.husbandry.balanced_diet.description": "Eat everything that is edible, even if it's not good for you", + "advancements.husbandry.complete_catalogue.title": "A Complete Catalogue", + "advancements.husbandry.complete_catalogue.description": "Tame all Cat variants!", + "advancements.husbandry.safely_harvest_honey.title": "Bee Our Guest", + "advancements.husbandry.safely_harvest_honey.description": "Use a Campfire to collect Honey from a Beehive using a Bottle without aggravating the Bees", + "advancements.husbandry.silk_touch_nest.title": "Total Beelocation", + "advancements.husbandry.silk_touch_nest.description": "Move a Bee Nest, with 3 Bees inside, using Silk Touch", + "advancements.husbandry.wax_on.title": "Wax On", + "advancements.husbandry.wax_on.description": "Apply Honeycomb to a Copper block!", + "advancements.husbandry.wax_off.title": "Wax Off", + "advancements.husbandry.wax_off.description": "Scrape Wax off of a Copper block!", + "advancements.husbandry.allay_deliver_item_to_player.title": "You've Got a Friend in Me", + "advancements.husbandry.allay_deliver_item_to_player.description": "Have an Allay deliver items to you", + "advancements.husbandry.allay_deliver_cake_to_note_block.title": "Birthday Song", + "advancements.husbandry.allay_deliver_cake_to_note_block.description": "Have an Allay drop a Cake at a Note Block", + "advancements.end.dragon_breath.title": "You Need a Mint", + "advancements.end.dragon_breath.description": "Collect Dragon's Breath in a Glass Bottle", + "advancements.end.dragon_egg.title": "The Next Generation", + "advancements.end.dragon_egg.description": "Hold the Dragon Egg", + "advancements.end.elytra.title": "Sky's the Limit", + "advancements.end.elytra.description": "Find Elytra", + "advancements.end.enter_end_gateway.title": "Remote Getaway", + "advancements.end.enter_end_gateway.description": "Escape the island", + "advancements.end.find_end_city.title": "The City at the End of the Game", + "advancements.end.find_end_city.description": "Go on in, what could happen?", + "advancements.end.kill_dragon.title": "Free the End", + "advancements.end.kill_dragon.description": "Good luck", + "advancements.end.levitate.title": "Great View From Up Here", + "advancements.end.levitate.description": "Levitate up 50 blocks from the attacks of a Shulker", + "advancements.end.respawn_dragon.title": "The End... Again...", + "advancements.end.respawn_dragon.description": "Respawn the Ender Dragon", + "advancements.end.root.title": "The End", + "advancements.end.root.description": "Or the beginning?", + "advancements.nether.brew_potion.title": "Local Brewery", + "advancements.nether.brew_potion.description": "Brew a Potion", + "advancements.nether.all_potions.title": "A Furious Cocktail", + "advancements.nether.all_potions.description": "Have every potion effect applied at the same time", + "advancements.nether.all_effects.title": "How Did We Get Here?", + "advancements.nether.all_effects.description": "Have every effect applied at the same time", + "advancements.nether.create_beacon.title": "Bring Home the Beacon", + "advancements.nether.create_beacon.description": "Construct and place a Beacon", + "advancements.nether.create_full_beacon.title": "Beaconator", + "advancements.nether.create_full_beacon.description": "Bring a Beacon to full power", + "advancements.nether.find_fortress.title": "A Terrible Fortress", + "advancements.nether.find_fortress.description": "Break your way into a Nether Fortress", + "advancements.nether.get_wither_skull.title": "Spooky Scary Skeleton", + "advancements.nether.get_wither_skull.description": "Obtain a Wither Skeleton's skull", + "advancements.nether.obtain_blaze_rod.title": "Into Fire", + "advancements.nether.obtain_blaze_rod.description": "Relieve a Blaze of its rod", + "advancements.nether.return_to_sender.title": "Return to Sender", + "advancements.nether.return_to_sender.description": "Destroy a Ghast with a fireball", + "advancements.nether.root.title": "Nether", + "advancements.nether.root.description": "Bring summer clothes", + "advancements.nether.summon_wither.title": "Withering Heights", + "advancements.nether.summon_wither.description": "Summon the Wither", + "advancements.nether.fast_travel.title": "Subspace Bubble", + "advancements.nether.fast_travel.description": "Use the Nether to travel 7 km in the Overworld", + "advancements.nether.uneasy_alliance.title": "Uneasy Alliance", + "advancements.nether.uneasy_alliance.description": "Rescue a Ghast from the Nether, bring it safely home to the Overworld... and then kill it", + "advancements.nether.obtain_ancient_debris.title": "Hidden in the Depths", + "advancements.nether.obtain_ancient_debris.description": "Obtain Ancient Debris", + "advancements.nether.netherite_armor.title": "Cover Me in Debris", + "advancements.nether.netherite_armor.description": "Get a full suit of Netherite armor", + "advancements.nether.use_lodestone.title": "Country Lode, Take Me Home", + "advancements.nether.use_lodestone.description": "Use a Compass on a Lodestone", + "advancements.nether.obtain_crying_obsidian.title": "Who is Cutting Onions?", + "advancements.nether.obtain_crying_obsidian.description": "Obtain Crying Obsidian", + "advancements.nether.charge_respawn_anchor.title": "Not Quite \"Nine\" Lives", + "advancements.nether.charge_respawn_anchor.description": "Charge a Respawn Anchor to the maximum", + "advancements.nether.ride_strider.title": "This Boat Has Legs", + "advancements.nether.ride_strider.description": "Ride a Strider with a Warped Fungus on a Stick", + "advancements.nether.ride_strider_in_overworld_lava.title": "Feels Like Home", + "advancements.nether.ride_strider_in_overworld_lava.description": "Take a Strider for a loooong ride on a lava lake in the Overworld", + "advancements.nether.explore_nether.title": "Hot Tourist Destinations", + "advancements.nether.explore_nether.description": "Explore all Nether biomes", + "advancements.nether.find_bastion.title": "Those Were the Days", + "advancements.nether.find_bastion.description": "Enter a Bastion Remnant", + "advancements.nether.loot_bastion.title": "War Pigs", + "advancements.nether.loot_bastion.description": "Loot a Chest in a Bastion Remnant", + "advancements.nether.distract_piglin.title": "Oh Shiny", + "advancements.nether.distract_piglin.description": "Distract Piglins with gold", + "advancements.story.cure_zombie_villager.title": "Zombie Doctor", + "advancements.story.cure_zombie_villager.description": "Weaken and then cure a Zombie Villager", + "advancements.story.deflect_arrow.title": "Not Today, Thank You", + "advancements.story.deflect_arrow.description": "Deflect a projectile with a Shield", + "advancements.story.enchant_item.title": "Enchanter", + "advancements.story.enchant_item.description": "Enchant an item at an Enchanting Table", + "advancements.story.enter_the_end.title": "The End?", + "advancements.story.enter_the_end.description": "Enter the End Portal", + "advancements.story.enter_the_nether.title": "We Need to Go Deeper", + "advancements.story.enter_the_nether.description": "Build, light and enter a Nether Portal", + "advancements.story.follow_ender_eye.title": "Eye Spy", + "advancements.story.follow_ender_eye.description": "Follow an Eye of Ender", + "advancements.story.form_obsidian.title": "Ice Bucket Challenge", + "advancements.story.form_obsidian.description": "Obtain a block of Obsidian", + "advancements.story.iron_tools.title": "Isn't It Iron Pick", + "advancements.story.iron_tools.description": "Upgrade your Pickaxe", + "advancements.story.lava_bucket.title": "Hot Stuff", + "advancements.story.lava_bucket.description": "Fill a Bucket with lava", + "advancements.story.mine_diamond.title": "Diamonds!", + "advancements.story.mine_diamond.description": "Acquire diamonds", + "advancements.story.mine_stone.title": "Stone Age", + "advancements.story.mine_stone.description": "Mine Stone with your new Pickaxe", + "advancements.story.obtain_armor.title": "Suit Up", + "advancements.story.obtain_armor.description": "Protect yourself with a piece of iron armor", + "advancements.story.root.title": "Minecraft", + "advancements.story.root.description": "The heart and story of the game", + "advancements.story.shiny_gear.title": "Cover Me with Diamonds", + "advancements.story.shiny_gear.description": "Diamond armor saves lives", + "advancements.story.smelt_iron.title": "Acquire Hardware", + "advancements.story.smelt_iron.description": "Smelt an Iron Ingot", + "advancements.story.upgrade_tools.title": "Getting an Upgrade", + "advancements.story.upgrade_tools.description": "Construct a better Pickaxe", + "team.visibility.always": "Always", + "team.visibility.never": "Never", + "team.visibility.hideForOtherTeams": "Hide for other teams", + "team.visibility.hideForOwnTeam": "Hide for own team", + "team.collision.always": "Always", + "team.collision.never": "Never", + "team.collision.pushOtherTeams": "Push other teams", + "team.collision.pushOwnTeam": "Push own team", + "argument.uuid.invalid": "Invalid UUID", + "argument.entity.selector.nearestPlayer": "Nearest player", + "argument.entity.selector.randomPlayer": "Random player", + "argument.entity.selector.allPlayers": "All players", + "argument.entity.selector.allEntities": "All entities", + "argument.entity.selector.self": "Current entity", + "argument.entity.options.name.description": "Entity name", + "argument.entity.options.distance.description": "Distance to entity", + "argument.entity.options.level.description": "Experience level", + "argument.entity.options.x.description": "x position", + "argument.entity.options.y.description": "y position", + "argument.entity.options.z.description": "z position", + "argument.entity.options.dx.description": "Entities between x and x + dx", + "argument.entity.options.dy.description": "Entities between y and y + dy", + "argument.entity.options.dz.description": "Entities between z and z + dz", + "argument.entity.options.x_rotation.description": "Entity's x rotation", + "argument.entity.options.y_rotation.description": "Entity's y rotation", + "argument.entity.options.limit.description": "Maximum number of entities to return", + "argument.entity.options.sort.description": "Sort the entities", + "argument.entity.options.gamemode.description": "Players with gamemode", + "argument.entity.options.team.description": "Entities on team", + "argument.entity.options.type.description": "Entities of type", + "argument.entity.options.tag.description": "Entities with tag", + "argument.entity.options.nbt.description": "Entities with NBT", + "argument.entity.options.scores.description": "Entities with scores", + "argument.entity.options.advancements.description": "Players with advancements", + "argument.entity.options.predicate.description": "Custom predicate", + "argument.resource.not_found": "Can't find element '%s' of type '%s'", + "argument.resource.invalid_type": "Element '%s' has wrong type '%s' (expected '%s')", + "argument.resource_tag.not_found": "Can't find tag '%s' of type '%s'", + "argument.resource_tag.invalid_type": "Tag '%s' has wrong type '%s' (expected '%s')", + "command.failed": "An unexpected error occurred trying to execute that command", + "command.context.here": "<--[HERE]", + "command.context.parse_error": "%s at position %s: %s", + "commands.publish.started": "Local game hosted on port %s", + "commands.publish.failed": "Unable to host local game", + "commands.advancement.advancementNotFound": "No advancement was found by the name '%1$s'", + "commands.advancement.criterionNotFound": "The advancement %1$s does not contain the criterion '%2$s'", + "commands.advancement.grant.one.to.one.success": "Granted the advancement %s to %s", + "commands.advancement.grant.one.to.one.failure": "Couldn't grant advancement %s to %s as they already have it", + "commands.advancement.grant.one.to.many.success": "Granted the advancement %s to %s players", + "commands.advancement.grant.one.to.many.failure": "Couldn't grant advancement %s to %s players as they already have it", + "commands.advancement.grant.many.to.one.success": "Granted %s advancements to %s", + "commands.advancement.grant.many.to.one.failure": "Couldn't grant %s advancements to %s as they already have them", + "commands.advancement.grant.many.to.many.success": "Granted %s advancements to %s players", + "commands.advancement.grant.many.to.many.failure": "Couldn't grant %s advancements to %s players as they already have them", + "commands.advancement.grant.criterion.to.one.success": "Granted criterion '%s' of advancement %s to %s", + "commands.advancement.grant.criterion.to.one.failure": "Couldn't grant criterion '%s' of advancement %s to %s as they already have it", + "commands.advancement.grant.criterion.to.many.success": "Granted criterion '%s' of advancement %s to %s players", + "commands.advancement.grant.criterion.to.many.failure": "Couldn't grant criterion '%s' of advancement %s to %s players as they already have it", + "commands.advancement.revoke.one.to.one.success": "Revoked the advancement %s from %s", + "commands.advancement.revoke.one.to.one.failure": "Couldn't revoke advancement %s from %s as they don't have it", + "commands.advancement.revoke.one.to.many.success": "Revoked the advancement %s from %s players", + "commands.advancement.revoke.one.to.many.failure": "Couldn't revoke advancement %s from %s players as they don't have it", + "commands.advancement.revoke.many.to.one.success": "Revoked %s advancements from %s", + "commands.advancement.revoke.many.to.one.failure": "Couldn't revoke %s advancements from %s as they don't have them", + "commands.advancement.revoke.many.to.many.success": "Revoked %s advancements from %s players", + "commands.advancement.revoke.many.to.many.failure": "Couldn't revoke %s advancements from %s players as they don't have them", + "commands.advancement.revoke.criterion.to.one.success": "Revoked criterion '%s' of advancement %s from %s", + "commands.advancement.revoke.criterion.to.one.failure": "Couldn't revoke criterion '%s' of advancement %s from %s as they don't have it", + "commands.advancement.revoke.criterion.to.many.success": "Revoked criterion '%s' of advancement %s from %s players", + "commands.advancement.revoke.criterion.to.many.failure": "Couldn't revoke criterion '%s' of advancement %s from %s players as they don't have it", + "commands.attribute.failed.entity": "%s is not a valid entity for this command", + "commands.attribute.failed.no_attribute": "Entity %s has no attribute %s", + "commands.attribute.failed.no_modifier": "Attribute %s for entity %s has no modifier %s", + "commands.attribute.failed.modifier_already_present": "Modifier %s is already present on attribute %s for entity %s", + "commands.attribute.value.get.success": "Value of attribute %s for entity %s is %s", + "commands.attribute.base_value.get.success": "Base value of attribute %s for entity %s is %s", + "commands.attribute.base_value.set.success": "Base value for attribute %s for entity %s set to %s", + "commands.attribute.modifier.add.success": "Added modifier %s to attribute %s for entity %s", + "commands.attribute.modifier.remove.success": "Removed modifier %s from attribute %s for entity %s", + "commands.attribute.modifier.value.get.success": "Value of modifier %s on attribute %s for entity %s is %s", + "commands.forceload.added.failure": "No chunks were marked for force loading", + "commands.forceload.added.single": "Marked chunk %s in %s to be force loaded", + "commands.forceload.added.multiple": "Marked %s chunks in %s from %s to %s to be force loaded", + "commands.forceload.query.success": "Chunk at %s in %s is marked for force loading", + "commands.forceload.query.failure": "Chunk at %s in %s is not marked for force loading", + "commands.forceload.list.single": "A force loaded chunk was found in %s at: %s", + "commands.forceload.list.multiple": "%s force loaded chunks were found in %s at: %s", + "commands.forceload.added.none": "No force loaded chunks were found in %s", + "commands.forceload.removed.all": "Unmarked all force loaded chunks in %s", + "commands.forceload.removed.failure": "No chunks were removed from force loading", + "commands.forceload.removed.single": "Unmarked chunk %s in %s for force loading", + "commands.forceload.removed.multiple": "Unmarked %s chunks in %s from %s to %s for force loading", + "commands.forceload.toobig": "Too many chunks in the specified area (maximum %s, specified %s)", + "commands.clear.success.single": "Removed %s items from player %s", + "commands.clear.success.multiple": "Removed %s items from %s players", + "commands.clear.test.single": "Found %s matching items on player %s", + "commands.clear.test.multiple": "Found %s matching items on %s players", + "commands.clone.success": "Successfully cloned %s blocks", + "commands.debug.started": "Started tick profiling", + "commands.debug.stopped": "Stopped tick profiling after %s seconds and %s ticks (%s ticks per second)", + "commands.debug.notRunning": "The tick profiler hasn't started", + "commands.debug.alreadyRunning": "The tick profiler is already started", + "commands.debug.function.success.single": "Traced %s commands from function '%s' to output file %s", + "commands.debug.function.success.multiple": "Traced %s commands from %s functions to output file %s", + "commands.debug.function.noRecursion": "Can't trace from inside of function", + "commands.debug.function.traceFailed": "Failed to trace function", + "commands.defaultgamemode.success": "The default game mode is now %s", + "commands.difficulty.success": "The difficulty has been set to %s", + "commands.difficulty.query": "The difficulty is %s", + "commands.drop.no_held_items": "Entity can't hold any items", + "commands.drop.no_loot_table": "Entity %s has no loot table", + "commands.drop.success.single": "Dropped %s %s", + "commands.drop.success.single_with_table": "Dropped %s %s from loot table %s", + "commands.drop.success.multiple": "Dropped %s items", + "commands.drop.success.multiple_with_table": "Dropped %s items from loot table %s", + "commands.effect.give.success.single": "Applied effect %s to %s", + "commands.effect.give.success.multiple": "Applied effect %s to %s targets", + "commands.effect.clear.everything.success.single": "Removed every effect from %s", + "commands.effect.clear.everything.success.multiple": "Removed every effect from %s targets", + "commands.effect.clear.specific.success.single": "Removed effect %s from %s", + "commands.effect.clear.specific.success.multiple": "Removed effect %s from %s targets", + "commands.enchant.success.single": "Applied enchantment %s to %s's item", + "commands.enchant.success.multiple": "Applied enchantment %s to %s entities", + "commands.experience.add.points.success.single": "Gave %s experience points to %s", + "commands.experience.add.points.success.multiple": "Gave %s experience points to %s players", + "commands.experience.add.levels.success.single": "Gave %s experience levels to %s", + "commands.experience.add.levels.success.multiple": "Gave %s experience levels to %s players", + "commands.experience.set.points.success.single": "Set %s experience points on %s", + "commands.experience.set.points.success.multiple": "Set %s experience points on %s players", + "commands.experience.set.levels.success.single": "Set %s experience levels on %s", + "commands.experience.set.levels.success.multiple": "Set %s experience levels on %s players", + "commands.experience.query.points": "%s has %s experience points", + "commands.experience.query.levels": "%s has %s experience levels", + "commands.fill.success": "Successfully filled %s blocks", + "commands.function.success.single": "Executed %s commands from function '%s'", + "commands.function.success.multiple": "Executed %s commands from %s functions", + "commands.give.failed.toomanyitems": "Can't give more than %s of %s", + "commands.give.success.single": "Gave %s %s to %s", + "commands.give.success.multiple": "Gave %s %s to %s players", + "commands.playsound.success.single": "Played sound %s to %s", + "commands.playsound.success.multiple": "Played sound %s to %s players", + "commands.publish.success": "Multiplayer game is now hosted on port %s", + "commands.list.players": "There are %s of a max of %s players online: %s", + "commands.list.nameAndId": "%s (%s)", + "commands.kill.success.single": "Killed %s", + "commands.kill.success.multiple": "Killed %s entities", + "commands.kick.success": "Kicked %s: %s", + "commands.message.display.outgoing": "You whisper to %s: %s", + "commands.message.display.incoming": "%s whispers to you: %s", + "commands.op.success": "Made %s a server operator", + "commands.deop.success": "Made %s no longer a server operator", + "commands.ban.success": "Banned %s: %s", + "commands.pardon.success": "Unbanned %s", + "commands.particle.success": "Displaying particle %s", + "commands.perf.started": "Started 10 second performance profiling run (use '/perf stop' to stop early)", + "commands.perf.stopped": "Stopped performance profiling after %s seconds and %s ticks (%s ticks per second)", + "commands.perf.reportSaved": "Created debug report in %s", + "commands.perf.reportFailed": "Failed to create debug report", + "commands.perf.notRunning": "The performance profiler hasn't started", + "commands.perf.alreadyRunning": "The performance profiler is already started", + "commands.jfr.started": "JFR profiling started", + "commands.jfr.start.failed": "Failed to start JFR profiling", + "commands.jfr.stopped": "JFR profiling stopped and dumped to %s", + "commands.jfr.dump.failed": "Failed to dump JFR recording: %s", + "commands.seed.success": "Seed: %s", + "commands.stop.stopping": "Stopping the server", + "commands.time.query": "The time is %s", + "commands.time.set": "Set the time to %s", + "commands.schedule.created.function": "Scheduled function '%s' in %s ticks at gametime %s", + "commands.schedule.created.tag": "Scheduled tag '%s' in %s ticks at gametime %s", + "commands.schedule.cleared.success": "Removed %s schedules with id %s", + "commands.schedule.cleared.failure": "No schedules with id %s", + "commands.schedule.same_tick": "Can't schedule for current tick", + "commands.gamemode.success.self": "Set own game mode to %s", + "commands.gamemode.success.other": "Set %s's game mode to %s", + "commands.gamerule.query": "Gamerule %s is currently set to: %s", + "commands.gamerule.set": "Gamerule %s is now set to: %s", + "commands.save.disabled": "Automatic saving is now disabled", + "commands.save.enabled": "Automatic saving is now enabled", + "commands.save.saving": "Saving the game (this may take a moment!)", + "commands.save.success": "Saved the game", + "commands.setidletimeout.success": "The player idle timeout is now %s minutes", + "commands.banlist.none": "There are no bans", + "commands.banlist.list": "There are %s bans:", + "commands.banlist.entry": "%s was banned by %s: %s", + "commands.bossbar.create.success": "Created custom bossbar %s", + "commands.bossbar.remove.success": "Removed custom bossbar %s", + "commands.bossbar.list.bars.none": "There are no custom bossbars active", + "commands.bossbar.list.bars.some": "There are %s custom bossbars active: %s", + "commands.bossbar.set.players.success.none": "Custom bossbar %s no longer has any players", + "commands.bossbar.set.players.success.some": "Custom bossbar %s now has %s players: %s", + "commands.bossbar.set.name.success": "Custom bossbar %s has been renamed", + "commands.bossbar.set.color.success": "Custom bossbar %s has changed color", + "commands.bossbar.set.style.success": "Custom bossbar %s has changed style", + "commands.bossbar.set.value.success": "Custom bossbar %s has changed value to %s", + "commands.bossbar.set.max.success": "Custom bossbar %s has changed maximum to %s", + "commands.bossbar.set.visible.success.visible": "Custom bossbar %s is now visible", + "commands.bossbar.set.visible.success.hidden": "Custom bossbar %s is now hidden", + "commands.bossbar.get.value": "Custom bossbar %s has a value of %s", + "commands.bossbar.get.max": "Custom bossbar %s has a maximum of %s", + "commands.bossbar.get.visible.visible": "Custom bossbar %s is currently shown", + "commands.bossbar.get.visible.hidden": "Custom bossbar %s is currently hidden", + "commands.bossbar.get.players.none": "Custom bossbar %s has no players currently online", + "commands.bossbar.get.players.some": "Custom bossbar %s has %s players currently online: %s", + "commands.recipe.give.success.single": "Unlocked %s recipes for %s", + "commands.recipe.give.success.multiple": "Unlocked %s recipes for %s players", + "commands.recipe.take.success.single": "Took %s recipes from %s", + "commands.recipe.take.success.multiple": "Took %s recipes from %s players", + "commands.summon.success": "Summoned new %s", + "commands.whitelist.enabled": "Whitelist is now turned on", + "commands.whitelist.disabled": "Whitelist is now turned off", + "commands.whitelist.none": "There are no whitelisted players", + "commands.whitelist.list": "There are %s whitelisted players: %s", + "commands.whitelist.add.success": "Added %s to the whitelist", + "commands.whitelist.remove.success": "Removed %s from the whitelist", + "commands.whitelist.reloaded": "Reloaded the whitelist", + "commands.weather.set.clear": "Set the weather to clear", + "commands.weather.set.rain": "Set the weather to rain", + "commands.weather.set.thunder": "Set the weather to rain & thunder", + "commands.spawnpoint.success.single": "Set spawn point to %s, %s, %s [%s] in %s for %s", + "commands.spawnpoint.success.multiple": "Set spawn point to %s, %s, %s [%s] in %s for %s players", + "commands.stopsound.success.source.sound": "Stopped sound '%s' on source '%s'", + "commands.stopsound.success.source.any": "Stopped all '%s' sounds", + "commands.stopsound.success.sourceless.sound": "Stopped sound '%s'", + "commands.stopsound.success.sourceless.any": "Stopped all sounds", + "commands.setworldspawn.success": "Set the world spawn point to %s, %s, %s [%s]", + "commands.spreadplayers.success.teams": "Spread %s teams around %s, %s with an average distance of %s blocks apart", + "commands.spreadplayers.success.entities": "Spread %s players around %s, %s with an average distance of %s blocks apart", + "commands.setblock.success": "Changed the block at %s, %s, %s", + "commands.banip.success": "Banned IP %s: %s", + "commands.banip.info": "This ban affects %s players: %s", + "commands.pardonip.success": "Unbanned IP %s", + "commands.teleport.success.entity.single": "Teleported %s to %s", + "commands.teleport.success.entity.multiple": "Teleported %s entities to %s", + "commands.teleport.success.location.single": "Teleported %s to %s, %s, %s", + "commands.teleport.success.location.multiple": "Teleported %s entities to %s, %s, %s", + "commands.teleport.invalidPosition": "Invalid position for teleport", + "commands.title.cleared.single": "Cleared titles for %s", + "commands.title.cleared.multiple": "Cleared titles for %s players", + "commands.title.reset.single": "Reset title options for %s", + "commands.title.reset.multiple": "Reset title options for %s players", + "commands.title.show.title.single": "Showing new title for %s", + "commands.title.show.title.multiple": "Showing new title for %s players", + "commands.title.show.subtitle.single": "Showing new subtitle for %s", + "commands.title.show.subtitle.multiple": "Showing new subtitle for %s players", + "commands.title.show.actionbar.single": "Showing new actionbar title for %s", + "commands.title.show.actionbar.multiple": "Showing new actionbar title for %s players", + "commands.title.times.single": "Changed title display times for %s", + "commands.title.times.multiple": "Changed title display times for %s players", + "commands.worldborder.set.grow": "Growing the world border to %s blocks wide over %s seconds", + "commands.worldborder.set.shrink": "Shrinking the world border to %s blocks wide over %s seconds", + "commands.worldborder.set.immediate": "Set the world border to %s blocks wide", + "commands.worldborder.center.success": "Set the center of the world border to %s, %s", + "commands.worldborder.get": "The world border is currently %s blocks wide", + "commands.worldborder.damage.buffer.success": "Set the world border damage buffer to %s blocks", + "commands.worldborder.damage.amount.success": "Set the world border damage to %s per block each second", + "commands.worldborder.warning.time.success": "Set the world border warning time to %s seconds", + "commands.worldborder.warning.distance.success": "Set the world border warning distance to %s blocks", + "commands.tag.add.success.single": "Added tag '%s' to %s", + "commands.tag.add.success.multiple": "Added tag '%s' to %s entities", + "commands.tag.remove.success.single": "Removed tag '%s' from %s", + "commands.tag.remove.success.multiple": "Removed tag '%s' from %s entities", + "commands.tag.list.single.empty": "%s has no tags", + "commands.tag.list.single.success": "%s has %s tags: %s", + "commands.tag.list.multiple.empty": "There are no tags on the %s entities", + "commands.tag.list.multiple.success": "The %s entities have %s total tags: %s", + "commands.team.list.members.empty": "There are no members on team %s", + "commands.team.list.members.success": "Team %s has %s members: %s", + "commands.team.list.teams.empty": "There are no teams", + "commands.team.list.teams.success": "There are %s teams: %s", + "commands.team.add.success": "Created team %s", + "commands.team.remove.success": "Removed team %s", + "commands.team.empty.success": "Removed %s members from team %s", + "commands.team.option.color.success": "Updated the color for team %s to %s", + "commands.team.option.name.success": "Updated the name of team %s", + "commands.team.option.friendlyfire.enabled": "Enabled friendly fire for team %s", + "commands.team.option.friendlyfire.disabled": "Disabled friendly fire for team %s", + "commands.team.option.seeFriendlyInvisibles.enabled": "Team %s can now see invisible teammates", + "commands.team.option.seeFriendlyInvisibles.disabled": "Team %s can no longer see invisible teammates", + "commands.team.option.nametagVisibility.success": "Nametag visibility for team %s is now \"%s\"", + "commands.team.option.deathMessageVisibility.success": "Death message visibility for team %s is now \"%s\"", + "commands.team.option.collisionRule.success": "Collision rule for team %s is now \"%s\"", + "commands.team.option.prefix.success": "Team prefix set to %s", + "commands.team.option.suffix.success": "Team suffix set to %s", + "commands.team.join.success.single": "Added %s to team %s", + "commands.team.join.success.multiple": "Added %s members to team %s", + "commands.team.leave.success.single": "Removed %s from any team", + "commands.team.leave.success.multiple": "Removed %s members from any team", + "commands.trigger.simple.success": "Triggered %s", + "commands.trigger.add.success": "Triggered %s (added %s to value)", + "commands.trigger.set.success": "Triggered %s (set value to %s)", + "commands.scoreboard.objectives.list.empty": "There are no objectives", + "commands.scoreboard.objectives.list.success": "There are %s objectives: %s", + "commands.scoreboard.objectives.add.success": "Created new objective %s", + "commands.scoreboard.objectives.remove.success": "Removed objective %s", + "commands.scoreboard.objectives.display.cleared": "Cleared any objectives in display slot %s", + "commands.scoreboard.objectives.display.set": "Set display slot %s to show objective %s", + "commands.scoreboard.objectives.modify.displayname": "Changed the display name of %s to %s", + "commands.scoreboard.objectives.modify.rendertype": "Changed the render type of objective %s", + "commands.scoreboard.players.list.empty": "There are no tracked entities", + "commands.scoreboard.players.list.success": "There are %s tracked entities: %s", + "commands.scoreboard.players.list.entity.empty": "%s has no scores to show", + "commands.scoreboard.players.list.entity.success": "%s has %s scores:", + "commands.scoreboard.players.list.entity.entry": "%s: %s", + "commands.scoreboard.players.set.success.single": "Set %s for %s to %s", + "commands.scoreboard.players.set.success.multiple": "Set %s for %s entities to %s", + "commands.scoreboard.players.add.success.single": "Added %s to %s for %s (now %s)", + "commands.scoreboard.players.add.success.multiple": "Added %s to %s for %s entities", + "commands.scoreboard.players.remove.success.single": "Removed %s from %s for %s (now %s)", + "commands.scoreboard.players.remove.success.multiple": "Removed %s from %s for %s entities", + "commands.scoreboard.players.reset.all.single": "Reset all scores for %s", + "commands.scoreboard.players.reset.all.multiple": "Reset all scores for %s entities", + "commands.scoreboard.players.reset.specific.single": "Reset %s for %s", + "commands.scoreboard.players.reset.specific.multiple": "Reset %s for %s entities", + "commands.scoreboard.players.enable.success.single": "Enabled trigger %s for %s", + "commands.scoreboard.players.enable.success.multiple": "Enabled trigger %s for %s entities", + "commands.scoreboard.players.operation.success.single": "Set %s for %s to %s", + "commands.scoreboard.players.operation.success.multiple": "Updated %s for %s entities", + "commands.scoreboard.players.get.success": "%s has %s %s", + "commands.reload.success": "Reloading!", + "commands.reload.failure": "Reload failed; keeping old data", + "commands.data.entity.modified": "Modified entity data of %s", + "commands.data.entity.query": "%s has the following entity data: %s", + "commands.data.entity.get": "%s on %s after scale factor of %s is %s", + "commands.data.block.modified": "Modified block data of %s, %s, %s", + "commands.data.block.query": "%s, %s, %s has the following block data: %s", + "commands.data.block.get": "%s on block %s, %s, %s after scale factor of %s is %s", + "commands.data.storage.modified": "Modified storage %s", + "commands.data.storage.query": "Storage %s has the following contents: %s", + "commands.data.storage.get": "%s in storage %s after scale factor of %s is %s", + "commands.datapack.list.enabled.success": "There are %s data packs enabled: %s", + "commands.datapack.list.enabled.none": "There are no data packs enabled", + "commands.datapack.list.available.success": "There are %s data packs available: %s", + "commands.datapack.list.available.none": "There are no more data packs available", + "commands.datapack.modify.enable": "Enabling data pack %s", + "commands.datapack.modify.disable": "Disabling data pack %s", + "commands.spectate.success.stopped": "No longer spectating an entity", + "commands.spectate.success.started": "Now spectating %s", + "commands.spectate.not_spectator": "%s is not in spectator mode", + "commands.spectate.self": "Cannot spectate yourself", + "commands.item.target.not_a_container": "Target position %s, %s, %s is not a container", + "commands.item.source.not_a_container": "Source position %s, %s, %s is not a container", + "commands.item.target.no_such_slot": "The target does not have slot %s", + "commands.item.source.no_such_slot": "The source does not have slot %s", + "commands.item.target.no_changes": "No targets accepted item into slot %s", + "commands.item.target.no_changed.known_item": "No targets accepted item %s into slot %s", + "commands.item.block.set.success": "Replaced a slot at %s, %s, %s with %s", + "commands.item.entity.set.success.single": "Replaced a slot on %s with %s", + "commands.item.entity.set.success.multiple": "Replaced a slot on %s entities with %s", + "argument.range.empty": "Expected value or range of values", + "argument.range.ints": "Only whole numbers allowed, not decimals", + "argument.range.swapped": "Min cannot be bigger than max", + "permissions.requires.player": "A player is required to run this command here", + "permissions.requires.entity": "An entity is required to run this command here", + "argument.angle.incomplete": "Incomplete (expected 1 angle)", + "argument.angle.invalid": "Invalid angle", + "argument.entity.toomany": "Only one entity is allowed, but the provided selector allows more than one", + "argument.player.toomany": "Only one player is allowed, but the provided selector allows more than one", + "argument.player.entities": "Only players may be affected by this command, but the provided selector includes entities", + "argument.entity.notfound.entity": "No entity was found", + "argument.entity.notfound.player": "No player was found", + "argument.player.unknown": "That player does not exist", + "arguments.nbtpath.node.invalid": "Invalid NBT path element", + "arguments.nbtpath.too_deep": "Resulting NBT too deeply nested", + "arguments.nbtpath.too_large": "Resulting NBT too large", + "arguments.nbtpath.nothing_found": "Found no elements matching %s", + "arguments.operation.invalid": "Invalid operation", + "arguments.operation.div0": "Cannot divide by zero", + "argument.scoreHolder.empty": "No relevant score holders could be found", + "argument.block.tag.disallowed": "Tags aren't allowed here, only actual blocks", + "argument.block.property.unclosed": "Expected closing ] for block state properties", + "argument.pos.unloaded": "That position is not loaded", + "argument.pos.outofworld": "That position is out of this world!", + "argument.pos.outofbounds": "That position is outside the allowed boundaries.", + "argument.rotation.incomplete": "Incomplete (expected 2 coordinates)", + "arguments.swizzle.invalid": "Invalid swizzle, expected combination of 'x', 'y' and 'z'", + "argument.pos2d.incomplete": "Incomplete (expected 2 coordinates)", + "argument.pos3d.incomplete": "Incomplete (expected 3 coordinates)", + "argument.pos.mixed": "Cannot mix world & local coordinates (everything must either use ^ or not)", + "argument.pos.missing.double": "Expected a coordinate", + "argument.pos.missing.int": "Expected a block position", + "argument.item.tag.disallowed": "Tags aren't allowed here, only actual items", + "argument.entity.invalid": "Invalid name or UUID", + "argument.entity.selector.missing": "Missing selector type", + "argument.entity.selector.not_allowed": "Selector not allowed", + "argument.entity.options.unterminated": "Expected end of options", + "argument.entity.options.distance.negative": "Distance cannot be negative", + "argument.entity.options.level.negative": "Level shouldn't be negative", + "argument.entity.options.limit.toosmall": "Limit must be at least 1", + "argument.nbt.trailing": "Unexpected trailing data", + "argument.nbt.expected.key": "Expected key", + "argument.nbt.expected.value": "Expected value", + "argument.id.invalid": "Invalid ID", + "argument.time.invalid_unit": "Invalid unit", + "argument.time.invalid_tick_count": "Tick count must be non-negative", + "argument.enum.invalid": "Invalid value \"%s\"", + "commands.banip.invalid": "Invalid IP address or unknown player", + "commands.banip.failed": "Nothing changed. That IP is already banned", + "commands.ban.failed": "Nothing changed. The player is already banned", + "commands.bossbar.set.players.unchanged": "Nothing changed. Those players are already on the bossbar with nobody to add or remove", + "commands.bossbar.set.name.unchanged": "Nothing changed. That's already the name of this bossbar", + "commands.bossbar.set.color.unchanged": "Nothing changed. That's already the color of this bossbar", + "commands.bossbar.set.style.unchanged": "Nothing changed. That's already the style of this bossbar", + "commands.bossbar.set.value.unchanged": "Nothing changed. That's already the value of this bossbar", + "commands.bossbar.set.max.unchanged": "Nothing changed. That's already the max of this bossbar", + "commands.bossbar.set.visibility.unchanged.hidden": "Nothing changed. The bossbar is already hidden", + "commands.bossbar.set.visibility.unchanged.visible": "Nothing changed. The bossbar is already visible", + "commands.clone.overlap": "The source and destination areas cannot overlap", + "commands.clone.failed": "No blocks were cloned", + "commands.deop.failed": "Nothing changed. The player is not an operator", + "commands.effect.give.failed": "Unable to apply this effect (target is either immune to effects, or has something stronger)", + "commands.effect.clear.everything.failed": "Target has no effects to remove", + "commands.effect.clear.specific.failed": "Target doesn't have the requested effect", + "commands.enchant.failed": "Nothing changed. Targets either have no item in their hands or the enchantment could not be applied", + "commands.experience.set.points.invalid": "Cannot set experience points above the maximum points for the player's current level", + "commands.fill.failed": "No blocks were filled", + "argument.gamemode.invalid": "Unknown gamemode: %s", + "commands.help.failed": "Unknown command or insufficient permissions", + "commands.locate.structure.success": "The nearest %s is at %s (%s blocks away)", + "commands.locate.structure.not_found": "Could not find a structure of type \"%s\" nearby", + "commands.locate.structure.invalid": "There is no structure with type \"%s\"", + "commands.locate.biome.success": "The nearest %s is at %s (%s blocks away)", + "commands.locate.biome.not_found": "Could not find a biome of type \"%s\" within reasonable distance", + "commands.locate.poi.success": "The nearest %s is at %s (%s blocks away)", + "commands.locate.poi.not_found": "Could not find a point of interest of type \"%s\" within reasonable distance", + "commands.op.failed": "Nothing changed. The player already is an operator", + "commands.pardon.failed": "Nothing changed. The player isn't banned", + "commands.pardonip.invalid": "Invalid IP address", + "commands.pardonip.failed": "Nothing changed. That IP isn't banned", + "commands.particle.failed": "The particle was not visible for anybody", + "commands.place.feature.failed": "Failed to place feature", + "commands.place.feature.invalid": "There is no feature with type \"%s\"", + "commands.place.feature.success": "Placed \"%s\" at %s, %s, %s", + "commands.place.jigsaw.failed": "Failed to generate jigsaw", + "commands.place.jigsaw.invalid": "There is no template pool with type \"%s\"", + "commands.place.jigsaw.success": "Generated jigsaw at %s, %s, %s", + "commands.place.structure.failed": "Failed to place structure", + "commands.place.structure.invalid": "There is no structure with type \"%s\"", + "commands.place.structure.success": "Generated structure \"%s\" at %s, %s, %s", + "commands.place.template.failed": "Failed to place template", + "commands.place.template.invalid": "There is no template with id \"%s\"", + "commands.place.template.success": "Loaded template \"%s\" at %s, %s, %s", + "commands.playsound.failed": "The sound is too far away to be heard", + "commands.recipe.give.failed": "No new recipes were learned", + "commands.recipe.take.failed": "No recipes could be forgotten", + "commands.save.failed": "Unable to save the game (is there enough disk space?)", + "commands.save.alreadyOff": "Saving is already turned off", + "commands.save.alreadyOn": "Saving is already turned on", + "commands.scoreboard.objectives.add.duplicate": "An objective already exists by that name", + "commands.scoreboard.objectives.display.alreadyEmpty": "Nothing changed. That display slot is already empty", + "commands.scoreboard.objectives.display.alreadySet": "Nothing changed. That display slot is already showing that objective", + "commands.scoreboard.players.enable.failed": "Nothing changed. That trigger is already enabled", + "commands.scoreboard.players.enable.invalid": "Enable only works on trigger-objectives", + "commands.setblock.failed": "Could not set the block", + "commands.summon.failed": "Unable to summon entity", + "commands.summon.failed.uuid": "Unable to summon entity due to duplicate UUIDs", + "commands.summon.invalidPosition": "Invalid position for summon", + "commands.tag.add.failed": "Target either already has the tag or has too many tags", + "commands.tag.remove.failed": "Target does not have this tag", + "commands.team.add.duplicate": "A team already exists by that name", + "commands.team.empty.unchanged": "Nothing changed. That team is already empty", + "commands.team.option.color.unchanged": "Nothing changed. That team already has that color", + "commands.team.option.name.unchanged": "Nothing changed. That team already has that name", + "commands.team.option.friendlyfire.alreadyEnabled": "Nothing changed. Friendly fire is already enabled for that team", + "commands.team.option.friendlyfire.alreadyDisabled": "Nothing changed. Friendly fire is already disabled for that team", + "commands.team.option.seeFriendlyInvisibles.alreadyEnabled": "Nothing changed. That team can already see invisible teammates", + "commands.team.option.seeFriendlyInvisibles.alreadyDisabled": "Nothing changed. That team already can't see invisible teammates", + "commands.team.option.nametagVisibility.unchanged": "Nothing changed. Nametag visibility is already that value", + "commands.team.option.deathMessageVisibility.unchanged": "Nothing changed. Death message visibility is already that value", + "commands.team.option.collisionRule.unchanged": "Nothing changed. Collision rule is already that value", + "commands.trigger.failed.unprimed": "You cannot trigger this objective yet", + "commands.trigger.failed.invalid": "You can only trigger objectives that are 'trigger' type", + "commands.whitelist.alreadyOn": "Whitelist is already turned on", + "commands.whitelist.alreadyOff": "Whitelist is already turned off", + "commands.whitelist.add.failed": "Player is already whitelisted", + "commands.whitelist.remove.failed": "Player is not whitelisted", + "commands.worldborder.center.failed": "Nothing changed. The world border is already centered there", + "commands.worldborder.set.failed.nochange": "Nothing changed. The world border is already that size", + "commands.worldborder.set.failed.small": "World border cannot be smaller than 1 block wide", + "commands.worldborder.set.failed.big": "World border cannot be bigger than %s blocks wide", + "commands.worldborder.set.failed.far": "World border cannot be further out than %s blocks", + "commands.worldborder.warning.time.failed": "Nothing changed. The world border warning is already that amount of time", + "commands.worldborder.warning.distance.failed": "Nothing changed. The world border warning is already that distance", + "commands.worldborder.damage.buffer.failed": "Nothing changed. The world border damage buffer is already that distance", + "commands.worldborder.damage.amount.failed": "Nothing changed. The world border damage is already that amount", + "commands.data.block.invalid": "The target block is not a block entity", + "commands.data.merge.failed": "Nothing changed. The specified properties already have these values", + "commands.data.modify.expected_list": "Expected list, got: %s", + "commands.data.modify.expected_object": "Expected object, got: %s", + "commands.data.modify.invalid_index": "Invalid list index: %s", + "commands.data.get.multiple": "This argument accepts a single NBT value", + "commands.data.entity.invalid": "Unable to modify player data", + "commands.teammsg.failed.noteam": "You must be on a team to message your team", + "argument.color.invalid": "Unknown color '%s'", + "argument.dimension.invalid": "Unknown dimension '%s'", + "argument.component.invalid": "Invalid chat component: %s", + "argument.anchor.invalid": "Invalid entity anchor position %s", + "lectern.take_book": "Take Book", + "arguments.objective.notFound": "Unknown scoreboard objective '%s'", + "arguments.objective.readonly": "Scoreboard objective '%s' is read-only", + "argument.criteria.invalid": "Unknown criterion '%s'", + "particle.notFound": "Unknown particle: %s", + "argument.id.unknown": "Unknown ID: %s", + "advancement.advancementNotFound": "Unknown advancement: %s", + "recipe.notFound": "Unknown recipe: %s", + "entity.not_summonable": "Can't summon entity of type %s", + "predicate.unknown": "Unknown predicate: %s", + "item_modifier.unknown": "Unknown item modifier: %s", + "argument.scoreboardDisplaySlot.invalid": "Unknown display slot '%s'", + "slot.unknown": "Unknown slot '%s'", + "team.notFound": "Unknown team '%s'", + "arguments.block.tag.unknown": "Unknown block tag '%s'", + "argument.block.id.invalid": "Unknown block type '%s'", + "argument.block.property.unknown": "Block %s does not have property '%s'", + "argument.block.property.duplicate": "Property '%s' can only be set once for block %s", + "argument.block.property.invalid": "Block %s does not accept '%s' for %s property", + "argument.block.property.novalue": "Expected value for property '%s' on block %s", + "arguments.function.tag.unknown": "Unknown function tag '%s'", + "arguments.function.unknown": "Unknown function %s", + "arguments.item.overstacked": "%s can only stack up to %s", + "argument.item.id.invalid": "Unknown item '%s'", + "arguments.item.tag.unknown": "Unknown item tag '%s'", + "argument.entity.selector.unknown": "Unknown selector type '%s'", + "argument.entity.options.valueless": "Expected value for option '%s'", + "argument.entity.options.unknown": "Unknown option '%s'", + "argument.entity.options.inapplicable": "Option '%s' isn't applicable here", + "argument.entity.options.sort.irreversible": "Invalid or unknown sort type '%s'", + "argument.entity.options.mode.invalid": "Invalid or unknown game mode '%s'", + "argument.entity.options.type.invalid": "Invalid or unknown entity type '%s'", + "argument.nbt.list.mixed": "Can't insert %s into list of %s", + "argument.nbt.array.mixed": "Can't insert %s into %s", + "argument.nbt.array.invalid": "Invalid array type '%s'", + "commands.bossbar.create.failed": "A bossbar already exists with the ID '%s'", + "commands.bossbar.unknown": "No bossbar exists with the ID '%s'", + "clear.failed.single": "No items were found on player %s", + "clear.failed.multiple": "No items were found on %s players", + "commands.clone.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", + "commands.datapack.unknown": "Unknown data pack '%s'", + "commands.datapack.enable.failed": "Pack '%s' is already enabled!", + "commands.datapack.enable.failed.no_flags": "Pack '%s' cannot be enabled, since required flags are not enabled in this world: %s!", + "commands.datapack.disable.failed": "Pack '%s' is not enabled!", + "commands.difficulty.failure": "The difficulty did not change; it is already set to %s", + "commands.enchant.failed.entity": "%s is not a valid entity for this command", + "commands.enchant.failed.itemless": "%s is not holding any item", + "commands.enchant.failed.incompatible": "%s cannot support that enchantment", + "commands.enchant.failed.level": "%s is higher than the maximum level of %s supported by that enchantment", + "commands.execute.blocks.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", + "commands.execute.conditional.pass": "Test passed", + "commands.execute.conditional.pass_count": "Test passed, count: %s", + "commands.execute.conditional.fail": "Test failed", + "commands.execute.conditional.fail_count": "Test failed, count: %s", + "commands.fill.toobig": "Too many blocks in the specified area (maximum %s, specified %s)", + "commands.fillbiome.toobig": "Too many blocks in the specified volume (maximum %s, specified %s)", + "commands.fillbiome.success": "Biomes set between %s, %s, %s and %s, %s, %s", + "commands.fillbiome.success.count": "%s biome entries set between %s, %s, %s and %s, %s, %s", + "commands.publish.alreadyPublished": "Multiplayer game is already hosted on port %s", + "commands.scoreboard.players.get.null": "Can't get value of %s for %s; none is set", + "commands.spreadplayers.failed.teams": "Could not spread %s teams around %s, %s (too many entities for space - try using spread of at most %s)", + "commands.spreadplayers.failed.entities": "Could not spread %s entities around %s, %s (too many entities for space - try using spread of at most %s)", + "commands.spreadplayers.failed.invalid.height": "Invalid maxHeight %s; expected higher than world minimum %s", + "commands.data.get.invalid": "Can't get %s; only numeric tags are allowed", + "commands.data.get.unknown": "Can't get %s; tag doesn't exist", + "argument.double.low": "Double must not be less than %s, found %s", + "argument.double.big": "Double must not be more than %s, found %s", + "argument.float.low": "Float must not be less than %s, found %s", + "argument.float.big": "Float must not be more than %s, found %s", + "argument.integer.low": "Integer must not be less than %s, found %s", + "argument.integer.big": "Integer must not be more than %s, found %s", + "argument.long.low": "Long must not be less than %s, found %s", + "argument.long.big": "Long must not be more than %s, found %s", + "argument.literal.incorrect": "Expected literal %s", + "parsing.quote.expected.start": "Expected quote to start a string", + "parsing.quote.expected.end": "Unclosed quoted string", + "parsing.quote.escape": "Invalid escape sequence '\\%s' in quoted string", + "parsing.bool.invalid": "Invalid boolean, expected 'true' or 'false' but found '%s'", + "parsing.int.invalid": "Invalid integer '%s'", + "parsing.int.expected": "Expected integer", + "parsing.long.invalid": "Invalid long '%s'", + "parsing.long.expected": "Expected long", + "command.exception": "Could not parse command: %s", + "parsing.double.invalid": "Invalid double '%s'", + "parsing.double.expected": "Expected double", + "parsing.float.invalid": "Invalid float '%s'", + "parsing.float.expected": "Expected float", + "parsing.bool.expected": "Expected boolean", + "parsing.expected": "Expected '%s'", + "command.unknown.command": "Unknown or incomplete command, see below for error", + "command.unknown.argument": "Incorrect argument for command", + "command.expected.separator": "Expected whitespace to end one argument, but found trailing data", + "biome.minecraft.badlands": "Badlands", + "biome.minecraft.bamboo_jungle": "Bamboo Jungle", + "biome.minecraft.basalt_deltas": "Basalt Deltas", + "biome.minecraft.beach": "Beach", + "biome.minecraft.birch_forest": "Birch Forest", + "biome.minecraft.cold_ocean": "Cold Ocean", + "biome.minecraft.crimson_forest": "Crimson Forest", + "biome.minecraft.dark_forest": "Dark Forest", + "biome.minecraft.deep_cold_ocean": "Deep Cold Ocean", + "biome.minecraft.deep_dark": "Deep Dark", + "biome.minecraft.deep_frozen_ocean": "Deep Frozen Ocean", + "biome.minecraft.deep_lukewarm_ocean": "Deep Lukewarm Ocean", + "biome.minecraft.deep_ocean": "Deep Ocean", + "biome.minecraft.desert": "Desert", + "biome.minecraft.dripstone_caves": "Dripstone Caves", + "biome.minecraft.old_growth_birch_forest": "Old Growth Birch Forest", + "biome.minecraft.old_growth_pine_taiga": "Old Growth Pine Taiga", + "biome.minecraft.old_growth_spruce_taiga": "Old Growth Spruce Taiga", + "biome.minecraft.end_barrens": "End Barrens", + "biome.minecraft.end_highlands": "End Highlands", + "biome.minecraft.end_midlands": "End Midlands", + "biome.minecraft.eroded_badlands": "Eroded Badlands", + "biome.minecraft.flower_forest": "Flower Forest", + "biome.minecraft.forest": "Forest", + "biome.minecraft.frozen_ocean": "Frozen Ocean", + "biome.minecraft.frozen_peaks": "Frozen Peaks", + "biome.minecraft.frozen_river": "Frozen River", + "biome.minecraft.grove": "Grove", + "biome.minecraft.ice_spikes": "Ice Spikes", + "biome.minecraft.jagged_peaks": "Jagged Peaks", + "biome.minecraft.jungle": "Jungle", + "biome.minecraft.lukewarm_ocean": "Lukewarm Ocean", + "biome.minecraft.lush_caves": "Lush Caves", + "biome.minecraft.mangrove_swamp": "Mangrove Swamp", + "biome.minecraft.meadow": "Meadow", + "biome.minecraft.mushroom_fields": "Mushroom Fields", + "biome.minecraft.nether_wastes": "Nether Wastes", + "biome.minecraft.ocean": "Ocean", + "biome.minecraft.plains": "Plains", + "biome.minecraft.river": "River", + "biome.minecraft.savanna_plateau": "Savanna Plateau", + "biome.minecraft.savanna": "Savanna", + "biome.minecraft.small_end_islands": "Small End Islands", + "biome.minecraft.snowy_beach": "Snowy Beach", + "biome.minecraft.snowy_plains": "Snowy Plains", + "biome.minecraft.snowy_slopes": "Snowy Slopes", + "biome.minecraft.snowy_taiga": "Snowy Taiga", + "biome.minecraft.soul_sand_valley": "Soul Sand Valley", + "biome.minecraft.sparse_jungle": "Sparse Jungle", + "biome.minecraft.stony_peaks": "Stony Peaks", + "biome.minecraft.stony_shore": "Stony Shore", + "biome.minecraft.sunflower_plains": "Sunflower Plains", + "biome.minecraft.swamp": "Swamp", + "biome.minecraft.taiga": "Taiga", + "biome.minecraft.the_end": "The End", + "biome.minecraft.the_void": "The Void", + "biome.minecraft.warm_ocean": "Warm Ocean", + "biome.minecraft.warped_forest": "Warped Forest", + "biome.minecraft.windswept_forest": "Windswept Forest", + "biome.minecraft.windswept_gravelly_hills": "Windswept Gravelly Hills", + "biome.minecraft.windswept_hills": "Windswept Hills", + "biome.minecraft.windswept_savanna": "Windswept Savanna", + "biome.minecraft.wooded_badlands": "Wooded Badlands", + "realms.missing.module.error.text": "Realms could not be opened right now, please try again later", + "realms.missing.snapshot.error.text": "Realms is currently not supported in snapshots", + "color.minecraft.white": "White", + "color.minecraft.orange": "Orange", + "color.minecraft.magenta": "Magenta", + "color.minecraft.light_blue": "Light Blue", + "color.minecraft.yellow": "Yellow", + "color.minecraft.lime": "Lime", + "color.minecraft.pink": "Pink", + "color.minecraft.gray": "Gray", + "color.minecraft.light_gray": "Light Gray", + "color.minecraft.cyan": "Cyan", + "color.minecraft.purple": "Purple", + "color.minecraft.blue": "Blue", + "color.minecraft.brown": "Brown", + "color.minecraft.green": "Green", + "color.minecraft.red": "Red", + "color.minecraft.black": "Black", + "title.singleplayer": "Singleplayer", + "title.multiplayer.realms": "Multiplayer (Realms)", + "title.multiplayer.lan": "Multiplayer (LAN)", + "title.multiplayer.other": "Multiplayer (3rd-party Server)", + "gamerule.announceAdvancements": "Announce advancements", + "gamerule.commandBlockOutput": "Broadcast command block output", + "gamerule.disableElytraMovementCheck": "Disable elytra movement check", + "gamerule.disableRaids": "Disable raids", + "gamerule.doDaylightCycle": "Advance time of day", + "gamerule.doEntityDrops": "Drop entity equipment", + "gamerule.doEntityDrops.description": "Controls drops from minecarts (including inventories), item frames, boats, etc.", + "gamerule.doFireTick": "Update fire", + "gamerule.doImmediateRespawn": "Respawn immediately", + "gamerule.doInsomnia": "Spawn phantoms", + "gamerule.doLimitedCrafting": "Require recipe for crafting", + "gamerule.doLimitedCrafting.description": "If enabled, players will be able to craft only unlocked recipes", + "gamerule.doMobLoot": "Drop mob loot", + "gamerule.doMobLoot.description": "Controls resource drops from mobs, including experience orbs", + "gamerule.doMobSpawning": "Spawn mobs", + "gamerule.doMobSpawning.description": "Some entities might have separate rules", + "gamerule.doPatrolSpawning": "Spawn pillager patrols", + "gamerule.doTileDrops": "Drop blocks", + "gamerule.doTileDrops.description": "Controls resource drops from blocks, including experience orbs", + "gamerule.doTraderSpawning": "Spawn Wandering Traders", + "gamerule.doWardenSpawning": "Spawn Wardens", + "gamerule.doWeatherCycle": "Update weather", + "gamerule.drowningDamage": "Deal drowning damage", + "gamerule.fallDamage": "Deal fall damage", + "gamerule.fireDamage": "Deal fire damage", + "gamerule.freezeDamage": "Deal freeze damage", + "gamerule.forgiveDeadPlayers": "Forgive dead players", + "gamerule.forgiveDeadPlayers.description": "Angered neutral mobs stop being angry when the targeted player dies nearby.", + "gamerule.keepInventory": "Keep inventory after death", + "gamerule.logAdminCommands": "Broadcast admin commands", + "gamerule.maxCommandChainLength": "Command chain size limit", + "gamerule.maxCommandChainLength.description": "Applies to command block chains and functions", + "gamerule.maxEntityCramming": "Entity cramming threshold", + "gamerule.mobGriefing": "Allow destructive mob actions", + "gamerule.naturalRegeneration": "Regenerate health", + "gamerule.randomTickSpeed": "Random tick speed rate", + "gamerule.reducedDebugInfo": "Reduce debug info", + "gamerule.reducedDebugInfo.description": "Limits contents of debug screen", + "gamerule.sendCommandFeedback": "Send command feedback", + "gamerule.showDeathMessages": "Show death messages", + "gamerule.playersSleepingPercentage": "Sleep percentage", + "gamerule.playersSleepingPercentage.description": "The percentage of players who must be sleeping to skip the night.", + "gamerule.spawnRadius": "Respawn location radius", + "gamerule.spectatorsGenerateChunks": "Allow spectators to generate terrain", + "gamerule.universalAnger": "Universal anger", + "gamerule.universalAnger.description": "Angered neutral mobs attack any nearby player, not just the player that angered them. Works best if forgiveDeadPlayers is disabled.", + "gamerule.blockExplosionDropDecay": "In block interaction explosions, some blocks won't drop their loot", + "gamerule.blockExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by block interactions are lost in the explosion.", + "gamerule.mobExplosionDropDecay": "In mob explosions, some blocks won't drop their loot", + "gamerule.mobExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by mobs are lost in the explosion.", + "gamerule.tntExplosionDropDecay": "In TNT explosions, some blocks won't drop their loot", + "gamerule.tntExplosionDropDecay.description": "Some of the drops from blocks destroyed by explosions caused by TNT are lost in the explosion.", + "gamerule.snowAccumulationHeight": "Snow accumulation height", + "gamerule.snowAccumulationHeight.description": "When it snows, layers of snow form on the ground up to at most this number of layers.", + "gamerule.waterSourceConversion": "Water converts to source", + "gamerule.waterSourceConversion.description": "When flowing water is surrounded on two sides by water sources it converts into a source.", + "gamerule.lavaSourceConversion": "Lava converts to source", + "gamerule.lavaSourceConversion.description": "When flowing lava is surrounded on two sides by lava sources it converts into a source.", + "gamerule.globalSoundEvents": "Global sound events", + "gamerule.globalSoundEvents.description": "When certain game events happen, like a boss spawning, the sound is heard everywhere.", + "gamerule.category.chat": "Chat", + "gamerule.category.spawning": "Spawning", + "gamerule.category.updates": "World Updates", + "gamerule.category.drops": "Drops", + "gamerule.category.mobs": "Mobs", + "gamerule.category.player": "Player", + "gamerule.category.misc": "Miscellaneous", + "pack.source.builtin": "built-in", + "pack.source.feature": "feature", + "pack.source.world": "world", + "pack.source.local": "local", + "pack.source.server": "server", + "mirror.none": "|", + "mirror.left_right": "\u2190 \u2192", + "mirror.front_back": "\u2191 \u2193", + "sleep.not_possible": "No amount of rest can pass this night", + "sleep.players_sleeping": "%s/%s players sleeping", + "sleep.skipping_night": "Sleeping through this night", + "compliance.playtime.greaterThan24Hours": "You've been playing for greater than 24 hours", + "compliance.playtime.message": "Excessive gaming may interfere with normal daily life", + "compliance.playtime.hours": "You've been playing for %s hour(s)", + "outOfMemory.title": "Out of memory!", + "outOfMemory.message": "Minecraft has run out of memory.\n\nThis could be caused by a bug in the game or by the Java Virtual Machine not being allocated enough memory.\n\nTo prevent level corruption, the current game has quit. We've tried to free up enough memory to let you go back to the main menu and back to playing, but this may not have worked.\n\nPlease restart the game if you see this message again.", + "mco.gui.ok": "Ok", + "mco.gui.button": "Button", + "mco.terms.buttons.agree": "Agree", + "mco.terms.buttons.disagree": "Don't agree", + "mco.terms.title": "Realms Terms of Service", + "mco.terms.sentence.1": "I agree to the Minecraft Realms", + "mco.terms.sentence.2": "Terms of Service", + "mco.selectServer.play": "Play", + "mco.selectServer.configure": "Configure", + "mco.selectServer.configureRealm": "Configure realm", + "mco.selectServer.leave": "Leave realm", + "mco.selectServer.create": "Create realm", + "mco.selectServer.purchase": "Add Realm", + "mco.selectServer.buy": "Buy a realm!", + "mco.selectServer.trial": "Get a trial!", + "mco.selectServer.close": "Close", + "mco.selectServer.expiredTrial": "Your trial has ended", + "mco.selectServer.expiredList": "Your subscription has expired", + "mco.selectServer.expiredSubscribe": "Subscribe", + "mco.selectServer.expiredRenew": "Renew", + "mco.selectServer.expired": "Expired realm", + "mco.selectServer.open": "Open realm", + "mco.selectServer.closed": "Closed realm", + "mco.selectServer.openserver": "Open realm", + "mco.selectServer.closeserver": "Close realm", + "mco.selectServer.minigame": "Minigame:", + "mco.selectServer.uninitialized": "Click to start your new realm!", + "mco.selectServer.expires.days": "Expires in %s days", + "mco.selectServer.expires.day": "Expires in a day", + "mco.selectServer.expires.soon": "Expires soon", + "mco.selectServer.note": "Note:", + "mco.selectServer.mapOnlySupportedForVersion": "This map is unsupported in %s", + "mco.selectServer.minigameNotSupportedInVersion": "Can't play this minigame in %s", + "mco.selectServer.popup": "Realms is a safe, simple way to enjoy an online Minecraft world with up to ten friends at a time. It supports loads of minigames and plenty of custom worlds! Only the owner of the realm needs to pay.", + "mco.configure.world.settings.title": "Settings", + "mco.configure.world.title": "Configure realm:", + "mco.configure.worlds.title": "Worlds", + "mco.configure.world.name": "Realm name", + "mco.configure.world.description": "Realm description", + "mco.configure.world.location": "Location", + "mco.configure.world.invited": "Invited", + "mco.configure.world.invite.narration": "You have %s new invites", + "mco.configure.world.buttons.edit": "Settings", + "mco.configure.world.buttons.done": "Done", + "mco.configure.world.buttons.delete": "Delete", + "mco.configure.world.buttons.open": "Open realm", + "mco.configure.world.buttons.close": "Close realm", + "mco.configure.world.buttons.invite": "Invite player", + "mco.configure.world.buttons.activity": "Player activity", + "mco.configure.world.activityfeed.disabled": "Player feed temporarily disabled", + "mco.configure.world.buttons.moreoptions": "More options", + "mco.configure.world.invite.profile.name": "Name", + "mco.configure.world.uninvite.question": "Are you sure that you want to uninvite", + "mco.configure.world.status": "Status", + "mco.configure.world.invites.ops.tooltip": "Operator", + "mco.configure.world.invites.normal.tooltip": "Normal user", + "mco.configure.world.invites.remove.tooltip": "Remove", + "mco.configure.world.closing": "Closing the realm...", + "mco.configure.world.opening": "Opening the realm...", + "mco.configure.world.buttons.players": "Players", + "mco.configure.world.buttons.settings": "Settings", + "mco.configure.world.buttons.subscription": "Subscription", + "mco.configure.world.buttons.options": "World options", + "mco.configure.world.backup": "World backups", + "mco.configure.world.buttons.resetworld": "Reset world", + "mco.configure.world.buttons.switchminigame": "Switch minigame", + "mco.configure.current.minigame": "Current", + "mco.configure.world.subscription.title": "Your subscription", + "mco.configure.world.subscription.timeleft": "Time left", + "mco.configure.world.subscription.unknown": "Unknown", + "mco.configure.world.subscription.recurring.daysleft": "Renewed automatically in", + "mco.configure.world.subscription.less_than_a_day": "Less than a day", + "mco.configure.world.subscription.expired": "Expired", + "mco.configure.world.subscription.start": "Start date", + "mco.configure.world.subscription.extend": "Extend subscription", + "mco.configure.world.subscription.day": "day", + "mco.configure.world.subscription.month": "month", + "mco.configure.world.subscription.days": "days", + "mco.configure.world.subscription.months": "months", + "mco.configure.world.pvp": "PVP", + "mco.configure.world.spawn_toggle.title": "Warning!", + "mco.configure.world.spawn_toggle.message": "Turning this option off will REMOVE ALL existing entities of that type", + "mco.configure.world.spawn_toggle.message.npc": "Turning this option off will REMOVE ALL existing entities of that type, like Villagers", + "mco.configure.world.spawnAnimals": "Spawn animals", + "mco.configure.world.spawnNPCs": "Spawn NPCs", + "mco.configure.world.spawnMonsters": "Spawn monsters", + "mco.configure.world.spawnProtection": "Spawn protection", + "mco.configure.world.commandBlocks": "Command blocks", + "mco.configure.world.forceGameMode": "Force game mode", + "mco.configure.world.slot": "World %s", + "mco.configure.world.slot.empty": "Empty", + "mco.create.world.wait": "Creating the realm...", + "mco.create.world.error": "You must enter a name!", + "mco.create.world.subtitle": "Optionally, select what world to put on your new realm", + "mco.create.world.skip": "Skip", + "mco.reset.world.title": "Reset world", + "mco.reset.world.warning": "This will replace the current world of your realm", + "mco.reset.world.seed": "Seed (Optional)", + "mco.reset.world.resetting.screen.title": "Resetting world...", + "mco.reset.world.generate": "New world", + "mco.reset.world.upload": "Upload world", + "mco.reset.world.adventure": "Adventures", + "mco.reset.world.template": "World templates", + "mco.reset.world.experience": "Experiences", + "mco.reset.world.inspiration": "Inspiration", + "mco.minigame.world.title": "Switch realm to minigame", + "mco.minigame.world.info.line1": "This will temporarily replace your world with a minigame!", + "mco.minigame.world.info.line2": "You can later return to your original world without losing anything.", + "mco.minigame.world.selected": "Selected minigame:", + "mco.minigame.world.noSelection": "Please make a selection", + "mco.minigame.world.startButton": "Switch", + "mco.minigame.world.starting.screen.title": "Starting minigame...", + "mco.minigame.world.changeButton": "Select another minigame", + "mco.minigame.world.stopButton": "End minigame", + "mco.minigame.world.switch.title": "Switch minigame", + "mco.minigame.world.switch.new": "Select another minigame?", + "mco.minigame.world.restore.question.line1": "The minigame will end and your realm will be restored.", + "mco.minigame.world.restore.question.line2": "Are you sure you want to continue?", + "mco.minigame.world.restore": "Ending minigame...", + "mco.configure.world.slot.tooltip": "Switch to world", + "mco.configure.world.slot.tooltip.minigame": "Switch to minigame", + "mco.configure.world.slot.tooltip.active": "Join", + "mco.configure.world.close.question.line1": "Your realm will become unavailable.", + "mco.configure.world.close.question.line2": "Are you sure you want to continue?", + "mco.configure.world.leave.question.line1": "If you leave this realm you won't see it unless you are invited again", + "mco.configure.world.leave.question.line2": "Are you sure you want to continue?", + "mco.configure.world.resourcepack.question.line1": "You need a custom resource pack to play on this realm", + "mco.configure.world.resourcepack.question.line2": "Do you want to download it and play?", + "mco.configure.world.reset.question.line1": "Your world will be regenerated and your current world will be lost", + "mco.configure.world.reset.question.line2": "Are you sure you want to continue?", + "mco.configure.world.restore.question.line1": "Your world will be restored to date '%s' (%s)", + "mco.configure.world.restore.question.line2": "Are you sure you want to continue?", + "mco.configure.world.restore.download.question.line1": "The world will be downloaded and added to your single player worlds.", + "mco.configure.world.restore.download.question.line2": "Do you want to continue?", + "mco.configure.world.slot.switch.question.line1": "Your realm will be switched to another world", + "mco.configure.world.slot.switch.question.line2": "Are you sure you want to continue?", + "mco.configure.world.switch.slot": "Create world", + "mco.configure.world.switch.slot.subtitle": "This world is empty, choose how to create your world", + "mco.minigame.world.slot.screen.title": "Switching world...", + "mco.configure.world.edit.subscreen.adventuremap": "Some settings are disabled since your current world is an adventure", + "mco.configure.world.edit.subscreen.experience": "Some settings are disabled since your current world is an experience", + "mco.configure.world.edit.subscreen.inspiration": "Some settings are disabled since your current world is an inspiration", + "mco.configure.world.edit.slot.name": "World name", + "mco.configure.world.players.title": "Players", + "mco.configure.world.players.error": "A player with the provided name does not exist", + "mco.configure.world.delete.button": "Delete realm", + "mco.configure.world.delete.question.line1": "Your realm will be permanently deleted", + "mco.configure.world.delete.question.line2": "Are you sure you want to continue?", + "mco.connect.connecting": "Connecting to the realm...", + "mco.connect.authorizing": "Logging in...", + "mco.connect.failed": "Failed to connect to the realm", + "mco.connect.success": "Done", + "mco.create.world": "Create", + "mco.create.world.reset.title": "Creating world...", + "mco.client.incompatible.title": "Client incompatible!", + "mco.client.incompatible.msg.line1": "Your client is not compatible with Realms.", + "mco.client.incompatible.msg.line2": "Please use the most recent version of Minecraft.", + "mco.client.incompatible.msg.line3": "Realms is not compatible with snapshot versions.", + "mco.backup.button.restore": "Restore", + "mco.backup.generate.world": "Generate world", + "mco.backup.restoring": "Restoring your realm", + "mco.backup.button.download": "Download latest", + "mco.backup.changes.tooltip": "Changes", + "mco.backup.nobackups": "This realm doesn't have any backups currently.", + "mco.backup.button.upload": "Upload world", + "mco.backup.button.reset": "Reset world", + "mco.download.title": "Downloading latest world", + "mco.download.cancelled": "Download cancelled", + "mco.download.failed": "Download failed", + "mco.download.done": "Download done", + "mco.download.downloading": "Downloading", + "mco.download.extracting": "Extracting", + "mco.download.preparing": "Preparing download", + "mco.download.confirmation.line1": "The world you are going to download is larger than %s", + "mco.download.confirmation.line2": "You won't be able to upload this world to your realm again", + "mco.template.title": "World templates", + "mco.template.title.minigame": "Minigames", + "mco.template.button.select": "Select", + "mco.template.button.trailer": "Trailer", + "mco.template.button.publisher": "Publisher", + "mco.template.default.name": "World template", + "mco.template.name": "Template", + "mco.template.info.tooltip": "Publisher website", + "mco.template.trailer.tooltip": "Map trailer", + "mco.template.select.none": "Oops, it looks like this content category is currently empty.\nPlease check back later for new content, or if you're a creator,\n%s.", + "mco.template.select.none.linkTitle": "consider submitting something yourself", + "mco.template.select.failure": "We couldn't retrieve the list of content for this category.\nPlease check your internet connection, or try again later.", + "mco.template.select.narrate.authors" : "Authors: %s", + "mco.template.select.narrate.version" : "version %s", + "mco.invites.button.accept": "Accept", + "mco.invites.button.reject": "Reject", + "mco.invites.title": "Pending Invites", + "mco.invites.pending": "New invites!", + "mco.invites.nopending": "No pending invites!", + "mco.upload.select.world.title": "Upload world", + "mco.upload.select.world.subtitle": "Please select a singleplayer world to upload", + "mco.upload.select.world.none": "No singleplayer worlds found!", + "mco.upload.button.name": "Upload", + "mco.upload.verifying": "Verifying your world", + "mco.upload.preparing": "Preparing your world", + "mco.upload.close.failure": "Could not close your realm, please try again later", + "mco.upload.size.failure.line1": "'%s' is too big!", + "mco.upload.size.failure.line2": "It is %s. The maximum allowed size is %s.", + "mco.upload.uploading": "Uploading '%s'", + "mco.upload.done": "Upload done", + "mco.upload.failed": "Upload failed! (%s)", + "mco.upload.cancelled": "Upload cancelled", + "mco.upload.hardcore": "Hardcore worlds can't be uploaded!", + "mco.activity.title": "Player activity", + "mco.activity.noactivity": "No activity for the past %s days", + "mco.errorMessage.6001": "Client outdated", + "mco.errorMessage.6002": "Terms of service not accepted", + "mco.errorMessage.6003": "Download limit reached", + "mco.errorMessage.6004": "Upload limit reached", + "mco.errorMessage.connectionFailure": "An error occurred, please try again later.", + "mco.errorMessage.serviceBusy": "Realms is busy at the moment.\nPlease try connecting to your Realm again in a couple of minutes.", + "mco.trial.message.line1": "Want to get your own realm?", + "mco.trial.message.line2": "Click here for more info!", + "mco.brokenworld.play": "Play", + "mco.brokenworld.download": "Download", + "mco.brokenworld.downloaded": "Downloaded", + "mco.brokenworld.reset": "Reset", + "mco.brokenworld.title": "Your current world is no longer supported", + "mco.brokenworld.message.line1": "Please reset or select another world.", + "mco.brokenworld.message.line2": "You can also choose to download the world to singleplayer.", + "mco.brokenworld.minigame.title": "This minigame is no longer supported", + "mco.brokenworld.nonowner.title": "World is out of date", + "mco.brokenworld.nonowner.error": "Please wait for the realm owner to reset the world", + "mco.error.invalid.session.title": "Invalid session", + "mco.error.invalid.session.message": "Please try restarting Minecraft", + "mco.news": "Realms news", + "mco.account.privacyinfo": "Mojang implements certain procedures to help protect children and their privacy including complying with the Children’s Online Privacy Protection Act (COPPA) and General Data Protection Regulation (GDPR).\n\nYou may need to obtain parental consent before accessing your Realms account.\n\nIf you have an older Minecraft account (you log in with your username), you need to migrate the account to a Mojang account in order to access Realms.", + "mco.account.update": "Update account", + "mco.account.privacy.info": "Read more about Mojang and privacy laws" +} diff --git a/MinecraftClient/Scripting/AssemblyResolver.cs b/MinecraftClient/Scripting/AssemblyResolver.cs index f9f970ea..85b16405 100644 --- a/MinecraftClient/Scripting/AssemblyResolver.cs +++ b/MinecraftClient/Scripting/AssemblyResolver.cs @@ -4,34 +4,36 @@ using System.Reflection; namespace MinecraftClient.Scripting; -public static class AssemblyResolver { +public static class AssemblyResolver +{ private static Dictionary ScriptAssemblies = new(); - static AssemblyResolver() { + static AssemblyResolver() + { // Manually resolve assemblies that .NET can't resolve automatically. - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { - var asmReqName = new AssemblyName(args.Name); - - // Check the script-referenced assemblies if we have the DLL that is required. - foreach (var dll in ScriptAssemblies) - { - // If we have the assembly, load it. - if (asmReqName.FullName == dll.Key) - { - return Assembly.LoadFile(dll.Value); - } - } + var asmReqName = new AssemblyName(args.Name); - ConsoleIO.WriteLogLine($"[Script Error] Failed to resolve assembly {args.Name} (are you missing a DLL file?)"); - return null; - }; + // Check the script-referenced assemblies if we have the DLL that is required. + foreach (var dll in ScriptAssemblies) + { + // If we have the assembly, load it. + if (asmReqName.FullName == dll.Key) + { + return Assembly.LoadFile(dll.Value); + } + } + + ConsoleIO.WriteLogLine($"[Script Error] Failed to resolve assembly {args.Name} (are you missing a DLL file?)"); + return null; + }; } - internal static void AddAssembly(string AssemblyFullName, string AssemblyPath) + internal static void AddAssembly(string AssemblyFullName, string AssemblyPath) { if (ScriptAssemblies.ContainsKey(AssemblyFullName)) return; - + ScriptAssemblies.Add(AssemblyFullName, AssemblyPath); } } \ No newline at end of file diff --git a/MinecraftClient/Scripting/CSharpRunner.cs b/MinecraftClient/Scripting/CSharpRunner.cs index 0d433595..d056a9a4 100644 --- a/MinecraftClient/Scripting/CSharpRunner.cs +++ b/MinecraftClient/Scripting/CSharpRunner.cs @@ -4,10 +4,10 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Text; -using DynamicRun.Builder; +using MinecraftClient.Scripting.DynamicRun.Builder; using static MinecraftClient.Settings; -namespace MinecraftClient +namespace MinecraftClient.Scripting { /// /// C# Script runner - Compile on-the-fly and run C# scripts @@ -77,7 +77,7 @@ namespace MinecraftClient script.Add("return null;"); //Generate a class from the given script - string code = String.Join("\n", new string[] + string code = string.Join("\n", new string[] { "using System;", "using System.Collections.Generic;", @@ -88,17 +88,18 @@ namespace MinecraftClient "using System.Net;", "using System.Threading;", "using MinecraftClient;", + "using MinecraftClient.Scripting;", "using MinecraftClient.Mapping;", "using MinecraftClient.Inventory;", - String.Join("\n", libs), + string.Join("\n", libs), "namespace ScriptLoader {", "public class Script {", "public CSharpAPI MCC;", "public object __run(CSharpAPI __apiHandler, string[] args) {", "this.MCC = __apiHandler;", - String.Join("\n", script), + string.Join("\n", script), "}", - String.Join("\n", extensions), + string.Join("\n", extensions), "}}", }); @@ -108,11 +109,13 @@ namespace MinecraftClient var result = compiler.Compile(code, Guid.NewGuid().ToString(), dlls); //Process compile warnings and errors - if (result.Failures != null) { - + if (result.Failures != null) + { + ConsoleIO.WriteLogLine("[Script] Compilation failed with error(s):"); - foreach (var failure in result.Failures) { + foreach (var failure in result.Failures) + { ConsoleIO.WriteLogLine($"[Script] Error in {scriptName}, line:col{failure.Location.GetMappedLineSpan()}: [{failure.Id}] {failure.GetMessage()}"); } @@ -226,7 +229,7 @@ namespace MinecraftClient /// TRUE if successfully sent (Deprectated, always returns TRUE for compatibility purposes with existing scripts) public bool SendText(object text) { - return base.SendText(text is string str ? str : (text.ToString() ?? string.Empty)); + return base.SendText(text is string str ? str : text.ToString() ?? string.Empty); } /// @@ -305,7 +308,7 @@ namespace MinecraftClient if (localVars != null && localVars.ContainsKey(varName)) return localVars[varName]; else - return Settings.Config.AppVar.GetVar(varName); + return Config.AppVar.GetVar(varName); } /// @@ -317,7 +320,7 @@ namespace MinecraftClient { if (localVars != null && localVars.ContainsKey(varName)) localVars.Remove(varName); - return Settings.Config.AppVar.SetVar(varName, varValue); + return Config.AppVar.SetVar(varName, varValue); } /// @@ -359,7 +362,7 @@ namespace MinecraftClient /// True if the account was found and loaded public bool SetAccount(string accountAlias, bool andReconnect = false) { - bool result = Settings.Config.Main.Advanced.SetAccount(accountAlias); + bool result = Config.Main.Advanced.SetAccount(accountAlias); if (result && andReconnect) ReconnectToTheServer(keepAccountAndServerSettings: true); return result; @@ -372,7 +375,7 @@ namespace MinecraftClient /// True if the server IP was valid and loaded, false otherwise public bool SetServer(string server, bool andReconnect = false) { - bool result = Settings.Config.Main.SetServerIP(new MainConfigHealper.MainConfig.ServerInfoConfig(server), true); + bool result = Config.Main.SetServerIP(new MainConfigHealper.MainConfig.ServerInfoConfig(server), true); if (result && andReconnect) ReconnectToTheServer(keepAccountAndServerSettings: true); return result; diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 2b6598d4..67bc3cb5 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -4,11 +4,13 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Brigadier.NET; +using MinecraftClient.CommandHandler; using MinecraftClient.Inventory; using MinecraftClient.Mapping; using static MinecraftClient.Settings; -namespace MinecraftClient +namespace MinecraftClient.Scripting { /// /// Welcome to the Bot API file ! @@ -40,7 +42,6 @@ namespace MinecraftClient private McClient? _handler = null; private ChatBot? master = null; private readonly List registeredPluginChannels = new(); - private readonly List registeredCommands = new(); private readonly object delayTasksLock = new(); private readonly List delayedTasks = new(); protected McClient Handler @@ -173,13 +174,13 @@ namespace MinecraftClient /// Called when properties for the Player entity are received from the server /// /// Dictionary of player properties - public virtual void OnPlayerProperty(Dictionary prop) { } + public virtual void OnPlayerProperty(Dictionary prop) { } /// /// Called when server TPS are recalculated by MCC based on world time updates /// /// New estimated server TPS (between 0 and 20) - public virtual void OnServerTpsUpdate(Double tps) { } + public virtual void OnServerTpsUpdate(double tps) { } /// /// Called when a time changed @@ -192,7 +193,7 @@ namespace MinecraftClient /// Called when an entity moved nearby /// /// Entity with updated location - public virtual void OnEntityMove(Mapping.Entity entity) { } + public virtual void OnEntityMove(Entity entity) { } /// /// Called after an internal MCC command has been performed @@ -200,19 +201,19 @@ namespace MinecraftClient /// MCC Command Name /// MCC Command Parameters /// MCC command result - public virtual void OnInternalCommand(string commandName, string commandParams, string Result) { } + public virtual void OnInternalCommand(string commandName, string commandParams, CmdResult Result) { } /// /// Called when an entity spawned nearby /// /// New Entity - public virtual void OnEntitySpawn(Mapping.Entity entity) { } + public virtual void OnEntitySpawn(Entity entity) { } /// /// Called when an entity despawns/dies nearby /// /// Entity wich has just disappeared - public virtual void OnEntityDespawn(Mapping.Entity entity) { } + public virtual void OnEntityDespawn(Entity entity) { } /// /// Called when the player held item has changed @@ -513,7 +514,7 @@ namespace MinecraftClient /// TRUE if the command was indeed an internal MCC command protected bool PerformInternalCommand(string command, Dictionary? localVars = null) { - string? temp = ""; + CmdResult temp = new(); return Handler.PerformInternalCommand(command, ref temp, localVars); } @@ -524,9 +525,9 @@ namespace MinecraftClient /// May contain a confirmation or error message after processing the command, or "" otherwise. /// Local variables passed along with the command /// TRUE if the command was indeed an internal MCC command - protected bool PerformInternalCommand(string command, ref string? response_msg, Dictionary? localVars = null) + protected bool PerformInternalCommand(string command, ref CmdResult result, Dictionary? localVars = null) { - return Handler.PerformInternalCommand(command, ref response_msg, localVars); + return Handler.PerformInternalCommand(command, ref result, localVars); } /// @@ -534,8 +535,8 @@ namespace MinecraftClient /// public static string GetVerbatim(string? text) { - if (String.IsNullOrEmpty(text)) - return String.Empty; + if (string.IsNullOrEmpty(text)) + return string.Empty; int idx = 0; var data = new char[text.Length]; @@ -554,13 +555,13 @@ namespace MinecraftClient /// public static bool IsValidName(string username) { - if (String.IsNullOrEmpty(username)) + if (string.IsNullOrEmpty(username)) return false; foreach (char c in username) - if (!((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') + if (!(c >= 'a' && c <= 'z' + || c >= 'A' && c <= 'Z' + || c >= '0' && c <= '9' || c == '_')) return false; @@ -576,7 +577,7 @@ namespace MinecraftClient /// Returns true if the text is a private message protected static bool IsPrivateMessage(string text, ref string message, ref string sender) { - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) return false; text = GetVerbatim(text); @@ -687,7 +688,7 @@ namespace MinecraftClient /// Returns true if the text is a chat message protected static bool IsChatMessage(string text, ref string message, ref string sender) { - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) return false; text = GetVerbatim(text); @@ -790,7 +791,7 @@ namespace MinecraftClient /// Returns true if the text is a teleport request protected static bool IsTeleportRequest(string text, ref string sender) { - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) return false; text = GetVerbatim(text); @@ -818,8 +819,8 @@ namespace MinecraftClient { // Username has requested... //[Rank] Username has requested... - if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) - || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) + if ((tmp[0].StartsWith("<") && tmp[0].EndsWith(">") + || tmp[0].StartsWith("[") && tmp[0].EndsWith("]")) && tmp.Length > 1) sender = tmp[1]; else //Username has requested.. @@ -845,12 +846,12 @@ namespace MinecraftClient { string botName = Translations.ResourceManager.GetString("botname." + GetType().Name) ?? GetType().Name; if (_handler == null || master == null) - ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", botName, text)); + ConsoleIO.WriteLogLine(string.Format("[{0}] {1}", botName, text)); else - Handler.Log.Info(String.Format("[{0}] {1}", botName, text)); - string logfile = Settings.Config.AppVar.ExpandVars(Config.Main.Advanced.ChatbotLogFile); + Handler.Log.Info(string.Format("[{0}] {1}", botName, text)); + string logfile = Config.AppVar.ExpandVars(Config.Main.Advanced.ChatbotLogFile); - if (!String.IsNullOrEmpty(logfile)) + if (!string.IsNullOrEmpty(logfile)) { if (!File.Exists(logfile)) { @@ -867,10 +868,10 @@ namespace MinecraftClient protected static void LogToConsole(string originBotName, object? text) { string botName = Translations.ResourceManager.GetString(originBotName) ?? originBotName; - ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", botName, text)); - string logfile = Settings.Config.AppVar.ExpandVars(Config.Main.Advanced.ChatbotLogFile); + ConsoleIO.WriteLogLine(string.Format("[{0}] {1}", botName, text)); + string logfile = Config.AppVar.ExpandVars(Config.Main.Advanced.ChatbotLogFile); - if (!String.IsNullOrEmpty(logfile)) + if (!string.IsNullOrEmpty(logfile)) { if (!File.Exists(logfile)) { @@ -890,7 +891,7 @@ namespace MinecraftClient /// Debug log text to write protected void LogDebugToConsole(object text) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) LogToConsole(text); } @@ -922,7 +923,7 @@ namespace MinecraftClient /// Optional delay, in seconds, before restarting protected void ReconnectToTheServer(int ExtraAttempts = 3, int delaySeconds = 0, bool keepAccountAndServerSettings = false) { - if (Settings.Config.Logging.DebugMessages) + if (Config.Logging.DebugMessages) { string botName = Translations.ResourceManager.GetString("botname." + GetType().Name) ?? GetType().Name; ConsoleIO.WriteLogLine(string.Format(Translations.chatbot_reconnect, botName)); @@ -944,10 +945,6 @@ namespace MinecraftClient /// protected void UnloadBot() { - foreach (string cmdName in registeredCommands) - { - Handler.UnregisterCommand(cmdName); - } Handler.BotUnLoad(this); } @@ -958,7 +955,7 @@ namespace MinecraftClient /// Message protected void SendPrivateMessage(string player, string message) { - SendText(String.Format("/{0} {1} {2}", Config.Main.Advanced.PrivateMsgsCmdName, player, message)); + SendText(string.Format("/{0} {1} {2}", Config.Main.Advanced.PrivateMsgsCmdName, player, message)); } /// @@ -1077,7 +1074,7 @@ namespace MinecraftClient /// Get the current location of the player (Feet location) /// /// Minecraft world or null if associated setting is disabled - protected Mapping.Location GetCurrentLocation() + protected Location GetCurrentLocation() { return Handler.GetCurrentLocation(); } @@ -1093,7 +1090,7 @@ namespace MinecraftClient /// How long to wait before stopping computation (default: 5 seconds) /// When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset /// True if a path has been found - protected bool MoveToLocation(Mapping.Location location, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null) + protected bool MoveToLocation(Location location, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null) { return Handler.MoveTo(location, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout); } @@ -1111,7 +1108,7 @@ namespace MinecraftClient /// Look at the specified location /// /// Location to look at - protected void LookAtLocation(Mapping.Location location) + protected void LookAtLocation(Location location) { Handler.UpdateLocation(Handler.GetCurrentLocation(), location); } @@ -1165,13 +1162,13 @@ namespace MinecraftClient //Read all lines from file, remove lines with no text, convert to lowercase, //remove duplicate entries, convert to a string array, and return the result. return File.ReadAllLines(file, Encoding.UTF8) - .Where(line => !String.IsNullOrWhiteSpace(line)) + .Where(line => !string.IsNullOrWhiteSpace(line)) .Select(line => line.ToLower()) .Distinct().ToArray(); } else { - LogToConsole("File not found: " + System.IO.Path.GetFullPath(file)); + LogToConsole("File not found: " + Path.GetFullPath(file)); return Array.Empty(); } } @@ -1313,7 +1310,7 @@ namespace MinecraftClient /// Get server current TPS (tick per second) /// /// tps - protected Double GetServerTPS() + protected double GetServerTPS() { return Handler.GetServerTPS(); } @@ -1539,21 +1536,6 @@ namespace MinecraftClient return Handler.UpdateCommandBlock(location, command, mode, flags); } - /// - /// Register a command in command prompt. Command will be automatically unregistered when unloading ChatBot - /// - /// Name of the command - /// Description/usage of the command - /// Method for handling the command - /// True if successfully registered - protected bool RegisterChatBotCommand(string cmdName, string cmdDesc, string cmdUsage, CommandRunner callback) - { - bool result = Handler.RegisterCommand(cmdName, cmdDesc, cmdUsage, callback); - if (result) - registeredCommands.Add(cmdName.ToLower()); - return result; - } - /// /// Close a opened inventory /// @@ -1685,9 +1667,9 @@ namespace MinecraftClient public override string CmdUsage { get { return _cmdUsage; } } public override string CmdDesc { get { return _cmdDesc; } } - public override string Run(McClient handler, string command, Dictionary? localVars) + public override void RegisterCommand(CommandDispatcher dispatcher) { - return Runner(command, GetArgs(command)); + } /// diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/CompileRunner.cs b/MinecraftClient/Scripting/DynamicRun/Builder/CompileRunner.cs index 8a1e492c..ee5bf460 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/CompileRunner.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/CompileRunner.cs @@ -8,9 +8,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; -using MinecraftClient; -namespace DynamicRun.Builder +namespace MinecraftClient.Scripting.DynamicRun.Builder { internal class CompileRunner { diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs index 42760bea..63e31239 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs @@ -14,11 +14,9 @@ using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; -using MinecraftClient; -using MinecraftClient.Scripting; using SingleFileExtractor.Core; -namespace DynamicRun.Builder +namespace MinecraftClient.Scripting.DynamicRun.Builder { internal class Compiler { @@ -38,7 +36,7 @@ namespace DynamicRun.Builder Failures = failures.ToList() }; } - + peStream.Seek(0, SeekOrigin.Begin); return new CompileResult() @@ -55,20 +53,20 @@ namespace DynamicRun.Builder var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9); var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); - + var references = new List(); // Find if any additional assembly DLL exists in the base directory where the .exe exists. - foreach (var assembly in additionalAssemblies) + foreach (var assembly in additionalAssemblies) { var dllPath = Path.Combine(AppContext.BaseDirectory, assembly); - if (File.Exists(dllPath)) + if (File.Exists(dllPath)) { references.Add(MetadataReference.CreateFromFile(dllPath)); // Store the reference in our Assembly Resolver for future reference. AssemblyResolver.AddAssembly(Assembly.LoadFile(dllPath).FullName!, dllPath); } - else + else { ConsoleIO.WriteLogLine($"[Script Error] {assembly} is defined in script, but cannot find DLL! Script may not run."); } @@ -79,7 +77,7 @@ namespace DynamicRun.Builder var SystemPrivateCoreLib = typeof(object).Assembly.Location; // System.Private.CoreLib var SystemConsole = typeof(Console).Assembly.Location; // System.Console var MinecraftClientDll = typeof(Program).Assembly.Location; // The path to MinecraftClient.dll - + // We're on a self-contained binary, so we need to extract the executable to get the assemblies. if (string.IsNullOrEmpty(MinecraftClientDll)) { diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/SimpleUnloadableAssemblyLoadContext.cs b/MinecraftClient/Scripting/DynamicRun/Builder/SimpleUnloadableAssemblyLoadContext.cs index de44652c..e210bcac 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/SimpleUnloadableAssemblyLoadContext.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/SimpleUnloadableAssemblyLoadContext.cs @@ -7,7 +7,7 @@ https://github.com/laurentkempe/DynamicRun/blob/master/LICENSE using System.Reflection; using System.Runtime.Loader; -namespace DynamicRun.Builder +namespace MinecraftClient.Scripting.DynamicRun.Builder { internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext { diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 67c5cf72..8ddd556b 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -16,6 +16,7 @@ using Tomlet.Models; using static MinecraftClient.Settings.AppVarConfigHelper; using static MinecraftClient.Settings.ChatBotConfigHealper; using static MinecraftClient.Settings.ChatFormatConfigHelper; +using static MinecraftClient.Settings.ConsoleConfigHealper; using static MinecraftClient.Settings.HeadCommentHealper; using static MinecraftClient.Settings.LoggingConfigHealper; using static MinecraftClient.Settings.MainConfigHealper; @@ -31,10 +32,10 @@ namespace MinecraftClient private const int CommentsAlignPosition = 45; private readonly static Regex CommentRegex = new(@"^(.*)\s?#\s\$([\w\.]+)\$\s*$$", RegexOptions.Compiled); - //Other Settings - public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\48\482e0dae05abfa35ab5cb076e41fda77b4fb9a08"; //MC 1.19 en_GB.lang - public static string TranslationsFile_Website_Index = "https://piston-meta.mojang.com/v1/packages/b5c7548ddb9e584e84a5f762da5b78211c715a63/1.19.json"; - public static string TranslationsFile_Website_Download = "http://resources.download.minecraft.net"; + // Other Settings + public const string TranslationsFile_Version = "1.19.3"; + public const string TranslationsFile_Website_Index = "https://piston-meta.mojang.com/v1/packages/c492375ded5da34b646b8c5c0842a0028bc69cec/2.json"; + public const string TranslationsFile_Website_Download = "http://resources.download.minecraft.net"; public const string TranslationProjectUrl = "https://crwd.in/minecraft-console-client"; @@ -90,6 +91,12 @@ namespace MinecraftClient set { LoggingConfigHealper.Config = value; LoggingConfigHealper.Config.OnSettingUpdate(); } } + public ConsoleConfig Console + { + get { return ConsoleConfigHealper.Config; } + set { ConsoleConfigHealper.Config = value; ConsoleConfigHealper.Config.OnSettingUpdate(); } + } + public AppVarConfig AppVar { get { return AppVarConfigHelper.Config; } @@ -345,12 +352,12 @@ namespace MinecraftClient "hy_am", "id_id", "ig_ng", "io_en", "is_is", "isv", "it_it", "ja_jp", "jbo_en", "ka_ge", "kk_kz", "kn_in", "ko_kr", "ksh", "kw_gb", "la_la", "lb_lu", "li_li", "lmo", "lol_us", "lt_lt", "lv_lv", "lzh", "mk_mk", - "mn_mn", "ms_my", "mt_mt", "nds_de", "nl_be", "nl_nl", "nn_no", "no_no", - "oc_fr", "ovd", "pl_pl", "pt_br", "pt_pt", "qya_aa", "ro_ro", "rpr", - "ru_ru", "se_no", "sk_sk", "sl_si", "so_so", "sq_al", "sr_sp", "sv_se", - "sxu", "szl", "ta_in", "th_th", "tl_ph", "tlh_aa", "tok", "tr_tr", - "tt_ru", "uk_ua", "val_es", "vec_it", "vi_vn", "yi_de", "yo_ng", "zh_cn", - "zh_hk", "zh_tw", "zlm_arab" + "mn_mn", "ms_my", "mt_mt", "nah", "nds_de", "nl_be", "nl_nl", "nn_no", + "no_no", "oc_fr", "ovd", "pl_pl", "pt_br", "pt_pt", "qya_aa", "ro_ro", + "rpr", "ru_ru", "ry_ua", "se_no", "sk_sk", "sl_si", "so_so", "sq_al", + "sr_sp", "sv_se", "sxu", "szl", "ta_in", "th_th", "tl_ph", "tlh_aa", + "tok", "tr_tr", "tt_ru", "uk_ua", "val_es", "vec_it", "vi_vn", "yi_de", + "yo_ng", "zh_cn", "zh_hk", "zh_tw", "zlm_arab" }; /// @@ -421,7 +428,7 @@ namespace MinecraftClient if (!Advanced.LoadMccTranslation) { - CultureInfo culture = CultureInfo.CreateSpecificCulture("en_gb"); + CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US"); CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture; Program.ActualCulture = culture; @@ -491,7 +498,7 @@ namespace MinecraftClient public class AdvancedConfig { [TomlInlineComment("$Main.Advanced.language$")] - public string Language = "en_gb"; + public string Language = "en_us"; [TomlInlineComment("$Main.Advanced.LoadMccTrans$")] public bool LoadMccTranslation = true; @@ -598,9 +605,6 @@ namespace MinecraftClient [TomlInlineComment("$Main.Advanced.enable_emoji$")] public bool EnableEmoji = true; - [TomlInlineComment("$Main.Advanced.TerminalColorDepth$")] - public TerminalColorDepthType TerminalColorDepth = TerminalColorDepthType.bit_24; - [TomlInlineComment("$Main.Advanced.MinTerminalWidth$")] public int MinTerminalWidth = 16; @@ -633,8 +637,6 @@ namespace MinecraftClient public enum ResolveSrvRecordType { no, fast, yes }; public enum ForgeConfigType { no, auto, force }; - - public enum TerminalColorDepthType { bit_4, bit_8, bit_24 }; } public struct AccountInfoConfig @@ -767,6 +769,162 @@ namespace MinecraftClient } } + public static class ConsoleConfigHealper + { + public static ConsoleConfig Config = new(); + + [TomlDoNotInlineObject] + public class ConsoleConfig + { + public MainConfig General = new(); + + [TomlPrecedingComment("$Console.CommandSuggestion$")] + public CommandSuggestionConfig CommandSuggestion = new(); + + public void OnSettingUpdate() + { + // Reader + ConsoleInteractive.ConsoleReader.DisplayUesrInput = General.Display_Input; + + // Writer + ConsoleInteractive.ConsoleWriter.EnableColor = General.ConsoleColorMode != ConsoleColorModeType.disable; + + ConsoleInteractive.ConsoleWriter.UseVT100ColorCode = General.ConsoleColorMode != ConsoleColorModeType.legacy_4bit; + + // Buffer + General.History_Input_Records = + ConsoleInteractive.ConsoleBuffer.SetBackreadBufferLimit(General.History_Input_Records); + + // Suggestion + if (General.ConsoleColorMode == ConsoleColorModeType.disable) + CommandSuggestion.Enable_Color = false; + + ConsoleInteractive.ConsoleSuggestion.EnableColor = CommandSuggestion.Enable_Color; + + ConsoleInteractive.ConsoleSuggestion.Enable24bitColor = General.ConsoleColorMode == ConsoleColorModeType.vt100_24bit; + + ConsoleInteractive.ConsoleSuggestion.UseBasicArrow = CommandSuggestion.Use_Basic_Arrow; + + CommandSuggestion.Max_Suggestion_Width = + ConsoleInteractive.ConsoleSuggestion.SetMaxSuggestionLength(CommandSuggestion.Max_Suggestion_Width); + + CommandSuggestion.Max_Displayed_Suggestions = + ConsoleInteractive.ConsoleSuggestion.SetMaxSuggestionCount(CommandSuggestion.Max_Displayed_Suggestions); + + // Suggestion color settings + { + if (!CheckColorCode(CommandSuggestion.Text_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.TextColor", CommandSuggestion.Text_Color)); + CommandSuggestion.Text_Color = "#f8fafc"; + } + if (!CheckColorCode(CommandSuggestion.Text_Background_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.TextBackgroundColor", CommandSuggestion.Text_Background_Color)); + CommandSuggestion.Text_Background_Color = "#64748b"; + } + if (!CheckColorCode(CommandSuggestion.Highlight_Text_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.HighlightTextColor", CommandSuggestion.Highlight_Text_Color)); + CommandSuggestion.Highlight_Text_Color = "#334155"; + } + if (!CheckColorCode(CommandSuggestion.Highlight_Text_Background_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.HighlightTextBackgroundColor", CommandSuggestion.Highlight_Text_Background_Color)); + CommandSuggestion.Highlight_Text_Background_Color = "#fde047"; + } + if (!CheckColorCode(CommandSuggestion.Tooltip_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.TooltipColor", CommandSuggestion.Tooltip_Color)); + CommandSuggestion.Tooltip_Color = "#7dd3fc"; + } + if (!CheckColorCode(CommandSuggestion.Highlight_Tooltip_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.HighlightTooltipColor", CommandSuggestion.Highlight_Tooltip_Color)); + CommandSuggestion.Highlight_Tooltip_Color = "#3b82f6"; + } + if (!CheckColorCode(CommandSuggestion.Arrow_Symbol_Color)) + { + ConsoleIO.WriteLine(string.Format(Translations.config_commandsuggestion_illegal_color, "CommandSuggestion.ArrowSymbolColor", CommandSuggestion.Arrow_Symbol_Color)); + CommandSuggestion.Arrow_Symbol_Color = "#d1d5db"; + } + + ConsoleInteractive.ConsoleSuggestion.SetColors( + CommandSuggestion.Text_Color, CommandSuggestion.Text_Background_Color, + CommandSuggestion.Highlight_Text_Color, CommandSuggestion.Highlight_Text_Background_Color, + CommandSuggestion.Tooltip_Color, CommandSuggestion.Highlight_Tooltip_Color, + CommandSuggestion.Arrow_Symbol_Color); + } + } + + private static bool CheckColorCode(string? input) + { + if (string.IsNullOrWhiteSpace(input)) + return false; + if (input.Length < 6 || input.Length > 7) + return false; + if (input.Length == 6 && input[0] == '#') + return false; + if (input.Length == 7 && input[0] != '#') + return false; + try + { + Convert.ToInt32(input.Length == 7 ? input[1..] : input, 16); + return true; + } + catch + { + return false; + } + } + + [TomlDoNotInlineObject] + public class MainConfig + { + [TomlInlineComment("$Console.General.ConsoleColorMode$")] + public ConsoleColorModeType ConsoleColorMode = ConsoleColorModeType.vt100_24bit; + + [TomlInlineComment("$Console.General.Display_Input$")] + public bool Display_Input = true; + + [TomlInlineComment("$Console.General.History_Input_Records$")] + public int History_Input_Records = 32; + } + + [TomlDoNotInlineObject] + public class CommandSuggestionConfig + { + [TomlInlineComment("$Console.CommandSuggestion.Enable$")] + public bool Enable = true; + + public bool Enable_Color = true; + + [TomlInlineComment("$Console.CommandSuggestion.Use_Basic_Arrow$")] + public bool Use_Basic_Arrow = false; + + public int Max_Suggestion_Width = 30; + + public int Max_Displayed_Suggestions = 6; + + public string Text_Color = "#f8fafc"; + + public string Text_Background_Color = "#64748b"; + + public string Highlight_Text_Color = "#334155"; + + public string Highlight_Text_Background_Color = "#fde047"; + + public string Tooltip_Color = "#7dd3fc"; + + public string Highlight_Tooltip_Color = "#3b82f6"; + + public string Arrow_Symbol_Color = "#d1d5db"; + } + + public enum ConsoleColorModeType { disable, legacy_4bit, vt100_4bit, vt100_8bit, vt100_24bit }; + } + } + public static class AppVarConfigHelper { public static AppVarConfig Config = new(); @@ -1238,7 +1396,7 @@ namespace MinecraftClient public static string GetDefaultGameLanguage() { - string gameLanguage = "en_gb"; + string gameLanguage = "en_us"; string systemLanguage = string.IsNullOrWhiteSpace(Program.ActualCulture.Name) ? Program.ActualCulture.Parent.Name : Program.ActualCulture.Name; @@ -1326,6 +1484,9 @@ namespace MinecraftClient case "en-TT": case "en-ZA": case "en-ZW": + case "en-US": + gameLanguage = "en_us"; + break; case "en-GB": gameLanguage = "en_gb"; break; @@ -1335,9 +1496,6 @@ namespace MinecraftClient case "en-CA": gameLanguage = "en_ca"; break; - case "en-US": - gameLanguage = "en_us"; - break; case "en-NZ": gameLanguage = "en_nz"; break; diff --git a/MinecraftClient/UpgradeHelper.cs b/MinecraftClient/UpgradeHelper.cs index 77c694d4..cca5b163 100644 --- a/MinecraftClient/UpgradeHelper.cs +++ b/MinecraftClient/UpgradeHelper.cs @@ -63,7 +63,7 @@ namespace MinecraftClient { string OSInfo = GetOSIdentifier(); if (Settings.Config.Logging.DebugMessages || string.IsNullOrEmpty(OSInfo)) - ConsoleIO.WriteLine(string.Format("OS: {0}, Arch: {1}, Framework: {2}", + ConsoleIO.WriteLine(string.Format("OS: {0}, Arch: {1}, Framework: {2}", RuntimeInformation.OSDescription, RuntimeInformation.ProcessArchitecture, RuntimeInformation.FrameworkDescription)); if (string.IsNullOrEmpty(OSInfo)) { @@ -308,7 +308,7 @@ namespace MinecraftClient { string proxyAddress; if (!string.IsNullOrWhiteSpace(Settings.Config.Proxy.Username) && !string.IsNullOrWhiteSpace(Settings.Config.Proxy.Password)) - proxyAddress = string.Format("{0}://{3}:{4}@{1}:{2}", + proxyAddress = string.Format("{0}://{3}:{4}@{1}:{2}", Settings.Config.Proxy.Proxy_Type.ToString().ToLower(), Settings.Config.Proxy.Server.Host, Settings.Config.Proxy.Server.Port, diff --git a/MinecraftClient/config/ChatBots/AutoTree.cs b/MinecraftClient/config/ChatBots/AutoTree.cs index 9b4aa849..5488d8ad 100644 --- a/MinecraftClient/config/ChatBots/AutoTree.cs +++ b/MinecraftClient/config/ChatBots/AutoTree.cs @@ -1,4 +1,16 @@ //MCCScript 1.0 +//using Brigadier.NET; +//using Brigadier.NET.Builder; +//using MinecraftClient; +//using MinecraftClient.CommandHandler; +//using MinecraftClient.CommandHandler.Patch; +//using MinecraftClient.Inventory; +//using MinecraftClient.Mapping; +//using MinecraftClient.Scripting; +//using System.Collections.Generic; +//using System.Linq; +//using static MinecraftClient.ChatBots.AutoCraft.Configs; +//using System.Text; MCC.LoadBot(new AutoTree()); @@ -6,6 +18,8 @@ MCC.LoadBot(new AutoTree()); public class AutoTree : ChatBot { + public const string CommandName = "autotree"; + // Auto sapling placer - made for auto tree machine // Put your bot in designed position for placing sapling // Set the tree type by "/autotree type " @@ -60,7 +74,8 @@ public class AutoTree : ChatBot } } } - public override void Initialize() + + public override void Initialize(CommandDispatcher dispatcher) { if (!GetTerrainEnabled()) { @@ -74,11 +89,71 @@ public class AutoTree : ChatBot } else { - RegisterChatBotCommand("autotree", "AutoTree ChatBot command", "Available commands: toggle, set, type", CommandHandler); + dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Then(l => l.Literal("set") + .Executes(r => OnCommandHelp(r.Source, "set"))) + .Then(l => l.Literal("type") + .Executes(r => OnCommandHelp(r.Source, "type"))) + ) + ); + + 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") + .Then(l => l.Argument("Location", MccArguments.Location()) + .Executes(r => OnCommandSet(r.Source, MccArguments.GetLocation(r, "Location"))))) + .Then(l => l.Literal("type") + .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))) + ); + LogToConsole("Loaded."); } } + public override void OnUnload(CommandDispatcher dispatcher) + { + dispatcher.Unregister(CommandName); + dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + "set" => "Set the location for placing sapling. Usage: set ", + "type" => "Set the tree type. Usage: type ", + _ => "Available commands: toggle, set, type" + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandSet(CmdResult r, Location location) + { + SetLocation(location.ToAbsolute(GetCurrentLocation())); + return r.SetAndReturn(CmdResult.Status.Done, "Location set to " + location.ToString()); + } + + private int OnCommandType(CmdResult r, string treeType) + { + for (int i = 0; i < saplingItems.Length; i++) + { + if (saplingItems[i].ToString().ToLower().StartsWith(treeType)) + { + treeTypeIndex = i; + break; + } + } + return r.SetAndReturn(CmdResult.Status.Done, "Tree sapling type set to " + saplingItems[treeTypeIndex].ToString()); + } + public bool SetTreeType(int index) { if (index >= 0 && index < saplingItems.Length) @@ -103,7 +178,7 @@ public class AutoTree : ChatBot public bool SwitchToSapling() { Container p = GetPlayerInventory(); - if (p.Items.ContainsKey(GetCurrentSlot() - 36) + if (p.Items.ContainsKey(GetCurrentSlot() - 36) && p.Items[GetCurrentSlot() - 36].Type == saplingItems[treeTypeIndex]) { // Already selected @@ -123,58 +198,4 @@ public class AutoTree : ChatBot return true; } } - - public string CommandHandler(string cmd, string[] args) - { - if (args.Length <= 0) - { - return "Available commands: toggle, set, type"; - } - string subCommand = args[0].ToLower(); - switch (subCommand) - { - case "toggle": - { - return Toggle() ? "Now is running" : "Now is stopping"; - } - case "set": - { - if (args.Length < 4) - { - return "Set the location for placing sapling. Usage: set "; - } - try - { - int x = int.Parse(args[1]); - int y = int.Parse(args[2]); - int z = int.Parse(args[3]); - var l = new Location(x, y, z); - SetLocation(l); - return "Location set to " + l.ToString(); - } - catch - { - return "Please input numbers. Usage: set "; - } - } - case "type": - { - if (args.Length < 2) - { - return "Set the tree type. Usage: type "; - } - string typeString = args[1].ToLower(); - for (int i = 0; i < saplingItems.Length; i++) - { - if (saplingItems[i].ToString().ToLower().StartsWith(typeString)) - { - treeTypeIndex = i; - break; - } - } - return "Tree sapling type set to " + saplingItems[treeTypeIndex].ToString(); - } - default: return "Available commands: toggle, set, type"; - } - } } diff --git a/MinecraftClient/config/ChatBots/DiscordWebhook.cs b/MinecraftClient/config/ChatBots/DiscordWebhook.cs index 778dbeed..c3d7f9c2 100644 --- a/MinecraftClient/config/ChatBots/DiscordWebhook.cs +++ b/MinecraftClient/config/ChatBots/DiscordWebhook.cs @@ -1,6 +1,17 @@ //MCCScript 1.0 //using System.Collections.Specialized; //using MinecraftClient.Protocol; +//using MinecraftClient.Scripting; +//using MinecraftClient; +//using static MinecraftClient.Scripting.ChatBot; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System; +//using Brigadier.NET.Builder; +//using MinecraftClient.CommandHandler.Patch; +//using MinecraftClient.CommandHandler; +//using Brigadier.NET; MCC.LoadBot(new DiscordWebhook()); @@ -278,6 +289,8 @@ class MessageCache class DiscordWebhook : ChatBot { + public const string CommandName = "discordWebhook"; + private WebhoookSettings settings = new WebhoookSettings(); private SkinAPI sAPI; private MessageCache cache; @@ -292,8 +305,19 @@ 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'."); - RegisterChatBotCommand("discordWebhook", "/DiscordWebhook 'size', 'scale', 'fallbackSkin', 'overlay', 'skintype'", GetHelp(), CommandHandler); - RegisterChatBotCommand("dw", "/DiscordWebhook 'size', 'scale', 'fallbackSkin', 'overlay', 'skintype'", GetHelp(), CommandHandler); + + Handler.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Argument("Commands", Arguments.GreedyString()) + .Executes(r => { + CommandHandler(Arguments.GetString(r, "Commands").Split(' ', StringSplitOptions.TrimEntries)); + return r.Source.SetAndReturn(CmdResult.Status.Done); + })) + ); + } + + public override void OnUnload() + { + Handler.dispatcher.Unregister(CommandName); } public override void Update() @@ -454,7 +478,7 @@ class DiscordWebhook : ChatBot { StringBuilder requestData = new StringBuilder(); - requestData.Append("{"); + requestData.Append('{'); requestData.Append("\"username\": \"" + msg.SenderName + "\", "); requestData.Append("\"content\": \"" + Sanizize(msg.Content) + "\", "); requestData.Append("\"avatar_url\": \"" + (msg.SenderName == "[Server]" ? sAPI.GetSkinURLCrafatar("f78a4d8dd51b4b3998a3230f2de0c670") : sAPI.GetSkinURLCrafatar(msg.SenderUUID)) + "\""); @@ -530,10 +554,9 @@ class DiscordWebhook : ChatBot /// /// Handles all commands. /// - /// Whole command /// Only arguments /// - public string CommandHandler(string cmd, string[] args) + public string CommandHandler(string[] args) { if (args.Length > 0) { diff --git a/MinecraftClient/config/ChatBots/MineCube.cs b/MinecraftClient/config/ChatBots/MineCube.cs index b8a04736..71d760a6 100644 --- a/MinecraftClient/config/ChatBots/MineCube.cs +++ b/MinecraftClient/config/ChatBots/MineCube.cs @@ -1,5 +1,19 @@ //MCCScript 1.0 +//using MinecraftClient.Inventory; +//using MinecraftClient.Mapping; +//using MinecraftClient.Scripting; +//using MinecraftClient; +//using System.Collections.Generic; +//using System.Linq; +//using System.Threading.Tasks; +//using System.Threading; +//using System; +//using Brigadier.NET.Builder; +//using MinecraftClient.CommandHandler.Patch; +//using Brigadier.NET; +//using MinecraftClient.CommandHandler; + MCC.LoadBot(new MineCube()); //MCCScript Extensions @@ -8,486 +22,499 @@ MCC.LoadBot(new MineCube()); class MineCube : ChatBot { - private CancellationTokenSource cts; - private Task currentMiningTask; - private TimeSpan breakTimeout; - private bool toolHandling; - private int cacheSize; + public const string CommandName = "mineup"; - public override void Initialize() - { - if (!GetTerrainEnabled()) - { - LogToConsole(Translations.extra_terrainandmovement_required); - UnloadBot(); - return; - } + private CancellationTokenSource cts; + private Task currentMiningTask; + private TimeSpan breakTimeout; + private bool toolHandling; + private int cacheSize; - currentMiningTask = null; - breakTimeout = TimeSpan.FromSeconds(15); - cacheSize = 10; - toolHandling = true; + public override void Initialize() + { + if (!GetTerrainEnabled()) + { + LogToConsole(Translations.extra_terrainandmovement_required); + UnloadBot(); + return; + } - RegisterChatBotCommand("mine", "Mine a cube from a to b", "/mine x y z OR /mine x1 y1 z1 x2 y2 z2", EvaluateMineCommand); - RegisterChatBotCommand("mineup", "Walk over a flat cubic platform of blocks and mine everything above you", "/mine x1 y1 z1 x2 y2 z2 (y1 = y2)", EvaluateMineCommand); - LogToConsole("Mining bot created by Daenges."); - } + currentMiningTask = null; + breakTimeout = TimeSpan.FromSeconds(15); + cacheSize = 10; + toolHandling = true; - /// - /// Walks in a 2 high area under an area of blocks and mines anything above its head. - /// - /// The current world - /// The start corner of walking - /// The stop corner of walking - /// CancellationToken to stop the task on cancel - public void MineUp(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct) - { - if (startBlock.Y != stopBlock.Y) - { - LogToConsole("Command FAILED. Both coordinates must be on the same y level."); - } + LogToConsole("Mining bot created by Daenges."); - IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X))); - IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z))); + Handler.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)); + return r.Source.SetAndReturn(CmdResult.Status.Done); + })) + ); + } - foreach (int currentXLoc in xLocationRange) - { - foreach (int currentZLoc in zLocationRange) - { - Location standLocation = new Location(currentXLoc, startBlock.Y, currentZLoc); + public override void OnUnload() + { + Handler.dispatcher.Unregister(CommandName); + } - // Walk to the new location. - waitForMoveToLocation(standLocation, maxOffset: 1); + /// + /// Walks in a 2 high area under an area of blocks and mines anything above its head. + /// + /// The current world + /// The start corner of walking + /// The stop corner of walking + /// CancellationToken to stop the task on cancel + public void MineUp(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct) + { + if (startBlock.Y != stopBlock.Y) + { + LogToConsole("Command FAILED. Both coordinates must be on the same y level."); + } - for (int height = Convert.ToInt32(startBlock.Y) + 2; height < Convert.ToInt32(startBlock.Y) + 7; height++) - { - if (ct.IsCancellationRequested) - { - currentMiningTask = null; - LogToConsole("Cancellation requested. STOP MINING."); - return; - } + IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X))); + IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z))); - Location mineLocation = new Location(currentXLoc, height, currentZLoc); - Material mineLocationMaterial = currentWorld.GetBlock(mineLocation).Type; + foreach (int currentXLoc in xLocationRange) + { + foreach (int currentZLoc in zLocationRange) + { + Location standLocation = new Location(currentXLoc, startBlock.Y, currentZLoc); - // Stop mining process if breaking the next block could endager the bot - // through falling blocks or liquids. - if (!IsGravitySave(currentWorld, mineLocation) || IsSorroundedByLiquid(currentWorld, mineLocation)) { break; } - // Skip this block if it can not be mined. - if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; } + // Walk to the new location. + waitForMoveToLocation(standLocation, maxOffset: 1); - if (GetInventoryEnabled() && toolHandling) - { - // Search this tool in hotbar and select the correct slot - SelectCorrectSlotInHotbar( - // Returns the correct tool for this type - Material2Tool.GetCorrectToolForBlock( - // returns the type of the current block - mineLocationMaterial)); - } + for (int height = Convert.ToInt32(startBlock.Y) + 2; height < Convert.ToInt32(startBlock.Y) + 7; height++) + { + if (ct.IsCancellationRequested) + { + currentMiningTask = null; + LogToConsole("Cancellation requested. STOP MINING."); + return; + } - // If we are able to reach the block && break sucessfully sent - if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation)) - { - AutoTimeout.Perform(() => - { - while (GetWorld().GetBlock(mineLocation).Type != Material.Air) - { - Thread.Sleep(100); + Location mineLocation = new Location(currentXLoc, height, currentZLoc); + Material mineLocationMaterial = currentWorld.GetBlock(mineLocation).Type; - if (ct.IsCancellationRequested) - break; - } - }, breakTimeout); - } - else - { - LogDebugToConsole("Unable to break this block: " + mineLocation.ToString()); - } - } - } - } - LogToConsole("Finished mining up."); - } + // Stop mining process if breaking the next block could endager the bot + // through falling blocks or liquids. + if (!IsGravitySave(currentWorld, mineLocation) || IsSorroundedByLiquid(currentWorld, mineLocation)) { break; } + // Skip this block if it can not be mined. + if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; } - /// - /// Mine a cube of blocks from top to bottom between start and stop location - /// - /// The current world - /// The upper corner of the cube to mine - /// The lower corner of the cube to mine - /// CancellationToken to stop the task on cancel - public void Mine(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct) - { - // Turn the cube around, so the bot always starts from the top. - if (stopBlock.Y > startBlock.Y) - { - Location temp = stopBlock; - stopBlock = startBlock; - startBlock = temp; - } + if (GetInventoryEnabled() && toolHandling) + { + // Search this tool in hotbar and select the correct slot + SelectCorrectSlotInHotbar( + // Returns the correct tool for this type + Material2Tool.GetCorrectToolForBlock( + // returns the type of the current block + mineLocationMaterial)); + } - IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X))); - IEnumerable yLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Y)), Convert.ToInt32(Math.Round(stopBlock.Y))); - IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z))); + // If we are able to reach the block && break sucessfully sent + if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation)) + { + AutoTimeout.Perform(() => + { + while (GetWorld().GetBlock(mineLocation).Type != Material.Air) + { + Thread.Sleep(100); - foreach (int currentYLoc in yLocationRange) - { - foreach (int currentXLoc in xLocationRange) - { + if (ct.IsCancellationRequested) + break; + } + }, breakTimeout); + } + else + { + LogDebugToConsole("Unable to break this block: " + mineLocation.ToString()); + } + } + } + } + LogToConsole("Finished mining up."); + } - if (ct.IsCancellationRequested) - { - currentMiningTask = null; - LogToConsole("Cancellation requested. STOP MINING."); - return; - } + /// + /// Mine a cube of blocks from top to bottom between start and stop location + /// + /// The current world + /// The upper corner of the cube to mine + /// The lower corner of the cube to mine + /// CancellationToken to stop the task on cancel + public void Mine(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct) + { + // Turn the cube around, so the bot always starts from the top. + if (stopBlock.Y > startBlock.Y) + { + Location temp = stopBlock; + stopBlock = startBlock; + startBlock = temp; + } - List blocksToMine = null; + IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X))); + IEnumerable yLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Y)), Convert.ToInt32(Math.Round(stopBlock.Y))); + IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z))); - // If the end of the new row is closer than the start, reverse the line and start here - Location currentStandingLoc = GetCurrentLocation(); - Queue currentZLocationRangeQueue = new Queue(currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.Last())) < currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.First())) ? - zLocationRange.Reverse() : - zLocationRange); + foreach (int currentYLoc in yLocationRange) + { + foreach (int currentXLoc in xLocationRange) + { - while (!ct.IsCancellationRequested && (currentZLocationRangeQueue.Count > 0 || blocksToMine.Count > 0)) - { - // Evaluate the next blocks to mine, while mining - Task> cacheEval = Task>.Factory.StartNew(() => // Get a new chunk of blocks that can be mined - EvaluateBlocks(currentWorld, currentXLoc, currentYLoc, currentZLocationRangeQueue, ct, cacheSize)); + if (ct.IsCancellationRequested) + { + currentMiningTask = null; + LogToConsole("Cancellation requested. STOP MINING."); + return; + } - // On the first run, we need the task to finish, otherwise we would not have any results - if (blocksToMine != null) - { - // For all blocks in this block chunk - foreach (Location mineLocation in blocksToMine) - { - if (ct.IsCancellationRequested) - break; + List blocksToMine = null; - Location currentLoc = GetCurrentLocation(); - Location currentBlockUnderFeet = new Location(Math.Floor(currentLoc.X), Math.Floor(currentLoc.Y) - 1, Math.Floor(currentLoc.Z)); + // If the end of the new row is closer than the start, reverse the line and start here + Location currentStandingLoc = GetCurrentLocation(); + Queue currentZLocationRangeQueue = new Queue(currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.Last())) < currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.First())) ? + zLocationRange.Reverse() : + zLocationRange); - // If we are too far away from the mining location - if (currentLoc.EyesLocation().DistanceSquared(mineLocation) > 25) - { - // Walk to the new location - waitForMoveToLocation(mineLocation, maxOffset:3); - } + while (!ct.IsCancellationRequested && (currentZLocationRangeQueue.Count > 0 || blocksToMine.Count > 0)) + { + // Evaluate the next blocks to mine, while mining + Task> cacheEval = Task>.Factory.StartNew(() => // Get a new chunk of blocks that can be mined + EvaluateBlocks(currentWorld, currentXLoc, currentYLoc, currentZLocationRangeQueue, ct, cacheSize)); - // Prevent falling into danger - if (mineLocation == currentBlockUnderFeet && !Movement.IsSafe(currentWorld, currentBlockUnderFeet)) - waitForMoveToLocation(mineLocation, maxOffset: 4, minOffset:3); + // On the first run, we need the task to finish, otherwise we would not have any results + if (blocksToMine != null) + { + // For all blocks in this block chunk + foreach (Location mineLocation in blocksToMine) + { + if (ct.IsCancellationRequested) + break; - // Is inventoryhandling activated? - if (GetInventoryEnabled() && toolHandling) - { - // Search this tool in hotbar and select the correct slot - SelectCorrectSlotInHotbar( - // Returns the correct tool for this type - Material2Tool.GetCorrectToolForBlock( - // returns the type of the current block - currentWorld.GetBlock(mineLocation).Type)); - } + Location currentLoc = GetCurrentLocation(); + Location currentBlockUnderFeet = new Location(Math.Floor(currentLoc.X), Math.Floor(currentLoc.Y) - 1, Math.Floor(currentLoc.Z)); - // If we are able to reach the block && break sucessfully sent - if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation)) - { - // Wait until the block is broken (== Air) - AutoTimeout.Perform(() => - { - while (GetWorld().GetBlock(mineLocation).Type != Material.Air) - { - Thread.Sleep(100); + // If we are too far away from the mining location + if (currentLoc.EyesLocation().DistanceSquared(mineLocation) > 25) + { + // Walk to the new location + waitForMoveToLocation(mineLocation, maxOffset: 3); + } - if (ct.IsCancellationRequested) - break; - } - }, breakTimeout); - } - else - { - LogDebugToConsole("Unable to break this block: " + mineLocation.ToString()); - } + // Prevent falling into danger + if (mineLocation == currentBlockUnderFeet && !Movement.IsSafe(currentWorld, currentBlockUnderFeet)) + waitForMoveToLocation(mineLocation, maxOffset: 4, minOffset: 3); - } - } + // Is inventoryhandling activated? + if (GetInventoryEnabled() && toolHandling) + { + // Search this tool in hotbar and select the correct slot + SelectCorrectSlotInHotbar( + // Returns the correct tool for this type + Material2Tool.GetCorrectToolForBlock( + // returns the type of the current block + currentWorld.GetBlock(mineLocation).Type)); + } - if (!ct.IsCancellationRequested) - { - // Wait for the block evaluation task to finish (if not already) and save the result - if (!cacheEval.IsCompleted) - { - cacheEval.Wait(); - } - blocksToMine = cacheEval.Result; - } - } - } - } - currentMiningTask = null; - LogToConsole("MINING FINISHED."); - } + // If we are able to reach the block && break sucessfully sent + if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation)) + { + // Wait until the block is broken (== Air) + AutoTimeout.Perform(() => + { + while (GetWorld().GetBlock(mineLocation).Type != Material.Air) + { + Thread.Sleep(100); - /// - /// This function selects a certain amount of minable blocks in a row - /// - /// The current world - /// The current x location of the row - /// The current y location of the row - /// All Z blocks that will be mined - /// CancellationToken to stop the task on cancel - /// Maximum amount of blocks to return - /// - private List EvaluateBlocks(World currentWorld, int xLoc, int yLoc, Queue zLocationQueue, CancellationToken ct, int cacheSize = 10) - { - List blockMiningCache = new List(); - int i = 0; - while (zLocationQueue.Count > 0 && i < cacheSize && !ct.IsCancellationRequested) - { - // Get the block to mine, relative to the startblock of the row - Location mineLocation = new Location(xLoc, yLoc, zLocationQueue.Dequeue()); + if (ct.IsCancellationRequested) + break; + } + }, breakTimeout); + } + else + { + LogDebugToConsole("Unable to break this block: " + mineLocation.ToString()); + } - // Add the current location to the mining cache if it is safe to mine - if (currentWorld.GetBlock(mineLocation).Type != Material.Air && - IsGravitySave(currentWorld, mineLocation) && - !IsSorroundedByLiquid(currentWorld, mineLocation) && - !Material2Tool.IsUnbreakable(currentWorld.GetBlock(mineLocation).Type)) - { - blockMiningCache.Add(mineLocation); - i++; - } - } - return blockMiningCache; - } + } + } - /// - /// Generates a sequence of numbers between a start and a stop number, including both - /// - /// Number to start from - /// Number to end with - /// a sequence of numbers between a start and a stop number, including both - private static IEnumerable GetNumbersFromTo(int start, int stop) - { - return start <= stop ? Enumerable.Range(start, stop - start + 1) : Enumerable.Range(stop, start - stop + 1).Reverse(); - } + if (!ct.IsCancellationRequested) + { + // Wait for the block evaluation task to finish (if not already) and save the result + if (!cacheEval.IsCompleted) + { + cacheEval.Wait(); + } + blocksToMine = cacheEval.Result; + } + } + } + } + currentMiningTask = null; + LogToConsole("MINING FINISHED."); + } - /// - /// Starts walk and waits until the client arrives - /// - /// Location to reach - /// Allow possible but unsafe locations thay may hurt the player: lava, cactus... - /// Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins - /// If no valid path can be found, also allow locations within specified distance of destination - /// Do not get closer of destination than specified distance - /// How long to wait before stopping computation (default: 5 seconds) - private void waitForMoveToLocation(Location goal, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null) - { - if (MoveToLocation(goal, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout)) - { - // Wait till the client stops moving - while (ClientIsMoving()) - { - Thread.Sleep(200); - } - } - else - { - LogDebugToConsole("Unable to walk to: " + goal.ToString()); - } - } + /// + /// This function selects a certain amount of minable blocks in a row + /// + /// The current world + /// The current x location of the row + /// The current y location of the row + /// All Z blocks that will be mined + /// CancellationToken to stop the task on cancel + /// Maximum amount of blocks to return + /// + private List EvaluateBlocks(World currentWorld, int xLoc, int yLoc, Queue zLocationQueue, CancellationToken ct, int cacheSize = 10) + { + List blockMiningCache = new List(); + int i = 0; + while (zLocationQueue.Count > 0 && i < cacheSize && !ct.IsCancellationRequested) + { + // Get the block to mine, relative to the startblock of the row + Location mineLocation = new Location(xLoc, yLoc, zLocationQueue.Dequeue()); - /// - /// Checks all slots of the hotbar for an Item and selects it if found - /// - /// List of items that may be selected, from worst to best - private void SelectCorrectSlotInHotbar(ItemType[] tools) - { - if (GetInventoryEnabled()) - { - foreach (ItemType tool in tools) - { - int[] tempArray = GetPlayerInventory().SearchItem(tool); - // Check whether an item could be found and make sure that it is in - // a hotbar slot (36-44). - if (tempArray.Length > 0 && tempArray[0] > 35) - { - // Changeslot takes numbers from 0-8 - ChangeSlot(Convert.ToInt16(tempArray[0] - 36)); - break; - } - } - } - else - { - LogToConsole("Activate Inventory Handling."); - } - } + // Add the current location to the mining cache if it is safe to mine + if (currentWorld.GetBlock(mineLocation).Type != Material.Air && + IsGravitySave(currentWorld, mineLocation) && + !IsSorroundedByLiquid(currentWorld, mineLocation) && + !Material2Tool.IsUnbreakable(currentWorld.GetBlock(mineLocation).Type)) + { + blockMiningCache.Add(mineLocation); + i++; + } + } + return blockMiningCache; + } - /// - /// Check if mining the current block would update others - /// - /// Current World - /// The block to be checked - /// true if mining the current block would not update others - public bool IsGravitySave(World currentWorld, Location blockToMine) - { - Location currentLoc = GetCurrentLocation(); - Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z)); - List gravityBlockList = new List(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, }); - Func isGravityBlock = (Location blockToCheck) => gravityBlockList.Contains(currentWorld.GetBlock(blockToCheck).Type); - Func isBlockSolid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsSolid(); + /// + /// Generates a sequence of numbers between a start and a stop number, including both + /// + /// Number to start from + /// Number to end with + /// a sequence of numbers between a start and a stop number, including both + private static IEnumerable GetNumbersFromTo(int start, int stop) + { + return start <= stop ? Enumerable.Range(start, stop - start + 1) : Enumerable.Range(stop, start - stop + 1).Reverse(); + } - return - // Block can not fall down on player e.g. Sand, Gravel etc. - !isGravityBlock(Movement.Move(block, Direction.Up)) && - (Movement.Move(currentLoc, Direction.Down) != blockToMine || currentWorld.GetBlock(Movement.Move(currentLoc, Direction.Down, 2)).Type.IsSolid()) && - // Prevent updating flying sand/gravel under player - !isGravityBlock(Movement.Move(block, Direction.Down)) || isBlockSolid(Movement.Move(block, Direction.Down, 2)); - } + /// + /// Starts walk and waits until the client arrives + /// + /// Location to reach + /// Allow possible but unsafe locations thay may hurt the player: lava, cactus... + /// Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins + /// If no valid path can be found, also allow locations within specified distance of destination + /// Do not get closer of destination than specified distance + /// How long to wait before stopping computation (default: 5 seconds) + private void waitForMoveToLocation(Location goal, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null) + { + if (MoveToLocation(goal, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout)) + { + // Wait till the client stops moving + while (ClientIsMoving()) + { + Thread.Sleep(200); + } + } + else + { + LogDebugToConsole("Unable to walk to: " + goal.ToString()); + } + } - /// - /// Checks if the current block is sorrounded by liquids - /// - /// Current World - /// The block to be checked - /// true if mining the current block results in liquid flow change - public bool IsSorroundedByLiquid(World currentWorld, Location blockToMine) - { - Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z)); - Func isLiquid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsLiquid(); + /// + /// Checks all slots of the hotbar for an Item and selects it if found + /// + /// List of items that may be selected, from worst to best + private void SelectCorrectSlotInHotbar(ItemType[] tools) + { + if (GetInventoryEnabled()) + { + foreach (ItemType tool in tools) + { + int[] tempArray = GetPlayerInventory().SearchItem(tool); + // Check whether an item could be found and make sure that it is in + // a hotbar slot (36-44). + if (tempArray.Length > 0 && tempArray[0] > 35) + { + // Changeslot takes numbers from 0-8 + ChangeSlot(Convert.ToInt16(tempArray[0] - 36)); + break; + } + } + } + else + { + LogToConsole("Activate Inventory Handling."); + } + } - return // Liquid can not flow down the hole. Liquid is unable to flow diagonally. - isLiquid(block) || - isLiquid(Movement.Move(block, Direction.Up)) || - isLiquid(Movement.Move(block, Direction.North)) || - isLiquid(Movement.Move(block, Direction.South)) || - isLiquid(Movement.Move(block, Direction.East)) || - isLiquid(Movement.Move(block, Direction.West)); - } + /// + /// Check if mining the current block would update others + /// + /// Current World + /// The block to be checked + /// true if mining the current block would not update others + public bool IsGravitySave(World currentWorld, Location blockToMine) + { + Location currentLoc = GetCurrentLocation(); + Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z)); + List gravityBlockList = new List(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, }); + Func isGravityBlock = (Location blockToCheck) => gravityBlockList.Contains(currentWorld.GetBlock(blockToCheck).Type); + Func isBlockSolid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsSolid(); - /// - /// The Help page for this command. - /// - /// a help page - private string getHelpPage() - { - return - "Usage of the mine bot:\n" + - "/mine OR /mine \n" + - "to excavate a cube of blocks from top to bottom. (There must be a 2 high area of air above the cube you want to mine.)\n" + - "/mineup OR /mineup \n" + - "to walk over a quadratic field of blocks and simultaniously mine everything above the head. \n" + - "(Mines up to 5 Blocks, stops if gravel or lava would fall. There must be a 2 high area of air below the cube you want to mine.)\n" + - "/mine OR /mineup cancel\n" + - "to cancel the current mining process.\n" + - "/mine OR /mineup cachesize\n" + - "to set the current cache size\n" + - "/mine OR /mineup breaktimeout\n" + - "to set the time to wait until a block is broken."; ; + return + // Block can not fall down on player e.g. Sand, Gravel etc. + !isGravityBlock(Movement.Move(block, Direction.Up)) && + (Movement.Move(currentLoc, Direction.Down) != blockToMine || currentWorld.GetBlock(Movement.Move(currentLoc, Direction.Down, 2)).Type.IsSolid()) && + // Prevent updating flying sand/gravel under player + !isGravityBlock(Movement.Move(block, Direction.Down)) || isBlockSolid(Movement.Move(block, Direction.Down, 2)); + } - } + /// + /// Checks if the current block is sorrounded by liquids + /// + /// Current World + /// The block to be checked + /// true if mining the current block results in liquid flow change + public bool IsSorroundedByLiquid(World currentWorld, Location blockToMine) + { + Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z)); + Func isLiquid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsLiquid(); - private string EvaluateMineCommand(string command, string[] args) - { - for (int i = 0; i < args.Length; i++) - { - switch (args[i]) - { - case "breaktimeout": - int temp; - if (int.TryParse(args[i + 1], out temp)) - breakTimeout = TimeSpan.FromMilliseconds(temp); - else return "Please enter a valid number."; - return string.Format("Set the break timout to {0} ms.", breakTimeout); + return // Liquid can not flow down the hole. Liquid is unable to flow diagonally. + isLiquid(block) || + isLiquid(Movement.Move(block, Direction.Up)) || + isLiquid(Movement.Move(block, Direction.North)) || + isLiquid(Movement.Move(block, Direction.South)) || + isLiquid(Movement.Move(block, Direction.East)) || + isLiquid(Movement.Move(block, Direction.West)); + } - case "cachesize": - return int.TryParse(args[i + 1], out cacheSize) ? string.Format("Set cache size to {0} blocks.", cacheSize) : "Please enter a valid number"; + /// + /// The Help page for this command. + /// + /// a help page + private string getHelpPage() + { + return + "Usage of the mine bot:\n" + + "/mine OR /mine \n" + + "to excavate a cube of blocks from top to bottom. (There must be a 2 high area of air above the cube you want to mine.)\n" + + "/mineup OR /mineup \n" + + "to walk over a quadratic field of blocks and simultaniously mine everything above the head. \n" + + "(Mines up to 5 Blocks, stops if gravel or lava would fall. There must be a 2 high area of air below the cube you want to mine.)\n" + + "/mine OR /mineup cancel\n" + + "to cancel the current mining process.\n" + + "/mine OR /mineup cachesize\n" + + "to set the current cache size\n" + + "/mine OR /mineup breaktimeout\n" + + "to set the time to wait until a block is broken."; ; - case "cancel": - cts.Cancel(); - currentMiningTask = null; - return "Cancelled current mining process."; + } - case "toolHandling": - toolHandling = !toolHandling; - return string.Format("Tool handling was set to: {0}", toolHandling.ToString()); - } - } + private string EvaluateMineCommand(string command, string[] args) + { + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "breaktimeout": + int temp; + if (int.TryParse(args[i + 1], out temp)) + breakTimeout = TimeSpan.FromMilliseconds(temp); + else return "Please enter a valid number."; + return string.Format("Set the break timout to {0} ms.", breakTimeout); - if (args.Length > 2) - { - Location startBlock; - Location stopBlock; + case "cachesize": + return int.TryParse(args[i + 1], out cacheSize) ? string.Format("Set cache size to {0} blocks.", cacheSize) : "Please enter a valid number"; - if (args.Length > 5) - { - try - { - startBlock = new Location( - double.Parse(args[0]), - double.Parse(args[1]), - double.Parse(args[2]) - ); + case "cancel": + cts.Cancel(); + currentMiningTask = null; + return "Cancelled current mining process."; - stopBlock = new Location( - double.Parse(args[3]), - double.Parse(args[4]), - double.Parse(args[5]) - ); + case "toolHandling": + toolHandling = !toolHandling; + return string.Format("Tool handling was set to: {0}", toolHandling.ToString()); + } + } - } - catch (Exception e) - { - LogDebugToConsole(e.ToString()); - return "Please enter correct coordinates as numbers.\n" + getHelpPage(); - } - } - else - { - Location tempLoc = GetCurrentLocation(); - startBlock = new Location(Math.Round(tempLoc.X), - Math.Round(tempLoc.Y), - Math.Round(tempLoc.Z)); + if (args.Length > 2) + { + Location startBlock; + Location stopBlock; - try - { - stopBlock = new Location( - double.Parse(args[0]), - double.Parse(args[1]), - double.Parse(args[2]) - ); - } - catch (Exception e) - { - LogDebugToConsole(e.ToString()); - return "Please enter correct coordinates as numbers.\n" + getHelpPage(); - } - } + if (args.Length > 5) + { + try + { + startBlock = new Location( + double.Parse(args[0]), + double.Parse(args[1]), + double.Parse(args[2]) + ); - if (currentMiningTask == null) - { - if (command.Contains("mineup")) - { - cts = new CancellationTokenSource(); + stopBlock = new Location( + double.Parse(args[3]), + double.Parse(args[4]), + double.Parse(args[5]) + ); - currentMiningTask = Task.Factory.StartNew(() => MineUp(GetWorld(), startBlock, stopBlock, cts.Token)); - return "Start mining up."; - } - else if (command.Contains("mine")) - { + } + catch (Exception e) + { + LogDebugToConsole(e.ToString()); + return "Please enter correct coordinates as numbers.\n" + getHelpPage(); + } + } + else + { + Location tempLoc = GetCurrentLocation(); + startBlock = new Location(Math.Round(tempLoc.X), + Math.Round(tempLoc.Y), + Math.Round(tempLoc.Z)); - cts = new CancellationTokenSource(); + try + { + stopBlock = new Location( + double.Parse(args[0]), + double.Parse(args[1]), + double.Parse(args[2]) + ); + } + catch (Exception e) + { + LogDebugToConsole(e.ToString()); + return "Please enter correct coordinates as numbers.\n" + getHelpPage(); + } + } - currentMiningTask = Task.Factory.StartNew(() => Mine(GetWorld(), startBlock, stopBlock, cts.Token)); - return "Start mining cube."; - } - } - else return "You are already mining. Cancel it with '/minecancel'"; - } + if (currentMiningTask == null) + { + if (command.Contains("mineup")) + { + cts = new CancellationTokenSource(); - return "Invalid command syntax.\n" + getHelpPage(); - } + currentMiningTask = Task.Factory.StartNew(() => MineUp(GetWorld(), startBlock, stopBlock, cts.Token)); + return "Start mining up."; + } + else if (command.Contains("mine")) + { + + cts = new CancellationTokenSource(); + + currentMiningTask = Task.Factory.StartNew(() => Mine(GetWorld(), startBlock, stopBlock, cts.Token)); + return "Start mining cube."; + } + } + else return "You are already mining. Cancel it with '/minecancel'"; + } + + return "Invalid command syntax.\n" + getHelpPage(); + } } diff --git a/MinecraftClient/config/ChatBots/QIWIAPI.cs b/MinecraftClient/config/ChatBots/QIWIAPI.cs index a70dd74c..4c2a9fdd 100644 --- a/MinecraftClient/config/ChatBots/QIWIAPI.cs +++ b/MinecraftClient/config/ChatBots/QIWIAPI.cs @@ -6,8 +6,8 @@ MCC.LoadBot(new QIWI_DonationBot()); //MCCScript Extensions public class QIWI_DonationBot : ChatBot -{ - //More info: https://github.com/Nekiplay/QIWI-API +{ + //More info: https://github.com/Nekiplay/QIWI-API public override void Initialize() { QIWI.Donation donation = new QIWI.Donation("token", OnDonate); diff --git a/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs index 18ca6d9c..68979e4b 100644 --- a/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs +++ b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs @@ -1,4 +1,15 @@ //MCCScript 1.0 +//using MinecraftClient.Inventory; +//using MinecraftClient.Mapping; +//using MinecraftClient.Scripting; +//using System.Collections.Generic; +//using System.Threading; +//using System; +//using Brigadier.NET.Builder; +//using MinecraftClient.CommandHandler.Patch; +//using Brigadier.NET; +//using MinecraftClient.CommandHandler; +//using System.Linq; MCC.LoadBot(new SugarCaneFarmer()); @@ -102,6 +113,8 @@ class SugarCaneFarmerBase : ChatBot class SugarCaneFarmer : SugarCaneFarmerBase { + public const string CommandName = "sugarcane"; + public enum CoordinateType { X, Y, Z }; /// @@ -137,7 +150,19 @@ class SugarCaneFarmer : SugarCaneFarmerBase public override void Initialize() { LogToConsole("Sugar Cane farming bot created by Daenges."); - RegisterChatBotCommand("sugarcane", "Farm sugar cane automatically", "/sugarcane [range x|y|z]/[stop]", commandHandler); + + Handler.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Argument("Commands", Arguments.GreedyString()) + .Executes(r => { + CommandHandler(Arguments.GetString(r, "Commands").Split(' ', StringSplitOptions.TrimEntries)); + return r.Source.SetAndReturn(CmdResult.Status.Done); + })) + ); + } + + public override void OnUnload() + { + Handler.dispatcher.Unregister(CommandName); } /// @@ -188,7 +213,7 @@ class SugarCaneFarmer : SugarCaneFarmerBase LogToConsole("[FARMING STOPPED]"); } - private string commandHandler(string command, string[] args) + private string CommandHandler(string[] args) { if (args.Length == 1) { diff --git a/MinecraftClient/config/ChatBots/SugarCaneMiner.cs b/MinecraftClient/config/ChatBots/SugarCaneMiner.cs index 7ec809a1..2bdb3913 100644 --- a/MinecraftClient/config/ChatBots/SugarCaneMiner.cs +++ b/MinecraftClient/config/ChatBots/SugarCaneMiner.cs @@ -1,5 +1,9 @@ //MCCScript 1.0 +//using MinecraftClient.CommandHandler; +//using MinecraftClient.Mapping; +//using MinecraftClient.Scripting; + MCC.LoadBot(new SugarCaneMiner()); //MCCScript Extensions diff --git a/MinecraftClient/config/ChatBots/TreeFarmer.cs b/MinecraftClient/config/ChatBots/TreeFarmer.cs index a1555e23..f19975a7 100644 --- a/MinecraftClient/config/ChatBots/TreeFarmer.cs +++ b/MinecraftClient/config/ChatBots/TreeFarmer.cs @@ -1,5 +1,10 @@ //MCCScript 1.0 +//using MinecraftClient.CommandHandler; +//using MinecraftClient.Mapping; +//using MinecraftClient.Scripting; +//using System.Threading; + MCC.LoadBot(new TreeFarmer()); //MCCScript Extensions diff --git a/MinecraftClient/config/ChatBots/VkMessager.cs b/MinecraftClient/config/ChatBots/VkMessager.cs index 9af37ee2..448e6840 100644 --- a/MinecraftClient/config/ChatBots/VkMessager.cs +++ b/MinecraftClient/config/ChatBots/VkMessager.cs @@ -3,6 +3,19 @@ //dll Newtonsoft.Json.dll //using Newtonsoft.Json; //using Newtonsoft.Json.Linq; +//using Brigadier.NET; +//using MinecraftClient.CommandHandler; +//using MinecraftClient.Scripting; +//using MinecraftClient; +//using Newtonsoft.Json.Linq; +//using Newtonsoft.Json; +//using System.Collections.Generic; +//using System.Net; +//using System.Text.RegularExpressions; +//using System.Text; +//using System.Threading.Tasks; +//using System; +//using System.Linq; //==== INFO START ==== // Download Newtonsoft.Json.dll and install it into the program folder Link: https://www.newtonsoft.com/json