diff --git a/MinecraftClient/Inventory/BookContent.cs b/MinecraftClient/Inventory/BookContent.cs index 239e3813..262e4680 100644 --- a/MinecraftClient/Inventory/BookContent.cs +++ b/MinecraftClient/Inventory/BookContent.cs @@ -100,7 +100,7 @@ public static class BookContentHelper { if (item.Components is not null) { - var component = item.Components.OfType().FirstOrDefault(); + var component = item.Components.OfType().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().FirstOrDefault(); + var component = item.Components.OfType().FirstOrDefault(); if (component is not null) { content = new BookContent( diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 180b174a..570c3da3 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -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> ReadNextPacket() { var size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size - Queue packetData = new(socketWrapper.ReadDataRAW(size)); //Packet contents + var rawBytes = socketWrapper.ReadDataRAW(size); + Queue 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()) diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponentComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponent.cs similarity index 93% rename from MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponentComponent.cs rename to MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponent.cs index eb5ed17d..f8679473 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponentComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/FoodComponent.cs @@ -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; } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/IntangibleProjectileComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/IntangibleProjectileComponent.cs index 2ecfe0f1..b6d24a5e 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/IntangibleProjectileComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/IntangibleProjectileComponent.cs @@ -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? Nbt { get; set; } = new(); - - public override void Parse(Queue data) - { - Nbt = DataTypes.ReadNextNbt(data); - } - - public override Queue Serialize() - { - var data = new List(); - data.AddRange(DataTypes.GetNbt(Nbt)); - return new Queue(data); - } -} \ No newline at end of file +} diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OmniousBottleAmplifierComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OminousBottleAmplifierComponent.cs similarity index 91% rename from MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OmniousBottleAmplifierComponent.cs rename to MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OminousBottleAmplifierComponent.cs index 1c35b944..ed92d953 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OmniousBottleAmplifierComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/OminousBottleAmplifierComponent.cs @@ -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; } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/UnbreakableComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/UnbreakableComponent.cs index d7fc891a..b4f354f6 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/UnbreakableComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/UnbreakableComponent.cs @@ -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 data) { - Unbrekable = DataTypes.ReadNextBool(data); + Unbreakable = DataTypes.ReadNextBool(data); } public override Queue Serialize() { var data = new List(); - data.AddRange(DataTypes.GetBool(Unbrekable)); + data.AddRange(DataTypes.GetBool(Unbreakable)); return new Queue(data); } } \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBlookContentComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBookContentComponent.cs similarity index 82% rename from MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBlookContentComponent.cs rename to MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBookContentComponent.cs index 9a2b9a30..59dc0409 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBlookContentComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WritableBookContentComponent.cs @@ -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 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)); } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBlookContentComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBookContentComponent.cs similarity index 94% rename from MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBlookContentComponent.cs rename to MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBookContentComponent.cs index 79e58af8..413c94d8 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBlookContentComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_20_6/WrittenBookContentComponent.cs @@ -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; } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent.cs deleted file mode 100644 index e1e0a3e1..00000000 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21/JukeBoxPlayableComponent.cs +++ /dev/null @@ -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 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 Serialize() - { - var data = new List(); - - 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(data); - } -} \ No newline at end of file diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/KineticWeaponComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/KineticWeaponComponent.cs index 9bfeb283..4d676f51 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/KineticWeaponComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/KineticWeaponComponent.cs @@ -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 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 data) + private KineticWeaponConditionData? ReadOptionalCondition(Queue 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 data) + private void WriteOptionalCondition(List 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 Serialize() { - return new Queue(); + var data = new List(); + 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(data); } } + +public sealed record KineticWeaponConditionData(int MaxDurationTicks, float MinSpeed, float MinRelativeSpeed); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/PiercingWeaponComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/PiercingWeaponComponent.cs index 904044ef..a6af8bef 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/PiercingWeaponComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_11/PiercingWeaponComponent.cs @@ -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 data) { DealsKnockback = DataTypes.ReadNextBool(data); Dismounts = DataTypes.ReadNextBool(data); - ReadOptionalSoundEventHolder(data); - ReadOptionalSoundEventHolder(data); - } - - private void ReadOptionalSoundEventHolder(Queue 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 Serialize() { - return new Queue(); + var data = new List(); + data.AddRange(DataTypes.GetBool(DealsKnockback)); + data.AddRange(DataTypes.GetBool(Dismounts)); + StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, Sound); + StructuredComponentCodecHelpers.WriteOptionalSoundEventHolder(DataTypes, data, HitSound); + return new Queue(data); } } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/BlocksAttacksComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/BlocksAttacksComponent.cs index 030133c7..5c497bf4 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/BlocksAttacksComponent.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/1_21_5/BlocksAttacksComponent.cs @@ -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 RawDamageReductions { get; set; } = []; + public List 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 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 as ResourceLocation + BypassedBy = DataTypes.ReadNextString(data); // TagKey as ResourceLocation - var hasBlockSound = DataTypes.ReadNextBool(data); - if (hasBlockSound) - ReadSoundEventHolder(data); - - var hasDisableSound = DataTypes.ReadNextBool(data); - if (hasDisableSound) - ReadSoundEventHolder(data); - } - - private void ReadHolderSet(Queue 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 registry ids - } - } - - private void ReadSoundEventHolder(Queue 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 Serialize() { - return new Queue(); + var data = new List(); + 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(data); } } + +public sealed record DamageReductionData(float HorizontalBlockingAngle, HolderSetData? Type, float Base, float Factor); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/26_1/TypedEntityDataComponent261.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/26_1/TypedEntityDataComponent261.cs index 15940d06..d05b147a 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/26_1/TypedEntityDataComponent261.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/26_1/TypedEntityDataComponent261.cs @@ -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? Nbt { get; set; } - - public override void Parse(Queue data) - { - EntityTypeId = DataTypes.ReadNextVarInt(data); - Nbt = DataTypes.ReadNextNbt(data); - } - - public override Queue Serialize() - { - var data = new List(); - data.AddRange(DataTypes.GetVarInt(EntityTypeId)); - data.AddRange(DataTypes.GetNbt(Nbt)); - return new Queue(data); - } -} + : TypedEntityDataComponent(dataTypes, itemPalette, subComponentRegistry) +{ } public class BlockEntityDataComponent261(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) - : TypedEntityDataComponent261(dataTypes, itemPalette, subComponentRegistry) + : TypedBlockEntityDataComponent(dataTypes, itemPalette, subComponentRegistry) { } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/StructuredComponentCodecHelpers.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/StructuredComponentCodecHelpers.cs new file mode 100644 index 00000000..dc3fc218 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/StructuredComponentCodecHelpers.cs @@ -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 data) + { + var sizeOrTag = dataTypes.ReadNextVarInt(data); + if (sizeOrTag == 0) + return new HolderSetData(dataTypes.ReadNextString(data), []); + + var count = sizeOrTag - 1; + var holderIds = new List(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 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 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 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 data) + { + return dataTypes.ReadNextBool(data) ? ReadSoundEventHolder(dataTypes, data) : null; + } + + public static void WriteOptionalSoundEventHolder(DataTypes dataTypes, List 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 HolderIds); + +public sealed record SoundEventHolderData(int HolderId, string? SoundLocation, bool HasFixedRange, float FixedRange); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TypedEntityDataComponent.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TypedEntityDataComponent.cs new file mode 100644 index 00000000..ab7ae279 --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Components/TypedEntityDataComponent.cs @@ -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? Nbt { get; set; } + + public override void Parse(Queue data) + { + EntityTypeId = DataTypes.ReadNextVarInt(data); + Nbt = DataTypes.ReadNextNbt(data); + } + + public override Queue Serialize() + { + var data = new List(); + data.AddRange(DataTypes.GetVarInt(EntityTypeId)); + data.AddRange(DataTypes.GetNbt(Nbt)); + return new Queue(data); + } +} + +public class TypedBlockEntityDataComponent(DataTypes dataTypes, ItemPalette itemPalette, SubComponentRegistry subComponentRegistry) + : TypedEntityDataComponent(dataTypes, itemPalette, subComponentRegistry) +{ } diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs index 18ae967c..8f5dee2e 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1206.cs @@ -13,7 +13,7 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry RegisterComponent(1, "minecraft:max_stack_size"); RegisterComponent(2, "minecraft:max_damage"); RegisterComponent(3, "minecraft:damage"); - RegisterComponent(4, "minecraft:unbreakable"); + RegisterComponent(4, "minecraft:unbreakable"); RegisterComponent(5, "minecraft:custom_name"); RegisterComponent(6, "minecraft:item_name"); RegisterComponent(7, "minecraft:lore"); @@ -29,7 +29,7 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry RegisterComponent(17, "minecraft:creative_slot_lock"); RegisterComponent(18, "minecraft:enchantment_glint_override"); RegisterComponent(19, "minecraft:intangible_projectile"); - RegisterComponent(20, "minecraft:food"); + RegisterComponent(20, "minecraft:food"); RegisterComponent(21, "minecraft:fire_resistant"); RegisterComponent(22, "minecraft:tool"); RegisterComponent(23, "minecraft:stored_enchantments"); @@ -42,15 +42,15 @@ public class StructuredComponentsRegistry1206 : StructuredComponentRegistry RegisterComponent(30, "minecraft:bundle_contents"); RegisterComponent(31, "minecraft:potion_contents"); RegisterComponent(32, "minecraft:suspicious_stew_effects"); - RegisterComponent(33, "minecraft:writable_book_content"); - RegisterComponent(34, "minecraft:written_book_content"); + RegisterComponent(33, "minecraft:writable_book_content"); + RegisterComponent(34, "minecraft:written_book_content"); RegisterComponent(35, "minecraft:trim"); RegisterComponent(36, "minecraft:debug_stick_state"); RegisterComponent(37, "minecraft:entity_data"); RegisterComponent(38, "minecraft:bucket_entity_data"); RegisterComponent(39, "minecraft:block_entity_data"); RegisterComponent(40, "minecraft:instrument"); - RegisterComponent(41, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(41, "minecraft:ominous_bottle_amplifier"); RegisterComponent(42, "minecraft:recipes"); RegisterComponent(43, "minecraft:lodestone_tracker"); RegisterComponent(44, "minecraft:firework_explosion"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs index 7a2376f3..5e22bd78 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry121.cs @@ -14,7 +14,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(1, "minecraft:max_stack_size"); RegisterComponent(2, "minecraft:max_damage"); RegisterComponent(3, "minecraft:damage"); - RegisterComponent(4, "minecraft:unbreakable"); + RegisterComponent(4, "minecraft:unbreakable"); RegisterComponent(5, "minecraft:custom_name"); RegisterComponent(6, "minecraft:item_name"); RegisterComponent(7, "minecraft:lore"); @@ -30,7 +30,7 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(17, "minecraft:creative_slot_lock"); RegisterComponent(18, "minecraft:enchantment_glint_override"); RegisterComponent(19, "minecraft:intangible_projectile"); - RegisterComponent(20, "minecraft:food"); + RegisterComponent(20, "minecraft:food"); RegisterComponent(21, "minecraft:fire_resistant"); RegisterComponent(22, "minecraft:tool"); RegisterComponent(23, "minecraft:stored_enchantments"); @@ -43,15 +43,15 @@ public class StructuredComponentsRegistry121 : StructuredComponentRegistry RegisterComponent(30, "minecraft:bundle_contents"); RegisterComponent(31, "minecraft:potion_contents"); RegisterComponent(32, "minecraft:suspicious_stew_effects"); - RegisterComponent(33, "minecraft:writable_book_content"); - RegisterComponent(34, "minecraft:written_book_content"); + RegisterComponent(33, "minecraft:writable_book_content"); + RegisterComponent(34, "minecraft:written_book_content"); RegisterComponent(35, "minecraft:trim"); RegisterComponent(36, "minecraft:debug_stick_state"); RegisterComponent(37, "minecraft:entity_data"); RegisterComponent(38, "minecraft:bucket_entity_data"); RegisterComponent(39, "minecraft:block_entity_data"); RegisterComponent(40, "minecraft:instrument"); - RegisterComponent(41, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(41, "minecraft:ominous_bottle_amplifier"); RegisterComponent(42, "minecraft:jukebox_playable"); RegisterComponent(43, "minecraft:recipes"); RegisterComponent(44, "minecraft:lodestone_tracker"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs index de00bbe2..0be0c7d2 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry12111.cs @@ -69,16 +69,16 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(49, "minecraft:potion_contents"); RegisterComponent(50, "minecraft:potion_duration_scale"); RegisterComponent(51, "minecraft:suspicious_stew_effects"); - RegisterComponent(52, "minecraft:writable_book_content"); - RegisterComponent(53, "minecraft:written_book_content"); + RegisterComponent(52, "minecraft:writable_book_content"); + RegisterComponent(53, "minecraft:written_book_content"); RegisterComponent(54, "minecraft:trim"); RegisterComponent(55, "minecraft:debug_stick_state"); - RegisterComponent(56, "minecraft:entity_data"); + RegisterComponent(56, "minecraft:entity_data"); RegisterComponent(57, "minecraft:bucket_entity_data"); - RegisterComponent(58, "minecraft:block_entity_data"); + RegisterComponent(58, "minecraft:block_entity_data"); RegisterComponent(59, "minecraft:instrument"); RegisterComponent(60, "minecraft:provides_trim_material"); - RegisterComponent(61, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(61, "minecraft:ominous_bottle_amplifier"); RegisterComponent(62, "minecraft:jukebox_playable"); RegisterComponent(63, "minecraft:provides_banner_patterns"); RegisterComponent(64, "minecraft:recipes"); @@ -111,7 +111,7 @@ public class StructuredComponentsRegistry12111 : StructuredComponentRegistry RegisterComponent(90, "minecraft:rabbit/variant"); RegisterComponent(91, "minecraft:pig/variant"); RegisterComponent(92, "minecraft:cow/variant"); - RegisterComponent(93, "minecraft:chicken/variant"); + RegisterComponent(93, "minecraft:chicken/variant"); RegisterComponent(94, "minecraft:zombie_nautilus/variant"); RegisterComponent(95, "minecraft:frog/variant"); RegisterComponent(96, "minecraft:horse/variant"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs index d6f519bb..ffe89d0f 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1212.cs @@ -15,7 +15,7 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry RegisterComponent(1, "minecraft:max_stack_size"); RegisterComponent(2, "minecraft:max_damage"); RegisterComponent(3, "minecraft:damage"); - RegisterComponent(4, "minecraft:unbreakable"); + RegisterComponent(4, "minecraft:unbreakable"); RegisterComponent(5, "minecraft:custom_name"); RegisterComponent(6, "minecraft:item_name"); RegisterComponent(7, "minecraft:item_model"); @@ -57,15 +57,15 @@ public class StructuredComponentsRegistry1212 : StructuredComponentRegistry RegisterComponent(40, "minecraft:bundle_contents"); RegisterComponent(41, "minecraft:potion_contents"); RegisterComponent(42, "minecraft:suspicious_stew_effects"); - RegisterComponent(43, "minecraft:writable_book_content"); - RegisterComponent(44, "minecraft:written_book_content"); + RegisterComponent(43, "minecraft:writable_book_content"); + RegisterComponent(44, "minecraft:written_book_content"); RegisterComponent(45, "minecraft:trim"); RegisterComponent(46, "minecraft:debug_stick_state"); RegisterComponent(47, "minecraft:entity_data"); RegisterComponent(48, "minecraft:bucket_entity_data"); RegisterComponent(49, "minecraft:block_entity_data"); RegisterComponent(50, "minecraft:instrument"); - RegisterComponent(51, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(51, "minecraft:ominous_bottle_amplifier"); RegisterComponent(52, "minecraft:jukebox_playable"); RegisterComponent(53, "minecraft:recipes"); RegisterComponent(54, "minecraft:lodestone_tracker"); diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs index cca30281..d78d87b9 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry1215.cs @@ -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(0, "minecraft:custom_data"); RegisterComponent(1, "minecraft:max_stack_size"); @@ -31,7 +33,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(10, "minecraft:enchantments"); RegisterComponent(11, "minecraft:can_place_on"); RegisterComponent(12, "minecraft:can_break"); - if (uses1218AttributeAndEquippableFormats) + if (uses1216AttributeAndEquippableFormats) RegisterComponent(13, "minecraft:attribute_modifiers"); else RegisterComponent(13, "minecraft:attribute_modifiers"); @@ -50,7 +52,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(25, "minecraft:tool"); RegisterComponent(26, "minecraft:weapon"); // NEW RegisterComponent(27, "minecraft:enchantable"); - if (uses1218AttributeAndEquippableFormats) + if (uses1216AttributeAndEquippableFormats) RegisterComponent(28, "minecraft:equippable"); else RegisterComponent(28, "minecraft:equippable"); @@ -70,16 +72,22 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(42, "minecraft:potion_contents"); RegisterComponent(43, "minecraft:potion_duration_scale"); // NEW RegisterComponent(44, "minecraft:suspicious_stew_effects"); - RegisterComponent(45, "minecraft:writable_book_content"); - RegisterComponent(46, "minecraft:written_book_content"); + RegisterComponent(45, "minecraft:writable_book_content"); + RegisterComponent(46, "minecraft:written_book_content"); RegisterComponent(47, "minecraft:trim"); RegisterComponent(48, "minecraft:debug_stick_state"); - RegisterComponent(49, "minecraft:entity_data"); + if (usesTypedEntityDataFormat) + RegisterComponent(49, "minecraft:entity_data"); + else + RegisterComponent(49, "minecraft:entity_data"); RegisterComponent(50, "minecraft:bucket_entity_data"); - RegisterComponent(51, "minecraft:block_entity_data"); + if (usesTypedEntityDataFormat) + RegisterComponent(51, "minecraft:block_entity_data"); + else + RegisterComponent(51, "minecraft:block_entity_data"); RegisterComponent(52, "minecraft:instrument"); // Changed to EitherHolder in 1.21.5 RegisterComponent(53, "minecraft:provides_trim_material"); // NEW - RegisterComponent(54, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(54, "minecraft:ominous_bottle_amplifier"); RegisterComponent(55, "minecraft:jukebox_playable"); RegisterComponent(56, "minecraft:provides_banner_patterns"); // NEW RegisterComponent(57, "minecraft:recipes"); @@ -116,7 +124,7 @@ public class StructuredComponentsRegistry1215 : StructuredComponentRegistry RegisterComponent(83, "minecraft:rabbit/variant"); RegisterComponent(84, "minecraft:pig/variant"); RegisterComponent(85, "minecraft:cow/variant"); - RegisterComponent(86, "minecraft:chicken/variant"); // EitherHolder + RegisterComponent(86, "minecraft:chicken/variant"); // EitherHolder RegisterComponent(87, "minecraft:frog/variant"); RegisterComponent(88, "minecraft:horse/variant"); RegisterComponent(89, "minecraft:painting/variant"); // Holder diff --git a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry261.cs b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry261.cs index c4d809fd..c0b4281d 100644 --- a/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry261.cs +++ b/MinecraftClient/Protocol/Handlers/StructuredComponents/Registries/StructuredComponentsRegistry261.cs @@ -71,16 +71,16 @@ public class StructuredComponentsRegistry261 : StructuredComponentRegistry RegisterComponent(51, "minecraft:potion_contents"); RegisterComponent(52, "minecraft:potion_duration_scale"); RegisterComponent(53, "minecraft:suspicious_stew_effects"); - RegisterComponent(54, "minecraft:writable_book_content"); - RegisterComponent(55, "minecraft:written_book_content"); + RegisterComponent(54, "minecraft:writable_book_content"); + RegisterComponent(55, "minecraft:written_book_content"); RegisterComponent(56, "minecraft:trim"); RegisterComponent(57, "minecraft:debug_stick_state"); - RegisterComponent(58, "minecraft:entity_data"); + RegisterComponent(58, "minecraft:entity_data"); RegisterComponent(59, "minecraft:bucket_entity_data"); - RegisterComponent(60, "minecraft:block_entity_data"); + RegisterComponent(60, "minecraft:block_entity_data"); RegisterComponent(61, "minecraft:instrument"); RegisterComponent(62, "minecraft:provides_trim_material"); - RegisterComponent(63, "minecraft:ominous_bottle_amplifier"); + RegisterComponent(63, "minecraft:ominous_bottle_amplifier"); RegisterComponent(64, "minecraft:jukebox_playable"); RegisterComponent(65, "minecraft:provides_banner_patterns"); RegisterComponent(66, "minecraft:recipes"); diff --git a/tools/run-structured-components-test.sh b/tools/run-structured-components-test.sh new file mode 100755 index 00000000..a188e7d6 --- /dev/null +++ b/tools/run-structured-components-test.sh @@ -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 +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 "; 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