mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
bugfix: Fixed and tested all structured components and issues with Empty packets sent by some servers
bugfix: Fixed and tested all structured components and issues with Empty packets sent by some servers
This commit is contained in:
commit
981a996344
27 changed files with 818 additions and 262 deletions
|
|
@ -115,7 +115,31 @@ Use this for TPS, movement-cadence, or packet-cadence work:
|
|||
|
||||
Run them against a real server with a temp config and summarize counts from the captured logs.
|
||||
|
||||
### 4. Full inventory regression sweep
|
||||
### 4. Structured components test
|
||||
|
||||
Use this after touching any `StructuredComponents` code (registries, component
|
||||
parsers, subcomponents, codec helpers) to prove every component type in a
|
||||
version parses on the wire without error:
|
||||
|
||||
```bash
|
||||
bash tools/run-structured-components-test.sh 1.21.11
|
||||
```
|
||||
|
||||
Run a single version (fast, ~2 min) or a matrix:
|
||||
|
||||
```bash
|
||||
for v in 1.20.6 1.21 1.21.2 1.21.5 1.21.11 26.1; do
|
||||
bash tools/run-structured-components-test.sh "$v"
|
||||
done
|
||||
```
|
||||
|
||||
The script gives items with every registered component via RCON `/give`, reads
|
||||
them back with `inventory player list`, and asserts no parse errors in the MCC
|
||||
log. Version-gated components (v1212+, v1215+, v12111+, v261) are tested only
|
||||
on the versions that support them. See `SC_Integration_Test_Report.md` for a
|
||||
reference run across all 6 version groups.
|
||||
|
||||
### 5. Full inventory regression sweep
|
||||
|
||||
Use this when touching inventory snapshots, player/container slot sync, creative inventory, item-slot serialization, packet palettes, game-mode updates, or block-use paths that open containers:
|
||||
|
||||
|
|
@ -198,6 +222,8 @@ Optionally override the login name with the fourth argument to the config helper
|
|||
- ordered creative-mode E2E regression scenario
|
||||
- `tools/run-inventory-full-sweep.sh`
|
||||
- full inventory command/API sweep across one or more versions
|
||||
- `tools/run-structured-components-test.sh`
|
||||
- exercises every structured component via RCON `/give` across versions 1.20.6-26.1
|
||||
|
||||
## Evidence Discipline
|
||||
|
||||
|
|
|
|||
|
|
@ -123,3 +123,4 @@ Read `docs/guide/ai-assisted-development.md` before starting development work on
|
|||
- Don't trust older docs over current code for supported versions or feature gates. When AGENTS.md, skills, and older docs disagree, prefer current code and current tool behavior, then update the stale source.
|
||||
- Don't hardcode user-facing strings (messages, labels, help text) directly in source code; always use `Translations.*` resources so the text can be localized.
|
||||
- Never use "—" ("em dash"), unless specifically being instructed to do so!
|
||||
- Never generate MCC config files (`.ini`) in the repo root. When `dotnet run --project MinecraftClient -- --help` is used to generate a config template, it writes `MinecraftClient.ini` to the current directory. Always run this command from a system temp directory (e.g. `mktemp -d`) or use `prepare_offline_mcc_config.sh` which already handles output routing.
|
||||
|
|
|
|||
|
|
@ -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.Debug("Empty packet after decompress: size={0}, sizeUncompressed={1}, protocol={2}, state={3}, rawBytes=[{4}]", size, sizeUncompressed, protocolVersion, currentState, rawHex);
|
||||
return new(-1, packetData);
|
||||
}
|
||||
|
||||
var packetId = dataTypes.ReadNextVarInt(packetData); // Packet ID
|
||||
LogIncomingPacket(packetId, packetData.Count, size, compressed, sizeUncompressed);
|
||||
if (handler.GetNetworkPacketCaptureEnabled())
|
||||
|
|
@ -4147,12 +4158,21 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
log.PacketDebug(string.Format(Translations.debug_packet_state_change, previousState, newState));
|
||||
}
|
||||
|
||||
private static bool IsPacketExcluded(string packetType)
|
||||
{
|
||||
var exclusions = Settings.Config.Logging.PacketDebugExclusions;
|
||||
return exclusions.Count > 0 && exclusions.Contains(packetType, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void LogIncomingPacket(int packetId, int payloadLength, int frameLength, bool compressed, int uncompressedLength)
|
||||
{
|
||||
if (!log.DebugEnabled)
|
||||
return;
|
||||
|
||||
var packetType = ResolveIncomingPacketType(packetId);
|
||||
if (IsPacketExcluded(packetType))
|
||||
return;
|
||||
|
||||
var compressionInfo = compression_treshold < 0
|
||||
? Translations.debug_packet_compression_disabled
|
||||
: compressed
|
||||
|
|
@ -4173,10 +4193,14 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (!log.DebugEnabled)
|
||||
return;
|
||||
|
||||
var resolvedType = packetType ?? ResolveOutgoingPacketType(packetId);
|
||||
if (IsPacketExcluded(resolvedType))
|
||||
return;
|
||||
|
||||
log.PacketDebug(string.Format(Translations.debug_packet_outgoing,
|
||||
currentState,
|
||||
packetId,
|
||||
packetType ?? ResolveOutgoingPacketType(packetId),
|
||||
resolvedType,
|
||||
payloadLength,
|
||||
compression_treshold));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -1541,6 +1541,15 @@ namespace MinecraftClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Packet types to exclude from packet debug logs, e.g. ["KeepAlive", "Ping"]..
|
||||
/// </summary>
|
||||
internal static string Logging_PacketDebugExclusions {
|
||||
get {
|
||||
return ResourceManager.GetString("Logging.PacketDebugExclusions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show error messages..
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -629,6 +629,9 @@ Want to upgrade to a newer version? See https://github.com/MCCTeam/Minecraft-Con
|
|||
<data name="Logging.PacketDebugMessages" xml:space="preserve">
|
||||
<value>Show low-level packet debug logs.</value>
|
||||
</data>
|
||||
<data name="Logging.PacketDebugExclusions" xml:space="preserve">
|
||||
<value>Packet types to exclude from packet debug logs, e.g. ["KeepAlive", "Ping"].</value>
|
||||
</data>
|
||||
<data name="Logging.ErrorMessages" xml:space="preserve">
|
||||
<value>Show error messages.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -1080,6 +1080,9 @@ namespace MinecraftClient
|
|||
[TomlInlineComment("$Logging.PacketDebugMessages$")]
|
||||
public bool PacketDebugMessages = false;
|
||||
|
||||
[TomlInlineComment("$Logging.PacketDebugExclusions$")]
|
||||
public List<string> PacketDebugExclusions = new();
|
||||
|
||||
[TomlInlineComment("$Logging.ChatMessages$")]
|
||||
public bool ChatMessages = true;
|
||||
|
||||
|
|
|
|||
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