From b6ddd0b090a6e6be0f79e3246dcf90ac901a8ff2 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:17:30 +0800 Subject: [PATCH] Update Jade Protocol, and fix #250 --- patches/server/0040-Jade-Protocol.patch | 415 +++++++++++++++++++++++- 1 file changed, 401 insertions(+), 14 deletions(-) diff --git a/patches/server/0040-Jade-Protocol.patch b/patches/server/0040-Jade-Protocol.patch index b0b0165a..940363a9 100644 --- a/patches/server/0040-Jade-Protocol.patch +++ b/patches/server/0040-Jade-Protocol.patch @@ -44,17 +44,97 @@ index 055f4b87c01ee7ecf7d2a111b72cc5aa85d9fbe8..5d9030f4787a43c56ae9455180badd56 protected long nextMobSpawnsAt; protected int totalMobsSpawned; public Optional nextSpawnData; +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java +index 38078c44b35e917d1d243a5f8599aa858d8611de..13dbadfb50278b79b33d9dce10413c93c9e4ff31 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java +@@ -36,7 +36,7 @@ public class LootPool { + ) + .apply(instance, LootPool::new) + ); +- private final List entries; ++ public final List entries; // Leaves - private -> public + private final List conditions; + private final Predicate compositeCondition; + private final List functions; +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +index edaf7f1692ae059581f3abc24bb228874e6d114b..f09cfc472d4dbdc8cb0b6a45ef240b25a865ffba 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +@@ -58,7 +58,7 @@ public class LootTable { + public static final Codec> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, LootTable.DIRECT_CODEC); + private final LootContextParamSet paramSet; + private final Optional randomSequence; +- private final List pools; ++ public final List pools; // Leaves - private -> public + private final List functions; + private final BiFunction compositeFunction; + public CraftLootTable craftLootTable; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java +index 128792f76f02d74c1ccf84beb8e7973453424639..775fbddf3e3b133e33f54aaa8e8a07d131095e34 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java +@@ -9,7 +9,7 @@ import net.minecraft.world.level.storage.loot.ValidationContext; + import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + + public abstract class CompositeEntryBase extends LootPoolEntryContainer { +- protected final List children; ++ public final List children; // Leaves - private -> public + private final ComposableEntryContainer composedChildren; + + protected CompositeEntryBase(List terms, List conditions) { +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +index 1d2f2bb352abf6772cd20293575fc79e8e64ce3b..b157dfaf1efffebd3f2ae8cb8fcf0972fe742258 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +@@ -13,7 +13,7 @@ import net.minecraft.world.level.storage.loot.predicates.ConditionUserBuilder; + import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; + + public abstract class LootPoolEntryContainer implements ComposableEntryContainer { +- protected final List conditions; ++ public final List conditions; // Leaves - private -> public + private final Predicate compositeCondition; + + protected LootPoolEntryContainer(List conditions) { +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java +index 71989359192c8f30a1a8d343a2c6cb5b92330491..ec273bd4d0e61f54532df599f00695e8b9d44800 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java +@@ -25,7 +25,7 @@ public class NestedLootTable extends LootPoolSingletonContainer { + .and(singletonFields(instance)) + .apply(instance, NestedLootTable::new) + ); +- private final Either, LootTable> contents; ++ public final Either, LootTable> contents; // Leaves - private -> public + + private NestedLootTable( + Either, LootTable> value, int weight, int quality, List conditions, List functions +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java +index 30d0133a42ce990352f5c492fcf9beb105364848..1ab2eab686b3a89d406f127a6036c0e2932db4a6 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java +@@ -11,7 +11,7 @@ import net.minecraft.world.level.storage.loot.LootContext; + import net.minecraft.world.level.storage.loot.ValidationContext; + + public abstract class CompositeLootItemCondition implements LootItemCondition { +- protected final List terms; ++ public final List terms; // Leaves - private -> public + private final Predicate composedPredicate; + + protected CompositeLootItemCondition(List terms, Predicate predicate) { diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java new file mode 100644 -index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5372f5978 +index 0000000000000000000000000000000000000000..0f40d3b95de7cefbf1318109cb45d13732f25534 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java -@@ -0,0 +1,343 @@ +@@ -0,0 +1,397 @@ +package org.leavesmc.leaves.protocol.jade; + +import io.netty.buffer.ByteBuf; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; @@ -75,6 +155,7 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.world.entity.monster.ZombieVillager; +import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.CampfireBlock; @@ -128,11 +209,13 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 +import org.leavesmc.leaves.protocol.jade.provider.entity.StatusEffectsProvider; +import org.leavesmc.leaves.protocol.jade.provider.entity.ZombieVillagerProvider; +import org.leavesmc.leaves.protocol.jade.util.HierarchyLookup; ++import org.leavesmc.leaves.protocol.jade.util.LootTableMineableCollector; +import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup; +import org.leavesmc.leaves.protocol.jade.util.PriorityStore; +import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup; + +import java.util.ArrayList; ++import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; @@ -140,14 +223,12 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + +@LeavesProtocol(namespace = "jade") +public class JadeProtocol { ++ + public static PriorityStore priorities; ++ private static List shearableBlocks = List.of(); + + public static final String PROTOCOL_ID = "jade"; + -+ // send -+ public static final ResourceLocation PACKET_SERVER_PING = id("server_ping"); -+ public static final ResourceLocation PACKET_RECEIVE_DATA = id("receive_data"); -+ + private static final HierarchyLookup> entityDataProviders = new HierarchyLookup<>(Entity.class); + private static final PairHierarchyLookup> blockDataProviders = new PairHierarchyLookup<>(new HierarchyLookup<>(Block.class), new HierarchyLookup<>(BlockEntity.class)); + @@ -224,12 +305,20 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE); + + itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE); ++ ++ try { ++ shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute( ++ MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.LOOT_TABLE), ++ Items.SHEARS.getDefaultInstance())); ++ } catch (Throwable ignore) { ++ LeavesLogger.LOGGER.severe("Failed to collect shearable blocks"); ++ } + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerJoin(ServerPlayer player) { + if (LeavesConfig.jadeProtocol) { -+ ProtocolUtils.sendPayloadPacket(player, PACKET_SERVER_PING, buf -> buf.writeUtf("")); ++ ProtocolUtils.sendPayloadPacket(player, new ServerPingPayload("", shearableBlocks)); + } + } + @@ -273,7 +362,7 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + } + tag.putInt("EntityId", entity.getId()); + -+ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); ++ ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); + }); + } + @@ -327,7 +416,7 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + tag.putInt("z", pos.getZ()); + tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString()); + -+ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); ++ ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag)); + }); + } + @@ -349,12 +438,12 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); + + @New -+ public RequestEntityPayload(ResourceLocation id, FriendlyByteBuf buf) { ++ public RequestEntityPayload(ResourceLocation id, @NotNull FriendlyByteBuf buf) { + this(buf.readBoolean(), buf.readVarInt(), buf.readVarInt(), new Vec3(buf.readVector3f())); + } + + @Override -+ public void write(FriendlyByteBuf buf) { ++ public void write(@NotNull FriendlyByteBuf buf) { + buf.writeBoolean(showDetails); + buf.writeVarInt(entityId); + buf.writeVarInt(partIndex); @@ -369,17 +458,18 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + } + + public record RequestBlockPayload(boolean showDetails, BlockHitResult hitResult, BlockState blockState, ItemStack fakeBlock) implements LeavesCustomPayload { ++ ++ private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block"); + private static final StreamCodec ITEM_STACK_CODEC = ItemStack.OPTIONAL_STREAM_CODEC; + private static final StreamCodec BLOCK_STATE_CODEC = ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY); -+ private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block"); + + @New -+ public RequestBlockPayload(ResourceLocation id, FriendlyByteBuf buf) { ++ public RequestBlockPayload(ResourceLocation id, @NotNull FriendlyByteBuf buf) { + this(buf.readBoolean(), buf.readBlockHitResult(), BLOCK_STATE_CODEC.decode(buf), ITEM_STACK_CODEC.decode(ProtocolUtils.decorate(buf))); + } + + @Override -+ public void write(FriendlyByteBuf buf) { ++ public void write(@NotNull FriendlyByteBuf buf) { + buf.writeBoolean(showDetails); + buf.writeBlockHitResult(hitResult); + BLOCK_STATE_CODEC.encode(buf, blockState); @@ -392,6 +482,48 @@ index 0000000000000000000000000000000000000000..33741d707715619929e5412a786470c5 + return PACKET_REQUEST_BLOCK; + } + } ++ ++ public record ServerPingPayload(String serverConfig, List shearableBlocks) implements LeavesCustomPayload { ++ ++ private static final ResourceLocation PACKET_SERVER_PING = JadeProtocol.id("server_ping_v1"); ++ private static final StreamCodec> SHEARABLE_BLOCKS_CODEC = ByteBufCodecs.registry(Registries.BLOCK).apply(ByteBufCodecs.list()); ++ ++ @New ++ public ServerPingPayload(ResourceLocation id, @NotNull FriendlyByteBuf buf) { ++ this(buf.readUtf(), SHEARABLE_BLOCKS_CODEC.decode(ProtocolUtils.decorate(buf))); ++ } ++ ++ @Override ++ public void write(FriendlyByteBuf buf) { ++ buf.writeUtf(serverConfig); ++ SHEARABLE_BLOCKS_CODEC.encode(ProtocolUtils.decorate(buf), shearableBlocks); ++ } ++ ++ @Override ++ public ResourceLocation id() { ++ return PACKET_SERVER_PING; ++ } ++ } ++ ++ public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload { ++ ++ private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data"); ++ ++ @New ++ public ReceiveDataPayload(ResourceLocation id, FriendlyByteBuf buf) { ++ this(buf.readNbt()); ++ } ++ ++ @Override ++ public void write(@NotNull FriendlyByteBuf buf) { ++ buf.writeNbt(tag); ++ } ++ ++ @Override ++ public ResourceLocation id() { ++ return PACKET_RECEIVE_DATA; ++ } ++ } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java new file mode 100644 @@ -1565,6 +1697,151 @@ index 0000000000000000000000000000000000000000..d48ef5d8c72b57ff5525ab06b59724d0 + return MC_ZOMBIE_VILLAGER; + } +} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0e6f19a4cf55d952d8f20bf703635c8584a5f0bd +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java +@@ -0,0 +1,43 @@ ++package org.leavesmc.leaves.protocol.jade.tool; ++ ++import com.google.common.collect.Sets; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++ ++import java.util.Collection; ++import java.util.List; ++import java.util.Set; ++ ++public class ShearsToolHandler extends SimpleToolHandler { ++ ++ private static final ShearsToolHandler INSTANCE = new ShearsToolHandler(); ++ ++ public static ShearsToolHandler getInstance() { ++ return INSTANCE; ++ } ++ ++ private final Set shearableBlocks = Sets.newIdentityHashSet(); ++ ++ public ShearsToolHandler() { ++ super(JadeProtocol.id("shears"), List.of(Items.SHEARS.getDefaultInstance()), true); ++ } ++ ++ @Override ++ public ItemStack test(BlockState state, Level world, BlockPos pos) { ++ if (state.is(Blocks.TRIPWIRE) || shearableBlocks.contains(state.getBlock())) { ++ return tools.getFirst(); ++ } ++ return super.test(state, world, pos); ++ } ++ ++ public void setShearableBlocks(Collection blocks) { ++ shearableBlocks.clear(); ++ shearableBlocks.addAll(blocks); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22fee6ecc49bbda94a7d32ee9dcf2a9ee661904b +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java +@@ -0,0 +1,67 @@ ++package org.leavesmc.leaves.protocol.jade.tool; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.collect.Lists; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.component.Tool; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++ ++import java.util.List; ++ ++public class SimpleToolHandler implements ToolHandler { ++ ++ protected final List tools = Lists.newArrayList(); ++ private final ResourceLocation uid; ++ private final boolean skipInstaBreakingBlock; ++ ++ protected SimpleToolHandler(ResourceLocation uid, List tools, boolean skipInstaBreakingBlock) { ++ this.uid = uid; ++ Preconditions.checkArgument(!tools.isEmpty(), "tools cannot be empty"); ++ this.tools.addAll(tools); ++ this.skipInstaBreakingBlock = skipInstaBreakingBlock; ++ } ++ ++ public static SimpleToolHandler create(ResourceLocation uid, List tools) { ++ return create(uid, tools, true); ++ } ++ ++ public static SimpleToolHandler create(ResourceLocation uid, List tools, boolean skipInstaBreakingBlock) { ++ return new SimpleToolHandler(uid, Lists.transform(tools, Item::getDefaultInstance), skipInstaBreakingBlock); ++ } ++ ++ @Override ++ public ItemStack test(BlockState state, Level world, BlockPos pos) { ++ if (skipInstaBreakingBlock && !state.requiresCorrectToolForDrops() && state.getDestroySpeed(world, pos) == 0) { ++ return ItemStack.EMPTY; ++ } ++ return test(state); ++ } ++ ++ public ItemStack test(BlockState state) { ++ for (ItemStack toolItem : tools) { ++ if (toolItem.isCorrectToolForDrops(state)) { ++ return toolItem; ++ } ++ Tool tool = toolItem.get(DataComponents.TOOL); ++ if (tool != null && tool.getMiningSpeed(state) > tool.defaultMiningSpeed()) { ++ return toolItem; ++ } ++ } ++ return ItemStack.EMPTY; ++ } ++ ++ @Override ++ public List getTools() { ++ return tools; ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return uid; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18f11e701189ce3615e08c631e31112d53ea5686 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java +@@ -0,0 +1,17 @@ ++package org.leavesmc.leaves.protocol.jade.tool; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++ ++import java.util.List; ++ ++public interface ToolHandler extends IJadeProvider { ++ ++ ItemStack test(BlockState state, Level world, BlockPos pos); ++ ++ List getTools(); ++ ++} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java new file mode 100644 index 0000000000000000000000000000000000000000..9e88dc87bd4a86c15b2b0d11ac4b095174b1c3d3 @@ -1962,6 +2239,116 @@ index 0000000000000000000000000000000000000000..4d65e9a8b5224bd268b1bf18bc39a58d + } + } +} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f979ebceb9 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java +@@ -0,0 +1,104 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import com.google.common.collect.Lists; ++import net.minecraft.advancements.critereon.ItemPredicate; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.storage.loot.LootPool; ++import net.minecraft.world.level.storage.loot.LootTable; ++import net.minecraft.world.level.storage.loot.entries.AlternativesEntry; ++import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; ++import net.minecraft.world.level.storage.loot.entries.NestedLootTable; ++import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; ++import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; ++import net.minecraft.world.level.storage.loot.predicates.MatchTool; ++import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler; ++ ++import java.util.List; ++import java.util.function.Function; ++ ++public class LootTableMineableCollector { ++ ++ private final Registry lootRegistry; ++ private final ItemStack toolItem; ++ ++ public LootTableMineableCollector(Registry lootRegistry, ItemStack toolItem) { ++ this.lootRegistry = lootRegistry; ++ this.toolItem = toolItem; ++ } ++ ++ public static List execute(Registry lootRegistry, ItemStack toolItem) { ++ LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem); ++ List list = Lists.newArrayList(); ++ for (Block block : BuiltInRegistries.BLOCK) { ++ if (!ShearsToolHandler.getInstance().test(block.defaultBlockState()).isEmpty()) { ++ continue; ++ } ++ ++ LootTable lootTable = lootRegistry.get(block.getLootTable()); ++ if (collector.doLootTable(lootTable)) { ++ list.add(block); ++ } ++ } ++ return list; ++ } ++ ++ private boolean doLootTable(LootTable lootTable) { ++ if (lootTable == null || lootTable == LootTable.EMPTY) { ++ return false; ++ } ++ ++ for (LootPool pool : lootTable.pools) { ++ if (doLootPool(pool)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private boolean doLootPool(LootPool lootPool) { ++ for (LootPoolEntryContainer entry : lootPool.entries) { ++ if (doLootPoolEntry(entry)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private boolean doLootPoolEntry(LootPoolEntryContainer entry) { ++ if (entry instanceof AlternativesEntry alternativesEntry) { ++ for (LootPoolEntryContainer child : alternativesEntry.children) { ++ if (doLootPoolEntry(child)) { ++ return true; ++ } ++ } ++ } else if (entry instanceof NestedLootTable nestedLootTable) { ++ LootTable lootTable = nestedLootTable.contents.map(lootRegistry::get, Function.identity()); ++ return doLootTable(lootTable); ++ } else { ++ return isCorrectConditions(entry.conditions, toolItem); ++ } ++ return false; ++ } ++ ++ public static boolean isCorrectConditions(List conditions, ItemStack toolItem) { ++ if (conditions.size() != 1) { ++ return false; ++ } ++ ++ LootItemCondition condition = conditions.getFirst(); ++ if (condition instanceof MatchTool matchTool) { ++ ItemPredicate itemPredicate = matchTool.predicate().orElse(null); ++ return itemPredicate != null && itemPredicate.test(toolItem); ++ } else if (condition instanceof AnyOfCondition anyOfCondition) { ++ for (LootItemCondition child : anyOfCondition.terms) { ++ if (isCorrectConditions(List.of(child), toolItem)) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java new file mode 100644 index 0000000000000000000000000000000000000000..580b299d9dc2514d9758c04caac39a82982c0ca1