From a2ff094369c8731ec4371065423691de3227f31d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:30:26 +0000 Subject: [PATCH 01/10] Initial plan From 4c1b2197d770e300f663364487426960de8828ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:43:05 +0000 Subject: [PATCH 02/10] Initial plan From 802cc696d6097408267e8c14ca8fa2591600e66a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:44:05 +0000 Subject: [PATCH 03/10] fix sample http request script on latest runtime Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/9bf31c0e-e47b-4654-be10-6ea50f27a581 --- .../Scripting/DynamicRun/Builder/Compiler.cs | 4 +++- .../config/sample-script-with-http-request.cs | 23 +++++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs index 2207bad8..2b201d0b 100644 --- a/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs +++ b/MinecraftClient/Scripting/DynamicRun/Builder/Compiler.cs @@ -132,6 +132,8 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder // Facade assemblies needed for compiling scripts that reference netstandard libraries (e.g. Brigadier.NET) assemblyrefs.Add(new("netstandard")); assemblyrefs.Add(new("System.Runtime")); + assemblyrefs.Add(new("System.Private.Uri")); + assemblyrefs.Add(new("System.Net.Requests")); foreach (var refs in assemblyrefs) { Assembly? loadedAssembly; @@ -180,7 +182,7 @@ namespace MinecraftClient.Scripting.DynamicRun.Builder // Add facade assemblies needed for Roslyn compilation when referencing // libraries that target netstandard (e.g. Brigadier.NET). var runtimeDir = Path.GetDirectoryName(SystemPrivateCoreLib)!; - foreach (var facadeName in new[] { "netstandard.dll", "System.Runtime.dll" }) + foreach (var facadeName in new[] { "netstandard.dll", "System.Runtime.dll", "System.Private.Uri.dll", "System.Net.Requests.dll" }) { var facadePath = Path.Combine(runtimeDir, facadeName); if (File.Exists(facadePath)) diff --git a/MinecraftClient/config/sample-script-with-http-request.cs b/MinecraftClient/config/sample-script-with-http-request.cs index 9b24fe34..a281540b 100644 --- a/MinecraftClient/config/sample-script-with-http-request.cs +++ b/MinecraftClient/config/sample-script-with-http-request.cs @@ -7,28 +7,17 @@ MCC.LogToConsole(mojangStatus); string PerformHttpRequest(string uri) { - var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri); - var response = (System.Net.HttpWebResponse)request.GetResponse(); - string responseString; - using (var stream = response.GetResponseStream()) - using (var reader = new StreamReader(stream)) - responseString = reader.ReadToEnd(); - return responseString; + using var httpClient = new System.Net.Http.HttpClient(); + return httpClient.GetStringAsync(uri).GetAwaiter().GetResult(); } void SendHttpPostAsync(string uri, string text) { new Thread(() => { - var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri); - request.ContentType = "text/plain"; - request.Method = "POST"; - using (var streamWriter = new StreamWriter(request.GetRequestStream())) - streamWriter.Write(text); - var response = (System.Net.HttpWebResponse)request.GetResponse(); - string responseString; - using (var stream = response.GetResponseStream()) - using (var reader = new StreamReader(stream)) - responseString = reader.ReadToEnd(); + using var httpClient = new System.Net.Http.HttpClient(); + using var content = new System.Net.Http.StringContent(text, System.Text.Encoding.UTF8, "text/plain"); + using var response = httpClient.PostAsync(uri, content).GetAwaiter().GetResult(); + string responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); //LogToConsole(responseString); }).Start(); } From 0168cf8aa174aea06c6644cef4d6f1fae5299948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:46:19 +0000 Subject: [PATCH 04/10] Initial plan From 7b534ca547b6040a7fac078ede9bf3460f81a6bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:47:10 +0000 Subject: [PATCH 05/10] feat: add built-in players app var Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/c9f7aa87-50b7-4725-aa39-613eba1a07fd --- .../ConfigComments/ConfigComments.resx | 4 +- MinecraftClient/Settings.cs | 70 ++++++++++++------- docs/guide/configuration.md | 2 +- docs/guide/creating-text-script.md | 2 +- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx index 454f78de..6735295a 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx @@ -119,7 +119,7 @@ can be used in some other fields as %yourvar% -%username% and %serverip% are reserved variables. +%username%, %login%, %serverip%, %serverport%, %datetime% and %players% are reserved read-only variables. =============================== # @@ -930,4 +930,4 @@ Note: This does NOT require a Bot Token, only an Application ID. Discord must be Yggdrasil authlib multi-user selection. - \ No newline at end of file + diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 5dd1f4c5..a956e1df 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -1024,6 +1024,34 @@ namespace MinecraftClient [NonSerialized] readonly Lock varLock = new(); + private static bool TryGetReadOnlyVar(string varName, [NotNullWhen(true)] out object? varData) + { + switch (Settings.ToLowerIfNeed(varName)) + { + case "username": + varData = InternalConfig.Username; + return true; + case "login": + varData = InternalConfig.Account.Login; + return true; + case "serverip": + varData = InternalConfig.ServerIP; + return true; + case "serverport": + varData = InternalConfig.ServerPort; + return true; + case "datetime": + varData = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + return true; + case "players": + varData = string.Join(", ", McClient.Instance?.GetOnlinePlayers() ?? []); + return true; + default: + varData = null; + return false; + } + } + /// /// Set a custom %variable% which will be available through expandVars() /// @@ -1066,6 +1094,8 @@ namespace MinecraftClient /// The value or null if the variable does not exists public object? GetVar(string varName) { + if (TryGetReadOnlyVar(varName, out object? readOnlyVar)) + return readOnlyVar; if (VarStirng.TryGetValue(varName, out string? valueString)) return valueString; else if (VarObject.TryGetValue(varName, out object? valueObject)) @@ -1081,6 +1111,8 @@ namespace MinecraftClient /// The value or null if the variable does not exists public bool TryGetVar(string varName, [NotNullWhen(true)] out object? varData) { + if (TryGetReadOnlyVar(varName, out varData)) + return true; if (VarStirng.TryGetValue(varName, out string? valueString)) { varData = valueString; @@ -1143,31 +1175,21 @@ namespace MinecraftClient string varname_lower = Settings.ToLowerIfNeed(varname); i = i + varname.Length + 1; - switch (varname_lower) + if (TryGetReadOnlyVar(varname_lower, out object? readOnlyVar)) { - case "username": result.Append(InternalConfig.Username); break; - case "login": result.Append(InternalConfig.Account.Login); break; - case "serverip": result.Append(InternalConfig.ServerIP); break; - case "serverport": result.Append(InternalConfig.ServerPort); break; - case "datetime": - DateTime time = DateTime.Now; - result.Append(String.Format("{0}-{1}-{2} {3}:{4}:{5}", - time.Year.ToString("0000"), - time.Month.ToString("00"), - time.Day.ToString("00"), - time.Hour.ToString("00"), - time.Minute.ToString("00"), - time.Second.ToString("00"))); - - break; - default: - if (localVars is not null && localVars.ContainsKey(varname_lower)) - result.Append(localVars[varname_lower].ToString()); - else if (TryGetVar(varname_lower, out object? var_value)) - result.Append(var_value.ToString()); - else - result.Append("%" + varname + '%'); - break; + result.Append(readOnlyVar?.ToString()); + } + else if (localVars is not null && localVars.ContainsKey(varname_lower)) + { + result.Append(localVars[varname_lower].ToString()); + } + else if (TryGetVar(varname_lower, out object? var_value)) + { + result.Append(var_value.ToString()); + } + else + { + result.Append("%" + varname + '%'); } } else result.Append(str[i]); diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 10d0f7e7..222c43ea 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1066,7 +1066,7 @@ Coordinate = { x = 145, y = 64, z = 2045 }

Tip

- **`%username%`, `%serverip%`, `%datetime%` are reserved variables** + **`%username%`, `%login%`, `%serverip%`, `%serverport%`, `%datetime%`, `%players%` are reserved read-only variables**
diff --git a/docs/guide/creating-text-script.md b/docs/guide/creating-text-script.md index 7b5178be..9ae2a6fb 100644 --- a/docs/guide/creating-text-script.md +++ b/docs/guide/creating-text-script.md @@ -6,7 +6,7 @@ title: Creating Simple Script A simple script is a text file with one command per line. See the [Internal Commands](usage.md#internal-commands) section, or type `/help` in the console to see the available commands. Any line beginning with `#` is ignored and treated as a comment. -Application variables defined with the `set` command or in the `[AppVars]` config section can be used. The following read-only variables are also available: `%username%`, `%login%`, `%serverip%`, `%serverport%`, `%datetime%`. +Application variables defined with the `set` command or in the `[AppVars]` config section can be used. The following read-only variables are also available: `%username%`, `%login%`, `%serverip%`, `%serverport%`, `%datetime%`, `%players%` (`%players%` expands to the current online player names separated by commas). ## Example From c9f1f88e6257749d36437ff2d8427565672d90ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:48:48 +0000 Subject: [PATCH 06/10] refine sample http client usage Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/9bf31c0e-e47b-4654-be10-6ea50f27a581 --- MinecraftClient/config/sample-script-with-http-request.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MinecraftClient/config/sample-script-with-http-request.cs b/MinecraftClient/config/sample-script-with-http-request.cs index a281540b..60e6d27d 100644 --- a/MinecraftClient/config/sample-script-with-http-request.cs +++ b/MinecraftClient/config/sample-script-with-http-request.cs @@ -5,18 +5,18 @@ MCC.LogToConsole(mojangStatus); //MCCScript Extensions +private static readonly System.Net.Http.HttpClient s_httpClient = new(); + string PerformHttpRequest(string uri) { - using var httpClient = new System.Net.Http.HttpClient(); - return httpClient.GetStringAsync(uri).GetAwaiter().GetResult(); + return s_httpClient.GetStringAsync(uri).GetAwaiter().GetResult(); } void SendHttpPostAsync(string uri, string text) { new Thread(() => { - using var httpClient = new System.Net.Http.HttpClient(); using var content = new System.Net.Http.StringContent(text, System.Text.Encoding.UTF8, "text/plain"); - using var response = httpClient.PostAsync(uri, content).GetAwaiter().GetResult(); + using var response = s_httpClient.PostAsync(uri, content).GetAwaiter().GetResult(); string responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); //LogToConsole(responseString); }).Start(); From 461be9f36876d3d23534d057189de33ed7197b00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:51:46 +0000 Subject: [PATCH 07/10] docs: clarify players app var behavior Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/c9f7aa87-50b7-4725-aa39-613eba1a07fd --- MinecraftClient/Settings.cs | 4 ++-- docs/guide/creating-text-script.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index a956e1df..0c25adb8 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -1174,10 +1174,10 @@ namespace MinecraftClient string varname = var_name.ToString(); string varname_lower = Settings.ToLowerIfNeed(varname); i = i + varname.Length + 1; - + if (TryGetReadOnlyVar(varname_lower, out object? readOnlyVar)) { - result.Append(readOnlyVar?.ToString()); + result.Append(readOnlyVar.ToString()); } else if (localVars is not null && localVars.ContainsKey(varname_lower)) { diff --git a/docs/guide/creating-text-script.md b/docs/guide/creating-text-script.md index 9ae2a6fb..ff07b67d 100644 --- a/docs/guide/creating-text-script.md +++ b/docs/guide/creating-text-script.md @@ -6,7 +6,7 @@ title: Creating Simple Script A simple script is a text file with one command per line. See the [Internal Commands](usage.md#internal-commands) section, or type `/help` in the console to see the available commands. Any line beginning with `#` is ignored and treated as a comment. -Application variables defined with the `set` command or in the `[AppVars]` config section can be used. The following read-only variables are also available: `%username%`, `%login%`, `%serverip%`, `%serverport%`, `%datetime%`, `%players%` (`%players%` expands to the current online player names separated by commas). +Application variables defined with the `set` command or in the `[AppVars]` config section can be used. The following read-only variables are also available: `%username%`, `%login%`, `%serverip%`, `%serverport%`, `%datetime%`, `%players%` (`%players%` expands to the current online player names separated by commas, or an empty string when not connected). ## Example From 8ab9f8505fa3ca29f95f37820708ac3cbf678596 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:58:58 +0000 Subject: [PATCH 08/10] Fix crash when scoreboard is updated on MC 1.20.4+ ChatParser.NbtToString crashed with InvalidCastException when a scoreboard update was received. The switch expressions for extra/with NBT arrays only handled int and string; everything else fell through to a hard cast to Dictionary, which fails for long/short/byte/float/double. Replace with a pattern that explicitly matches string and Dictionary, and converts all other values (any numeric NBT type) to a text component via ToString(). This matches the behavior that ReadNbtField can return for TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, and TAG_Double. Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/44b66082-14ee-4966-9c23-4074134e0187 --- MinecraftClient/Protocol/Message/ChatParser.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index 28311400..802ed0c7 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -531,12 +531,9 @@ namespace MinecraftClient.Protocol.Message { var extraDict = extras[i] switch { - int => new Dictionary { { "text", $"{extras[i]}" } }, - string => new Dictionary - { - { "text", (string)extras[i] } - }, - _ => (Dictionary)extras[i] + string s => new Dictionary { { "text", s } }, + Dictionary d => d, + _ => new Dictionary { { "text", extras[i]?.ToString() ?? string.Empty } } }; extraBuilder.Append(NbtToString(extraDict) + "§r"); @@ -556,12 +553,9 @@ namespace MinecraftClient.Protocol.Message { var withDict = withs[i] switch { - int => new Dictionary { { "text", $"{withs[i]}" } }, - string => new Dictionary - { - { "text", (string)withs[i] } - }, - _ => (Dictionary)withs[i] + string s => new Dictionary { { "text", s } }, + Dictionary d => d, + _ => new Dictionary { { "text", withs[i]?.ToString() ?? string.Empty } } }; translateString.Add(NbtToString(withDict)); From 17808bb6528d4dfe16991735bb91ff02e4fb6fa2 Mon Sep 17 00:00:00 2001 From: Anon Date: Wed, 25 Mar 2026 01:08:27 +0100 Subject: [PATCH 09/10] Fixed the component system from 1.20.6 - 1.21.11 --- .../Protocol/Handlers/DataTypes.cs | 43 ++++++- .../1_20_6/CustomModelDataComponent.cs | 38 +++++- .../1_20_6/CustomModelDataComponent1206.cs | 21 ++++ .../1_21/JukeBoxPlayableComponent121.cs | 81 +++++++++++++ .../1_21_2/PotionContentsComponent1212.cs | 61 ++++++++++ .../1_21_5/AttributeModifiersComponent1215.cs | 37 ++++++ .../1_21_5/EquippableComponent1215.cs | 104 ++++++++++++++++ .../1_21_5/JukeBoxPlayableComponent1215.cs | 77 ++++++++++++ .../Components/1_21_5/ToolComponent1215.cs | 48 ++++++++ .../1_21_8/AttributeModifiersComponent1218.cs | 99 +++++++++++++++ .../1_21_8/EquippableComponent1218.cs | 113 ++++++++++++++++++ .../StructuredComponentsRegistry1206.cs | 4 +- .../StructuredComponentsRegistry121.cs | 6 +- .../StructuredComponentsRegistry12111.cs | 11 +- .../StructuredComponentsRegistry1212.cs | 6 +- .../StructuredComponentsRegistry1215.cs | 11 +- .../StructuredComponentsHandler.cs | 14 ++- 17 files changed, 749 insertions(+), 25 deletions(-) create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent1206.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent121.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_2/PotionContentsComponent1212.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/AttributeModifiersComponent1215.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/EquippableComponent1215.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/JukeBoxPlayableComponent1215.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ToolComponent1215.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/AttributeModifiersComponent1218.cs create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/EquippableComponent1218.cs diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index 0edf3010..473757c1 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -436,13 +436,29 @@ namespace MinecraftClient.Protocol.Handlers var numberOfComponentsToAdd = ReadNextVarInt(cache); var numberofComponentsToRemove = ReadNextVarInt(cache); + var structuredComponentHandler = new StructuredComponentsHandler(protocolversion, this, itemPalette); + var parsedComponents = new List(numberOfComponentsToAdd); for (var i = 0; i < numberOfComponentsToAdd; i++) { var componentTypeId = ReadNextVarInt(cache); + var componentName = structuredComponentHandler.GetComponentName(componentTypeId); + parsedComponents.Add($"{i}:{componentTypeId}:{componentName}:next={GetQueuePreview(cache, 24)}"); - var strcuturedComponentHandler = new StructuredComponentsHandler(protocolversion, this, itemPalette); - strcturedComponentsToAdd.Add(strcuturedComponentHandler.Parse(componentTypeId, cache)); + try + { + strcturedComponentsToAdd.Add(structuredComponentHandler.Parse(componentTypeId, cache)); + } + catch (Exception ex) + { + var preview = GetQueuePreview(cache, 48); + throw new System.IO.InvalidDataException( + $"Failed to decode item component {componentTypeId} ({componentName}) for itemId {itemId}, " + + $"itemCount {itemCount}, addCount {numberOfComponentsToAdd}, removeCount {numberofComponentsToRemove}, " + + $"componentIndex {i}, remainingBytes {cache.Count}, nextBytes {preview}, " + + $"componentTrace [{string.Join(" | ", parsedComponents)}].", + ex); + } } for (var i = 0; i < numberofComponentsToRemove; i++) @@ -516,6 +532,29 @@ namespace MinecraftClient.Protocol.Handlers } } + private static string GetQueuePreview(Queue cache, int maxBytes) + { + if (cache.Count == 0) + return ""; + + var bytes = cache.ToArray(); + var length = Math.Min(bytes.Length, maxBytes); + var preview = new StringBuilder(length * 3); + + for (var i = 0; i < length; i++) + { + if (i > 0) + preview.Append(' '); + + preview.Append(bytes[i].ToString("X2")); + } + + if (bytes.Length > maxBytes) + preview.Append(" ..."); + + return preview.ToString(); + } + /// /// Read entity information from a cache of bytes and remove it from the cache /// diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent.cs index 03fabecd..2aa9ee0b 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent.cs @@ -6,17 +6,47 @@ namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_2 public class CustomModelDataComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) { - public int Value { get; set; } + public List Floats { get; set; } = []; + public List Flags { get; set; } = []; + public List Strings { get; set; } = []; + public List Colors { get; set; } = []; public override void Parse(Queue data) { - Value = DataTypes.ReadNextVarInt(data); + Floats = ReadList(data, static (dataTypes, componentData) => dataTypes.ReadNextFloat(componentData)); + Flags = ReadList(data, static (dataTypes, componentData) => dataTypes.ReadNextBool(componentData)); + Strings = ReadList(data, static (dataTypes, componentData) => dataTypes.ReadNextString(componentData)); + Colors = ReadList(data, static (dataTypes, componentData) => dataTypes.ReadNextInt(componentData)); } public override Queue Serialize() { var data = new List(); - data.AddRange(DataTypes.GetVarInt(Value)); + WriteList(data, Floats, static (dataTypes, value) => dataTypes.GetFloat(value)); + WriteList(data, Flags, static (dataTypes, value) => dataTypes.GetBool(value)); + WriteList(data, Strings, static (dataTypes, value) => dataTypes.GetString(value)); + WriteList(data, Colors, static (_, value) => DataTypes.GetInt(value)); return new Queue(data); } -} \ No newline at end of file + + private List ReadList(Queue data, ReadDelegate read) + { + var count = DataTypes.ReadNextVarInt(data); + var values = new List(count); + + for (var i = 0; i < count; i++) + values.Add(read(DataTypes, data)); + + return values; + } + + private void WriteList(List data, List values, WriteDelegate write) + { + data.AddRange(DataTypes.GetVarInt(values.Count)); + foreach (var value in values) + data.AddRange(write(DataTypes, value)); + } + + private delegate T ReadDelegate(DataTypes dataTypes, Queue data); + private delegate byte[] WriteDelegate(DataTypes dataTypes, T value); +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent1206.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent1206.cs new file mode 100644 index 00000000..5483b384 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/CustomModelDataComponent1206.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6; + +public class CustomModelDataComponent1206(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int Value { get; set; } + + public override void Parse(Queue data) + { + Value = DataTypes.ReadNextVarInt(data); + } + + public override Queue Serialize() + { + return new Queue(DataTypes.GetVarInt(Value)); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent121.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent121.cs new file mode 100644 index 00000000..0b4266fd --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent121.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_21; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; +using MinecraftClient.Protocol.Message; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21; + +public class JukeBoxPlayableComponent121(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public bool IsHolder { get; set; } + public int HolderId { get; set; } + public string? ResourceKey { get; set; } + public SoundEventSubComponent? SoundEvent { get; set; } + public Dictionary? DescriptionNbt { get; set; } + public string Description { get; set; } = string.Empty; + public float Duration { get; set; } + public int ComparatorOutput { get; set; } + public bool ShowTooltip { get; set; } + + public override void Parse(Queue data) + { + IsHolder = DataTypes.ReadNextBool(data); + + if (IsHolder) + { + HolderId = DataTypes.ReadNextVarInt(data); + if (HolderId == 0) + { + SoundEvent = (SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data); + DescriptionNbt = DataTypes.ReadNextNbt(data); + Description = ChatParser.ParseText(DescriptionNbt); + Duration = DataTypes.ReadNextFloat(data); + ComparatorOutput = DataTypes.ReadNextVarInt(data); + } + } + else + { + ResourceKey = DataTypes.ReadNextString(data); + } + + ShowTooltip = DataTypes.ReadNextBool(data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetBool(IsHolder)); + + if (IsHolder) + { + data.AddRange(DataTypes.GetVarInt(HolderId)); + if (HolderId == 0) + { + if (SoundEvent is null) + throw new ArgumentNullException(nameof(SoundEvent), "Inline jukebox song requires a sound event."); + + if (DescriptionNbt is null) + throw new ArgumentNullException(nameof(DescriptionNbt), "Inline jukebox song requires a description."); + + data.AddRange(SoundEvent.Serialize()); + data.AddRange(DataTypes.GetNbt(DescriptionNbt)); + data.AddRange(DataTypes.GetFloat(Duration)); + data.AddRange(DataTypes.GetVarInt(ComparatorOutput)); + } + } + else + { + if (string.IsNullOrEmpty(ResourceKey)) + throw new ArgumentNullException(nameof(ResourceKey), "Resource key is required for key-backed jukebox songs."); + + data.AddRange(DataTypes.GetString(ResourceKey)); + } + + data.AddRange(DataTypes.GetBool(ShowTooltip)); + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_2/PotionContentsComponent1212.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_2/PotionContentsComponent1212.cs new file mode 100644 index 00000000..a729adb0 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_2/PotionContentsComponent1212.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_20_6; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_2; + +public class PotionContentsComponent1212(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public bool HasPotionId { get; set; } + public int PotionId { get; set; } + public bool HasCustomColor { get; set; } + public int CustomColor { get; set; } + public List Effects { get; set; } = []; + public bool HasCustomName { get; set; } + public string? CustomName { get; set; } + + public override void Parse(Queue data) + { + HasPotionId = DataTypes.ReadNextBool(data); + if (HasPotionId) + PotionId = DataTypes.ReadNextVarInt(data); + + HasCustomColor = DataTypes.ReadNextBool(data); + if (HasCustomColor) + CustomColor = DataTypes.ReadNextInt(data); + + var numberOfEffects = DataTypes.ReadNextVarInt(data); + Effects = new List(numberOfEffects); + for (var i = 0; i < numberOfEffects; i++) + Effects.Add((PotionEffectSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.PotionEffect, data)); + + HasCustomName = DataTypes.ReadNextBool(data); + if (HasCustomName) + CustomName = DataTypes.ReadNextString(data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetBool(HasPotionId)); + if (HasPotionId) + data.AddRange(DataTypes.GetVarInt(PotionId)); + + data.AddRange(DataTypes.GetBool(HasCustomColor)); + if (HasCustomColor) + data.AddRange(DataTypes.GetInt(CustomColor)); + + data.AddRange(DataTypes.GetVarInt(Effects.Count)); + foreach (var effect in Effects) + data.AddRange(effect.Serialize()); + + data.AddRange(DataTypes.GetBool(HasCustomName)); + if (HasCustomName && CustomName is not null) + data.AddRange(DataTypes.GetString(CustomName)); + + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/AttributeModifiersComponent1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/AttributeModifiersComponent1215.cs new file mode 100644 index 00000000..f7371eac --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/AttributeModifiersComponent1215.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; + +public class AttributeModifiersComponent1215(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int NumberOfAttributes { get; set; } + public List Attributes { get; set; } = []; + + public override void Parse(Queue data) + { + NumberOfAttributes = DataTypes.ReadNextVarInt(data); + Attributes = new List(NumberOfAttributes); + + for (var i = 0; i < NumberOfAttributes; i++) + Attributes.Add(SubComponentRegistry.ParseSubComponent(SubComponents.Attribute, data)); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(NumberOfAttributes)); + + if (Attributes.Count != NumberOfAttributes) + throw new ArgumentNullException(nameof(Attributes), "Attributes count must match NumberOfAttributes."); + + foreach (var attribute in Attributes) + data.AddRange(attribute.Serialize()); + + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/EquippableComponent1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/EquippableComponent1215.cs new file mode 100644 index 00000000..4376574c --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/EquippableComponent1215.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_21; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; + +public class EquippableComponent1215(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int Slot { get; set; } + public SoundEventSubComponent? EquipSound { get; set; } + public bool HasAssetId { get; set; } + public string? AssetId { get; set; } + public bool HasCameraOverlay { get; set; } + public string? CameraOverlay { get; set; } + public bool HasAllowedEntities { get; set; } + public int AllowedEntitiesType { get; set; } + public string? AllowedEntitiesTag { get; set; } + public List? AllowedEntitiesIds { get; set; } + public bool Dispensable { get; set; } + public bool Swappable { get; set; } + public bool DamageOnHurt { get; set; } + public bool EquipOnInteract { get; set; } + + public override void Parse(Queue data) + { + Slot = DataTypes.ReadNextVarInt(data); + EquipSound = (SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data); + + HasAssetId = DataTypes.ReadNextBool(data); + if (HasAssetId) + AssetId = DataTypes.ReadNextString(data); + + HasCameraOverlay = DataTypes.ReadNextBool(data); + if (HasCameraOverlay) + CameraOverlay = DataTypes.ReadNextString(data); + + HasAllowedEntities = DataTypes.ReadNextBool(data); + if (HasAllowedEntities) + { + AllowedEntitiesType = DataTypes.ReadNextVarInt(data); + if (AllowedEntitiesType == 0) + { + AllowedEntitiesTag = DataTypes.ReadNextString(data); + AllowedEntitiesIds = null; + } + else + { + AllowedEntitiesTag = null; + AllowedEntitiesIds = new List(Math.Max(AllowedEntitiesType - 1, 0)); + for (var i = 0; i < AllowedEntitiesType - 1; i++) + AllowedEntitiesIds.Add(DataTypes.ReadNextVarInt(data)); + } + } + + Dispensable = DataTypes.ReadNextBool(data); + Swappable = DataTypes.ReadNextBool(data); + DamageOnHurt = DataTypes.ReadNextBool(data); + EquipOnInteract = DataTypes.ReadNextBool(data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(Slot)); + + if (EquipSound is null) + throw new ArgumentNullException(nameof(EquipSound), "EquipSound is required."); + + data.AddRange(EquipSound.Serialize()); + + data.AddRange(DataTypes.GetBool(HasAssetId)); + if (HasAssetId && AssetId is not null) + data.AddRange(DataTypes.GetString(AssetId)); + + data.AddRange(DataTypes.GetBool(HasCameraOverlay)); + if (HasCameraOverlay && CameraOverlay is not null) + data.AddRange(DataTypes.GetString(CameraOverlay)); + + data.AddRange(DataTypes.GetBool(HasAllowedEntities)); + if (HasAllowedEntities) + { + data.AddRange(DataTypes.GetVarInt(AllowedEntitiesType)); + if (AllowedEntitiesType == 0 && AllowedEntitiesTag is not null) + { + data.AddRange(DataTypes.GetString(AllowedEntitiesTag)); + } + else if (AllowedEntitiesIds is not null) + { + foreach (var id in AllowedEntitiesIds) + data.AddRange(DataTypes.GetVarInt(id)); + } + } + + data.AddRange(DataTypes.GetBool(Dispensable)); + data.AddRange(DataTypes.GetBool(Swappable)); + data.AddRange(DataTypes.GetBool(DamageOnHurt)); + data.AddRange(DataTypes.GetBool(EquipOnInteract)); + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/JukeBoxPlayableComponent1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/JukeBoxPlayableComponent1215.cs new file mode 100644 index 00000000..10985e31 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/JukeBoxPlayableComponent1215.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_21; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; +using MinecraftClient.Protocol.Message; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; + +public class JukeBoxPlayableComponent1215(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public bool IsHolder { get; set; } + public int HolderId { get; set; } + public string? ResourceKey { get; set; } + public SoundEventSubComponent? SoundEvent { get; set; } + public Dictionary? DescriptionNbt { get; set; } + public string Description { get; set; } = string.Empty; + public float Duration { get; set; } + public int ComparatorOutput { get; set; } + + public override void Parse(Queue data) + { + IsHolder = DataTypes.ReadNextBool(data); + + if (IsHolder) + { + HolderId = DataTypes.ReadNextVarInt(data); + if (HolderId == 0) + { + SoundEvent = (SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data); + DescriptionNbt = DataTypes.ReadNextNbt(data); + Description = ChatParser.ParseText(DescriptionNbt); + Duration = DataTypes.ReadNextFloat(data); + ComparatorOutput = DataTypes.ReadNextVarInt(data); + } + } + else + { + ResourceKey = DataTypes.ReadNextString(data); + } + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetBool(IsHolder)); + + if (IsHolder) + { + data.AddRange(DataTypes.GetVarInt(HolderId)); + if (HolderId == 0) + { + if (SoundEvent is null) + throw new ArgumentNullException(nameof(SoundEvent), "Inline jukebox song requires a sound event."); + + if (DescriptionNbt is null) + throw new ArgumentNullException(nameof(DescriptionNbt), "Inline jukebox song requires a description."); + + data.AddRange(SoundEvent.Serialize()); + data.AddRange(DataTypes.GetNbt(DescriptionNbt)); + data.AddRange(DataTypes.GetFloat(Duration)); + data.AddRange(DataTypes.GetVarInt(ComparatorOutput)); + } + } + else + { + if (string.IsNullOrEmpty(ResourceKey)) + throw new ArgumentNullException(nameof(ResourceKey), "Resource key is required for key-backed jukebox songs."); + + data.AddRange(DataTypes.GetString(ResourceKey)); + } + + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ToolComponent1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ToolComponent1215.cs new file mode 100644 index 00000000..c617fabd --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ToolComponent1215.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_20_6; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; + +public class ToolComponent1215(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int NumberOfRules { get; set; } + public List Rules { get; set; } = []; + public float DefaultMiningSpeed { get; set; } + public int DamagePerBlock { get; set; } + public bool CanDestroyBlocksInCreative { get; set; } + + public override void Parse(Queue data) + { + NumberOfRules = DataTypes.ReadNextVarInt(data); + Rules = new List(NumberOfRules); + + for (var i = 0; i < NumberOfRules; i++) + Rules.Add((RuleSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.Rule, data)); + + DefaultMiningSpeed = DataTypes.ReadNextFloat(data); + DamagePerBlock = DataTypes.ReadNextVarInt(data); + CanDestroyBlocksInCreative = DataTypes.ReadNextBool(data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(NumberOfRules)); + + if (Rules.Count != NumberOfRules) + throw new ArgumentNullException(nameof(Rules), "Rules count must match NumberOfRules."); + + foreach (var rule in Rules) + data.AddRange(rule.Serialize()); + + data.AddRange(DataTypes.GetFloat(DefaultMiningSpeed)); + data.AddRange(DataTypes.GetVarInt(DamagePerBlock)); + data.AddRange(DataTypes.GetBool(CanDestroyBlocksInCreative)); + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/AttributeModifiersComponent1218.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/AttributeModifiersComponent1218.cs new file mode 100644 index 00000000..92ce146e --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/AttributeModifiersComponent1218.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; +using MinecraftClient.Protocol.Message; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; + +public class AttributeModifiersComponent1218(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int NumberOfAttributes { get; set; } + public List Attributes { get; set; } = []; + public List Displays { get; set; } = []; + + public override void Parse(Queue data) + { + NumberOfAttributes = DataTypes.ReadNextVarInt(data); + Attributes = new List(NumberOfAttributes); + Displays = new List(NumberOfAttributes); + + for (var i = 0; i < NumberOfAttributes; i++) + { + Attributes.Add(SubComponentRegistry.ParseSubComponent(SubComponents.Attribute, data)); + Displays.Add(ReadDisplay(data)); + } + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(NumberOfAttributes)); + + if (Attributes.Count != NumberOfAttributes) + throw new ArgumentNullException(nameof(Attributes), "Attributes count must match NumberOfAttributes."); + + if (Displays.Count != NumberOfAttributes) + throw new ArgumentNullException(nameof(Displays), "Displays count must match NumberOfAttributes."); + + for (var i = 0; i < NumberOfAttributes; i++) + { + data.AddRange(Attributes[i].Serialize()); + data.AddRange(SerializeDisplay(Displays[i])); + } + + return new Queue(data); + } + + private AttributeModifierDisplay ReadDisplay(Queue data) + { + var displayType = DataTypes.ReadNextVarInt(data); + return displayType switch + { + 0 => new AttributeModifierDisplay(AttributeModifierDisplayType.Default), + 1 => new AttributeModifierDisplay(AttributeModifierDisplayType.Hidden), + 2 => ReadOverrideDisplay(data), + _ => throw new InvalidDataException($"Unknown attribute modifier display type: {displayType}") + }; + } + + private AttributeModifierDisplay ReadOverrideDisplay(Queue data) + { + var overrideTextNbt = DataTypes.ReadNextNbt(data); + var overrideText = ChatParser.ParseText(overrideTextNbt); + return new AttributeModifierDisplay(AttributeModifierDisplayType.Override, overrideTextNbt, overrideText); + } + + private Queue SerializeDisplay(AttributeModifierDisplay display) + { + var data = new List + { + }; + data.AddRange(DataTypes.GetVarInt((int)display.Type)); + + if (display.Type == AttributeModifierDisplayType.Override) + { + if (display.OverrideTextNbt is null) + throw new ArgumentNullException(nameof(display.OverrideTextNbt), "Override display requires component NBT."); + + data.AddRange(DataTypes.GetNbt(display.OverrideTextNbt)); + } + + return new Queue(data); + } +} + +public sealed record AttributeModifierDisplay( + AttributeModifierDisplayType Type, + Dictionary? OverrideTextNbt = null, + string? OverrideText = null); + +public enum AttributeModifierDisplayType +{ + Default = 0, + Hidden = 1, + Override = 2 +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/EquippableComponent1218.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/EquippableComponent1218.cs new file mode 100644 index 00000000..224e1b05 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_8/EquippableComponent1218.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_21; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; + +public class EquippableComponent1218(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int Slot { get; set; } + public SoundEventSubComponent? EquipSound { get; set; } + public bool HasAssetId { get; set; } + public string? AssetId { get; set; } + public bool HasCameraOverlay { get; set; } + public string? CameraOverlay { get; set; } + public bool HasAllowedEntities { get; set; } + public int AllowedEntitiesType { get; set; } + public string? AllowedEntitiesTag { get; set; } + public List? AllowedEntitiesIds { get; set; } + public bool Dispensable { get; set; } + public bool Swappable { get; set; } + public bool DamageOnHurt { get; set; } + public bool EquipOnInteract { get; set; } + public bool CanBeSheared { get; set; } + public SoundEventSubComponent? ShearingSound { get; set; } + + public override void Parse(Queue data) + { + Slot = DataTypes.ReadNextVarInt(data); + EquipSound = (SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data); + + HasAssetId = DataTypes.ReadNextBool(data); + if (HasAssetId) + AssetId = DataTypes.ReadNextString(data); + + HasCameraOverlay = DataTypes.ReadNextBool(data); + if (HasCameraOverlay) + CameraOverlay = DataTypes.ReadNextString(data); + + HasAllowedEntities = DataTypes.ReadNextBool(data); + if (HasAllowedEntities) + { + AllowedEntitiesType = DataTypes.ReadNextVarInt(data); + if (AllowedEntitiesType == 0) + { + AllowedEntitiesTag = DataTypes.ReadNextString(data); + AllowedEntitiesIds = null; + } + else + { + AllowedEntitiesTag = null; + AllowedEntitiesIds = new List(Math.Max(AllowedEntitiesType - 1, 0)); + for (var i = 0; i < AllowedEntitiesType - 1; i++) + AllowedEntitiesIds.Add(DataTypes.ReadNextVarInt(data)); + } + } + + Dispensable = DataTypes.ReadNextBool(data); + Swappable = DataTypes.ReadNextBool(data); + DamageOnHurt = DataTypes.ReadNextBool(data); + EquipOnInteract = DataTypes.ReadNextBool(data); + CanBeSheared = DataTypes.ReadNextBool(data); + ShearingSound = (SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(Slot)); + + if (EquipSound is null) + throw new ArgumentNullException(nameof(EquipSound), "EquipSound is required."); + + if (ShearingSound is null) + throw new ArgumentNullException(nameof(ShearingSound), "ShearingSound is required."); + + data.AddRange(EquipSound.Serialize()); + + data.AddRange(DataTypes.GetBool(HasAssetId)); + if (HasAssetId && AssetId is not null) + data.AddRange(DataTypes.GetString(AssetId)); + + data.AddRange(DataTypes.GetBool(HasCameraOverlay)); + if (HasCameraOverlay && CameraOverlay is not null) + data.AddRange(DataTypes.GetString(CameraOverlay)); + + data.AddRange(DataTypes.GetBool(HasAllowedEntities)); + if (HasAllowedEntities) + { + data.AddRange(DataTypes.GetVarInt(AllowedEntitiesType)); + if (AllowedEntitiesType == 0 && AllowedEntitiesTag is not null) + { + data.AddRange(DataTypes.GetString(AllowedEntitiesTag)); + } + else if (AllowedEntitiesIds is not null) + { + foreach (var id in AllowedEntitiesIds) + data.AddRange(DataTypes.GetVarInt(id)); + } + } + + data.AddRange(DataTypes.GetBool(Dispensable)); + data.AddRange(DataTypes.GetBool(Swappable)); + data.AddRange(DataTypes.GetBool(DamageOnHurt)); + data.AddRange(DataTypes.GetBool(EquipOnInteract)); + data.AddRange(DataTypes.GetBool(CanBeSheared)); + data.AddRange(ShearingSound.Serialize()); + return new Queue(data); + } +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs index afc5a134..2e0a9eb4 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs @@ -22,7 +22,7 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry RegisterComponent(10, "minecraft:can_place_on"); RegisterComponent(11, "minecraft:can_break"); RegisterComponent(12, "minecraft:attribute_modifiers"); - RegisterComponent(13, "minecraft:custom_model_data"); + RegisterComponent(13, "minecraft:custom_model_data"); RegisterComponent(14, "minecraft:hide_additional_tooltip"); RegisterComponent(15, "minecraft:hide_tooltip"); RegisterComponent(16, "minecraft:repair_cost"); @@ -66,4 +66,4 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry RegisterComponent(54, "minecraft:lock"); RegisterComponent(55, "minecraft:container_loot"); } -} \ No newline at end of file +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs index 076b49d0..922c1d90 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs @@ -23,7 +23,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(10, "minecraft:can_place_on"); RegisterComponent(11, "minecraft:can_break"); RegisterComponent(12, "minecraft:attribute_modifiers"); - RegisterComponent(13, "minecraft:custom_model_data"); + RegisterComponent(13, "minecraft:custom_model_data"); RegisterComponent(14, "minecraft:hide_additional_tooltip"); RegisterComponent(15, "minecraft:hide_tooltip"); RegisterComponent(16, "minecraft:repair_cost"); @@ -52,7 +52,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(39, "minecraft:block_entity_data"); RegisterComponent(40, "minecraft:instrument"); RegisterComponent(41, "minecraft:ominous_bottle_amplifier"); - RegisterComponent(42, "minecraft:jukebox_playable"); + RegisterComponent(42, "minecraft:jukebox_playable"); RegisterComponent(43, "minecraft:recipes"); RegisterComponent(44, "minecraft:lodestone_tracker"); RegisterComponent(45, "minecraft:firework_explosion"); @@ -68,4 +68,4 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(55, "minecraft:lock"); RegisterComponent(56, "minecraft:container_loot"); } -} \ No newline at end of file +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs index c7faf6a4..4c310f8e 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs @@ -4,6 +4,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_2; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_11; using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; @@ -30,7 +31,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(13, "minecraft:enchantments"); RegisterComponent(14, "minecraft:can_place_on"); RegisterComponent(15, "minecraft:can_break"); - RegisterComponent(16, "minecraft:attribute_modifiers"); + RegisterComponent(16, "minecraft:attribute_modifiers"); RegisterComponent(17, "minecraft:custom_model_data"); RegisterComponent(18, "minecraft:tooltip_display"); RegisterComponent(19, "minecraft:repair_cost"); @@ -42,11 +43,11 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(25, "minecraft:use_remainder"); RegisterComponent(26, "minecraft:use_cooldown"); RegisterComponent(27, "minecraft:damage_resistant"); - RegisterComponent(28, "minecraft:tool"); + RegisterComponent(28, "minecraft:tool"); RegisterComponent(29, "minecraft:weapon"); RegisterComponent(30, "minecraft:attack_range"); RegisterComponent(31, "minecraft:enchantable"); - RegisterComponent(32, "minecraft:equippable"); + RegisterComponent(32, "minecraft:equippable"); RegisterComponent(33, "minecraft:repairable"); RegisterComponent(34, "minecraft:glider"); RegisterComponent(35, "minecraft:tooltip_style"); @@ -63,7 +64,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(46, "minecraft:map_post_processing"); RegisterComponent(47, "minecraft:charged_projectiles"); RegisterComponent(48, "minecraft:bundle_contents"); - RegisterComponent(49, "minecraft:potion_contents"); + RegisterComponent(49, "minecraft:potion_contents"); RegisterComponent(50, "minecraft:potion_duration_scale"); RegisterComponent(51, "minecraft:suspicious_stew_effects"); RegisterComponent(52, "minecraft:writable_book_content"); @@ -76,7 +77,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(59, "minecraft:instrument"); RegisterComponent(60, "minecraft:provides_trim_material"); RegisterComponent(61, "minecraft:ominous_bottle_amplifier"); - RegisterComponent(62, "minecraft:jukebox_playable"); + RegisterComponent(62, "minecraft:jukebox_playable"); RegisterComponent(63, "minecraft:provides_banner_patterns"); RegisterComponent(64, "minecraft:recipes"); RegisterComponent(65, "minecraft:lodestone_tracker"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs index 361cbfae..79c9475e 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs @@ -25,7 +25,7 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry RegisterComponent(11, "minecraft:can_place_on"); RegisterComponent(12, "minecraft:can_break"); RegisterComponent(13, "minecraft:attribute_modifiers"); - RegisterComponent(14, "minecraft:custom_model_data"); + RegisterComponent(14, "minecraft:custom_model_data"); RegisterComponent(15, "minecraft:hide_additional_tooltip"); RegisterComponent(16, "minecraft:hide_tooltip"); RegisterComponent(17, "minecraft:repair_cost"); @@ -52,7 +52,7 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry RegisterComponent(38, "minecraft:map_post_processing"); RegisterComponent(39, "minecraft:charged_projectiles"); RegisterComponent(40, "minecraft:bundle_contents"); - RegisterComponent(41, "minecraft:potion_contents"); + RegisterComponent(41, "minecraft:potion_contents"); RegisterComponent(42, "minecraft:suspicious_stew_effects"); RegisterComponent(43, "minecraft:writable_book_content"); RegisterComponent(44, "minecraft:written_book_content"); @@ -63,7 +63,7 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry RegisterComponent(49, "minecraft:block_entity_data"); RegisterComponent(50, "minecraft:instrument"); RegisterComponent(51, "minecraft:ominous_bottle_amplifier"); - RegisterComponent(52, "minecraft:jukebox_playable"); + RegisterComponent(52, "minecraft:jukebox_playable"); RegisterComponent(53, "minecraft:recipes"); RegisterComponent(54, "minecraft:lodestone_tracker"); RegisterComponent(55, "minecraft:firework_explosion"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs index a0b8a4c7..7649bcbf 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs @@ -4,6 +4,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_2; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries; @@ -26,7 +27,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(10, "minecraft:enchantments"); RegisterComponent(11, "minecraft:can_place_on"); RegisterComponent(12, "minecraft:can_break"); - RegisterComponent(13, "minecraft:attribute_modifiers"); + RegisterComponent(13, "minecraft:attribute_modifiers"); RegisterComponent(14, "minecraft:custom_model_data"); // 15: tooltip_display (NEW, replaces hide_additional_tooltip + hide_tooltip) RegisterComponent(15, "minecraft:tooltip_display"); @@ -39,10 +40,10 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(22, "minecraft:use_remainder"); RegisterComponent(23, "minecraft:use_cooldown"); RegisterComponent(24, "minecraft:damage_resistant"); - RegisterComponent(25, "minecraft:tool"); + RegisterComponent(25, "minecraft:tool"); RegisterComponent(26, "minecraft:weapon"); // NEW RegisterComponent(27, "minecraft:enchantable"); - RegisterComponent(28, "minecraft:equippable"); + RegisterComponent(28, "minecraft:equippable"); RegisterComponent(29, "minecraft:repairable"); RegisterComponent(30, "minecraft:glider"); RegisterComponent(31, "minecraft:tooltip_style"); @@ -56,7 +57,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(39, "minecraft:map_post_processing"); RegisterComponent(40, "minecraft:charged_projectiles"); RegisterComponent(41, "minecraft:bundle_contents"); - RegisterComponent(42, "minecraft:potion_contents"); + RegisterComponent(42, "minecraft:potion_contents"); RegisterComponent(43, "minecraft:potion_duration_scale"); // NEW RegisterComponent(44, "minecraft:suspicious_stew_effects"); RegisterComponent(45, "minecraft:writable_book_content"); @@ -69,7 +70,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(52, "minecraft:instrument"); // Changed to EitherHolder in 1.21.5 RegisterComponent(53, "minecraft:provides_trim_material"); // NEW RegisterComponent(54, "minecraft:ominous_bottle_amplifier"); - RegisterComponent(55, "minecraft:jukebox_playable"); + RegisterComponent(55, "minecraft:jukebox_playable"); RegisterComponent(56, "minecraft:provides_banner_patterns"); // NEW RegisterComponent(57, "minecraft:recipes"); RegisterComponent(58, "minecraft:lodestone_tracker"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs index 19644397..bd148ab3 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs @@ -48,4 +48,16 @@ public class StructuredComponentsHandler { return ComponentRegistry.ParseComponent(componentId, data); } -} \ No newline at end of file + + public string GetComponentName(int componentId) + { + try + { + return ComponentRegistry.GetComponentNameById(componentId); + } + catch + { + return ""; + } + } +} From c43b157b554d6d0d9b37ed144a86dab25bcdeaf7 Mon Sep 17 00:00:00 2001 From: Anon Date: Wed, 25 Mar 2026 02:08:59 +0100 Subject: [PATCH 10/10] More fixed for 1.21.5, 1.21.9, 1.21.11, stress tested against everything from 1.20.6 - 1.21.11 --- .../1_21_5/InstrumentComponent1215.cs | 7 +-- .../1_21_5/PaintingVariantHolderComponent.cs | 4 +- .../1_21_5/ProvidesTrimMaterialComponent.cs | 4 +- .../Components/1_21_9/BeesComponent1219.cs | 50 +++++++++++++++++++ .../StructuredComponentsRegistry12111.cs | 3 +- .../StructuredComponentsRegistry1215.cs | 19 +++++-- 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_9/BeesComponent1219.cs diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/InstrumentComponent1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/InstrumentComponent1215.cs index e8780324..beba31d8 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/InstrumentComponent1215.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/InstrumentComponent1215.cs @@ -16,7 +16,7 @@ public class InstrumentComponent1215(DataTypes dataTypes, ItemPalette itemPalett var holderId = DataTypes.ReadNextVarInt(data); if (holderId == 0) { - // Inline Instrument: SoundEvent holder + VarInt useDuration + Float range + Component description + // Inline Instrument: SoundEvent holder + Float useDuration + Float range + Component description var soundHolderId = DataTypes.ReadNextVarInt(data); if (soundHolderId == 0) { @@ -25,9 +25,10 @@ public class InstrumentComponent1215(DataTypes dataTypes, ItemPalette itemPalett if (hasFixedRange) DataTypes.ReadNextFloat(data); } - DataTypes.ReadNextVarInt(data); // useDuration + DataTypes.ReadNextFloat(data); // useDuration DataTypes.ReadNextFloat(data); // range - DataTypes.ReadNextString(data); // description (Component as JSON string) + // ComponentSerialization.STREAM_CODEC is NBT-backed, not a plain string. + DataTypes.ReadNextNbt(data); } } else diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/PaintingVariantHolderComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/PaintingVariantHolderComponent.cs index 0065441a..4e061f13 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/PaintingVariantHolderComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/PaintingVariantHolderComponent.cs @@ -20,11 +20,11 @@ public class PaintingVariantHolderComponent(DataTypes dataTypes, ItemPalette ite // Optional title if (DataTypes.ReadNextBool(data)) - DataTypes.ReadNextString(data); + DataTypes.ReadNextNbt(data); // Optional author if (DataTypes.ReadNextBool(data)) - DataTypes.ReadNextString(data); + DataTypes.ReadNextNbt(data); } } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ProvidesTrimMaterialComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ProvidesTrimMaterialComponent.cs index a8e5725a..f3ad9fac 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ProvidesTrimMaterialComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/ProvidesTrimMaterialComponent.cs @@ -25,8 +25,8 @@ public class ProvidesTrimMaterialComponent(DataTypes dataTypes, ItemPalette item DataTypes.ReadNextString(data); // ResourceKey DataTypes.ReadNextString(data); // override suffix } - // description Component - DataTypes.ReadNextString(data); + // ComponentSerialization.STREAM_CODEC is NBT-backed, not a plain string. + DataTypes.ReadNextNbt(data); } } else diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_9/BeesComponent1219.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_9/BeesComponent1219.cs new file mode 100644 index 00000000..b8142caa --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_9/BeesComponent1219.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using MinecraftClient.Inventory.ItemPalettes; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; + +namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_9; + +public class BeesComponent1219(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : StructuredComponent(dataTypes, itemPalette, subComponentRegistry) +{ + public int NumberOfBees { get; set; } + public List Bees { get; set; } = []; + + public override void Parse(Queue data) + { + NumberOfBees = DataTypes.ReadNextVarInt(data); + Bees = new List(NumberOfBees); + + for (var i = 0; i < NumberOfBees; i++) + { + Bees.Add( + new TypedBee( + DataTypes.ReadNextVarInt(data), + DataTypes.ReadNextNbt(data), + DataTypes.ReadNextVarInt(data), + DataTypes.ReadNextVarInt(data))); + } + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(NumberOfBees)); + + if (NumberOfBees != Bees.Count) + throw new InvalidOperationException("Can't serialize the BeesComponent1219 because NumberOfBees and Bees.Count differ!"); + + foreach (var bee in Bees) + { + data.AddRange(DataTypes.GetVarInt(bee.EntityTypeId)); + data.AddRange(DataTypes.GetNbt(bee.EntityDataNbt)); + data.AddRange(DataTypes.GetVarInt(bee.TicksInHive)); + data.AddRange(DataTypes.GetVarInt(bee.MinTicksInHive)); + } + + return new Queue(data); + } +} + +public sealed record TypedBee(int EntityTypeId, Dictionary? EntityDataNbt, int TicksInHive, int MinTicksInHive); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs index 4c310f8e..7e9ec9c7 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs @@ -5,6 +5,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_2; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_9; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_11; using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; @@ -90,7 +91,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(72, "minecraft:pot_decorations"); RegisterComponent(73, "minecraft:container"); RegisterComponent(74, "minecraft:block_state"); - RegisterComponent(75, "minecraft:bees"); + RegisterComponent(75, "minecraft:bees"); RegisterComponent(76, "minecraft:lock"); RegisterComponent(77, "minecraft:container_loot"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs index 7649bcbf..3980e34d 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs @@ -5,6 +5,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_2; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_8; +using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_9; using MinecraftClient.Protocol.Handlers.StructuredComponents.Core; namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries; @@ -14,6 +15,9 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry public StructuredComponentsRegistry1215(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : base(dataTypes, itemPalette, subComponentRegistry) { + var uses1218AttributeAndEquippableFormats = dataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_6_Version; + var usesTypedBeesFormat = dataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_9_Version; + RegisterComponent(0, "minecraft:custom_data"); RegisterComponent(1, "minecraft:max_stack_size"); RegisterComponent(2, "minecraft:max_damage"); @@ -27,7 +31,10 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(10, "minecraft:enchantments"); RegisterComponent(11, "minecraft:can_place_on"); RegisterComponent(12, "minecraft:can_break"); - RegisterComponent(13, "minecraft:attribute_modifiers"); + if (uses1218AttributeAndEquippableFormats) + RegisterComponent(13, "minecraft:attribute_modifiers"); + else + RegisterComponent(13, "minecraft:attribute_modifiers"); RegisterComponent(14, "minecraft:custom_model_data"); // 15: tooltip_display (NEW, replaces hide_additional_tooltip + hide_tooltip) RegisterComponent(15, "minecraft:tooltip_display"); @@ -43,7 +50,10 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(25, "minecraft:tool"); RegisterComponent(26, "minecraft:weapon"); // NEW RegisterComponent(27, "minecraft:enchantable"); - RegisterComponent(28, "minecraft:equippable"); + if (uses1218AttributeAndEquippableFormats) + RegisterComponent(28, "minecraft:equippable"); + else + RegisterComponent(28, "minecraft:equippable"); RegisterComponent(29, "minecraft:repairable"); RegisterComponent(30, "minecraft:glider"); RegisterComponent(31, "minecraft:tooltip_style"); @@ -83,7 +93,10 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(65, "minecraft:pot_decorations"); RegisterComponent(66, "minecraft:container"); RegisterComponent(67, "minecraft:block_state"); - RegisterComponent(68, "minecraft:bees"); // Wire format unchanged in 1.21.5 + if (usesTypedBeesFormat) + RegisterComponent(68, "minecraft:bees"); + else + RegisterComponent(68, "minecraft:bees"); RegisterComponent(69, "minecraft:lock"); RegisterComponent(70, "minecraft:container_loot");