diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs
index d3b77945..b62d511c 100644
--- a/MinecraftClient/Protocol/Handlers/DataTypes.cs
+++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs
@@ -6,6 +6,8 @@ using MinecraftClient.Inventory;
using MinecraftClient.Inventory.ItemPalettes;
using MinecraftClient.Mapping;
using MinecraftClient.Mapping.EntityPalettes;
+using MinecraftClient.Protocol.Handlers.StructuredComponents;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
using MinecraftClient.Protocol.Message;
namespace MinecraftClient.Protocol.Handlers
@@ -13,7 +15,7 @@ namespace MinecraftClient.Protocol.Handlers
///
/// Handle data types encoding / decoding
///
- class DataTypes
+ public class DataTypes
{
///
/// Protocol version for adjusting data types
@@ -419,37 +421,92 @@ namespace MinecraftClient.Protocol.Handlers
/// The item that was read or NULL for an empty slot
public Item? ReadNextItemSlot(Queue cache, ItemPalette itemPalette)
{
- // MC 1.13.2 and greater
- if (protocolversion >= Protocol18Handler.MC_1_13_Version)
+ var itemId = -1;
+ var itemCount = 0;
+ var nbt = null as Dictionary;
+ var item = null as Item;
+ var strcturedComponentsToAdd = new List();
+
+ switch (protocolversion)
{
- var itemPresent = ReadNextBool(cache);
+ // MC 1.13.2 and greater
+ case >= Protocol18Handler.MC_1_20_6_Version:
+ itemCount = ReadNextVarInt(cache);
- if (!itemPresent)
- return null;
+ if (itemCount <= 0) return null;
+
+ itemId = ReadNextVarInt(cache);
+ item = new Item(itemPalette.FromId(itemId), itemCount, null);
+
+ var numberOfComponentsToAdd = ReadNextVarInt(cache);
+ var numberofComponentsToRemove = ReadNextVarInt(cache);
- var itemId = ReadNextVarInt(cache);
+ for (var i = 0; i < numberOfComponentsToAdd; i++)
+ {
+ var componentTypeId = ReadNextVarInt(cache);
- if (itemId == -1)
- return null;
+ var strcuturedComponentHandler = new StructuredComponentsHandler(protocolversion, this);
+ strcturedComponentsToAdd.Add(strcuturedComponentHandler.Parse(componentTypeId, cache));
+ }
- var type = itemPalette.FromId(itemId);
- var itemCount = ReadNextByte(cache);
- var nbt = ReadNextNbt(cache);
- return new Item(type, itemCount, nbt);
+ for (var i = 0; i < numberofComponentsToRemove; i++)
+ {
+ // TODO
+ }
+
+ // TODO: Wire up the strctured components in the Item class (extract info, update fields, etc..)
+ // Look at: https://wiki.vg/index.php?title=Slot_Data&oldid=19350#Structured_components
+
+ return item;
+ case >= Protocol18Handler.MC_1_13_Version:
+ {
+ var itemPresent = ReadNextBool(cache);
+
+ if (!itemPresent)
+ return null;
+
+ itemId = ReadNextVarInt(cache);
+
+ if (itemId == -1)
+ return null;
+
+ var type = itemPalette.FromId(itemId);
+ itemCount = ReadNextByte(cache);
+ nbt = ReadNextNbt(cache);
+ return new Item(type, itemCount, nbt);
+ }
+ default:
+ {
+ itemId = ReadNextShort(cache);
+
+ if (itemId == -1)
+ return null;
+
+ itemCount = ReadNextByte(cache);
+ var data = ReadNextShort(cache);
+ nbt = ReadNextNbt(cache);
+
+ // For 1.8 - 1.12.2 we combine Item Id and Item Data/Damage to a single value using: (id << 16) | data
+ return new Item(itemPalette.FromId((itemId << 16) | (ushort)data), itemCount, data, nbt);
+ }
}
- else
+ }
+
+ private void ReadNextDetail(Queue cache)
+ {
+ var potionEffectId = ReadNextVarInt(cache);
+
+ // Details
+ var potionEffectAmplifier = ReadNextVarInt(cache);
+ var duration = ReadNextVarInt(cache); // -1 for infinite
+ var ambient = ReadNextBool(cache);
+ var showParticles = ReadNextBool(cache);
+ var showIcon = ReadNextBool(cache);
+ var hasHiddenEffect = ReadNextBool(cache);
+
+ if (hasHiddenEffect)
{
- var itemId = ReadNextShort(cache);
-
- if (itemId == -1)
- return null;
-
- var itemCount = ReadNextByte(cache);
- var data = ReadNextShort(cache);
- var nbt = ReadNextNbt(cache);
-
- // For 1.8 - 1.12.2 we combine Item Id and Item Data/Damage to a single value using: (id << 16) | data
- return new Item(itemPalette.FromId((itemId << 16) | (ushort)data), itemCount, data, nbt);
+ ReadNextDetail(cache);
}
}
diff --git a/MinecraftClient/Protocol/Handlers/SocketWrapper.cs b/MinecraftClient/Protocol/Handlers/SocketWrapper.cs
index d9793024..e74fc84a 100644
--- a/MinecraftClient/Protocol/Handlers/SocketWrapper.cs
+++ b/MinecraftClient/Protocol/Handlers/SocketWrapper.cs
@@ -7,7 +7,7 @@ namespace MinecraftClient.Protocol.Handlers
///
/// Wrapper for handling unencrypted & encrypted socket
///
- class SocketWrapper
+ public class SocketWrapper
{
readonly TcpClient c;
AesCfb8Stream? s;
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs
new file mode 100644
index 00000000..fbed4b70
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/Subcomponents/TestSubComonent.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents;
+
+public class TestSubComonent(DataTypes dataTypes) : SubComponent(dataTypes)
+{
+ public int Test { get; set; }
+
+ public override void Parse(Queue data)
+ {
+ Test = DataTypes.ReadNextVarInt(data);
+ }
+
+ public override Queue Serialize()
+ {
+ throw new System.NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs
new file mode 100644
index 00000000..befa1217
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TestComponent.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
+
+public class TestComponent(DataTypes dataTypes, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, subComponentRegistry)
+{
+ public int TestInt { get; set; }
+ public string TestString { get; set; } = null!;
+
+ public TestSubComonent TestSubComonent { get; set; } = null!;
+
+ public override void Parse(Queue data)
+ {
+ TestInt = dataTypes.ReadNextVarInt(data);
+ TestString = dataTypes.ReadNextString(data);
+ TestSubComonent = (SubComponentRegistry.ParseSubComponent("TestSubComponent", data) as TestSubComonent)!;
+ }
+
+ public override Queue Serialize()
+ {
+ var data = new List();
+ data.AddRange(DataTypes.GetVarInt(TestInt));
+ data.AddRange(DataTypes.GetString(TestString));
+ data.AddRange(TestSubComonent.Serialize());
+ return new Queue(data);
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs
new file mode 100644
index 00000000..01995dc2
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+public abstract class StructuredComponent(DataTypes dataTypes, SubComponentRegistry subComponentRegistry)
+{
+ protected DataTypes DataTypes { get; private set; } = dataTypes;
+ protected SubComponentRegistry SubComponentRegistry { get; private set; } = subComponentRegistry;
+
+ public abstract void Parse(Queue data);
+ public abstract Queue Serialize();
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs
new file mode 100644
index 00000000..4dffe457
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+public abstract class StructuredComponentRegistry(SubComponentRegistry subComponentRegistry, DataTypes dataTypes)
+{
+ private Dictionary ComponentParsers { get; } = new();
+ private Dictionary IdToComponent { get; } = new();
+ private Dictionary ComponentToId { get; } = new();
+
+ protected void RegisterComponent(int id, string name) where T : StructuredComponent
+ {
+ if (string.IsNullOrEmpty(name) || string.IsNullOrWhiteSpace(name))
+ throw new ArgumentNullException(nameof(name));
+
+ name = name.ToLower();
+
+ if (ComponentParsers.ContainsKey(name) || IdToComponent.ContainsValue(name)
+ || ComponentToId.ContainsKey(name) || IdToComponent.ContainsKey(id))
+ throw new InvalidOperationException($"A component with name '{name}' or id '{id}' is already registered.");
+
+ ComponentParsers[name] = typeof(T);
+ IdToComponent[id] = name;
+ ComponentToId[name] = id;
+ }
+
+ public StructuredComponent ParseComponent(int id, Queue data)
+ {
+ if (IdToComponent.TryGetValue(id, out var name))
+ {
+ if (ComponentParsers.TryGetValue(name, out var type))
+ {
+ var component =
+ Activator.CreateInstance(type, dataTypes, subComponentRegistry) as StructuredComponent
+ ?? throw new InvalidOperationException($"Could not instantiate a parser for a structured component type {name}");
+
+ component.Parse(data);
+ return component;
+ }
+ }
+
+ throw new Exception($"No parser found for component with ID {id}");
+ }
+
+ public string GetComponentNameById(int id)
+ {
+ if (IdToComponent.TryGetValue(id, out var value))
+ return value;
+
+ throw new Exception($"No component found for ID {id}");
+ }
+
+ public int GetComponentIdByName(string name)
+ {
+ name = name.ToLower();
+
+ if (ComponentToId.TryGetValue(name, out var value))
+ return value;
+
+ throw new Exception($"No ID found for component {name}");
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs
new file mode 100644
index 00000000..af63dce8
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponent.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+public abstract class SubComponent(DataTypes dataTypes)
+{
+ protected DataTypes DataTypes { get; private set; } = dataTypes;
+
+ public abstract void Parse(Queue data);
+ public abstract Queue Serialize();
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs
new file mode 100644
index 00000000..844107c1
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/SubComponentRegistry.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+public abstract class SubComponentRegistry(DataTypes dataTypes)
+{
+ private readonly Dictionary _subComponentParsers = new();
+
+ protected void RegisterSubComponent(string name) where T : SubComponent
+ {
+ if(_subComponentParsers.TryGetValue(name, out _))
+ throw new Exception($"Sub component {name} already registered!");
+
+ _subComponentParsers.Add(name, typeof(T));
+ }
+
+ public SubComponent ParseSubComponent(string name, Queue data)
+ {
+ if(!_subComponentParsers.TryGetValue(name, out var subComponentParserType))
+ throw new Exception($"Sub component {name} not registered!");
+
+ var instance= Activator.CreateInstance(subComponentParserType, dataTypes) as SubComponent ??
+ throw new InvalidOperationException($"Could not create instance of a sub component parser type: {subComponentParserType.Name}");
+
+ instance.Parse(data);
+ return instance;
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs
new file mode 100644
index 00000000..d69507ab
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs
@@ -0,0 +1,13 @@
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries;
+
+public class StructuredComponentsRegistry1206 : StructuredComponentRegistry
+{
+ public StructuredComponentsRegistry1206(SubComponentRegistry subComponentRegistry, DataTypes dataTypes) : base(
+ subComponentRegistry, dataTypes)
+ {
+ RegisterComponent(0, "minecraft:test");
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs
new file mode 100644
index 00000000..c10bbc49
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/Subcomponents/TestSubComponentRegistry.cs
@@ -0,0 +1,12 @@
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries.Subcomponents;
+
+public class TestSubComponentRegistry : SubComponentRegistry
+{
+ public TestSubComponentRegistry(DataTypes dataTypes) : base(dataTypes)
+ {
+ RegisterSubComponent("TestSubcomponent");
+ }
+}
\ No newline at end of file
diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs
new file mode 100644
index 00000000..6a03e040
--- /dev/null
+++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/StructuredComponentsHandler.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using MinecraftClient.Inventory.ItemPalettes;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Registries;
+using MinecraftClient.Protocol.Handlers.StructuredComponents.Registries.Subcomponents;
+
+namespace MinecraftClient.Protocol.Handlers.StructuredComponents;
+
+public class StructuredComponentsHandler
+{
+ private StructuredComponentRegistry ComponentRegistry { get; }
+
+ public StructuredComponentsHandler(
+ int protocolVersion,
+ DataTypes dataTypes)
+ {
+ // Get the appropriate subcomponent registry type based on the protocol version and then instantiate it
+ var subcomponentRegistryType = protocolVersion switch
+ {
+ Protocol18Handler.MC_1_20_6_Version => typeof(TestSubComponentRegistry),
+ _ => throw new NotSupportedException($"Protocol version {protocolVersion} is not supported for subcomponent registries!")
+ };
+
+ var subcomponentRegistry = Activator.CreateInstance(subcomponentRegistryType, dataTypes) as SubComponentRegistry
+ ?? throw new InvalidOperationException($"Failed to instantiate a component registry for type {nameof(subcomponentRegistryType)}");
+
+ // Get the appropriate component registry type based on the protocol version and then instantiate it
+ var registryType = protocolVersion switch
+ {
+ Protocol18Handler.MC_1_20_6_Version => typeof(StructuredComponentsRegistry1206),
+ _ => throw new NotSupportedException($"Protocol version {protocolVersion} is not supported for structured component registries!")
+ };
+
+ ComponentRegistry = Activator.CreateInstance(registryType, subcomponentRegistry, dataTypes) as StructuredComponentRegistry
+ ?? throw new InvalidOperationException($"Failed to instantiate a component registry for type {nameof(registryType)}");
+ }
+
+ public StructuredComponent Parse(int componentId, Queue data)
+ {
+ return ComponentRegistry.ParseComponent(componentId, data);
+ }
+}
\ No newline at end of file