First Version of Structured Components

This commit is contained in:
Anon 2024-09-01 20:42:39 +02:00
parent 5a6fd577e5
commit 63b027d84a
11 changed files with 314 additions and 26 deletions

View file

@ -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
/// <summary>
/// Handle data types encoding / decoding
/// </summary>
class DataTypes
public class DataTypes
{
/// <summary>
/// Protocol version for adjusting data types
@ -419,37 +421,92 @@ namespace MinecraftClient.Protocol.Handlers
/// <returns>The item that was read or NULL for an empty slot</returns>
public Item? ReadNextItemSlot(Queue<byte> 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<string, object>;
var item = null as Item;
var strcturedComponentsToAdd = new List<StructuredComponent>();
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<byte> 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);
}
}

View file

@ -7,7 +7,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Wrapper for handling unencrypted & encrypted socket
/// </summary>
class SocketWrapper
public class SocketWrapper
{
readonly TcpClient c;
AesCfb8Stream? s;

View file

@ -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<byte> data)
{
Test = DataTypes.ReadNextVarInt(data);
}
public override Queue<byte> Serialize()
{
throw new System.NotImplementedException();
}
}

View file

@ -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<byte> data)
{
TestInt = dataTypes.ReadNextVarInt(data);
TestString = dataTypes.ReadNextString(data);
TestSubComonent = (SubComponentRegistry.ParseSubComponent("TestSubComponent", data) as TestSubComonent)!;
}
public override Queue<byte> Serialize()
{
var data = new List<byte>();
data.AddRange(DataTypes.GetVarInt(TestInt));
data.AddRange(DataTypes.GetString(TestString));
data.AddRange(TestSubComonent.Serialize());
return new Queue<byte>(data);
}
}

View file

@ -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<byte> data);
public abstract Queue<byte> Serialize();
}

View file

@ -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<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, 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}");
}
}

View file

@ -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<byte> data);
public abstract Queue<byte> Serialize();
}

View file

@ -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<string, Type> _subComponentParsers = new();
protected void RegisterSubComponent<T>(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<byte> 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;
}
}

View file

@ -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<TestComponent>(0, "minecraft:test");
}
}

View file

@ -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<TestSubComonent>("TestSubcomponent");
}
}

View file

@ -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<byte> data)
{
return ComponentRegistry.ParseComponent(componentId, data);
}
}