Minecraft-Console-Client/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs
BruceChen 8eac21b4a4 Wire up 1.20.6 structured components to Item and fix GetItemSlot serialization
In 1.20.6+, items use structured components instead of NBT for metadata.
Previously, ReadNextItemSlot parsed the components but never stored them
on the Item instance, leaving DisplayName/Lores/Damage/Enchantments all
empty. GetItemSlot also still used the pre-1.20.6 format (bool + VarInt +
byte + NBT), causing the server to reject any item operation packets.

Changes:

Item.cs:
- Add List<StructuredComponent>? Components field to hold the raw
  component list for round-trip serialization
- DisplayName property: read from CustomNameComponent (with
  ItemNameComponent as fallback) when Components is present
- Lores property: read from LoreNameComponent1206 when Components is
  present
- Damage property: read from DamageComponent when Components is present
- Add EnchantmentList property: read from EnchantmentsComponent (covers
  both normal and StoredEnchantmentsComponent for enchanted books)
- ToFullString(): use EnchantmentList with EnchantmentMapping for display
  when available, fall back to NBT path for older versions
- Add CloneWithCount() method that preserves both NBT and Components

DataTypes.cs - ReadNextItemSlot:
- Assign parsed strcturedComponentsToAdd to item.Components

DataTypes.cs - GetItemSlot:
- Add 1.20.6+ branch: write VarInt(count) + VarInt(itemId) + component
  counts + serialized components (using each component's TypeId and
  Serialize() method)
- Empty slot sends VarInt(0) per the 1.20.6 protocol spec

StructuredComponent.cs:
- Add int TypeId property (default -1) to store the registry type ID
  assigned during parsing, enabling round-trip serialization

StructuredComponentRegistry.cs:
- Set component.TypeId = id after instantiation in ParseComponent()

McClient.cs:
- Replace manual Item constructor calls (new Item(type, count, nbt))
  with Item.CloneWithCount() to preserve Components during inventory
  operations like slot moves, stack splits, and right-click placement

Made-with: Cursor
2026-03-19 00:34:10 +08:00

65 lines
No EOL
2.4 KiB
C#

using System;
using System.Collections.Generic;
using MinecraftClient.Inventory.ItemPalettes;
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
public abstract class StructuredComponentRegistry(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
{
private Dictionary<string, Type> ComponentParsers { get; } = new();
private Dictionary<int, string> IdToComponent { get; } = new();
private Dictionary<string, int> ComponentToId { get; } = new();
protected void RegisterComponent<T>(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<byte> data)
{
if (IdToComponent.TryGetValue(id, out var name))
{
if (ComponentParsers.TryGetValue(name, out var type))
{
var component =
Activator.CreateInstance(type, dataTypes, itemPalette, subComponentRegistry) as StructuredComponent
?? throw new InvalidOperationException($"Could not instantiate a parser for a structured component type {name}");
component.TypeId = id;
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}");
}
}