chore: polish recipe book support

Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/00c8527f-5755-43c1-8916-8d571d28860b

Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-29 18:07:09 +00:00
parent d5308ba8c6
commit 893be203e5
5 changed files with 71 additions and 10 deletions

View file

@ -78,15 +78,29 @@ namespace MinecraftClient.Commands
if (!handler.GetInventoryEnabled()) if (!handler.GetInventoryEnabled())
return r.SetAndReturn(CmdResult.Status.FailNeedInventory); 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) if (handler.GetProtocolVersion() < Protocol.Handlers.Protocol18Handler.MC_1_13_Version)
return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_unsupported); return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_unsupported);
if (handler.GetActiveRecipeBookInventory() is null) if (handler.GetActiveRecipeBookInventory() is null)
return r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_recipebook_no_active_inventory); 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) return handler.SendPlaceRecipe(recipeId, makeAll)
? r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.cmd_recipebook_craft_sent, recipeId, makeAll)) ? r.SetAndReturn(CmdResult.Status.Done, successMessage)
: r.SetAndReturn(CmdResult.Status.Fail, string.Format(Translations.cmd_recipebook_craft_failed, recipeId)); : 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;
} }
} }
} }

View file

@ -1411,7 +1411,7 @@ namespace MinecraftClient
if (inventories.Count == 0) if (inventories.Count == 0)
return null; return null;
Container activeInventory = inventories.Values.Last(); Container activeInventory = inventories.MaxBy(static pair => pair.Key).Value;
return SupportsRecipeBook(activeInventory.Type) ? activeInventory : null; return SupportsRecipeBook(activeInventory.Type) ? activeInventory : null;
} }
@ -2728,7 +2728,11 @@ namespace MinecraftClient
if (activeInventory is null) if (activeInventory is null)
return false; 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 #endregion
@ -4172,6 +4176,9 @@ namespace MinecraftClient
private static string NormalizeRecipeId(string recipeId) private static string NormalizeRecipeId(string recipeId)
{ {
string trimmedRecipeId = recipeId.Trim(); string trimmedRecipeId = recipeId.Trim();
if (trimmedRecipeId.Length == 0)
return string.Empty;
return trimmedRecipeId.Contains(':', StringComparison.Ordinal) return trimmedRecipeId.Contains(':', StringComparison.Ordinal)
? trimmedRecipeId ? trimmedRecipeId
: "minecraft:" + trimmedRecipeId; : "minecraft:" + trimmedRecipeId;

View file

@ -3122,10 +3122,12 @@ namespace MinecraftClient.Protocol.Handlers
break; break;
case PacketTypesIn.RecipeBookAdd: case PacketTypesIn.RecipeBookAdd:
HandleRecipeBookAdd(packetData); if (protocolVersion >= MC_1_21_2_Version)
HandleRecipeBookAdd(packetData);
break; break;
case PacketTypesIn.RecipeBookRemove: case PacketTypesIn.RecipeBookRemove:
handler.OnRecipeBookRemove(ReadRecipeBookRecipeIds(packetData)); if (protocolVersion >= MC_1_21_2_Version)
handler.OnRecipeBookRemove(ReadRecipeBookRecipeIds(packetData));
break; break;
case PacketTypesIn.RecipeBookSettings: case PacketTypesIn.RecipeBookSettings:
break; break;
@ -3140,7 +3142,8 @@ namespace MinecraftClient.Protocol.Handlers
private void HandleUnlockRecipes(Queue<byte> packetData) private void HandleUnlockRecipes(Queue<byte> packetData)
{ {
int action = dataTypes.ReadNextVarInt(packetData); int action = dataTypes.ReadNextVarInt(packetData);
SkipRecipeBookSettings(packetData); if (!SkipRecipeBookSettings(packetData))
return;
string[] recipeIds = ReadRecipeBookRecipeIds(packetData); string[] recipeIds = ReadRecipeBookRecipeIds(packetData);
@ -3148,10 +3151,15 @@ namespace MinecraftClient.Protocol.Handlers
{ {
case 0: case 0:
handler.OnRecipeBookAdd(recipeIds, replace: true); 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); _ = ReadRecipeBookRecipeIds(packetData);
break; break;
case 1: case 1:
handler.OnRecipeBookAdd(recipeIds, replace: false);
break;
case 3: case 3:
// Action 3 is the silent-add variant, so MCC tracks it like a regular add.
handler.OnRecipeBookAdd(recipeIds, replace: false); handler.OnRecipeBookAdd(recipeIds, replace: false);
break; break;
case 2: case 2:
@ -3165,6 +3173,9 @@ namespace MinecraftClient.Protocol.Handlers
int entryCount = dataTypes.ReadNextVarInt(packetData); int entryCount = dataTypes.ReadNextVarInt(packetData);
string[] recipeIds = new string[entryCount]; 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++) for (int i = 0; i < entryCount; i++)
{ {
recipeIds[i] = dataTypes.ReadNextString(packetData); recipeIds[i] = dataTypes.ReadNextString(packetData);
@ -3187,11 +3198,16 @@ namespace MinecraftClient.Protocol.Handlers
return recipeIds; return recipeIds;
} }
private void SkipRecipeBookSettings(Queue<byte> packetData) private bool SkipRecipeBookSettings(Queue<byte> packetData)
{ {
int boolCount = protocolVersion >= MC_1_14_Version ? 8 : 4; int boolCount = protocolVersion >= MC_1_14_Version ? 8 : 4;
if (packetData.Count < boolCount)
return false;
for (int i = 0; i < boolCount; i++) for (int i = 0; i < boolCount; i++)
_ = dataTypes.ReadNextBool(packetData); _ = dataTypes.ReadNextBool(packetData);
return true;
} }
/// <summary> /// <summary>

View file

@ -4370,7 +4370,7 @@ namespace MinecraftClient {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Requested recipe {0} (craft all: {1}).. /// Looks up a localized string similar to Requested recipe {0}..
/// </summary> /// </summary>
internal static string cmd_recipebook_craft_sent { internal static string cmd_recipebook_craft_sent {
get { get {
@ -4378,6 +4378,15 @@ namespace MinecraftClient {
} }
} }
/// <summary>
/// Looks up a localized string similar to Requested recipe {0} with craft-all..
/// </summary>
internal static string cmd_recipebook_craftall_sent {
get {
return ResourceManager.GetString("cmd.recipebook.craftall.sent", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to List unlocked recipe book recipes and craft them through the active recipe book inventory.. /// Looks up a localized string similar to List unlocked recipe book recipes and craft them through the active recipe book inventory..
/// </summary> /// </summary>
@ -4414,6 +4423,15 @@ namespace MinecraftClient {
} }
} }
/// <summary>
/// Looks up a localized string similar to The recipe identifier cannot be empty..
/// </summary>
internal static string cmd_recipebook_recipe_id_empty {
get {
return ResourceManager.GetString("cmd.recipebook.recipe.id.empty", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Recipe book crafting is only supported on Minecraft 1.13 and newer.. /// Looks up a localized string similar to Recipe book crafting is only supported on Minecraft 1.13 and newer..
/// </summary> /// </summary>

View file

@ -2212,7 +2212,10 @@ Logging in...</value>
<value>Failed to send recipe book craft request for {0}.</value> <value>Failed to send recipe book craft request for {0}.</value>
</data> </data>
<data name="cmd.recipebook.craft.sent" xml:space="preserve"> <data name="cmd.recipebook.craft.sent" xml:space="preserve">
<value>Requested recipe {0} (craft all: {1}).</value> <value>Requested recipe {0}.</value>
</data>
<data name="cmd.recipebook.craftall.sent" xml:space="preserve">
<value>Requested recipe {0} with craft-all.</value>
</data> </data>
<data name="cmd.recipebook.desc" xml:space="preserve"> <data name="cmd.recipebook.desc" xml:space="preserve">
<value>List unlocked recipe book recipes and craft them through the active recipe book inventory.</value> <value>List unlocked recipe book recipes and craft them through the active recipe book inventory.</value>
@ -2226,6 +2229,9 @@ Logging in...</value>
<data name="cmd.recipebook.no.recipes" xml:space="preserve"> <data name="cmd.recipebook.no.recipes" xml:space="preserve">
<value>No unlocked recipe book recipes are currently tracked.</value> <value>No unlocked recipe book recipes are currently tracked.</value>
</data> </data>
<data name="cmd.recipebook.recipe.id.empty" xml:space="preserve">
<value>The recipe identifier cannot be empty.</value>
</data>
<data name="cmd.recipebook.unsupported" xml:space="preserve"> <data name="cmd.recipebook.unsupported" xml:space="preserve">
<value>Recipe book crafting is only supported on Minecraft 1.13 and newer.</value> <value>Recipe book crafting is only supported on Minecraft 1.13 and newer.</value>
</data> </data>