From 25dfcd8856e225fb5dc4da751609c03357cbe75e Mon Sep 17 00:00:00 2001 From: BruceChen Date: Wed, 5 Oct 2022 19:16:33 +0800 Subject: [PATCH] Merge AutoCraft's config --- MinecraftClient/ChatBots/AutoCraft.cs | 374 +++++++++----------------- MinecraftClient/Resources/lang/en.ini | 7 +- MinecraftClient/Settings.cs | 4 +- 3 files changed, 138 insertions(+), 247 deletions(-) diff --git a/MinecraftClient/ChatBots/AutoCraft.cs b/MinecraftClient/ChatBots/AutoCraft.cs index 0b3b93de..173f1394 100644 --- a/MinecraftClient/ChatBots/AutoCraft.cs +++ b/MinecraftClient/ChatBots/AutoCraft.cs @@ -5,6 +5,7 @@ using System.Linq; using MinecraftClient.Inventory; using MinecraftClient.Mapping; using Tomlet.Attributes; +using static MinecraftClient.ChatBots.AutoCraft.Configs; namespace MinecraftClient.ChatBots { @@ -20,12 +21,105 @@ namespace MinecraftClient.ChatBots public bool Enabled = false; - public string configFile = @"autocraft\config.ini"; + [TomlInlineComment("$config.ChatBot.AutoCraft.Table_Location$")] + public LocationConfig Table_Location = new(123, 65, 456); + + [TomlInlineComment("$config.ChatBot.AutoCraft.On_Failure$")] + public OnFailConfig On_Failure = OnFailConfig.abort; + + [TomlPrecedingComment("$config.ChatBot.AutoCraft.Recipes$")] + public RecipeConfig[] Recipes = new RecipeConfig[] + { + new RecipeConfig( + Name: "Recipe Name 1", + Type: CraftTypeConfig.player, + Result: ItemType.StoneBricks, + Slots: new ItemType[4] { ItemType.Stone, ItemType.Stone, ItemType.Stone, ItemType.Stone } + ), + new RecipeConfig( + Name: "Recipe Name 2", + Type: CraftTypeConfig.table, + Result: ItemType.StoneBricks, + Slots: new ItemType[9] { + ItemType.Stone, ItemType.Stone, ItemType.Null, + ItemType.Stone, ItemType.Stone, ItemType.Null, + ItemType.Null, ItemType.Null, ItemType.Null, + } + ), + }; + + [NonSerialized] + public Location _Table_Location = Location.Zero; public void OnSettingUpdate() { - configFile ??= string.Empty; + _Table_Location = new Location(Table_Location.X, Table_Location.Y, Table_Location.Z).ToFloor(); + foreach (RecipeConfig recipe in Recipes) + { + recipe.Name ??= string.Empty; + + int fixLength = -1; + if (recipe.Type == CraftTypeConfig.player && recipe.Slots.Length != 4) + fixLength = 4; + else if (recipe.Type == CraftTypeConfig.table && recipe.Slots.Length != 9) + fixLength = 9; + + if (fixLength > 0) + { + ItemType[] Slots = new ItemType[fixLength]; + for (int i = 0; i < fixLength; ++i) + Slots[i] = (i < recipe.Slots.Length) ? recipe.Slots[i] : ItemType.Null; + recipe.Slots = Slots; + LogToConsole(BotName, Translations.TryGet("bot.autocraft.invaild_slots")); + } + + if (recipe.Result == ItemType.Air || recipe.Result == ItemType.Null) + { + LogToConsole(BotName, Translations.TryGet("bot.autocraft.invaild_result")); + } + } } + + public struct LocationConfig + { + public double X, Y, Z; + + public LocationConfig(double X, double Y, double Z) + { + this.X = X; + this.Y = Y; + this.Z = Z; + } + } + + public enum OnFailConfig { abort, wait } + + public class RecipeConfig + { + public string Name = "Recipe Name"; + + public CraftTypeConfig Type = CraftTypeConfig.player; + + public ItemType Result = ItemType.Air; + + public ItemType[] Slots = new ItemType[9] { + ItemType.Null, ItemType.Null, ItemType.Null, + ItemType.Null, ItemType.Null, ItemType.Null, + ItemType.Null, ItemType.Null, ItemType.Null, + }; + + public RecipeConfig() { } + + public RecipeConfig(string Name, CraftTypeConfig Type, ItemType Result, ItemType[] Slots) + { + this.Name = Name; + this.Type = Type; + this.Result = Result; + this.Slots = Slots; + } + } + + public enum CraftTypeConfig { player, table } } private bool waitingForMaterials = false; @@ -37,18 +131,12 @@ namespace MinecraftClient.ChatBots private Recipe? recipeInUse; private readonly List actionSteps = new(); - private Location tableLocation = new(); - private bool abortOnFailure = true; private int updateDebounceValue = 2; private int updateDebounce = 0; private readonly int updateTimeoutValue = 10; private int updateTimeout = 0; private string timeoutAction = "unspecified"; - private string lastRecipe = ""; // Used in parsing recipe config - - private readonly Dictionary recipes = new(); - private void ResetVar() { craftingFailed = false; @@ -187,7 +275,6 @@ namespace MinecraftClient.ChatBots } RegisterChatBotCommand("autocraft", Translations.Get("bot.autoCraft.cmd"), GetHelp(), CommandHandler); RegisterChatBotCommand("ac", Translations.Get("bot.autoCraft.alias"), GetHelp(), CommandHandler); - LoadConfig(); } public string CommandHandler(string cmd, string[] args) @@ -196,32 +283,37 @@ namespace MinecraftClient.ChatBots { switch (args[0]) { - case "load": - LoadConfig(); - return ""; case "list": - string names = string.Join(", ", recipes.Keys.ToList()); - return Translations.Get("bot.autoCraft.cmd.list", recipes.Count, names); - case "reload": - recipes.Clear(); - LoadConfig(); - return ""; - case "resetcfg": - WriteDefaultConfig(); - return Translations.Get("bot.autoCraft.cmd.resetcfg"); + string names = string.Join(", ", Config.Recipes.ToList()); + return Translations.Get("bot.autoCraft.cmd.list", Config.Recipes.Length, names); case "start": if (args.Length >= 2) { string name = args[1]; - if (recipes.ContainsKey(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(recipes[name]); + PrepareCrafting(recipe!); return ""; } - else return Translations.Get("bot.autoCraft.recipe_not_exist"); + else + return Translations.Get("bot.autoCraft.recipe_not_exist"); } - else return Translations.Get("bot.autoCraft.no_recipe_name"); + else + return Translations.Get("bot.autoCraft.no_recipe_name"); case "stop": StopCrafting(); return Translations.Get("bot.autoCraft.stop"); @@ -256,220 +348,6 @@ namespace MinecraftClient.ChatBots }; } - #region Config handling - - public void LoadConfig() - { - if (!File.Exists(Config.configFile)) - { - if (!Directory.Exists(Config.configFile)) - { - Directory.CreateDirectory(@"autocraft"); - } - WriteDefaultConfig(); - LogDebugToConsoleTranslated("bot.autoCraft.debug.no_config"); - } - try - { - ParseConfig(); - LogToConsoleTranslated("bot.autoCraft.loaded"); - } - catch (Exception e) - { - LogToConsoleTranslated("bot.autoCraft.error.config", "\n" + e.Message); - } - } - - private void WriteDefaultConfig() - { - string[] content = - { - "[AutoCraft]", - "# A valid autocraft config must begin with [AutoCraft]", - "", - "tablelocation=0,65,0 # Location of the crafting table if you intended to use it. Terrain and movements must be enabled. Format: x,y,z", - "onfailure=abort # What to do on crafting failure, abort or wait", - "", - "# You can define multiple recipes in a single config file", - "# This is an example of how to define a recipe", - "[Recipe]", - "name=whatever # name could be whatever you like. This field must be defined first", - "type=player # crafting table type: player or table", - "result=StoneButton # the resulting item", - "", - "# define slots with their deserved item", - "slot1=Stone # slot start with 1, count from left to right, top to bottom", - "# For the naming of the items, please see", - "# https://github.com/MCCTeam/Minecraft-Console-Client/blob/master/MinecraftClient/Inventory/ItemType.cs" - }; - File.WriteAllLines(Config.configFile, content); - } - - private void ParseConfig() - { - string[] content = File.ReadAllLines(Config.configFile); - if (content.Length <= 0) - { - throw new Exception(Translations.Get("bot.autoCraft.exception.empty", Config.configFile)); - } - if (content[0].ToLower() != "[autocraft]") - { - throw new Exception(Translations.Get("bot.autoCraft.exception.invalid", Config.configFile)); - } - - // local variable for use in parsing config - string section = ""; - Dictionary recipes = new(); - - foreach (string l in content) - { - // ignore comment start with # - if (l.StartsWith("#")) - continue; - string line = l.Split('#')[0].Trim(); - if (line.Length <= 0) - continue; - - if (line[0] == '[' && line[^1] == ']') - { - section = line[1..^1].ToLower(); - continue; - } - - string key = line.Split('=')[0].ToLower(); - if (!(line.Length > (key.Length + 1))) - continue; - string value = line[(key.Length + 1)..]; - switch (section) - { - case "recipe": ParseRecipe(key, value); break; - case "autocraft": ParseMain(key, value); break; - } - } - - // check and save recipe - foreach (var pair in recipes) - { - if ((pair.Value.CraftingAreaType == ContainerType.PlayerInventory - || pair.Value.CraftingAreaType == ContainerType.Crafting) - && (pair.Value.Materials != null - && pair.Value.Materials.Count > 0) - && pair.Value.ResultItem != ItemType.Air) - { - // checking pass - this.recipes.Add(pair.Key, pair.Value); - } - else - { - throw new Exception(Translations.Get("bot.autoCraft.exception.item_miss", pair.Key)); - } - } - - - } - - #region Method for parsing different section of config - - private void ParseMain(string key, string value) - { - switch (key) - { - case "tablelocation": - string[] values = value.Split(','); - if (values.Length == 3) - { - tableLocation.X = Convert.ToInt32(values[0]); - tableLocation.Y = Convert.ToInt32(values[1]); - tableLocation.Z = Convert.ToInt32(values[2]); - } - else throw new Exception(Translations.Get("bot.autoCraft.exception.invalid_table", key)); - break; - case "onfailure": - abortOnFailure = value.ToLower() == "abort"; - break; - case "updatedebounce": - updateDebounceValue = Convert.ToInt32(value); - break; - } - } - - private void ParseRecipe(string key, string value) - { - if (key.StartsWith("slot")) - { - int slot = Convert.ToInt32(key[^1].ToString()); - if (slot > 0 && slot < 10) - { - if (recipes.ContainsKey(lastRecipe)) - { - if (Enum.TryParse(value, true, out ItemType itemType)) - { - Dictionary? materials = recipes[lastRecipe].Materials; - if (materials != null && materials.Count > 0) - { - materials.Add(slot, itemType); - } - else - { - recipes[lastRecipe].Materials = new Dictionary() - { - { slot, itemType } - }; - } - return; - } - else - { - throw new Exception(Translations.Get("bot.autoCraft.exception.item_name", lastRecipe, key)); - } - } - else - { - throw new Exception(Translations.Get("bot.autoCraft.exception.name_miss")); - } - } - else - { - throw new Exception(Translations.Get("bot.autoCraft.exception.slot", key)); - } - } - else - { - switch (key) - { - case "name": - if (!recipes.ContainsKey(value)) - { - recipes.Add(value, new Recipe()); - lastRecipe = value; - } - else - { - throw new Exception(Translations.Get("bot.autoCraft.exception.duplicate", value)); - } - break; - case "type": - if (recipes.ContainsKey(lastRecipe)) - { - recipes[lastRecipe].CraftingAreaType = value.ToLower() == "player" ? ContainerType.PlayerInventory : ContainerType.Crafting; - } - break; - case "result": - if (recipes.ContainsKey(lastRecipe)) - { - if (Enum.TryParse(value, true, out ItemType itemType)) - { - recipes[lastRecipe].ResultItem = itemType; - } - } - break; - } - } - } - #endregion - - #endregion - #region Core part of auto-crafting public override void OnInventoryUpdate(int inventoryId) @@ -538,9 +416,19 @@ namespace MinecraftClient.ChatBots /// Prepare the crafting action steps by the given recipe name and start crafting /// /// Name of the recipe to craft - private void PrepareCrafting(string name) + private void PrepareCrafting(RecipeConfig recipeConfig) { - PrepareCrafting(recipes[name]); + Dictionary materials = new(); + for (int i = 0; i < recipeConfig.Slots.Length; ++i) + if (recipeConfig.Slots[i] != ItemType.Null) + materials[i] = recipeConfig.Slots[i]; + + ItemType ResultItem = recipeConfig.Result; + + ContainerType CraftingAreaType = + (recipeConfig.Type == CraftTypeConfig.player) ? ContainerType.PlayerInventory : ContainerType.Crafting; + + PrepareCrafting(new Recipe(materials, ResultItem, CraftingAreaType)); } /// @@ -561,7 +449,7 @@ namespace MinecraftClient.ChatBots if (inventoryInUse == -2) { // table required but not found. Try to open one - OpenTable(tableLocation); + OpenTable(Config._Table_Location); waitingForTable = true; SetTimeout(Translations.Get("bot.autoCraft.table_not_found")); return; @@ -711,7 +599,7 @@ namespace MinecraftClient.ChatBots // Inform user the missing meterial name LogToConsoleTranslated("bot.autoCraft.missing_material", actionSteps[index - 1].ItemType.ToString()); } - if (abortOnFailure) + if (Config.On_Failure == OnFailConfig.abort) { StopCrafting(); LogToConsoleTranslated("bot.autoCraft.aborted"); diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index 58dc2248..52ee50c4 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -501,6 +501,8 @@ bot.autoCraft.exception.name_miss=Missing recipe name while parsing a recipe bot.autoCraft.exception.slot=Invalid slot field in recipe: {0} bot.autoCraft.exception.duplicate=Duplicate recipe name specified: {0} bot.autoCraft.debug.no_config=No config found. Writing a new one. +bot.autocraft.invaild_slots=The number of slots does not match and has been adjusted automatically. +bot.autocraft.invaild_invaild_result=Invalid result item! # AutoDrop bot.autoDrop.cmd=AutoDrop ChatBot command @@ -787,6 +789,9 @@ config.ChatBot.AutoAttack.Entites_List=All entity types can be found here: https # ChatBot.AutoCraft config.ChatBot.AutoCraft=Automatically craft items in your inventory\n# See README > 'Using the AutoCraft bot' for how to use\n# You need to enable Inventory Handling to use this bot\n# You should also enable Terrain and Movements if you need to use a crafting table +config.ChatBot.AutoCraft.Table_Location=Location of the crafting table if you intended to use it. Terrain and movements must be enabled. +config.ChatBot.AutoCraft.On_Failure=What to do on crafting failure, "abort" or "wait". +config.ChatBot.AutoCraft.Recipes=Recipes.Name: The name can be whatever you like and it is used to represent the recipe.\n# Recipes.Type: crafting table type: player or table\n# Recipes.Result: the resulting item\n# Recipes.Slots: All slots, counting from left to right, top to bottom. Please fill in "Null" for empty slots.\n# For the naming of the items, please see:\n# https://github.com/MCCTeam/Minecraft-Console-Client/blob/master/MinecraftClient/Inventory/ItemType.cs # ChatBot.AutoDrop config.ChatBot.AutoDrop=Automatically drop items in inventory\n# You need to enable Inventory Handling to use this bot\n# See this file for an up-to-date list of item types you can use with this bot:\n# https://github.com/MCCTeam/Minecraft-Console-Client/blob/master/MinecraftClient/Inventory/ItemType.cs @@ -809,7 +814,7 @@ config.ChatBot.AutoFishing.Auto_Rod_Switch=Switch to a new rod from inventory af config.ChatBot.AutoFishing.Stationary_Threshold=Hooks moving in the X and Z axes below this threshold will be considered stationary. config.ChatBot.AutoFishing.Hook_Threshold=A stationary hook moving on the Y-axis above this threshold will be considered to have caught a fish. config.ChatBot.AutoFishing.Log_Fish_Bobber=For debugging purposes, you can use this log to adjust the two thresholds mentioned above. -config.ChatBot.AutoFishing.Movements=Some plugins do not allow the player to fish in one place. This allows the player to change position/angle after each fish caught. Usage can be found in the Guide. +config.ChatBot.AutoFishing.Movements=This allows the player to change position/angle after each fish caught. Usage can be found in the Guide. # ChatBot.AutoRelog diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 8c3bcdee..c68c9ad0 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -126,6 +126,7 @@ namespace MinecraftClient try { document = TomlParser.ParseFile(filepath); + Config = TomletMain.To(document); } catch (Exception ex) { @@ -148,9 +149,6 @@ namespace MinecraftClient ConsoleIO.WriteLine(Translations.GetOrNull("mcc.run_with_default_settings") ?? "\nMCC is running with default settings."); return false; } - - Config = TomletMain.To(document); - return false; }