diff --git a/MinecraftClient/Commands/RecipeBook.cs b/MinecraftClient/Commands/RecipeBook.cs index a66b2004..b51d211b 100644 --- a/MinecraftClient/Commands/RecipeBook.cs +++ b/MinecraftClient/Commands/RecipeBook.cs @@ -78,15 +78,29 @@ namespace MinecraftClient.Commands if (!handler.GetInventoryEnabled()) return r.SetAndReturn(CmdResult.Status.FailNeedInventory); + if (string.IsNullOrWhiteSpace(recipeId)) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_recipe_id_empty); + if (handler.GetProtocolVersion() < Protocol.Handlers.Protocol18Handler.MC_1_13_Version) return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_unsupported); if (handler.GetActiveRecipeBookInventory() is null) return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_no_active_inventory); + string normalizedRecipeId = NormalizeRecipeId(recipeId); + string successMessage = string.Format(makeAll ? Translations.cmd_recipebook_craftall_sent : Translations.cmd_recipebook_craft_sent, normalizedRecipeId); + return handler.SendPlaceRecipe(recipeId, makeAll) - ? r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_recipebook_craft_sent, recipeId, makeAll)) - : r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_recipebook_craft_failed, recipeId)); + ? r.SetAndReturn(CmdResult.Status.Done, successMessage) + : r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_recipebook_craft_failed, normalizedRecipeId)); + } + + private static string NormalizeRecipeId(string recipeId) + { + string trimmedRecipeId = recipeId.Trim(); + return trimmedRecipeId.Contains(':') + ? trimmedRecipeId + : "minecraft:" + trimmedRecipeId; } } } diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 3e12d920..751b79e6 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1411,7 +1411,7 @@ namespace MinecraftClient if (inventories.Count == 0) return null; - Container activeInventory = inventories.Values.Last(); + Container activeInventory = inventories.MaxBy(static pair => pair.Key).Value; return SupportsRecipeBook(activeInventory.Type) ? activeInventory : null; } @@ -2728,7 +2728,11 @@ namespace MinecraftClient if (activeInventory is null) return false; - return handler.SendPlaceRecipe(activeInventory.ID, NormalizeRecipeId(recipeId), makeAll); + string normalizedRecipeId = NormalizeRecipeId(recipeId); + if (normalizedRecipeId.Length == 0) + return false; + + return handler.SendPlaceRecipe(activeInventory.ID, normalizedRecipeId, makeAll); } #endregion @@ -4172,6 +4176,9 @@ namespace MinecraftClient private static string NormalizeRecipeId(string recipeId) { string trimmedRecipeId = recipeId.Trim(); + if (trimmedRecipeId.Length == 0) + return string.Empty; + return trimmedRecipeId.Contains(':', StringComparison.Ordinal) ? trimmedRecipeId : "minecraft:" + trimmedRecipeId; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 317090a9..c5636cd1 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -3122,10 +3122,12 @@ namespace MinecraftClient.Protocol.Handlers break; case PacketTypesIn.RecipeBookAdd: - HandleRecipeBookAdd(packetData); + if (protocolVersion >= MC_1_21_2_Version) + HandleRecipeBookAdd(packetData); break; case PacketTypesIn.RecipeBookRemove: - handler.OnRecipeBookRemove(ReadRecipeBookRecipeIds(packetData)); + if (protocolVersion >= MC_1_21_2_Version) + handler.OnRecipeBookRemove(ReadRecipeBookRecipeIds(packetData)); break; case PacketTypesIn.RecipeBookSettings: break; @@ -3140,7 +3142,8 @@ namespace MinecraftClient.Protocol.Handlers private void HandleUnlockRecipes(Queue packetData) { int action = dataTypes.ReadNextVarInt(packetData); - SkipRecipeBookSettings(packetData); + if (!SkipRecipeBookSettings(packetData)) + return; string[] recipeIds = ReadRecipeBookRecipeIds(packetData); @@ -3148,10 +3151,15 @@ namespace MinecraftClient.Protocol.Handlers { case 0: handler.OnRecipeBookAdd(recipeIds, replace: true); + // INIT packets also include a second "to be displayed" recipe list. + // MCC only needs the unlocked recipe identifiers for listing/crafting. _ = ReadRecipeBookRecipeIds(packetData); break; case 1: + handler.OnRecipeBookAdd(recipeIds, replace: false); + break; case 3: + // Action 3 is the silent-add variant, so MCC tracks it like a regular add. handler.OnRecipeBookAdd(recipeIds, replace: false); break; case 2: @@ -3165,6 +3173,9 @@ namespace MinecraftClient.Protocol.Handlers int entryCount = dataTypes.ReadNextVarInt(packetData); string[] recipeIds = new string[entryCount]; + // RecipeBookAdd contains one entry per recipe: + // recipe id, notification flag, then highlight flag. + // MCC only tracks the unlocked recipe identifiers for now. for (int i = 0; i < entryCount; i++) { recipeIds[i] = dataTypes.ReadNextString(packetData); @@ -3187,11 +3198,16 @@ namespace MinecraftClient.Protocol.Handlers return recipeIds; } - private void SkipRecipeBookSettings(Queue packetData) + private bool SkipRecipeBookSettings(Queue packetData) { int boolCount = protocolVersion >= MC_1_14_Version ? 8 : 4; + if (packetData.Count < boolCount) + return false; + for (int i = 0; i < boolCount; i++) _ = dataTypes.ReadNextBool(packetData); + + return true; } /// diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index b6bea198..022e9cad 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -4370,7 +4370,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Requested recipe {0} (craft all: {1}).. + /// Looks up a localized string similar to Requested recipe {0}.. /// internal static string cmd_recipebook_craft_sent { get { @@ -4378,6 +4378,15 @@ namespace MinecraftClient { } } + /// + /// Looks up a localized string similar to Requested recipe {0} with craft-all.. + /// + internal static string cmd_recipebook_craftall_sent { + get { + return ResourceManager.GetString("cmd.recipebook.craftall.sent", resourceCulture); + } + } + /// /// Looks up a localized string similar to List unlocked recipe book recipes and craft them through the active recipe book inventory.. /// @@ -4414,6 +4423,15 @@ namespace MinecraftClient { } } + /// + /// Looks up a localized string similar to The recipe identifier cannot be empty.. + /// + internal static string cmd_recipebook_recipe_id_empty { + get { + return ResourceManager.GetString("cmd.recipebook.recipe.id.empty", resourceCulture); + } + } + /// /// Looks up a localized string similar to Recipe book crafting is only supported on Minecraft 1.13 and newer.. /// diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index 42a9d8db..c3ebe466 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -2212,7 +2212,10 @@ Logging in... Failed to send recipe book craft request for {0}. - Requested recipe {0} (craft all: {1}). + Requested recipe {0}. + + + Requested recipe {0} with craft-all. List unlocked recipe book recipes and craft them through the active recipe book inventory. @@ -2226,6 +2229,9 @@ Logging in... No unlocked recipe book recipes are currently tracked. + + The recipe identifier cannot be empty. + Recipe book crafting is only supported on Minecraft 1.13 and newer.