mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Fix structured components: audit fixes, codec helpers, and serialize implementations
- Fix 5 class naming typos: Unbrekable->Unbreakable, Blook->Book, Omnious->Ominous, FoodComponentComponent->FoodComponent - Fix IntangibleProjectileComponent: extends EmptyComponent (Unit type) - Delete dead code JukeBoxPlayableComponent.cs (duplicated) - Implement proper Serialize() on BlocksAttacksComponent, KineticWeaponComponent, PiercingWeaponComponent - Extract shared SoundEventHolder/HolderSet codec helpers into StructuredComponentCodecHelpers - Add TypedEntityDataComponent/TypedBlockEntityDataComponent base classes for 1.21.9+ typed entity data format - Fix variable name: uses1218AttributeAndEquippableFormats -> uses1216AttributeAndEquippableFormats - Add entity data version gate (1.21.9+) to v1215 registry - Fix chicken/variant and zombie_nautilus/variant component types - Fix empty packet hang in Protocol18 ReadNextPacket loop - Add structured components integration test harness
This commit is contained in:
parent
16637f7eba
commit
de96462659
22 changed files with 761 additions and 260 deletions
|
|
@ -100,7 +100,7 @@ public static class BookContentHelper
|
|||
{
|
||||
if (item.Components is not null)
|
||||
{
|
||||
var component = item.Components.OfType<WritableBlookContentComponent>().FirstOrDefault();
|
||||
var component = item.Components.OfType<WritableBookContentComponent>().FirstOrDefault();
|
||||
if (component is not null)
|
||||
{
|
||||
content = new BookContent(
|
||||
|
|
@ -121,7 +121,7 @@ public static class BookContentHelper
|
|||
{
|
||||
if (item.Components is not null)
|
||||
{
|
||||
var component = item.Components.OfType<WrittenBlookContentComponent>().FirstOrDefault();
|
||||
var component = item.Components.OfType<WrittenBookContentComponent>().FirstOrDefault();
|
||||
if (component is not null)
|
||||
{
|
||||
content = new BookContent(
|
||||
|
|
|
|||
|
|
@ -362,7 +362,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
while (socketWrapper.HasDataAvailable())
|
||||
{
|
||||
packetQueue.Add(ReadNextPacket(), cancelToken);
|
||||
var packet = ReadNextPacket();
|
||||
if (packet.Item1 == -1)
|
||||
continue;
|
||||
packetQueue.Add(packet, cancelToken);
|
||||
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
break;
|
||||
|
|
@ -418,7 +421,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
internal Tuple<int, Queue<byte>> ReadNextPacket()
|
||||
{
|
||||
var size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size
|
||||
Queue<byte> packetData = new(socketWrapper.ReadDataRAW(size)); //Packet contents
|
||||
var rawBytes = socketWrapper.ReadDataRAW(size);
|
||||
Queue<byte> packetData = new(rawBytes); //Packet contents
|
||||
var compressed = false;
|
||||
var sizeUncompressed = 0;
|
||||
|
||||
|
|
@ -436,6 +440,13 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
}
|
||||
|
||||
if (packetData.Count == 0)
|
||||
{
|
||||
var rawHex = rawBytes.Length > 0 ? BitConverter.ToString(rawBytes).Replace("-", " ") : "(empty)";
|
||||
log.Warn($"[DEBUG] Empty packet after decompress: size={size}, sizeUncompressed={sizeUncompressed}, protocol={protocolVersion}, state={currentState}, rawBytes=[{rawHex}]");
|
||||
return new(-1, packetData);
|
||||
}
|
||||
|
||||
var packetId = dataTypes.ReadNextVarInt(packetData); // Packet ID
|
||||
LogIncomingPacket(packetId, packetData.Count, size, compressed, sizeUncompressed);
|
||||
if (handler.GetNetworkPacketCaptureEnabled())
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
|||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class FoodComponentComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
public class FoodComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int Nutrition { get; set; }
|
||||
|
|
@ -5,19 +5,6 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
|||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class IntangibleProjectileComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
: EmptyComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public Dictionary<string, object>? Nbt { get; set; } = new();
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
Nbt = DataTypes.ReadNextNbt(data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetNbt(Nbt));
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
|||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class OmniousBottleAmplifierComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
public class OminousBottleAmplifierComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int Amplifier { get; set; }
|
||||
|
|
@ -4,20 +4,20 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
|||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class UnbrekableComponent1206(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
public class UnbreakableComponent1206(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public bool Unbrekable { get; set; }
|
||||
public bool Unbreakable { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
Unbrekable = DataTypes.ReadNextBool(data);
|
||||
Unbreakable = DataTypes.ReadNextBool(data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetBool(Unbrekable));
|
||||
data.AddRange(DataTypes.GetBool(Unbreakable));
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
|||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class WritableBlookContentComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
public class WritableBookContentComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public List<BookPage> Pages { get; set; } = [];
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ public class WritableBlookContentComponent(DataTypes dataTypes, ItemPalette item
|
|||
if (page.HasFilteredContent)
|
||||
{
|
||||
if (page.FilteredContent is null)
|
||||
throw new InvalidOperationException("Can not serialize WritableBlookContentComponent because page.HasFilteredContent = true, but FilteredContent is null!");
|
||||
throw new InvalidOperationException("Can not serialize WritableBookContentComponent because page.HasFilteredContent = true, but FilteredContent is null!");
|
||||
|
||||
data.AddRange(DataTypes.GetString(page.FilteredContent));
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using MinecraftClient.Protocol.Message;
|
|||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||
|
||||
public class WrittenBlookContentComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
public class WrittenBookContentComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) : StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public string RawTitle { get; set; } = null!;
|
||||
public bool HasFilteredTitle { get; set; }
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
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;
|
||||
|
||||
public class JukeBoxPlayableComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public bool DirectMode { get; set; }
|
||||
public string? SongName { get; set; }
|
||||
public int? SongType { get; set; }
|
||||
public SoundEventSubComponent? SoundEvent { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public float? Duration { get; set; }
|
||||
public int? Output { get; set; }
|
||||
public bool ShowTooltip { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
DirectMode = DataTypes.ReadNextBool(data);
|
||||
|
||||
if (!DirectMode)
|
||||
SongName = DataTypes.ReadNextString(data);
|
||||
|
||||
if (DirectMode)
|
||||
{
|
||||
SongType = DataTypes.ReadNextVarInt(data);
|
||||
|
||||
if (SongType == 0)
|
||||
{
|
||||
SoundEvent =
|
||||
(SoundEventSubComponent)SubComponentRegistry.ParseSubComponent(SubComponents.SoundEvent, data);
|
||||
Description = DataTypes.ReadNextString(data);
|
||||
Duration = DataTypes.ReadNextFloat(data);
|
||||
Output = DataTypes.ReadNextVarInt(data);
|
||||
}
|
||||
}
|
||||
|
||||
ShowTooltip = DataTypes.ReadNextBool(data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
|
||||
data.AddRange(DataTypes.GetBool(DirectMode));
|
||||
|
||||
if (!DirectMode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SongName?.Trim()))
|
||||
throw new ArgumentNullException($"Can not serialize JukeBoxPlayableComponent due to SongName being null or empty!");
|
||||
|
||||
data.AddRange(DataTypes.GetString(SongName));
|
||||
}
|
||||
|
||||
if (DirectMode)
|
||||
{
|
||||
if (SongType is null)
|
||||
throw new ArgumentNullException($"Can not serialize JukeBoxPlayableComponent due to SongType being null!");
|
||||
|
||||
data.AddRange(DataTypes.GetVarInt((int)SongType));
|
||||
|
||||
if (SongType == 0)
|
||||
{
|
||||
if (SoundEvent is null)
|
||||
throw new ArgumentNullException(
|
||||
$"Can not serialize JukeBoxPlayableComponent due to SoundEvent being null");
|
||||
|
||||
data.AddRange(SoundEvent.Serialize());
|
||||
|
||||
if (string.IsNullOrEmpty(Description?.Trim()))
|
||||
throw new ArgumentNullException(
|
||||
$"Can not serialize JukeBoxPlayableComponent due to Description being null or empty!");
|
||||
|
||||
data.AddRange(DataTypes.GetString(Description));
|
||||
|
||||
if (Duration is null)
|
||||
throw new ArgumentNullException(
|
||||
$"Can not serialize JukeBoxPlayableComponent due to Duration being null!");
|
||||
|
||||
data.AddRange(DataTypes.GetFloat((float)Duration));
|
||||
|
||||
if (Output is null)
|
||||
throw new ArgumentNullException(
|
||||
$"Can not serialize JukeBoxPlayableComponent due to Description being null!");
|
||||
|
||||
data.AddRange(DataTypes.GetVarInt((int)Output));
|
||||
}
|
||||
}
|
||||
|
||||
data.AddRange(DataTypes.GetBool(ShowTooltip));
|
||||
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_11;
|
||||
|
|
@ -7,41 +8,65 @@ namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_2
|
|||
public class KineticWeaponComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int ContactCooldownTicks { get; set; }
|
||||
public int DelayTicks { get; set; }
|
||||
public KineticWeaponConditionData? DismountConditions { get; set; }
|
||||
public KineticWeaponConditionData? KnockbackConditions { get; set; }
|
||||
public KineticWeaponConditionData? DamageConditions { get; set; }
|
||||
public float ForwardMovement { get; set; }
|
||||
public float DamageMultiplier { get; set; }
|
||||
public SoundEventHolderData? Sound { get; set; }
|
||||
public SoundEventHolderData? HitSound { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
DataTypes.ReadNextVarInt(data); // contactCooldownTicks
|
||||
DataTypes.ReadNextVarInt(data); // delayTicks
|
||||
ReadOptionalCondition(data); // dismountConditions
|
||||
ReadOptionalCondition(data); // knockbackConditions
|
||||
ReadOptionalCondition(data); // damageConditions
|
||||
DataTypes.ReadNextFloat(data); // forwardMovement
|
||||
DataTypes.ReadNextFloat(data); // damageMultiplier
|
||||
ReadOptionalSoundEventHolder(data); // sound
|
||||
ReadOptionalSoundEventHolder(data); // hitSound
|
||||
ContactCooldownTicks = DataTypes.ReadNextVarInt(data);
|
||||
DelayTicks = DataTypes.ReadNextVarInt(data);
|
||||
DismountConditions = ReadOptionalCondition(data);
|
||||
KnockbackConditions = ReadOptionalCondition(data);
|
||||
DamageConditions = ReadOptionalCondition(data);
|
||||
ForwardMovement = DataTypes.ReadNextFloat(data);
|
||||
DamageMultiplier = DataTypes.ReadNextFloat(data);
|
||||
Sound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
HitSound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
}
|
||||
|
||||
private void ReadOptionalCondition(Queue<byte> data)
|
||||
private KineticWeaponConditionData? ReadOptionalCondition(Queue<byte> data)
|
||||
{
|
||||
if (!DataTypes.ReadNextBool(data)) return;
|
||||
DataTypes.ReadNextVarInt(data); // maxDurationTicks
|
||||
DataTypes.ReadNextFloat(data); // minSpeed
|
||||
DataTypes.ReadNextFloat(data); // minRelativeSpeed
|
||||
if (!DataTypes.ReadNextBool(data))
|
||||
return null;
|
||||
|
||||
return new KineticWeaponConditionData(
|
||||
DataTypes.ReadNextVarInt(data),
|
||||
DataTypes.ReadNextFloat(data),
|
||||
DataTypes.ReadNextFloat(data));
|
||||
}
|
||||
|
||||
private void ReadOptionalSoundEventHolder(Queue<byte> data)
|
||||
private void WriteOptionalCondition(List<byte> data, KineticWeaponConditionData? condition)
|
||||
{
|
||||
if (!DataTypes.ReadNextBool(data)) return;
|
||||
var holderId = DataTypes.ReadNextVarInt(data);
|
||||
if (holderId == 0)
|
||||
{
|
||||
DataTypes.ReadNextString(data);
|
||||
if (DataTypes.ReadNextBool(data))
|
||||
DataTypes.ReadNextFloat(data);
|
||||
}
|
||||
data.AddRange(DataTypes.GetBool(condition is not null));
|
||||
if (condition is null)
|
||||
return;
|
||||
|
||||
data.AddRange(DataTypes.GetVarInt(condition.MaxDurationTicks));
|
||||
data.AddRange(DataTypes.GetFloat(condition.MinSpeed));
|
||||
data.AddRange(DataTypes.GetFloat(condition.MinRelativeSpeed));
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
return new Queue<byte>();
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(ContactCooldownTicks));
|
||||
data.AddRange(DataTypes.GetVarInt(DelayTicks));
|
||||
WriteOptionalCondition(data, DismountConditions);
|
||||
WriteOptionalCondition(data, KnockbackConditions);
|
||||
WriteOptionalCondition(data, DamageConditions);
|
||||
data.AddRange(DataTypes.GetFloat(ForwardMovement));
|
||||
data.AddRange(DataTypes.GetFloat(DamageMultiplier));
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, Sound);
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, HitSound);
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record KineticWeaponConditionData(int MaxDurationTicks, float MinSpeed, float MinRelativeSpeed);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_11;
|
||||
|
|
@ -9,29 +10,24 @@ public class PiercingWeaponComponent(DataTypes dataTypes, ItemPalette itemPalett
|
|||
{
|
||||
public bool DealsKnockback { get; set; }
|
||||
public bool Dismounts { get; set; }
|
||||
public SoundEventHolderData? Sound { get; set; }
|
||||
public SoundEventHolderData? HitSound { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
DealsKnockback = DataTypes.ReadNextBool(data);
|
||||
Dismounts = DataTypes.ReadNextBool(data);
|
||||
ReadOptionalSoundEventHolder(data);
|
||||
ReadOptionalSoundEventHolder(data);
|
||||
}
|
||||
|
||||
private void ReadOptionalSoundEventHolder(Queue<byte> data)
|
||||
{
|
||||
if (!DataTypes.ReadNextBool(data)) return;
|
||||
var holderId = DataTypes.ReadNextVarInt(data);
|
||||
if (holderId == 0)
|
||||
{
|
||||
DataTypes.ReadNextString(data);
|
||||
if (DataTypes.ReadNextBool(data))
|
||||
DataTypes.ReadNextFloat(data);
|
||||
}
|
||||
Sound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
HitSound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
return new Queue<byte>();
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetBool(DealsKnockback));
|
||||
data.AddRange(DataTypes.GetBool(Dismounts));
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, Sound);
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, HitSound);
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5;
|
||||
|
|
@ -9,10 +10,13 @@ public class BlocksAttacksComponent(DataTypes dataTypes, ItemPalette itemPalette
|
|||
{
|
||||
public float BlockDelaySeconds { get; set; }
|
||||
public float DisableCooldownScale { get; set; }
|
||||
public List<byte[]> RawDamageReductions { get; set; } = [];
|
||||
public List<DamageReductionData> DamageReductions { get; set; } = [];
|
||||
public float ItemDamageThreshold { get; set; }
|
||||
public float ItemDamageBase { get; set; }
|
||||
public float ItemDamageFactor { get; set; }
|
||||
public string? BypassedBy { get; set; }
|
||||
public SoundEventHolderData? BlockSound { get; set; }
|
||||
public SoundEventHolderData? DisableSound { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
|
|
@ -25,11 +29,13 @@ public class BlocksAttacksComponent(DataTypes dataTypes, ItemPalette itemPalette
|
|||
var horizontalBlockingAngle = DataTypes.ReadNextFloat(data);
|
||||
|
||||
var hasTypeFilter = DataTypes.ReadNextBool(data);
|
||||
if (hasTypeFilter)
|
||||
ReadHolderSet(data);
|
||||
var typeFilter = hasTypeFilter
|
||||
? StructuredComponentCodecHelpers.ReadHolderSet(DataTypes, data)
|
||||
: null;
|
||||
|
||||
var baseDmg = DataTypes.ReadNextFloat(data);
|
||||
var factor = DataTypes.ReadNextFloat(data);
|
||||
DamageReductions.Add(new DamageReductionData(horizontalBlockingAngle, typeFilter, baseDmg, factor));
|
||||
}
|
||||
|
||||
ItemDamageThreshold = DataTypes.ReadNextFloat(data);
|
||||
|
|
@ -38,46 +44,38 @@ public class BlocksAttacksComponent(DataTypes dataTypes, ItemPalette itemPalette
|
|||
|
||||
var hasBypassedBy = DataTypes.ReadNextBool(data);
|
||||
if (hasBypassedBy)
|
||||
DataTypes.ReadNextString(data); // TagKey<DamageType> as ResourceLocation
|
||||
BypassedBy = DataTypes.ReadNextString(data); // TagKey<DamageType> as ResourceLocation
|
||||
|
||||
var hasBlockSound = DataTypes.ReadNextBool(data);
|
||||
if (hasBlockSound)
|
||||
ReadSoundEventHolder(data);
|
||||
|
||||
var hasDisableSound = DataTypes.ReadNextBool(data);
|
||||
if (hasDisableSound)
|
||||
ReadSoundEventHolder(data);
|
||||
}
|
||||
|
||||
private void ReadHolderSet(Queue<byte> data)
|
||||
{
|
||||
var sizeOrTag = DataTypes.ReadNextVarInt(data);
|
||||
if (sizeOrTag == 0)
|
||||
{
|
||||
DataTypes.ReadNextString(data); // Tag ResourceLocation
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = sizeOrTag - 1;
|
||||
for (var i = 0; i < count; i++)
|
||||
DataTypes.ReadNextVarInt(data); // Holder<DamageType> registry ids
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadSoundEventHolder(Queue<byte> data)
|
||||
{
|
||||
var holderId = DataTypes.ReadNextVarInt(data);
|
||||
if (holderId == 0)
|
||||
{
|
||||
DataTypes.ReadNextString(data); // ResourceLocation
|
||||
var hasFixedRange = DataTypes.ReadNextBool(data);
|
||||
if (hasFixedRange)
|
||||
DataTypes.ReadNextFloat(data);
|
||||
}
|
||||
BlockSound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
DisableSound = StructuredComponentCodecHelpers.ReadOptionalSoundEventHolder(DataTypes, data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
return new Queue<byte>();
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetFloat(BlockDelaySeconds));
|
||||
data.AddRange(DataTypes.GetFloat(DisableCooldownScale));
|
||||
data.AddRange(DataTypes.GetVarInt(DamageReductions.Count));
|
||||
foreach (var reduction in DamageReductions)
|
||||
{
|
||||
data.AddRange(DataTypes.GetFloat(reduction.HorizontalBlockingAngle));
|
||||
data.AddRange(DataTypes.GetBool(reduction.Type is not null));
|
||||
if (reduction.Type is not null)
|
||||
StructuredComponentCodecHelpers.WriteHolderSet(DataTypes, data, reduction.Type);
|
||||
data.AddRange(DataTypes.GetFloat(reduction.Base));
|
||||
data.AddRange(DataTypes.GetFloat(reduction.Factor));
|
||||
}
|
||||
|
||||
data.AddRange(DataTypes.GetFloat(ItemDamageThreshold));
|
||||
data.AddRange(DataTypes.GetFloat(ItemDamageBase));
|
||||
data.AddRange(DataTypes.GetFloat(ItemDamageFactor));
|
||||
data.AddRange(DataTypes.GetBool(BypassedBy is not null));
|
||||
if (BypassedBy is not null)
|
||||
data.AddRange(DataTypes.GetString(BypassedBy));
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, BlockSound);
|
||||
StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, DisableSound);
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record DamageReductionData(float HorizontalBlockingAngle, HolderSetData? Type, float Base, float Factor);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components._26_1;
|
||||
|
||||
public class TypedEntityDataComponent261(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int EntityTypeId { get; set; }
|
||||
public Dictionary<string, object>? Nbt { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
EntityTypeId = DataTypes.ReadNextVarInt(data);
|
||||
Nbt = DataTypes.ReadNextNbt(data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(EntityTypeId));
|
||||
data.AddRange(DataTypes.GetNbt(Nbt));
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
: TypedEntityDataComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{ }
|
||||
|
||||
public class BlockEntityDataComponent261(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: TypedEntityDataComponent261(dataTypes, itemPalette, subComponentRegistry)
|
||||
: TypedBlockEntityDataComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
|
||||
internal static class StructuredComponentCodecHelpers
|
||||
{
|
||||
public static HolderSetData ReadHolderSet(DataTypes dataTypes, Queue<byte> data)
|
||||
{
|
||||
var sizeOrTag = dataTypes.ReadNextVarInt(data);
|
||||
if (sizeOrTag == 0)
|
||||
return new HolderSetData(dataTypes.ReadNextString(data), []);
|
||||
|
||||
var count = sizeOrTag - 1;
|
||||
var holderIds = new List<int>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
holderIds.Add(dataTypes.ReadNextVarInt(data));
|
||||
|
||||
return new HolderSetData(null, holderIds);
|
||||
}
|
||||
|
||||
public static void WriteHolderSet(DataTypes dataTypes, List<byte> bytes, HolderSetData holderSet)
|
||||
{
|
||||
if (holderSet.Tag is not null)
|
||||
{
|
||||
bytes.AddRange(DataTypes.GetVarInt(0));
|
||||
bytes.AddRange(dataTypes.GetString(holderSet.Tag));
|
||||
return;
|
||||
}
|
||||
|
||||
bytes.AddRange(DataTypes.GetVarInt(holderSet.HolderIds.Count + 1));
|
||||
foreach (var holderId in holderSet.HolderIds)
|
||||
bytes.AddRange(DataTypes.GetVarInt(holderId));
|
||||
}
|
||||
|
||||
public static SoundEventHolderData ReadSoundEventHolder(DataTypes dataTypes, Queue<byte> data)
|
||||
{
|
||||
var holderId = dataTypes.ReadNextVarInt(data);
|
||||
if (holderId != 0)
|
||||
return new SoundEventHolderData(holderId, null, false, 0);
|
||||
|
||||
var soundLocation = dataTypes.ReadNextString(data);
|
||||
var hasFixedRange = dataTypes.ReadNextBool(data);
|
||||
var fixedRange = hasFixedRange ? dataTypes.ReadNextFloat(data) : 0;
|
||||
return new SoundEventHolderData(holderId, soundLocation, hasFixedRange, fixedRange);
|
||||
}
|
||||
|
||||
public static void WriteSoundEventHolder(DataTypes dataTypes, List<byte> bytes, SoundEventHolderData soundEvent)
|
||||
{
|
||||
bytes.AddRange(DataTypes.GetVarInt(soundEvent.HolderId));
|
||||
if (soundEvent.HolderId != 0)
|
||||
return;
|
||||
|
||||
bytes.AddRange(dataTypes.GetString(soundEvent.SoundLocation ?? ""));
|
||||
bytes.AddRange(dataTypes.GetBool(soundEvent.HasFixedRange));
|
||||
if (soundEvent.HasFixedRange)
|
||||
bytes.AddRange(dataTypes.GetFloat(soundEvent.FixedRange));
|
||||
}
|
||||
|
||||
public static SoundEventHolderData? ReadOptionalSoundEventHolder(DataTypes dataTypes, Queue<byte> data)
|
||||
{
|
||||
return dataTypes.ReadNextBool(data) ? ReadSoundEventHolder(dataTypes, data) : null;
|
||||
}
|
||||
|
||||
public static void WriteOptionalSoundEventHolder(DataTypes dataTypes, List<byte> bytes, SoundEventHolderData? soundEvent)
|
||||
{
|
||||
bytes.AddRange(dataTypes.GetBool(soundEvent is not null));
|
||||
if (soundEvent is not null)
|
||||
WriteSoundEventHolder(dataTypes, bytes, soundEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record HolderSetData(string? Tag, List<int> HolderIds);
|
||||
|
||||
public sealed record SoundEventHolderData(int HolderId, string? SoundLocation, bool HasFixedRange, float FixedRange);
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using System.Collections.Generic;
|
||||
using MinecraftClient.Inventory.ItemPalettes;
|
||||
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Components;
|
||||
|
||||
public class TypedEntityDataComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: StructuredComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{
|
||||
public int EntityTypeId { get; set; }
|
||||
public Dictionary<string, object>? Nbt { get; set; }
|
||||
|
||||
public override void Parse(Queue<byte> data)
|
||||
{
|
||||
EntityTypeId = DataTypes.ReadNextVarInt(data);
|
||||
Nbt = DataTypes.ReadNextNbt(data);
|
||||
}
|
||||
|
||||
public override Queue<byte> Serialize()
|
||||
{
|
||||
var data = new List<byte>();
|
||||
data.AddRange(DataTypes.GetVarInt(EntityTypeId));
|
||||
data.AddRange(DataTypes.GetNbt(Nbt));
|
||||
return new Queue<byte>(data);
|
||||
}
|
||||
}
|
||||
|
||||
public class TypedBlockEntityDataComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry)
|
||||
: TypedEntityDataComponent(dataTypes, itemPalette, subComponentRegistry)
|
||||
{ }
|
||||
|
|
@ -13,7 +13,7 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry
|
|||
RegisterComponent<MaxStackSizeComponent>(1, "minecraft:max_stack_size");
|
||||
RegisterComponent<MaxDamageComponent>(2, "minecraft:max_damage");
|
||||
RegisterComponent<DamageComponent>(3, "minecraft:damage");
|
||||
RegisterComponent<UnbrekableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<UnbreakableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<CustomNameComponent>(5, "minecraft:custom_name");
|
||||
RegisterComponent<ItemNameComponent>(6, "minecraft:item_name");
|
||||
RegisterComponent<LoreNameComponent1206>(7, "minecraft:lore");
|
||||
|
|
@ -29,7 +29,7 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry
|
|||
RegisterComponent<CreativeSlotLockComponent>(17, "minecraft:creative_slot_lock");
|
||||
RegisterComponent<EnchantmentGlintOverrideComponent>(18, "minecraft:enchantment_glint_override");
|
||||
RegisterComponent<IntangibleProjectileComponent>(19, "minecraft:intangible_projectile");
|
||||
RegisterComponent<FoodComponentComponent>(20, "minecraft:food");
|
||||
RegisterComponent<FoodComponent>(20, "minecraft:food");
|
||||
RegisterComponent<FireResistantComponent>(21, "minecraft:fire_resistant");
|
||||
RegisterComponent<ToolComponent>(22, "minecraft:tool");
|
||||
RegisterComponent<StoredEnchantmentsComponent>(23, "minecraft:stored_enchantments");
|
||||
|
|
@ -42,15 +42,15 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry
|
|||
RegisterComponent<BundleContentsComponent>(30, "minecraft:bundle_contents");
|
||||
RegisterComponent<PotionContentsComponent>(31, "minecraft:potion_contents");
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(32, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(33, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(34, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(33, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(34, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent>(35, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(36, "minecraft:debug_stick_state");
|
||||
RegisterComponent<EntityDataComponent>(37, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(38, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent>(39, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent>(40, "minecraft:instrument");
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(41, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(41, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<RecipesComponent>(42, "minecraft:recipes");
|
||||
RegisterComponent<LodestoneTrackerComponent>(43, "minecraft:lodestone_tracker");
|
||||
RegisterComponent<FireworkExplosionComponent>(44, "minecraft:firework_explosion");
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry
|
|||
RegisterComponent<MaxStackSizeComponent>(1, "minecraft:max_stack_size");
|
||||
RegisterComponent<MaxDamageComponent>(2, "minecraft:max_damage");
|
||||
RegisterComponent<DamageComponent>(3, "minecraft:damage");
|
||||
RegisterComponent<UnbrekableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<UnbreakableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<CustomNameComponent>(5, "minecraft:custom_name");
|
||||
RegisterComponent<ItemNameComponent>(6, "minecraft:item_name");
|
||||
RegisterComponent<LoreNameComponent1206>(7, "minecraft:lore");
|
||||
|
|
@ -30,7 +30,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry
|
|||
RegisterComponent<CreativeSlotLockComponent>(17, "minecraft:creative_slot_lock");
|
||||
RegisterComponent<EnchantmentGlintOverrideComponent>(18, "minecraft:enchantment_glint_override");
|
||||
RegisterComponent<IntangibleProjectileComponent>(19, "minecraft:intangible_projectile");
|
||||
RegisterComponent<FoodComponentComponent>(20, "minecraft:food");
|
||||
RegisterComponent<FoodComponent>(20, "minecraft:food");
|
||||
RegisterComponent<FireResistantComponent>(21, "minecraft:fire_resistant");
|
||||
RegisterComponent<ToolComponent>(22, "minecraft:tool");
|
||||
RegisterComponent<StoredEnchantmentsComponent>(23, "minecraft:stored_enchantments");
|
||||
|
|
@ -43,15 +43,15 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry
|
|||
RegisterComponent<BundleContentsComponent>(30, "minecraft:bundle_contents");
|
||||
RegisterComponent<PotionContentsComponent>(31, "minecraft:potion_contents");
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(32, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(33, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(34, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(33, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(34, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent>(35, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(36, "minecraft:debug_stick_state");
|
||||
RegisterComponent<EntityDataComponent>(37, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(38, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent>(39, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent>(40, "minecraft:instrument");
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(41, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(41, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<JukeBoxPlayableComponent121>(42, "minecraft:jukebox_playable");
|
||||
RegisterComponent<RecipesComponent>(43, "minecraft:recipes");
|
||||
RegisterComponent<LodestoneTrackerComponent>(44, "minecraft:lodestone_tracker");
|
||||
|
|
|
|||
|
|
@ -69,16 +69,16 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry
|
|||
RegisterComponent<PotionContentsComponent1212>(49, "minecraft:potion_contents");
|
||||
RegisterComponent<PotionDurationScaleComponent>(50, "minecraft:potion_duration_scale");
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(51, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(52, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(53, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(52, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(53, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent1215>(54, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(55, "minecraft:debug_stick_state");
|
||||
RegisterComponent<TypedEntityDataComponent261>(56, "minecraft:entity_data");
|
||||
RegisterComponent<TypedEntityDataComponent>(56, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(57, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent261>(58, "minecraft:block_entity_data");
|
||||
RegisterComponent<TypedBlockEntityDataComponent>(58, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent1215>(59, "minecraft:instrument");
|
||||
RegisterComponent<ProvidesTrimMaterialComponent>(60, "minecraft:provides_trim_material");
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(61, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(61, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<JukeBoxPlayableComponent1215>(62, "minecraft:jukebox_playable");
|
||||
RegisterComponent<ProvidesBannerPatternsComponent>(63, "minecraft:provides_banner_patterns");
|
||||
RegisterComponent<RecipesComponent>(64, "minecraft:recipes");
|
||||
|
|
@ -111,7 +111,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry
|
|||
RegisterComponent<VarIntComponent>(90, "minecraft:rabbit/variant");
|
||||
RegisterComponent<VarIntComponent>(91, "minecraft:pig/variant");
|
||||
RegisterComponent<VarIntComponent>(92, "minecraft:cow/variant");
|
||||
RegisterComponent<EitherHolderComponent>(93, "minecraft:chicken/variant");
|
||||
RegisterComponent<RegistryEitherHolderComponent>(93, "minecraft:chicken/variant");
|
||||
RegisterComponent<RegistryEitherHolderComponent>(94, "minecraft:zombie_nautilus/variant");
|
||||
RegisterComponent<VarIntComponent>(95, "minecraft:frog/variant");
|
||||
RegisterComponent<VarIntComponent>(96, "minecraft:horse/variant");
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry
|
|||
RegisterComponent<MaxStackSizeComponent>(1, "minecraft:max_stack_size");
|
||||
RegisterComponent<MaxDamageComponent>(2, "minecraft:max_damage");
|
||||
RegisterComponent<DamageComponent>(3, "minecraft:damage");
|
||||
RegisterComponent<UnbrekableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<UnbreakableComponent1206>(4, "minecraft:unbreakable");
|
||||
RegisterComponent<CustomNameComponent>(5, "minecraft:custom_name");
|
||||
RegisterComponent<ItemNameComponent>(6, "minecraft:item_name");
|
||||
RegisterComponent<ItemModelComponent>(7, "minecraft:item_model");
|
||||
|
|
@ -57,15 +57,15 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry
|
|||
RegisterComponent<BundleContentsComponent>(40, "minecraft:bundle_contents");
|
||||
RegisterComponent<PotionContentsComponent1212>(41, "minecraft:potion_contents");
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(42, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(43, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(44, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(43, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(44, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent>(45, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(46, "minecraft:debug_stick_state");
|
||||
RegisterComponent<EntityDataComponent>(47, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(48, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent>(49, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent>(50, "minecraft:instrument");
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(51, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(51, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<JukeBoxPlayableComponent121>(52, "minecraft:jukebox_playable");
|
||||
RegisterComponent<RecipesComponent>(53, "minecraft:recipes");
|
||||
RegisterComponent<LodestoneTrackerComponent>(54, "minecraft:lodestone_tracker");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.StructuredComponents.Registries;
|
||||
|
|
@ -15,8 +16,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 uses1216AttributeAndEquippableFormats = dataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_6_Version;
|
||||
var usesTypedBeesFormat = dataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_9_Version;
|
||||
var usesTypedEntityDataFormat = dataTypes.ProtocolVersion >= Protocol18Handler.MC_1_21_9_Version;
|
||||
|
||||
RegisterComponent<CustomDataComponent>(0, "minecraft:custom_data");
|
||||
RegisterComponent<MaxStackSizeComponent>(1, "minecraft:max_stack_size");
|
||||
|
|
@ -31,7 +33,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry
|
|||
RegisterComponent<EnchantmentsComponent1215>(10, "minecraft:enchantments");
|
||||
RegisterComponent<CanPlaceOnComponent1215>(11, "minecraft:can_place_on");
|
||||
RegisterComponent<CanBreakComponent1215>(12, "minecraft:can_break");
|
||||
if (uses1218AttributeAndEquippableFormats)
|
||||
if (uses1216AttributeAndEquippableFormats)
|
||||
RegisterComponent<AttributeModifiersComponent1218>(13, "minecraft:attribute_modifiers");
|
||||
else
|
||||
RegisterComponent<AttributeModifiersComponent1215>(13, "minecraft:attribute_modifiers");
|
||||
|
|
@ -50,7 +52,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry
|
|||
RegisterComponent<ToolComponent1215>(25, "minecraft:tool");
|
||||
RegisterComponent<WeaponComponent>(26, "minecraft:weapon"); // NEW
|
||||
RegisterComponent<EnchantableComponent>(27, "minecraft:enchantable");
|
||||
if (uses1218AttributeAndEquippableFormats)
|
||||
if (uses1216AttributeAndEquippableFormats)
|
||||
RegisterComponent<EquippableComponent1218>(28, "minecraft:equippable");
|
||||
else
|
||||
RegisterComponent<EquippableComponent1215>(28, "minecraft:equippable");
|
||||
|
|
@ -70,16 +72,22 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry
|
|||
RegisterComponent<PotionContentsComponent1212>(42, "minecraft:potion_contents");
|
||||
RegisterComponent<PotionDurationScaleComponent>(43, "minecraft:potion_duration_scale"); // NEW
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(44, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(45, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(46, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(45, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(46, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent1215>(47, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(48, "minecraft:debug_stick_state");
|
||||
RegisterComponent<EntityDataComponent>(49, "minecraft:entity_data");
|
||||
if (usesTypedEntityDataFormat)
|
||||
RegisterComponent<TypedEntityDataComponent>(49, "minecraft:entity_data");
|
||||
else
|
||||
RegisterComponent<EntityDataComponent>(49, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(50, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent>(51, "minecraft:block_entity_data");
|
||||
if (usesTypedEntityDataFormat)
|
||||
RegisterComponent<TypedBlockEntityDataComponent>(51, "minecraft:block_entity_data");
|
||||
else
|
||||
RegisterComponent<BlockEntityDataComponent>(51, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent1215>(52, "minecraft:instrument"); // Changed to EitherHolder<Instrument> in 1.21.5
|
||||
RegisterComponent<ProvidesTrimMaterialComponent>(53, "minecraft:provides_trim_material"); // NEW
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(54, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(54, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<JukeBoxPlayableComponent1215>(55, "minecraft:jukebox_playable");
|
||||
RegisterComponent<ProvidesBannerPatternsComponent>(56, "minecraft:provides_banner_patterns"); // NEW
|
||||
RegisterComponent<RecipesComponent>(57, "minecraft:recipes");
|
||||
|
|
@ -116,7 +124,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry
|
|||
RegisterComponent<VarIntComponent>(83, "minecraft:rabbit/variant");
|
||||
RegisterComponent<VarIntComponent>(84, "minecraft:pig/variant");
|
||||
RegisterComponent<VarIntComponent>(85, "minecraft:cow/variant");
|
||||
RegisterComponent<EitherHolderComponent>(86, "minecraft:chicken/variant"); // EitherHolder<ChickenVariant>
|
||||
RegisterComponent<RegistryEitherHolderComponent>(86, "minecraft:chicken/variant"); // EitherHolder<ChickenVariant>
|
||||
RegisterComponent<VarIntComponent>(87, "minecraft:frog/variant");
|
||||
RegisterComponent<VarIntComponent>(88, "minecraft:horse/variant");
|
||||
RegisterComponent<PaintingVariantHolderComponent>(89, "minecraft:painting/variant"); // Holder<PaintingVariant>
|
||||
|
|
|
|||
|
|
@ -71,16 +71,16 @@ public class StructuredComponentsRegistry261 : StructuredComponentRegistry
|
|||
RegisterComponent<PotionContentsComponent1212>(51, "minecraft:potion_contents");
|
||||
RegisterComponent<PotionDurationScaleComponent>(52, "minecraft:potion_duration_scale");
|
||||
RegisterComponent<SuspiciousStewEffectsComponent>(53, "minecraft:suspicious_stew_effects");
|
||||
RegisterComponent<WritableBlookContentComponent>(54, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBlookContentComponent>(55, "minecraft:written_book_content");
|
||||
RegisterComponent<WritableBookContentComponent>(54, "minecraft:writable_book_content");
|
||||
RegisterComponent<WrittenBookContentComponent>(55, "minecraft:written_book_content");
|
||||
RegisterComponent<TrimComponent1215>(56, "minecraft:trim");
|
||||
RegisterComponent<DebugStickStateComponent>(57, "minecraft:debug_stick_state");
|
||||
RegisterComponent<TypedEntityDataComponent261>(58, "minecraft:entity_data");
|
||||
RegisterComponent<TypedEntityDataComponent>(58, "minecraft:entity_data");
|
||||
RegisterComponent<BucketEntityDataComponent>(59, "minecraft:bucket_entity_data");
|
||||
RegisterComponent<BlockEntityDataComponent261>(60, "minecraft:block_entity_data");
|
||||
RegisterComponent<TypedBlockEntityDataComponent>(60, "minecraft:block_entity_data");
|
||||
RegisterComponent<InstrumentComponent261>(61, "minecraft:instrument");
|
||||
RegisterComponent<ProvidesTrimMaterialComponent261>(62, "minecraft:provides_trim_material");
|
||||
RegisterComponent<OmniousBottleAmplifierComponent>(63, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<OminousBottleAmplifierComponent>(63, "minecraft:ominous_bottle_amplifier");
|
||||
RegisterComponent<JukeBoxPlayableComponent1215>(64, "minecraft:jukebox_playable");
|
||||
RegisterComponent<HolderSetComponent261>(65, "minecraft:provides_banner_patterns");
|
||||
RegisterComponent<NbtTagComponent261>(66, "minecraft:recipes");
|
||||
|
|
|
|||
488
tools/run-structured-components-test.sh
Executable file
488
tools/run-structured-components-test.sh
Executable file
|
|
@ -0,0 +1,488 @@
|
|||
#!/usr/bin/env bash
|
||||
# Structured Components Integration Test
|
||||
# Tests every structured component across supported versions (1.20.6 to 26.1)
|
||||
# Usage: bash tools/run-structured-components-test.sh <version>
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||
source "$REPO_ROOT/.skills/mcc-integration-testing/scripts/common.sh"
|
||||
|
||||
usage() { echo "Usage: $0 <version>"; echo " e.g. $0 1.20.6"; exit 1; }
|
||||
VERSION="${1:-}"; [[ -z "$VERSION" ]] && usage
|
||||
SERVER_DIR="${VERSION}"
|
||||
|
||||
# Version group detection
|
||||
case "$VERSION" in
|
||||
1.20.6) VER_GROUP="v1206" ;;
|
||||
1.21|1.21.1) VER_GROUP="v121" ;;
|
||||
1.21.2|1.21.3|1.21.4) VER_GROUP="v1212" ;;
|
||||
1.21.5|1.21.6|1.21.7|1.21.8|1.21.9|1.21.10) VER_GROUP="v1215" ;;
|
||||
1.21.11) VER_GROUP="v12111" ;;
|
||||
26.1) VER_GROUP="v261" ;;
|
||||
*) echo "Unsupported version: $VERSION"; exit 1 ;;
|
||||
esac
|
||||
|
||||
SESSION="sc-${VERSION//./_}"
|
||||
TEST_ROOT="${TMPDIR:-/tmp}/mcc-sc-test/${VERSION}"
|
||||
CFG="$TEST_ROOT/MinecraftClient.${VERSION}.ini"
|
||||
INPUT_FILE="$(_mcc_session_input_file "$SESSION")"
|
||||
MCC_LOG="$(_mcc_session_log_file "$SESSION")"
|
||||
PID_FILE="$(_mcc_session_pid_file "$SESSION")"
|
||||
META_FILE="$(_mcc_session_meta_file "$SESSION")"
|
||||
MCC_TMUX_SESSION="$(_mcc_tmux_session_name "$SESSION")"
|
||||
USERNAME="$(_mcc_resolve_username "$SESSION")"
|
||||
mkdir -p "$TEST_ROOT"
|
||||
mkdir -p "$(dirname "$INPUT_FILE")"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
FAILURES=()
|
||||
|
||||
pass() { PASS_COUNT=$((PASS_COUNT + 1)); printf ' [PASS] %s\n' "$1"; }
|
||||
fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); FAILURES+=("$1"); printf ' [FAIL] %s\n' "$1"; }
|
||||
|
||||
# Give item via RCON, verify success
|
||||
give_item() {
|
||||
local name="$1" rcon_cmd="$2"
|
||||
local out
|
||||
out="$(mc-rcon "$rcon_cmd" 2>/dev/null || true)"
|
||||
if echo "$out" | grep -qiE "(Gave|given|No item was|Cannot give|Unknown item)"; then
|
||||
if echo "$out" | grep -qi "Gave"; then
|
||||
pass "$name"
|
||||
else
|
||||
fail "$name | give failed: $out"
|
||||
fi
|
||||
else
|
||||
# RCON returns empty sometimes; still check MCC log for errors
|
||||
pass "$name"
|
||||
fi
|
||||
}
|
||||
|
||||
# Read inventory and check for component parse errors
|
||||
check_inv() {
|
||||
sleep 1
|
||||
mcc-cmd --session "$SESSION" "inventory player list" 2>/dev/null || true
|
||||
sleep 2
|
||||
if grep -qiE "(error|exception|fail|unhandled|unknown component|System\." "$MCC_LOG" 2>/dev/null; then
|
||||
local err_line
|
||||
err_line="$(grep -iE "(error|exception|fail|unhandled|unknown component)" "$MCC_LOG" | head -3 2>/dev/null)"
|
||||
fail "component parse error detected: $err_line"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
mcc-cmd --session "$SESSION" "quit" 2>/dev/null || true
|
||||
sleep 1
|
||||
mcc-kill --session "$SESSION" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "=== Structured Components Test: $VERSION ($VER_GROUP) ==="
|
||||
|
||||
# Phase 1: Preflight
|
||||
bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/preflight_test_env.sh" "$SERVER_DIR" >/dev/null 2>&1 || true
|
||||
|
||||
# Phase 2: Ensure server configured for offline+RCON
|
||||
bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh" "$SERVER_DIR" >/dev/null 2>&1 || true
|
||||
|
||||
# Phase 3: Start server if not running
|
||||
if ! server_running "$SERVER_DIR"; then
|
||||
mc-start "$SERVER_DIR" >/dev/null 2>&1
|
||||
fi
|
||||
wait_for_server_ready "$SERVER_DIR" || { echo "Server failed to start"; exit 1; }
|
||||
echo " Server ready."
|
||||
|
||||
# Phase 4: Prepare MCC config
|
||||
echo " Preparing MCC config..."
|
||||
bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh" \
|
||||
"$CFG" "$VERSION" "$USERNAME" >/dev/null 2>&1
|
||||
|
||||
sed_in_place \
|
||||
-e 's/^TerrainAndMovements = false/TerrainAndMovements = true/' \
|
||||
-e 's/^InventoryHandling = false/InventoryHandling = true/' \
|
||||
-e 's/^EntityHandling = false/EntityHandling = true/' \
|
||||
-e 's/^AutoRespawn = false/AutoRespawn = true/' \
|
||||
"$CFG" 2>/dev/null || true
|
||||
disable_noisy_bots_in_ini "$CFG" 2>/dev/null || true
|
||||
|
||||
# Set server host/port in config
|
||||
SERVER_PORT="$(bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/get_server_port.sh" "$SERVER_DIR" 2>/dev/null || echo "25565")"
|
||||
sed_in_place \
|
||||
-e "s#^Server = .*#Server = { Host = \"localhost\", Port = $SERVER_PORT }#" \
|
||||
"$CFG" 2>/dev/null || true
|
||||
|
||||
# Phase 5: Start MCC in file-input mode
|
||||
echo " Starting MCC..."
|
||||
: > "$INPUT_FILE" 2>/dev/null || true
|
||||
rm -f "$MCC_LOG" "$PID_FILE"
|
||||
|
||||
MCC_ARGS=("$CFG" "$USERNAME" "-" "localhost:$SERVER_PORT")
|
||||
MCC_ARGS_CMD="$(printf '%q ' "${MCC_ARGS[@]}")"
|
||||
|
||||
tmux kill-session -t "$MCC_TMUX_SESSION" 2>/dev/null || true
|
||||
tmux new-session -d -s "$MCC_TMUX_SESSION" -x 160 -y 50 \
|
||||
"cd '$REPO_ROOT' && printf '%s\n' \"\$\$\" > '$PID_FILE' && exec env MCC_FILE_INPUT=1 MCC_INPUT_FILE='$INPUT_FILE' dotnet run --project MinecraftClient -c Release --no-build -- $MCC_ARGS_CMD > '$MCC_LOG' 2>&1"
|
||||
|
||||
for _ in $(seq 1 25); do
|
||||
if [[ -s "$PID_FILE" ]]; then break; fi
|
||||
sleep 0.2
|
||||
done
|
||||
MCC_PID="$(tr -cd '0-9' < "$PID_FILE" 2>/dev/null || true)"
|
||||
|
||||
echo -n " Waiting for MCC to join..."
|
||||
JOINED=false
|
||||
for _ in $(seq 1 60); do
|
||||
if [[ -f "$MCC_LOG" ]] && grep -q "Server was successfully joined" "$MCC_LOG" 2>/dev/null; then
|
||||
echo " joined."
|
||||
JOINED=true
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
if ! $JOINED; then
|
||||
echo " TIMEOUT"
|
||||
echo "MCC log:"
|
||||
tail -30 "$MCC_LOG" 2>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Phase 6: Op and prepare player
|
||||
for _ in 1 2 3; do
|
||||
if mc-rcon "op $USERNAME" 2>/dev/null | grep -qi "Made"; then break; fi
|
||||
sleep 2
|
||||
done
|
||||
mc-rcon "gamerule sendCommandFeedback true" 2>/dev/null || true
|
||||
mc-rcon "time set day" 2>/dev/null || true
|
||||
mc-rcon "weather clear" 2>/dev/null || true
|
||||
mc-rcon "gamemode creative $USERNAME" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Safety
|
||||
mc-rcon "attribute $USERNAME minecraft:generic.max_health base set 100" 2>/dev/null || true
|
||||
mc-rcon "effect give $USERNAME minecraft:regeneration 60 4" 2>/dev/null || true
|
||||
mc-rcon "effect give $USERNAME minecraft:absorption 60 4" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "--- Base Components ---"
|
||||
|
||||
# 1. custom_name
|
||||
give_item "custom_name" "give $USERNAME minecraft:diamond_sword[custom_name='\"{\\\"text\\\":\\\"Test Sword\\\",\\\"color\\\":\\\"gold\\\"}\"'] 1"
|
||||
check_inv
|
||||
|
||||
# 2. lore
|
||||
give_item "lore" "give $USERNAME minecraft:diamond_sword[lore='[\\\"{\\\\\\\"text\\\\\\\":\\\\\\\"Line 1\\\\\\\"}\\\",\\\"{\\\\\\\"text\\\\\\\":\\\\\\\"Line 2\\\\\\\"}\\\"]'] 1"
|
||||
check_inv
|
||||
|
||||
# 3. enchantments
|
||||
if [[ "$VER_GROUP" == "v1206" || "$VER_GROUP" == "v121" || "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "enchantments" "give $USERNAME minecraft:diamond_sword[enchantments={levels:{sharpness:3,unbreaking:2},show_in_tooltip:true}] 1"
|
||||
else
|
||||
give_item "enchantments" "give $USERNAME minecraft:diamond_sword[enchantments={levels:{sharpness:3,unbreaking:2}}] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 4. unbreakable
|
||||
if [[ "$VER_GROUP" == "v1206" || "$VER_GROUP" == "v121" || "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "unbreakable" "give $USERNAME minecraft:diamond_sword[unbreakable={}] 1"
|
||||
else
|
||||
give_item "unbreakable" "give $USERNAME minecraft:diamond_sword[unbreakable] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 5. rarity
|
||||
give_item "rarity" "give $USERNAME minecraft:diamond_sword[rarity=epic] 1"
|
||||
check_inv
|
||||
|
||||
# 6. attribute_modifiers (check slot format per version)
|
||||
if [[ "$VER_GROUP" == "v261" ]]; then
|
||||
give_item "attribute_modifiers" "give $USERNAME minecraft:diamond_sword[attribute_modifiers=[{type:attack_damage,amount:10.0,operation:add_value,slot:mainhand}]] 1"
|
||||
elif [[ "$VER_GROUP" == "v12111" ]]; then
|
||||
give_item "attribute_modifiers" "give $USERNAME minecraft:diamond_sword[attribute_modifiers=[{type:attack_damage,amount:10.0,operation:add_value,slot:mainhand}]] 1"
|
||||
elif [[ "$VER_GROUP" == "v1215" ]]; then
|
||||
give_item "attribute_modifiers" "give $USERNAME minecraft:diamond_sword[attribute_modifiers=[{type:attack_damage,amount:10.0,operation:add_value,slot:mainhand}]] 1"
|
||||
elif [[ "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "attribute_modifiers" "give $USERNAME minecraft:diamond_sword[attribute_modifiers=[{type:attack_damage,amount:10.0,operation:add_value,slot:mainhand}]] 1"
|
||||
else
|
||||
give_item "attribute_modifiers" "give $USERNAME minecraft:diamond_sword[attribute_modifiers=[{type:attack_damage,amount:10.0,operation:add_value,slot:mainhand}]] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 7. custom_model_data
|
||||
give_item "custom_model_data" "give $USERNAME minecraft:stick[custom_model_data=12345] 1"
|
||||
check_inv
|
||||
|
||||
# 8. dyed_color
|
||||
if [[ "$VER_GROUP" == "v1206" || "$VER_GROUP" == "v121" || "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "dyed_color" "give $USERNAME minecraft:leather_chestplate[dyed_color={rgb:16711680}] 1"
|
||||
else
|
||||
give_item "dyed_color" "give $USERNAME minecraft:leather_chestplate[dyed_color=16711680] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 9. potion_contents
|
||||
give_item "potion_contents" "give $USERNAME minecraft:potion[potion_contents={potion:swiftness}] 1"
|
||||
check_inv
|
||||
|
||||
# 10. trim
|
||||
if [[ "$VER_GROUP" == "v1206" || "$VER_GROUP" == "v121" || "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "trim" "give $USERNAME minecraft:diamond_helmet[trim={material:redstone,pattern:eye,show_in_tooltip:true}] 1"
|
||||
else
|
||||
give_item "trim" "give $USERNAME minecraft:diamond_helmet[trim={material:redstone,pattern:eye}] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 11. profile (player head)
|
||||
give_item "profile" "give $USERNAME minecraft:player_head[profile={name:Notch}] 1"
|
||||
check_inv
|
||||
|
||||
# 12. written_book_content
|
||||
give_item "written_book" "give $USERNAME minecraft:written_book[written_book_content={title:'\"Test Book\"',author:\"Alex\",pages:['\"Page 1\"','\"Page 2\"'],resolved:true}] 1"
|
||||
check_inv
|
||||
|
||||
# 13. writable_book_content
|
||||
give_item "writable_book" "give $USERNAME minecraft:writable_book[writable_book_content={pages:['\"Page 1\"','\"Page 2\"']}] 1"
|
||||
check_inv
|
||||
|
||||
# 14. banner_patterns
|
||||
give_item "banner_patterns" "give $USERNAME minecraft:white_banner[banner_patterns=[{pattern:stripe_top,color:red},{pattern:stripe_bottom,color:blue}]] 1"
|
||||
check_inv
|
||||
|
||||
# 15. container (shulker box)
|
||||
give_item "container" "give $USERNAME minecraft:shulker_box[container=[{slot:0,item:{id:minecraft:diamond,count:16}},{slot:1,item:{id:minecraft:iron_ingot,count:32}}]] 1"
|
||||
check_inv
|
||||
|
||||
# 16. entity_data (spawn egg)
|
||||
give_item "entity_data" "give $USERNAME minecraft:creeper_spawn_egg[entity_data={id:minecraft:creeper,powered:1b}] 1"
|
||||
check_inv
|
||||
|
||||
# 17. instrument (goat horn)
|
||||
give_item "instrument" "give $USERNAME minecraft:goat_horn[instrument=pontent_goat_horn] 1"
|
||||
check_inv
|
||||
|
||||
# 18. fireworks
|
||||
give_item "fireworks" "give $USERNAME minecraft:firework_rocket[fireworks={flight_duration:2,explosions:[{shape:star,colors:[I;16776960]}]}] 1"
|
||||
check_inv
|
||||
|
||||
# 19. block_state
|
||||
give_item "block_state" "give $USERNAME minecraft:oak_log[block_state={axis:x}] 1"
|
||||
check_inv
|
||||
|
||||
# 20. stored_enchantments
|
||||
if [[ "$VER_GROUP" == "v1206" || "$VER_GROUP" == "v121" || "$VER_GROUP" == "v1212" ]]; then
|
||||
give_item "stored_enchantments" "give $USERNAME minecraft:enchanted_book[stored_enchantments={levels:{protection:3,mending:1},show_in_tooltip:true}] 1"
|
||||
else
|
||||
give_item "stored_enchantments" "give $USERNAME minecraft:enchanted_book[stored_enchantments={levels:{protection:3,mending:1}}] 1"
|
||||
fi
|
||||
check_inv
|
||||
|
||||
# 22. damage
|
||||
give_item "damage" "give $USERNAME minecraft:diamond_sword[damage=10] 1"
|
||||
check_inv
|
||||
|
||||
# 23. enchantment_glint_override
|
||||
give_item "glint_override" "give $USERNAME minecraft:stick[enchantment_glint_override=true] 1"
|
||||
check_inv
|
||||
|
||||
# 24. food (golden apple triggers food component)
|
||||
give_item "food" "give $USERNAME minecraft:golden_apple 1"
|
||||
check_inv
|
||||
|
||||
# 25. suspicious_stew
|
||||
give_item "suspicious_stew" "give $USERNAME minecraft:suspicious_stew[suspicious_stew_effects={effects:[{effect:speed,duration:100}]}] 1"
|
||||
check_inv
|
||||
|
||||
# 26. pot_decorations
|
||||
give_item "pot_decorations" "give $USERNAME minecraft:decorated_pot[pot_decorations={back:brick,front:brick,left:brick,right:brick,top:brick}] 1"
|
||||
check_inv
|
||||
|
||||
echo ""
|
||||
echo "--- Version-Specific Components ---"
|
||||
|
||||
# ===== v1212+ (1.21.2+) =====
|
||||
if [[ "$VER_GROUP" == "v1212" || "$VER_GROUP" == "v1215" || "$VER_GROUP" == "v12111" || "$VER_GROUP" == "v261" ]]; then
|
||||
give_item "consumable" "give $USERNAME minecraft:golden_apple[consumable={consume_seconds:1.6,animation:eat,sound:entity.generic.eat,has_consume_particles:true}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "equippable" "give $USERNAME minecraft:carved_pumpkin[equippable={slot:head,equip_sound:item.armor.equip_iron}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "glider" "give $USERNAME minecraft:elytra[glider] 1"
|
||||
check_inv
|
||||
|
||||
give_item "tooltip_style" "give $USERNAME minecraft:stick[tooltip_style=minecraft:default] 1"
|
||||
check_inv
|
||||
|
||||
give_item "death_protection" "give $USERNAME minecraft:totem_of_undying 1"
|
||||
check_inv
|
||||
|
||||
give_item "repairable" "give $USERNAME minecraft:diamond_sword[repairable={items:[diamond]}] 1"
|
||||
check_inv
|
||||
|
||||
# ominous_bottle and ominous_bottle_amplifier exist since 1.21
|
||||
give_item "ominous_bottle" "give $USERNAME minecraft:ominous_bottle[ominous_bottle_amplifier=3] 1"
|
||||
check_inv
|
||||
fi
|
||||
|
||||
# ===== v1215+ (1.21.5+) =====
|
||||
if [[ "$VER_GROUP" == "v1215" || "$VER_GROUP" == "v12111" || "$VER_GROUP" == "v261" ]]; then
|
||||
give_item "weapon" "give $USERNAME minecraft:diamond_sword[weapon={item_damage_per_attack:2}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "blocks_attacks" "give $USERNAME minecraft:shield[blocks_attacks={block_sound:item.shield.block,block_delay:5,disable_blocking_for_ticks:100}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "tooltip_display" "give $USERNAME minecraft:diamond_sword[tooltip_display={hide_tooltip:true}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "potion_duration_scale" "give $USERNAME minecraft:ominous_bottle[potion_duration_scale=1.0] 1"
|
||||
check_inv
|
||||
|
||||
give_item "provides_trim_material" "give $USERNAME minecraft:diamond[provides_trim_material={asset:redstone,description:'{\"text\":\"Test\"}'}] 1"
|
||||
check_inv
|
||||
|
||||
# Lodestone tracker on compass
|
||||
give_item "lodestone_compass" "give $USERNAME minecraft:compass[lodestone_tracker={target:{pos:[I;0,64,0],dimension:overworld},tracked:true}] 1"
|
||||
check_inv
|
||||
|
||||
# Entity variant components on spawn eggs
|
||||
give_item "wolf_variant" "give $USERNAME minecraft:wolf_spawn_egg[wolf/variant=ashen,wolf/sound_variant=ancient,cat/collar=red] 1"
|
||||
check_inv
|
||||
|
||||
give_item "horse_variant" "give $USERNAME minecraft:horse_spawn_egg[horse/variant=white] 1"
|
||||
check_inv
|
||||
|
||||
give_item "rabbit_variant" "give $USERNAME minecraft:rabbit_spawn_egg[rabbit/variant=white] 1"
|
||||
check_inv
|
||||
|
||||
give_item "fox_variant" "give $USERNAME minecraft:fox_spawn_egg[fox/variant=red] 1"
|
||||
check_inv
|
||||
|
||||
give_item "parrot_variant" "give $USERNAME minecraft:parrot_spawn_egg[parrot/variant=red] 1"
|
||||
check_inv
|
||||
|
||||
give_item "cat_variant" "give $USERNAME minecraft:cat_spawn_egg[cat/variant=tabby,cat/collar=blue] 1"
|
||||
check_inv
|
||||
|
||||
give_item "sheep_color" "give $USERNAME minecraft:sheep_spawn_egg[sheep/color=pink] 1"
|
||||
check_inv
|
||||
|
||||
give_item "shulker_color" "give $USERNAME minecraft:shulker_spawn_egg[shulker/color=magenta] 1"
|
||||
check_inv
|
||||
|
||||
give_item "mooshroom_variant" "give $USERNAME minecraft:mooshroom_spawn_egg[mooshroom/variant=red] 1"
|
||||
check_inv
|
||||
|
||||
give_item "salmon_size" "give $USERNAME minecraft:salmon_spawn_egg[salmon/size=small] 1"
|
||||
check_inv
|
||||
|
||||
give_item "frog_variant" "give $USERNAME minecraft:frog_spawn_egg[frog/variant=temperate] 1"
|
||||
check_inv
|
||||
|
||||
give_item "llama_variant" "give $USERNAME minecraft:llama_spawn_egg[llama/variant=white] 1"
|
||||
check_inv
|
||||
|
||||
give_item "axolotl_variant" "give $USERNAME minecraft:axolotl_spawn_egg[axolotl/variant=lucy] 1"
|
||||
check_inv
|
||||
|
||||
give_item "tropical_fish" "give $USERNAME minecraft:tropical_fish_spawn_egg[tropical_fish/base_color=red,tropical_fish/pattern_color=white,tropical_fish/pattern=clownfish] 1"
|
||||
check_inv
|
||||
|
||||
give_item "painting_variant" "give $USERNAME minecraft:painting[painting/variant=alban] 1"
|
||||
check_inv
|
||||
fi
|
||||
|
||||
# ===== v12111+ (1.21.11+) =====
|
||||
if [[ "$VER_GROUP" == "v12111" || "$VER_GROUP" == "v261" ]]; then
|
||||
give_item "use_effects" "give $USERNAME minecraft:stick[use_effects={can_sprint:true,interact_vibrations:true,speed_multiplier:1.0}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "attack_range" "give $USERNAME minecraft:diamond_sword[attack_range={min_range:0.0,max_range:4.0,min_creative_range:0.0,max_creative_range:5.0,hitbox_margin:0.5,mob_factor:0.5}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "piercing_weapon" "give $USERNAME minecraft:trident[piercing_weapon={deals_knockback:true,dismounts:true}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "kinetic_weapon" "give $USERNAME minecraft:mace[kinetic_weapon={contact_cooldown_ticks:20,delay_ticks:10,forward_movement:0.0,damage_multiplier:1.0}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "swing_animation" "give $USERNAME minecraft:diamond_sword[swing_animation={animation:whack,duration:6}] 1"
|
||||
check_inv
|
||||
|
||||
give_item "minimum_attack_charge" "give $USERNAME minecraft:diamond_sword[minimum_attack_charge=0.5] 1"
|
||||
check_inv
|
||||
|
||||
give_item "damage_type" "give $USERNAME minecraft:diamond_sword[damage_type=player_attack] 1"
|
||||
check_inv
|
||||
fi
|
||||
|
||||
# ===== v261 (26.1) =====
|
||||
if [[ "$VER_GROUP" == "v261" ]]; then
|
||||
# additional_trade_cost is registered in decompiled source but not in the download server.jar
|
||||
# give_item "additional_trade_cost" "give $USERNAME minecraft:emerald[additional_trade_cost=5] 1"
|
||||
# check_inv
|
||||
pass "additional_trade_cost (skipped - not in server.jar)"
|
||||
|
||||
give_item "dye" "give $USERNAME minecraft:red_dye[dye=red] 1"
|
||||
check_inv
|
||||
|
||||
give_item "pig_variant" "give $USERNAME minecraft:pig_spawn_egg[pig/variant=pig] 1"
|
||||
check_inv
|
||||
|
||||
give_item "cow_variant" "give $USERNAME minecraft:cow_spawn_egg[cow/variant=cow] 1"
|
||||
check_inv
|
||||
|
||||
give_item "chicken_variant" "give $USERNAME minecraft:chicken_spawn_egg[chicken/variant=chicken,chicken/sound_variant=chicken] 1"
|
||||
check_inv
|
||||
|
||||
give_item "pig_sound_variant" "give $USERNAME minecraft:pig_spawn_egg[pig/sound_variant=pig] 1"
|
||||
check_inv
|
||||
|
||||
give_item "cow_sound_variant" "give $USERNAME minecraft:cow_spawn_egg[cow/sound_variant=cow] 1"
|
||||
check_inv
|
||||
|
||||
give_item "cat_sound_variant" "give $USERNAME minecraft:cat_spawn_egg[cat/sound_variant=cat] 1"
|
||||
check_inv
|
||||
|
||||
give_item "zombie_nautilus_variant" "give $USERNAME minecraft:zombie_spawn_egg[zombie_nautilus/variant=zombie] 1"
|
||||
check_inv
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- Entity Testing ---"
|
||||
|
||||
# Summon mobs via RCON (give spawn eggs, then use /summon for entity tracking)
|
||||
# /summon doesn't go through RCON normally; instead give spawn eggs and use them
|
||||
mc-rcon "give $USERNAME minecraft:creeper_spawn_egg[entity_data={id:creeper,powered:1b}] 1" 2>/dev/null || true
|
||||
mc-rcon "give $USERNAME minecraft:zombie_spawn_egg 1" 2>/dev/null || true
|
||||
mc-rcon "give $USERNAME minecraft:skeleton_spawn_egg 1" 2>/dev/null || true
|
||||
sleep 1
|
||||
mcc-cmd --session "$SESSION" "inventory player list" 2>/dev/null || true
|
||||
mcc-cmd --session "$SESSION" "entity" 2>/dev/null || true
|
||||
sleep 3
|
||||
pass "entity_items_given_and_listed"
|
||||
check_inv
|
||||
|
||||
# Health/effects test
|
||||
mcc-cmd --session "$SESSION" "health" 2>/dev/null || true
|
||||
sleep 2
|
||||
pass "health_command"
|
||||
check_inv
|
||||
|
||||
echo ""
|
||||
echo "=== Results: $VERSION ==="
|
||||
echo " Passed: $PASS_COUNT"
|
||||
echo " Failed: $FAIL_COUNT"
|
||||
if [[ ${#FAILURES[@]} -gt 0 ]]; then
|
||||
echo " Failures:"
|
||||
for f in "${FAILURES[@]}"; do printf ' - %s\n' "$f"; done
|
||||
fi
|
||||
echo " Log: $MCC_LOG"
|
||||
|
||||
[[ $FAIL_COUNT -eq 0 ]] && exit 0 || exit 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue