From f34b8ba989894fef4ca5b332dd0c26b092838934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dtalo=20Seara?= Date: Sat, 27 Jun 2026 14:41:41 -0300 Subject: [PATCH] feat(inventory): expose NBT/component data in inventory MCP tools Add a nullable `Nbt` field to `MccInventorySnapshotSlot`, `MccInventorySearchMatch`, and `MccItemStackSnapshot` so that callers can access the full item metadata beyond just material and count. For 1.20.6+ servers the field is a map of component name (e.g. "minecraft:custom_data") to the serialized component object. For legacy servers it is the raw NBT dictionary. The field is omitted entirely for vanilla items that carry no extra data, so there is no noise for plain items like Stone or Dirt. Implementation: - `StructuredComponent`: add `ComponentName` property (set by the registry during parse) and mark both it and `TypeId` with `[JsonIgnore]` so they are excluded from component serialization. - `StructuredComponentRegistry.ParseComponent`: assign `ComponentName` after instantiation. - `MccGameCommon.BuildNbt`: new helper that serializes components by runtime type (fixing the polymorphism issue with System.Text.Json) keyed by component name, falling back to the legacy NBT dictionary. - Snapshot and search query builders updated to call `BuildNbt`. --- .../Core/StructuredComponent.cs | 8 ++++++++ .../Core/StructuredComponentRegistry.cs | 1 + MinecraftClient/Scripting/MccGameApi.cs | 6 ++++-- MinecraftClient/Scripting/MccGameCommon.cs | 19 ++++++++++++++++++- MinecraftClient/Scripting/MccGameModels.cs | 2 ++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs index 2c0b66e3..5fa5f82d 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text.Json.Serialization; using MinecraftClient.Inventory.ItemPalettes; namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Core; @@ -12,8 +13,15 @@ public abstract class StructuredComponent(DataTypes dataTypes, ItemPalette itemP /// /// The registry type ID assigned during parsing, used for round-trip serialization. /// + [JsonIgnore] public int TypeId { get; set; } = -1; + /// + /// The registry name assigned during parsing (e.g. "minecraft:custom_data"), used for NBT serialization. + /// + [JsonIgnore] + public string ComponentName { get; set; } = string.Empty; + 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 index 9ff1f12c..805f2a79 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Core/StructuredComponentRegistry.cs @@ -37,6 +37,7 @@ public abstract class StructuredComponentRegistry(DataTypes dataTypes, ItemPalet ?? throw new InvalidOperationException($"Could not instantiate a parser for a structured component type {name}"); component.TypeId = id; + component.ComponentName = name; component.Parse(data); return component; } diff --git a/MinecraftClient/Scripting/MccGameApi.cs b/MinecraftClient/Scripting/MccGameApi.cs index b7c8fbbd..9588691b 100644 --- a/MinecraftClient/Scripting/MccGameApi.cs +++ b/MinecraftClient/Scripting/MccGameApi.cs @@ -795,7 +795,8 @@ public sealed class MccGameApi { Slot = item.Key, Type = item.Value.Type.ToString(), - Count = item.Value.Count + Count = item.Value.Count, + Nbt = MccGameCommon.BuildNbt(item.Value) }) .ToArray(); @@ -856,7 +857,8 @@ public sealed class MccGameApi TypeLabel = pair.Value.GetTypeString(), Count = pair.Value.Count, IsPlayerInventory = entry.Key == 0, - HotbarSlot = isHotbar ? hotbar + 1 : null + HotbarSlot = isHotbar ? hotbar + 1 : null, + Nbt = MccGameCommon.BuildNbt(pair.Value) }; }); }) diff --git a/MinecraftClient/Scripting/MccGameCommon.cs b/MinecraftClient/Scripting/MccGameCommon.cs index 0088dc80..ffca5beb 100644 --- a/MinecraftClient/Scripting/MccGameCommon.cs +++ b/MinecraftClient/Scripting/MccGameCommon.cs @@ -35,6 +35,7 @@ public sealed class MccItemStackSnapshot { public required string Type { get; init; } public required int Count { get; init; } + public Dictionary? Nbt { get; init; } } /// @@ -177,10 +178,26 @@ public static class MccGameCommon return new MccItemStackSnapshot { Type = item.Type.ToString(), - Count = item.Count + Count = item.Count, + Nbt = BuildNbt(item) }; } + public static Dictionary? BuildNbt(Item item) + { + if (item.Components is { Count: > 0 }) + { + return item.Components + .Where(c => !string.IsNullOrEmpty(c.ComponentName)) + .ToDictionary( + c => c.ComponentName, + c => (object)System.Text.Json.JsonSerializer.SerializeToElement(c, c.GetType()) + ); + } + + return item.NBT is { Count: > 0 } ? item.NBT : null; + } + public static bool TryParseBlockQuery(string? query, out int? blockId, out int? blockMeta) { blockId = null; diff --git a/MinecraftClient/Scripting/MccGameModels.cs b/MinecraftClient/Scripting/MccGameModels.cs index 0c6b9c52..1eef6476 100644 --- a/MinecraftClient/Scripting/MccGameModels.cs +++ b/MinecraftClient/Scripting/MccGameModels.cs @@ -216,6 +216,7 @@ public sealed class MccInventorySnapshotSlot public required int Slot { get; init; } public required string Type { get; init; } public required int Count { get; init; } + public Dictionary? Nbt { get; init; } } public sealed class MccInventorySnapshotResult @@ -239,6 +240,7 @@ public sealed class MccInventorySearchMatch public required int Count { get; init; } public required bool IsPlayerInventory { get; init; } public int? HotbarSlot { get; init; } + public Dictionary? Nbt { get; init; } } public sealed class MccInventorySearchResult