diff --git a/patches/server/0030-Leaves-Server-Utils.patch b/patches/server/0030-Leaves-Server-Utils.patch index d22b27f4..dba2823d 100644 --- a/patches/server/0030-Leaves-Server-Utils.patch +++ b/patches/server/0030-Leaves-Server-Utils.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Leaves: Server Utils Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index b152cf0c89e735470ea61b4bb0d88f3467f1a8d0..1da09a313f73c35be5b1b46da6b489a599f745f4 100644 diff --git a/patches/server/0031-Leaves-Protocol-Core.patch b/patches/server/0031-Leaves-Protocol-Core.patch index a991ebe2..a9609618 100644 --- a/patches/server/0031-Leaves-Protocol-Core.patch +++ b/patches/server/0031-Leaves-Protocol-Core.patch @@ -8,7 +8,7 @@ TODO - Dreeam: Configurable leaves protocol listening Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 diff --git a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java index 663b3b12d9a7cdc04b7f86ccfe6bc6fcfd5028bc..a58ad6f41fc0eacf020e9ab6c8e5f7dfc4977f8d 100644 @@ -98,7 +98,7 @@ index 2e98c6598e589b498991c745058db3c0efb9cbf6..0d65a53b23c82cbc4539afd28c52b5fd org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur ServerLevel worldserver = entityplayer.serverLevel(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 97890866386b382e131e624011ad16cce413a752..a5a7a5fc506c0ca094f37d26b2b27192d09a0a66 100644 +index a3e90368e1e34c08508a54fc8c279f12b2bf763e..88bc9033981662e8ba62b833eac2a0301ab504da 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -485,6 +485,7 @@ public final class CraftServer implements Server { @@ -117,6 +117,30 @@ index 97890866386b382e131e624011ad16cce413a752..a5a7a5fc506c0ca094f37d26b2b27192 int pollCount = 0; +diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c852b3a8d31bc73d32aef215e4d3a82aa1fd8f19 +--- /dev/null ++++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +@@ -0,0 +1,18 @@ ++package org.dreeam.leaf.config.modules.network; ++ ++import org.dreeam.leaf.config.ConfigInfo; ++import org.dreeam.leaf.config.EnumConfigCategory; ++import org.dreeam.leaf.config.IConfigModule; ++ ++public class ProtocolSupport implements IConfigModule { ++ ++ @Override ++ public EnumConfigCategory getCategory() { ++ return EnumConfigCategory.NETWORK; ++ } ++ ++ @Override ++ public String getBaseName() { ++ return "protocol_support"; ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..243a8d77d67b9634d6442b60e481d18831367a74 @@ -610,23 +634,29 @@ index 0000000000000000000000000000000000000000..f9dc0a60ca4287e5ec91dd3fc1ae315e +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java new file mode 100644 -index 0000000000000000000000000000000000000000..562f3d515679965571597945e8682712e1e1f0b9 +index 0000000000000000000000000000000000000000..f54381eec7ec0ffde39e87b39b16e02d3ed1b419 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java -@@ -0,0 +1,37 @@ +@@ -0,0 +1,47 @@ +package org.leavesmc.leaves.protocol.core; + ++import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; ++import java.util.function.Function; + +public class ProtocolUtils { + ++ private static final Function bufDecorator = RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess()); ++ + public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id))); + } @@ -650,4 +680,8 @@ index 0000000000000000000000000000000000000000..562f3d515679965571597945e8682712 + public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) { + player.connection.send(new ClientboundCustomPayloadPacket(payload)); + } ++ ++ public static RegistryFriendlyByteBuf decorate(ByteBuf buf) { ++ return bufDecorator.apply(buf); ++ } +} diff --git a/patches/server/0032-Leaves-Jade-Protocol.patch b/patches/server/0032-Leaves-Jade-Protocol.patch new file mode 100644 index 00000000..1edf23b7 --- /dev/null +++ b/patches/server/0032-Leaves-Jade-Protocol.patch @@ -0,0 +1,2353 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Sat, 3 Dec 2022 08:57:15 +0800 +Subject: [PATCH] Leaves: Jade Protocol + +Original license: GPLv3 +Original project: https://github.com/LeavesMC/Leaves + +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 + +This patch is Powered by Jade (https://github.com/Snownee/Jade) + +diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java +index 063dde771ade593a29481f14b8f44a0f72f15953..877fe6312051f2669360b85c0caded7b47f04866 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java ++++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java +@@ -58,7 +58,7 @@ public class Armadillo extends Animal { + public final AnimationState rollOutAnimationState = new AnimationState(); + public final AnimationState rollUpAnimationState = new AnimationState(); + public final AnimationState peekAnimationState = new AnimationState(); +- private int scuteTime; ++ public int scuteTime; // Leaves - private -> public + private boolean peekReceivedClient = false; + + public Armadillo(EntityType type, Level world) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +index e6861fd5f9817ec54294976f0e93952baa387773..45f92d8fd06027f1487e24987c3fb9a389f619a9 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -290,7 +290,7 @@ public class Tadpole extends AbstractFish { + + } + +- private int getTicksLeftUntilAdult() { ++ public int getTicksLeftUntilAdult() { // Leaves - private -> public + return Math.max(0, Tadpole.ticksToBeFrog - this.age); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +index fca0131b9a90ac026a24cf579b17928c19173f3f..f8d0a8ea39cd90a9b45ff97e32e0e7224ddd8808 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java +@@ -68,7 +68,7 @@ public class TrialSpawnerData { + }); + public final Set detectedPlayers; + public final Set currentMobs; +- protected long cooldownEndsAt; ++ public long cooldownEndsAt; // Leaves - protected -> public + protected long nextMobSpawnsAt; + protected int totalMobsSpawned; + public Optional nextSpawnData; +diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +index c852b3a8d31bc73d32aef215e4d3a82aa1fd8f19..b63d87d0593d116b0c5ee835dc4372f1b5542453 100644 +--- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java ++++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +@@ -15,4 +15,7 @@ public class ProtocolSupport implements IConfigModule { + public String getBaseName() { + return "protocol_support"; + } ++ ++ @ConfigInfo(baseName = "jade-protocol") ++ public static boolean jadeProtocol = false; + } +diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java +index 2356bb3bc68d2a33329b7c4a41b48f1a52576ccb..25dc0a249d47d20d52fa279cb03acfa0181885e2 100644 +--- a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java ++++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java +@@ -34,7 +34,7 @@ import java.util.jar.JarFile; + + public class LeavesProtocolManager { + +- private static final Logger LOGGER = LogManager.getLogger("Leaves"); ++ public static final Logger LOGGER = LogManager.getLogger("Leaves"); + + private static final Map>>> KNOWN_TYPES = new HashMap<>(); + private static final Map> KNOW_RECEIVERS = new HashMap<>(); +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..f3347bea49b80621b6f69b0e0aac63cf55db6b3e +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java +@@ -0,0 +1,343 @@ ++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.network.FriendlyByteBuf; ++import net.minecraft.network.RegistryFriendlyByteBuf; ++import net.minecraft.network.codec.ByteBufCodecs; ++import net.minecraft.network.codec.StreamCodec; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.AgeableMob; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Chicken; ++import net.minecraft.world.entity.animal.allay.Allay; ++import net.minecraft.world.entity.animal.armadillo.Armadillo; ++import net.minecraft.world.entity.animal.frog.Tadpole; ++import net.minecraft.world.entity.boss.EnderDragonPart; ++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.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.CampfireBlock; ++import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; ++import net.minecraft.world.level.block.entity.BeehiveBlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; ++import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; ++import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; ++import net.minecraft.world.level.block.entity.CommandBlockEntity; ++import net.minecraft.world.level.block.entity.ComparatorBlockEntity; ++import net.minecraft.world.level.block.entity.HopperBlockEntity; ++import net.minecraft.world.level.block.entity.JukeboxBlockEntity; ++import net.minecraft.world.level.block.entity.LecternBlockEntity; ++import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.phys.BlockHitResult; ++import net.minecraft.world.phys.Vec3; ++import org.dreeam.leaf.config.modules.network.ProtocolSupport; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; ++import org.leavesmc.leaves.protocol.core.LeavesProtocol; ++import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; ++import org.leavesmc.leaves.protocol.core.ProtocolHandler; ++import org.leavesmc.leaves.protocol.core.ProtocolUtils; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; ++import org.leavesmc.leaves.protocol.jade.provider.ItemStorageExtensionProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.BeehiveProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.BlockStorageProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.BrewingStandProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.CampfireProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.ChiseledBookshelfProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.CommandBlockProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.FurnaceProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.JukeboxProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.LecternProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.MobSpawnerCooldownProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.ObjectNameProvider; ++import org.leavesmc.leaves.protocol.jade.provider.block.RedstoneProvider; ++import org.leavesmc.leaves.protocol.jade.provider.entity.AnimalOwnerProvider; ++import org.leavesmc.leaves.protocol.jade.provider.entity.EntityStorageProvider; ++import org.leavesmc.leaves.protocol.jade.provider.entity.MobBreedingProvider; ++import org.leavesmc.leaves.protocol.jade.provider.entity.MobGrowthProvider; ++import org.leavesmc.leaves.protocol.jade.provider.entity.NextEntityDropProvider; ++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.PairHierarchyLookup; ++import org.leavesmc.leaves.protocol.jade.util.PriorityStore; ++import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup; ++ ++import java.util.ArrayList; ++import java.util.Comparator; ++import java.util.List; ++import java.util.function.Predicate; ++import java.util.stream.Collectors; ++ ++@LeavesProtocol(namespace = "jade") ++public class JadeProtocol { ++ public static PriorityStore priorities; ++ ++ 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)); ++ ++ public static final WrappedHierarchyLookup> itemStorageProviders = new WrappedHierarchyLookup<>(); ++ ++ @Contract("_ -> new") ++ public static @NotNull ResourceLocation id(String path) { ++ return new ResourceLocation(PROTOCOL_ID, path); ++ } ++ ++ @Contract("_ -> new") ++ public static @NotNull ResourceLocation mc_id(String path) { ++ return new ResourceLocation(path); ++ } ++ ++ private static boolean isPrimaryKey(ResourceLocation key) { ++ return !key.getPath().contains("."); ++ } ++ ++ private static ResourceLocation getPrimaryKey(ResourceLocation key) { ++ return new ResourceLocation(key.getNamespace(), key.getPath().substring(0, key.getPath().indexOf('.'))); ++ } ++ ++ @ProtocolHandler.Init ++ public static void init() { ++ priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid); ++ priorities.setSortingFunction((store, allKeys) -> { ++ List keys = allKeys.stream() ++ .filter(JadeProtocol::isPrimaryKey) ++ .sorted(Comparator.comparingInt(store::byKey)) ++ .collect(Collectors.toCollection(ArrayList::new)); ++ allKeys.stream().filter(Predicate.not(JadeProtocol::isPrimaryKey)).forEach($ -> { ++ int index = keys.indexOf(JadeProtocol.getPrimaryKey($)); ++ keys.add(index + 1, $); ++ }); ++ return keys; ++ }); ++ ++ // core plugin ++ blockDataProviders.register(BlockEntity.class, ObjectNameProvider.INSTANCE); ++ ++ // universal plugin ++ entityDataProviders.register(Entity.class, EntityStorageProvider.INSTANCE); ++ blockDataProviders.register(Block.class, BlockStorageProvider.INSTANCE); ++ ++ itemStorageProviders.register(Object.class, ItemStorageExtensionProvider.INSTANCE); ++ itemStorageProviders.register(Block.class, ItemStorageExtensionProvider.INSTANCE); ++ ++ // vanilla plugin ++ entityDataProviders.register(Entity.class, AnimalOwnerProvider.INSTANCE); ++ entityDataProviders.register(LivingEntity.class, StatusEffectsProvider.INSTANCE); ++ entityDataProviders.register(AgeableMob.class, MobGrowthProvider.INSTANCE); ++ entityDataProviders.register(Tadpole.class, MobGrowthProvider.INSTANCE); ++ entityDataProviders.register(Animal.class, MobBreedingProvider.INSTANCE); ++ entityDataProviders.register(Allay.class, MobBreedingProvider.INSTANCE); ++ ++ entityDataProviders.register(Chicken.class, NextEntityDropProvider.INSTANCE); ++ entityDataProviders.register(Armadillo.class, NextEntityDropProvider.INSTANCE); ++ ++ entityDataProviders.register(ZombieVillager.class, ZombieVillagerProvider.INSTANCE); ++ ++ blockDataProviders.register(BrewingStandBlockEntity.class, BrewingStandProvider.INSTANCE); ++ blockDataProviders.register(BeehiveBlockEntity.class, BeehiveProvider.INSTANCE); ++ blockDataProviders.register(CommandBlockEntity.class, CommandBlockProvider.INSTANCE); ++ blockDataProviders.register(JukeboxBlockEntity.class, JukeboxProvider.INSTANCE); ++ blockDataProviders.register(LecternBlockEntity.class, LecternProvider.INSTANCE); ++ ++ blockDataProviders.register(ComparatorBlockEntity.class, RedstoneProvider.INSTANCE); ++ blockDataProviders.register(HopperBlockEntity.class, RedstoneProvider.INSTANCE); ++ blockDataProviders.register(CalibratedSculkSensorBlockEntity.class, RedstoneProvider.INSTANCE); ++ ++ blockDataProviders.register(AbstractFurnaceBlockEntity.class, FurnaceProvider.INSTANCE); ++ blockDataProviders.register(ChiseledBookShelfBlockEntity.class, ChiseledBookshelfProvider.INSTANCE); ++ blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE); ++ ++ itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE); ++ } ++ ++ @ProtocolHandler.PlayerJoin ++ public static void onPlayerJoin(ServerPlayer player) { ++ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { ++ ProtocolUtils.sendPayloadPacket(player, PACKET_SERVER_PING, buf -> buf.writeUtf("")); ++ } ++ } ++ ++ @ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class, payloadId = "request_entity") ++ public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) { ++ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { ++ return; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ server.execute(() -> { ++ Level world = player.level(); ++ boolean showDetails = payload.showDetails; ++ Entity entity = world.getEntity(payload.entityId); ++ double maxDistance = Mth.square(player.entityInteractionRange() + 21); ++ ++ if (entity == null || player.distanceToSqr(entity) > maxDistance) { ++ return; ++ } ++ ++ if (payload.partIndex >= 0 && entity instanceof EnderDragon dragon) { ++ EnderDragonPart[] parts = dragon.getSubEntities(); ++ if (payload.partIndex < parts.length) { ++ entity = parts[payload.partIndex]; ++ } ++ } ++ ++ var providers = entityDataProviders.get(entity); ++ if (providers.isEmpty()) { ++ return; ++ } ++ ++ DataAccessor tag = new DataAccessor(world); ++ EntityAccessor accessor = new EntityAccessor(player, world, entity, payload.hitVec, showDetails); ++ for (IJadeDataProvider provider : providers) { ++ try { ++ provider.saveData(tag, accessor); ++ } catch (Exception e) { ++ LeavesProtocolManager.LOGGER.warn("Error while saving data for entity {}", entity); ++ } ++ } ++ tag.putInt("EntityId", entity.getId()); ++ ++ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); ++ }); ++ } ++ ++ @ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class, payloadId = "request_block") ++ public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) { ++ if (!ProtocolSupport.jadeProtocol) { ++ return; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ server.execute(() -> { ++ Level world = player.level(); ++ BlockState blockState = payload.blockState; ++ Block block = blockState.getBlock(); ++ BlockHitResult result = payload.hitResult; ++ BlockPos pos = result.getBlockPos(); ++ boolean showDetails = payload.showDetails; ++ ++ double maxDistance = Mth.square(player.blockInteractionRange() + 21); ++ if (pos.distSqr(player.blockPosition()) > maxDistance || !world.isLoaded(pos)) { ++ return; ++ } ++ ++ BlockEntity blockEntity = null; ++ if (blockState.hasBlockEntity()) { ++ blockEntity = world.getBlockEntity(pos); ++ } ++ ++ List> providers; ++ if (blockEntity != null) { ++ providers = blockDataProviders.getMerged(block, blockEntity); ++ } else { ++ providers = blockDataProviders.first.get(block); ++ } ++ ++ if (providers.isEmpty()) { ++ return; ++ } ++ ++ DataAccessor tag = new DataAccessor(world); ++ BlockAccessor accessor = new BlockAccessor(player, world, blockEntity, result, block, blockState, pos, showDetails); ++ for (IJadeDataProvider provider : providers) { ++ try { ++ provider.saveData(tag, accessor); ++ } catch (Exception e) { ++ LeavesProtocolManager.LOGGER.warn("Error while saving data for block {}", blockState); ++ } ++ } ++ tag.putInt("x", pos.getX()); ++ tag.putInt("y", pos.getY()); ++ tag.putInt("z", pos.getZ()); ++ tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString()); ++ ++ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); ++ }); ++ } ++ ++ @ProtocolHandler.ReloadServer ++ public static void onServerReload() { ++ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { ++ enableAllPlayer(); ++ } ++ } ++ ++ public static void enableAllPlayer() { ++ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) { ++ onPlayerJoin(player); ++ } ++ } ++ ++ public record RequestEntityPayload(boolean showDetails, int entityId, int partIndex, Vec3 hitVec) implements LeavesCustomPayload { ++ ++ private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); ++ ++ @New ++ public RequestEntityPayload(ResourceLocation id, FriendlyByteBuf buf) { ++ this(buf.readBoolean(), buf.readVarInt(), buf.readVarInt(), new Vec3(buf.readVector3f())); ++ } ++ ++ @Override ++ public void write(FriendlyByteBuf buf) { ++ buf.writeBoolean(showDetails); ++ buf.writeVarInt(entityId); ++ buf.writeVarInt(partIndex); ++ buf.writeVector3f(hitVec.toVector3f()); ++ } ++ ++ @Override ++ @NotNull ++ public ResourceLocation id() { ++ return PACKET_REQUEST_ENTITY; ++ } ++ } ++ ++ public record RequestBlockPayload(boolean showDetails, BlockHitResult hitResult, BlockState blockState, ItemStack fakeBlock) implements LeavesCustomPayload { ++ 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) { ++ this(buf.readBoolean(), buf.readBlockHitResult(), BLOCK_STATE_CODEC.decode(buf), ITEM_STACK_CODEC.decode(ProtocolUtils.decorate(buf))); ++ } ++ ++ @Override ++ public void write(FriendlyByteBuf buf) { ++ buf.writeBoolean(showDetails); ++ buf.writeBlockHitResult(hitResult); ++ BLOCK_STATE_CODEC.encode(buf, blockState); ++ ITEM_STACK_CODEC.encode(ProtocolUtils.decorate(buf), fakeBlock); ++ } ++ ++ @Override ++ @NotNull ++ public ResourceLocation id() { ++ return PACKET_REQUEST_BLOCK; ++ } ++ } ++} +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 +index 0000000000000000000000000000000000000000..e4b037b6c09c4d7e7c28f5edac65fbb0b0431bb5 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java +@@ -0,0 +1,13 @@ ++package org.leavesmc.leaves.protocol.jade.accessor; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.phys.BlockHitResult; ++ ++public record BlockAccessor(ServerPlayer player, Level world, BlockEntity target, BlockHitResult hitResult, ++ Block block, BlockState blockState, BlockPos pos, boolean showDetails) implements RequestAccessor { ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/DataAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/DataAccessor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f8215ffdc2cfe39ab1be89c31a68ef0925eaa3a6 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/DataAccessor.java +@@ -0,0 +1,32 @@ ++package org.leavesmc.leaves.protocol.jade.accessor; ++ ++import com.mojang.serialization.DynamicOps; ++import com.mojang.serialization.MapEncoder; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.resources.RegistryOps; ++import net.minecraft.world.level.Level; ++ ++public class DataAccessor extends CompoundTag { ++ ++ private final Level level; ++ private DynamicOps ops; ++ ++ public DataAccessor(Level level) { ++ this.level = level; ++ } ++ ++ public DynamicOps nbtOps() { ++ if (ops == null) { ++ ops = RegistryOps.create(NbtOps.INSTANCE, level.registryAccess()); ++ } ++ ++ return ops; ++ } ++ ++ public void writeMapData(MapEncoder codec, D value) { ++ Tag tag = codec.encode(value, nbtOps(), nbtOps().mapBuilder()).build(new CompoundTag()).getOrThrow(); ++ this.merge((CompoundTag) tag); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2dac76f4c06259c0fc767894a30564a8ffdbd2f +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java +@@ -0,0 +1,9 @@ ++package org.leavesmc.leaves.protocol.jade.accessor; ++ ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.Vec3; ++ ++public record EntityAccessor(ServerPlayer player, Level world, Entity target, Vec3 hitVec3, boolean showDetails) implements RequestAccessor { ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/RequestAccessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/RequestAccessor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e490c17cdf25fdb9a7965785bb1189868471c5b +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/RequestAccessor.java +@@ -0,0 +1,15 @@ ++package org.leavesmc.leaves.protocol.jade.accessor; ++ ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.level.Level; ++ ++public interface RequestAccessor { ++ ++ ServerPlayer player(); ++ ++ Level world(); ++ ++ T target(); ++ ++ boolean showDetails(); ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeDataProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeDataProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..710a225a20ba0cfcb9ad7878b5ef797c94890926 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeDataProvider.java +@@ -0,0 +1,8 @@ ++package org.leavesmc.leaves.protocol.jade.provider; ++ ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.RequestAccessor; ++ ++public interface IJadeDataProvider> extends IJadeProvider { ++ void saveData(DataAccessor data, T request); ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d62fc8f96fcdee7dbb0204d2460ff6fee4074e1a +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IJadeProvider.java +@@ -0,0 +1,12 @@ ++package org.leavesmc.leaves.protocol.jade.provider; ++ ++import net.minecraft.resources.ResourceLocation; ++ ++public interface IJadeProvider { ++ ++ ResourceLocation getUid(); ++ ++ default int getDefaultPriority() { ++ return 0; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14613e35a6785fc599b1520a667e1311eba12f57 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerExtensionProvider.java +@@ -0,0 +1,10 @@ ++package org.leavesmc.leaves.protocol.jade.provider; ++ ++import org.leavesmc.leaves.protocol.jade.accessor.RequestAccessor; ++import org.leavesmc.leaves.protocol.jade.util.ViewGroup; ++ ++import java.util.List; ++ ++public interface IServerExtensionProvider extends IJadeProvider { ++ List> getGroups(RequestAccessor request); ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ef50f5b7437d8ec918d26b7a564161ed08aabbd +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java +@@ -0,0 +1,142 @@ ++package org.leavesmc.leaves.protocol.jade.provider; ++ ++import com.google.common.cache.Cache; ++import com.google.common.cache.CacheBuilder; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.Container; ++import net.minecraft.world.LockCode; ++import net.minecraft.world.RandomizableContainer; ++import net.minecraft.world.WorldlyContainer; ++import net.minecraft.world.WorldlyContainerHolder; ++import net.minecraft.world.entity.animal.horse.AbstractHorse; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.entity.vehicle.ContainerEntity; ++import net.minecraft.world.inventory.PlayerEnderChestContainer; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.ChestBlock; ++import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; ++import net.minecraft.world.level.block.entity.ChestBlockEntity; ++import net.minecraft.world.level.block.entity.EnderChestBlockEntity; ++import org.leavesmc.leaves.LeavesLogger; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.RequestAccessor; ++import org.leavesmc.leaves.protocol.jade.util.ItemCollector; ++import org.leavesmc.leaves.protocol.jade.util.ItemIterator; ++import org.leavesmc.leaves.protocol.jade.util.ViewGroup; ++ ++import java.util.List; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.TimeUnit; ++ ++public enum ItemStorageExtensionProvider implements IServerExtensionProvider { ++ INSTANCE; ++ ++ public static final Cache> targetCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(60, TimeUnit.SECONDS).build(); ++ public static final Cache> containerCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(120, TimeUnit.SECONDS).build(); ++ ++ private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); ++ ++ @Override ++ public List> getGroups(RequestAccessor request) { ++ Object target = request.target(); ++ if (target == null && request instanceof BlockAccessor blockAccessor && blockAccessor.block() instanceof WorldlyContainerHolder holder) { ++ WorldlyContainer container = holder.getContainer(blockAccessor.blockState(), request.world(), blockAccessor.pos()); ++ return containerGroup(container, request); ++ } ++ ++ switch (target) { ++ case null -> { ++ return List.of(); ++ } ++ case RandomizableContainer te when te.getLootTable() != null -> { ++ return List.of(); ++ } ++ case ContainerEntity containerEntity when containerEntity.getLootTable() != null -> { ++ return List.of(); ++ } ++ default -> { ++ } ++ } ++ ++ Player player = request.player(); ++ if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { ++ if (te.lockKey != LockCode.NO_LOCK) { ++ return List.of(); ++ } ++ } ++ ++ if (target instanceof EnderChestBlockEntity) { ++ PlayerEnderChestContainer inventory = player.getEnderChestInventory(); ++ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)).update(inventory, request.world().getGameTime()); ++ } ++ ++ ItemCollector itemCollector; ++ try { ++ itemCollector = targetCache.get(target, () -> createItemCollector(target)); ++ } catch (ExecutionException e) { ++ LeavesLogger.LOGGER.severe("Failed to get item collector for " + target); ++ return null; ++ } ++ ++ if (itemCollector == ItemCollector.EMPTY) { ++ return null; ++ } ++ ++ return itemCollector.update(target, request.world().getGameTime()); ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return UNIVERSAL_ITEM_STORAGE; ++ } ++ ++ public static List> containerGroup(Container container, RequestAccessor accessor) { ++ try { ++ return containerCache.get(container, () -> new ItemCollector<>(new ItemIterator.ContainerItemIterator(0))).update(container, accessor.world().getGameTime()); ++ } catch (ExecutionException e) { ++ return null; ++ } ++ } ++ ++ public static ItemCollector createItemCollector(Object target) { ++ if (target instanceof AbstractHorse) { ++ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { ++ if (o instanceof AbstractHorse horse) { ++ return horse.inventory; ++ } ++ return null; ++ }, 2)); ++ } ++ ++ // TODO BlockEntity like fabric's ItemStorage ++ ++ if (target instanceof Container) { ++ if (target instanceof ChestBlockEntity) { ++ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { ++ if (o instanceof ChestBlockEntity blockEntity) { ++ if (blockEntity.getBlockState().getBlock() instanceof ChestBlock chestBlock) { ++ Container compound = null; ++ if (blockEntity.getLevel() != null) { ++ compound = ChestBlock.getContainer(chestBlock, blockEntity.getBlockState(), blockEntity.getLevel(), blockEntity.getBlockPos(), false); ++ } ++ if (compound != null) { ++ return compound; ++ } ++ } ++ return blockEntity; ++ } ++ return null; ++ }, 0)); ++ } ++ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)); ++ } ++ ++ return ItemCollector.EMPTY; ++ } ++ ++ @Override ++ public int getDefaultPriority() { ++ return IServerExtensionProvider.super.getDefaultPriority() + 1000; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9d2b6bf80eaaf67b4a9df6bd46470838986a9aee +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java +@@ -0,0 +1,27 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.block.entity.BeehiveBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum BeehiveProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_BEEHIVE = JadeProtocol.mc_id("beehive"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof BeehiveBlockEntity beehive) { ++ data.putByte("Bees", (byte) beehive.getOccupantCount()); ++ data.putBoolean("Full", beehive.isFull()); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_BEEHIVE; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BlockStorageProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BlockStorageProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..89f040d390d017807f2baf7ed8925acd62d083bb +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BlockStorageProvider.java +@@ -0,0 +1,65 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.LockCode; ++import net.minecraft.world.RandomizableContainer; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++import org.leavesmc.leaves.protocol.jade.util.ViewGroup; ++ ++public enum BlockStorageProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ BlockEntity target = request.target(); ++ Player player = request.player(); ++ for (var provider : JadeProtocol.itemStorageProviders.get(request)) { ++ var groups = provider.getGroups(request); ++ if (groups == null) { ++ continue; ++ } ++ ++ if (ViewGroup.saveList(data, "JadeItemStorage", groups, item -> { ++ int count = item.getCount(); ++ if (count > item.getMaxStackSize()) { ++ item.setCount(1); ++ } ++ CompoundTag itemTag = (CompoundTag) item.save(request.world().registryAccess()); ++ if (count > item.getMaxStackSize()) { ++ itemTag.putInt("NewCount", count); ++ item.setCount(count); ++ } ++ return itemTag; ++ })) { ++ data.putString("JadeItemStorageUid", provider.getUid().toString()); ++ } else if (target instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) { ++ data.putBoolean("Loot", true); ++ } else if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { ++ if (te.lockKey != LockCode.NO_LOCK) { ++ data.putBoolean("Locked", true); ++ } ++ } ++ break; ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return UNIVERSAL_ITEM_STORAGE; ++ } ++ ++ @Override ++ public int getDefaultPriority() { ++ return IJadeDataProvider.super.getDefaultPriority() + 1000; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fd4112ed1911171b3c6b5840b7184b5f076617ee +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java +@@ -0,0 +1,30 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum BrewingStandProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_BREWING_STAND = JadeProtocol.mc_id("brewing_stand"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof BrewingStandBlockEntity brewingStand) { ++ CompoundTag compound = new CompoundTag(); ++ compound.putInt("Time", brewingStand.brewTime); ++ compound.putInt("Fuel", brewingStand.fuel); ++ data.put("BrewingStand", compound); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_BREWING_STAND; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d22013480b53017db71697af37c0b3daa19c7ac5 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java +@@ -0,0 +1,52 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import com.google.common.collect.Lists; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.MapCodec; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.component.CustomData; ++import net.minecraft.world.level.block.entity.CampfireBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.RequestAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; ++import org.leavesmc.leaves.protocol.jade.util.ViewGroup; ++ ++import java.util.List; ++ ++public enum CampfireProvider implements IServerExtensionProvider { ++ INSTANCE; ++ ++ private static final MapCodec COOKING_TIME_CODEC = Codec.INT.fieldOf("jade:cooking"); ++ private static final ResourceLocation MC_CAMPFIRE = JadeProtocol.mc_id("campfire"); ++ ++ @Override ++ public List> getGroups(RequestAccessor request) { ++ if (request.target() instanceof CampfireBlockEntity campfire) { ++ List list = Lists.newArrayList(); ++ for (int i = 0; i < campfire.cookingTime.length; i++) { ++ ItemStack stack = campfire.getItems().get(i); ++ if (stack.isEmpty()) { ++ continue; ++ } ++ stack = stack.copy(); ++ ++ CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) ++ .update(COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i]) ++ .getOrThrow(); ++ stack.set(DataComponents.CUSTOM_DATA, customData); ++ ++ list.add(stack); ++ } ++ return List.of(new ViewGroup<>(list)); ++ } ++ return null; ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_CAMPFIRE; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..054e44252259ed54b7365072b0bc6dbfce6af466 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java +@@ -0,0 +1,43 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import com.mojang.serialization.MapCodec; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.ChiseledBookShelfBlock; ++import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum ChiseledBookshelfProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ public static final MapCodec BOOK_CODEC = ItemStack.CODEC.fieldOf("book"); ++ private static final ResourceLocation MC_CHISELED_BOOKSHELF = JadeProtocol.mc_id("chiseled_bookshelf"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof ChiseledBookShelfBlockEntity bookshelf) { ++ int slot = ((ChiseledBookShelfBlock) request.block()).getHitSlot(request.hitResult(), request.blockState()).orElse(-1); ++ if (slot == -1) { ++ return; ++ } ++ ++ ItemStack book = bookshelf.getItem(slot); ++ if (!book.isEmpty()) { ++ data.writeMapData(BOOK_CODEC, book); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_CHISELED_BOOKSHELF; ++ } ++ ++ @Override ++ public int getDefaultPriority() { ++ return BlockStorageProvider.INSTANCE.getDefaultPriority() + 1; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2af51302185a16b3d9eae1e91fc3153273881ccd +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java +@@ -0,0 +1,41 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.BaseCommandBlock; ++import net.minecraft.world.level.block.entity.CommandBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum CommandBlockProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_COMMAND_BLOCK = JadeProtocol.mc_id("command_block"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor accessor) { ++ Player player = accessor.player(); ++ if (!player.canUseGameMasterBlocks()) { ++ return; ++ } ++ ++ if (accessor.target() instanceof CommandBlockEntity commandBlock) { ++ BaseCommandBlock logic = commandBlock.getCommandBlock(); ++ String command = logic.getCommand(); ++ if (command.isEmpty()) { ++ return; ++ } ++ if (command.length() > 40) { ++ command = command.substring(0, 37) + "..."; ++ } ++ data.putString("Command", command); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_COMMAND_BLOCK; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e53ede434dbe3d4289b69869958e42b5b208a911 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java +@@ -0,0 +1,41 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum FurnaceProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_FURNACE = JadeProtocol.mc_id("furnace"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (!(request.target() instanceof AbstractFurnaceBlockEntity furnace)) { ++ return; ++ } ++ ++ if (furnace.isEmpty()) { ++ return; ++ } ++ ++ ListTag items = new ListTag(); ++ for (int i = 0; i < 3; i++) { ++ items.add(furnace.getItem(i).saveOptional(request.world().registryAccess())); ++ } ++ data.put("furnace", items); ++ CompoundTag furnaceTag = furnace.saveWithoutMetadata(request.world().registryAccess()); ++ data.putInt("progress", furnaceTag.getInt("CookTime")); ++ data.putInt("total", furnaceTag.getInt("CookTimeTotal")); ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_FURNACE; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6085390614045b94a68d96dacf778af1d31033b3 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/JukeboxProvider.java +@@ -0,0 +1,32 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import com.mojang.serialization.MapCodec; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.block.entity.JukeboxBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum JukeboxProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final MapCodec RECORD_CODEC = ItemStack.CODEC.fieldOf("record"); ++ private static final ResourceLocation MC_JUKEBOX = JadeProtocol.mc_id("jukebox"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof JukeboxBlockEntity jukebox) { ++ ItemStack stack = jukebox.getTheItem(); ++ if (!stack.isEmpty()) { ++ data.writeMapData(RECORD_CODEC, stack); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_JUKEBOX; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ea6cf2482807b327d8ff10f0aa117b9b9b45c675 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java +@@ -0,0 +1,34 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.block.entity.LecternBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum LecternProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_LECTERN = JadeProtocol.mc_id("lectern"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof LecternBlockEntity lectern) { ++ ItemStack stack = lectern.getBook(); ++ if (!stack.isEmpty()) { ++ if (stack.has(DataComponents.CUSTOM_NAME) || stack.getItem() != Items.WRITABLE_BOOK) { ++ data.writeMapData(ChiseledBookshelfProvider.BOOK_CODEC, stack); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_LECTERN; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..63af91ae159cdcdb1195d98530f6e779449fb60b +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java +@@ -0,0 +1,32 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; ++import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum MobSpawnerCooldownProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_MOB_SPAWNER_COOLDOWN = JadeProtocol.mc_id("mob_spawner.cooldown"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ if (request.target() instanceof TrialSpawnerBlockEntity spawner) { ++ TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); ++ Level level = request.world(); ++ if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) { ++ data.putInt("Cooldown", (int) (spawnerData.cooldownEndsAt - level.getGameTime())); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_MOB_SPAWNER_COOLDOWN; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f001c01b0739f77879791b4a6163f84596a7349a +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java +@@ -0,0 +1,53 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import com.mojang.serialization.MapCodec; ++import net.minecraft.network.chat.Component; ++import net.minecraft.network.chat.ComponentSerialization; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.MenuProvider; ++import net.minecraft.world.Nameable; ++import net.minecraft.world.level.block.ChestBlock; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.ChestBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum ObjectNameProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final MapCodec GIVEN_NAME_CODEC = ComponentSerialization.CODEC.fieldOf("given_name"); ++ private static final ResourceLocation CORE_OBJECT_NAME = JadeProtocol.id("object_name"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ BlockEntity blockEntity = request.target(); ++ if (blockEntity instanceof Nameable nameable) { ++ Component name = null; ++ ++ if (blockEntity instanceof ChestBlockEntity && request.block() instanceof ChestBlock) { ++ MenuProvider menuProvider = request.blockState().getMenuProvider(request.world(), request.pos()); ++ if (menuProvider != null) { ++ name = menuProvider.getDisplayName(); ++ } ++ } else if (nameable.hasCustomName()) { ++ name = nameable.getDisplayName(); ++ } ++ ++ if (name != null) { ++ data.writeMapData(GIVEN_NAME_CODEC, name); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return CORE_OBJECT_NAME; ++ } ++ ++ @Override ++ public int getDefaultPriority() { ++ return -10100; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..53b6f7dd85875b9f519831b888b45a17c4ec90d6 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java +@@ -0,0 +1,43 @@ ++package org.leavesmc.leaves.protocol.jade.provider.block; ++ ++import net.minecraft.core.Direction; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.block.CalibratedSculkSensorBlock; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; ++import net.minecraft.world.level.block.entity.ComparatorBlockEntity; ++import net.minecraft.world.level.block.entity.HopperBlockEntity; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.block.state.properties.BlockStateProperties; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum RedstoneProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_REDSTONE = JadeProtocol.mc_id("redstone"); ++ ++ @Override ++ public void saveData(DataAccessor data, BlockAccessor request) { ++ BlockEntity blockEntity = request.target(); ++ if (blockEntity instanceof ComparatorBlockEntity comparator) { ++ data.putInt("Signal", comparator.getOutputSignal()); ++ } else if (blockEntity instanceof HopperBlockEntity) { ++ BlockState state = request.blockState(); ++ if (state.hasProperty(BlockStateProperties.ENABLED) && !state.getValue(BlockStateProperties.ENABLED)) { ++ data.putBoolean("HopperLocked", true); ++ } ++ } else if (blockEntity instanceof CalibratedSculkSensorBlockEntity) { ++ Direction direction = request.blockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite(); ++ int signal = request.world().getSignal(request.pos().relative(direction), direction); ++ data.putInt("Signal", signal); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_REDSTONE; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d75c6889c16d77c251fbc5d921d43cee7e2ad4d1 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java +@@ -0,0 +1,39 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import com.mojang.authlib.GameProfile; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.players.GameProfileCache; ++import net.minecraft.world.entity.OwnableEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++import java.util.UUID; ++ ++public enum AnimalOwnerProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_ANIMAL_OWNER = JadeProtocol.mc_id("animal_owner"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ UUID ownerUUID = null; ++ if (request.target() instanceof OwnableEntity ownable) { ++ ownerUUID = ownable.getOwnerUUID(); ++ } ++ ++ if (ownerUUID != null) { ++ GameProfileCache cache = MinecraftServer.getServer().getProfileCache(); ++ if (cache != null) { ++ cache.get(ownerUUID).map(GameProfile::getName).ifPresent(name -> data.putString("OwnerName", name)); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_ANIMAL_OWNER; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/EntityStorageProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/EntityStorageProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..851dd6f0a8e8a13746a40c8b372103fd3d03bfc6 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/EntityStorageProvider.java +@@ -0,0 +1,56 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.LockCode; ++import net.minecraft.world.RandomizableContainer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++import org.leavesmc.leaves.protocol.jade.util.ViewGroup; ++ ++public enum EntityStorageProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ for (var provider : JadeProtocol.itemStorageProviders.get(request)) { ++ var groups = provider.getGroups(request); ++ if (groups == null) { ++ continue; ++ } ++ ++ if (ViewGroup.saveList(data, "JadeItemStorage", groups, item -> { ++ int count = item.getCount(); ++ if (count > item.getMaxStackSize()) { ++ item.setCount(1); ++ } ++ CompoundTag itemTag = (CompoundTag) item.save(request.world().registryAccess()); ++ if (count > item.getMaxStackSize()) { ++ itemTag.putInt("NewCount", count); ++ item.setCount(count); ++ } ++ return itemTag; ++ })) { ++ data.putString("JadeItemStorageUid", provider.getUid().toString()); ++ } ++ break; ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return UNIVERSAL_ITEM_STORAGE; ++ } ++ ++ @Override ++ public int getDefaultPriority() { ++ return IJadeDataProvider.super.getDefaultPriority() + 1000; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d7ed10be4700c68fe5c04b483ec7f558d3c4c686 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java +@@ -0,0 +1,39 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.allay.Allay; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum MobBreedingProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_MOB_BREEDING = JadeProtocol.mc_id("mob_breeding"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ int time = 0; ++ Entity entity = request.target(); ++ ++ if (entity instanceof Allay allay) { ++ if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) { ++ time = (int) allay.duplicationCooldown; ++ } ++ } else if (entity instanceof Animal animal) { ++ time = animal.getAge(); ++ } ++ ++ if (time > 0) { ++ data.putInt("BreedingCD", time); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_MOB_BREEDING; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7635011da4b099205a8d5ec4445707dbdd0f35c +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java +@@ -0,0 +1,37 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.AgeableMob; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.animal.frog.Tadpole; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum MobGrowthProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_MOB_GROWTH = JadeProtocol.mc_id("mob_growth"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ int time = -1; ++ Entity entity = request.target(); ++ ++ if (entity instanceof AgeableMob ageable) { ++ time = -ageable.getAge(); ++ } else if (entity instanceof Tadpole tadpole) { ++ time = tadpole.getTicksLeftUntilAdult(); ++ } ++ ++ if (time > 0) { ++ data.putInt("GrowingTime", time); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_MOB_GROWTH; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7380ccfe98ad78b3a153da1efd3712a6a780a918 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java +@@ -0,0 +1,37 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.animal.Chicken; ++import net.minecraft.world.entity.animal.armadillo.Armadillo; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum NextEntityDropProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_NEXT_ENTITY_DROP = JadeProtocol.mc_id("next_entity_drop"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ int max = 24000 * 2; ++ Entity entity = request.target(); ++ ++ if (entity instanceof Chicken chicken) { ++ if (!chicken.isBaby() && chicken.eggTime < max) { ++ data.putInt("NextEggIn", chicken.eggTime); ++ } ++ } else if (entity instanceof Armadillo armadillo) { ++ if (!armadillo.isBaby() && armadillo.scuteTime < max) { ++ data.putInt("NextScuteIn", armadillo.scuteTime); ++ } ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_NEXT_ENTITY_DROP; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e92ce371a3d41ab334e4349bb4023c03c89ac73c +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java +@@ -0,0 +1,41 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import com.mojang.serialization.MapCodec; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.effect.MobEffectInstance; ++import net.minecraft.world.entity.LivingEntity; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++import java.util.Collection; ++import java.util.List; ++ ++public enum StatusEffectsProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final MapCodec> EFFECTS_CODEC = MobEffectInstance.CODEC.listOf().fieldOf("mob_effects"); ++ private static final ResourceLocation MC_POTION_EFFECTS = JadeProtocol.mc_id("potion_effects"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ LivingEntity living = (LivingEntity) request.target(); ++ Collection effects = living.getActiveEffects(); ++ if (effects.isEmpty()) { ++ return; ++ } ++ ++ List effectList = effects.stream().filter(MobEffectInstance::isVisible).toList(); ++ if (effectList.isEmpty()) { ++ return; ++ } ++ ++ data.writeMapData(EFFECTS_CODEC, effectList); ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_POTION_EFFECTS; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d48ef5d8c72b57ff5525ab06b59724d0f42ad42c +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java +@@ -0,0 +1,27 @@ ++package org.leavesmc.leaves.protocol.jade.provider.entity; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.monster.ZombieVillager; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.accessor.DataAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeDataProvider; ++ ++public enum ZombieVillagerProvider implements IJadeDataProvider { ++ INSTANCE; ++ ++ private static final ResourceLocation MC_ZOMBIE_VILLAGER = JadeProtocol.mc_id("zombie_villager"); ++ ++ @Override ++ public void saveData(DataAccessor data, EntityAccessor request) { ++ ZombieVillager entity = (ZombieVillager) request.target(); ++ if (entity.villagerConversionTime > 0) { ++ data.putInt("ConversionTime", entity.villagerConversionTime); ++ } ++ } ++ ++ @Override ++ public ResourceLocation getUid() { ++ return MC_ZOMBIE_VILLAGER; ++ } ++} +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 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java +@@ -0,0 +1,120 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.cache.Cache; ++import com.google.common.cache.CacheBuilder; ++import com.google.common.collect.ArrayListMultimap; ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableListMultimap; ++import com.google.common.collect.ListMultimap; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Sets; ++import net.minecraft.resources.ResourceLocation; ++import org.leavesmc.leaves.LeavesLogger; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++ ++import java.util.Collection; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.concurrent.ExecutionException; ++import java.util.stream.Stream; ++ ++public class HierarchyLookup implements IHierarchyLookup { ++ ++ ++ private final Class baseClass; ++ private final Cache, List> resultCache = CacheBuilder.newBuilder().build(); ++ private final boolean singleton; ++ private ListMultimap, T> objects = ArrayListMultimap.create(); ++ ++ public HierarchyLookup(Class baseClass) { ++ this(baseClass, false); ++ } ++ ++ public HierarchyLookup(Class baseClass, boolean singleton) { ++ this.baseClass = baseClass; ++ this.singleton = singleton; ++ } ++ ++ @Override ++ public void register(Class clazz, T provider) { ++ Preconditions.checkArgument(isClassAcceptable(clazz), "Class %s is not acceptable", clazz); ++ Objects.requireNonNull(provider.getUid()); ++ JadeProtocol.priorities.put(provider); ++ objects.put(clazz, provider); ++ } ++ ++ @Override ++ public boolean isClassAcceptable(Class clazz) { ++ return baseClass.isAssignableFrom(clazz); ++ } ++ ++ @Override ++ public List get(Class clazz) { ++ try { ++ return resultCache.get(clazz, () -> { ++ List list = Lists.newArrayList(); ++ getInternal(clazz, list); ++ list = ImmutableList.sortedCopyOf(Comparator.comparingInt(JadeProtocol.priorities::byValue), list); ++ if (singleton && !list.isEmpty()) { ++ return ImmutableList.of(list.getFirst()); ++ } ++ return list; ++ }); ++ } catch (ExecutionException e) { ++ LeavesLogger.LOGGER.severe(e.getMessage()); ++ } ++ return List.of(); ++ } ++ ++ private void getInternal(Class clazz, List list) { ++ if (clazz != baseClass && clazz != Object.class) { ++ getInternal(clazz.getSuperclass(), list); ++ } ++ list.addAll(objects.get(clazz)); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return objects.isEmpty(); ++ } ++ ++ @Override ++ public Stream, Collection>> entries() { ++ return objects.asMap().entrySet().stream(); ++ } ++ ++ @Override ++ public void invalidate() { ++ resultCache.invalidateAll(); ++ } ++ ++ @Override ++ public void loadComplete(PriorityStore priorityStore) { ++ objects.asMap().forEach((clazz, list) -> { ++ if (list.size() < 2) { ++ return; ++ } ++ Set set = Sets.newHashSetWithExpectedSize(list.size()); ++ for (T provider : list) { ++ if (set.contains(provider.getUid())) { ++ throw new IllegalStateException("Duplicate UID: %s for %s".formatted(provider.getUid(), list.stream() ++ .filter(p -> p.getUid().equals(provider.getUid())) ++ .map(p -> p.getClass().getName()) ++ .toList() ++ )); ++ } ++ set.add(provider.getUid()); ++ } ++ }); ++ ++ objects = ImmutableListMultimap., T>builder() ++ .orderValuesBy(Comparator.comparingInt(priorityStore::byValue)) ++ .putAll(objects) ++ .build(); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1bcd562ef4b88308fcfee1dae3675671b10edb15 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java +@@ -0,0 +1,37 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import net.minecraft.resources.ResourceLocation; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++ ++import java.util.Collection; ++import java.util.List; ++import java.util.Map; ++import java.util.stream.Stream; ++ ++public interface IHierarchyLookup { ++ default IHierarchyLookup cast() { ++ return this; ++ } ++ ++ void register(Class clazz, T provider); ++ ++ boolean isClassAcceptable(Class clazz); ++ ++ default List get(Object obj) { ++ if (obj == null) { ++ return List.of(); ++ } ++ return get(obj.getClass()); ++ } ++ ++ List get(Class clazz); ++ ++ boolean isEmpty(); ++ ++ Stream, Collection>> entries(); ++ ++ void invalidate(); ++ ++ void loadComplete(PriorityStore priorityStore); ++} ++ +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1386291a6c651dfbbcecb2e469b1bd943861e4cc +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemCollector.java +@@ -0,0 +1,114 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; ++import net.minecraft.core.component.DataComponentPatch; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.component.CustomData; ++ ++import java.util.List; ++import java.util.Locale; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Predicate; ++ ++public class ItemCollector { ++ public static final int MAX_SIZE = 54; ++ public static final ItemCollector EMPTY = new ItemCollector<>(null); ++ private static final Predicate NON_EMPTY = stack -> { ++ if (stack.isEmpty()) { ++ return false; ++ } ++ CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); ++ if (customData.contains("CustomModelData")) { ++ CompoundTag tag = customData.getUnsafe(); ++ for (String key : tag.getAllKeys()) { ++ if (key.toLowerCase(Locale.ENGLISH).endsWith("clear") && tag.getBoolean(key)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ }; ++ private final Object2IntLinkedOpenHashMap items = new Object2IntLinkedOpenHashMap<>(); ++ private final ItemIterator iterator; ++ public long version; ++ public long lastTimeFinished; ++ public List> mergedResult; ++ ++ public ItemCollector(ItemIterator iterator) { ++ this.iterator = iterator; ++ } ++ ++ public List> update(Object target, long gameTime) { ++ if (iterator == null) { ++ return null; ++ } ++ T container = iterator.find(target); ++ if (container == null) { ++ return null; ++ } ++ long currentVersion = iterator.getVersion(container); ++ if (mergedResult != null && iterator.isFinished()) { ++ if (version == currentVersion) { ++ return mergedResult; // content not changed ++ } ++ if (lastTimeFinished + 5 > gameTime) { ++ return mergedResult; // avoid update too frequently ++ } ++ iterator.reset(); ++ } ++ AtomicInteger count = new AtomicInteger(); ++ iterator.populate(container).forEach(stack -> { ++ count.incrementAndGet(); ++ if (NON_EMPTY.test(stack)) { ++ ItemDefinition def = new ItemDefinition(stack); ++ items.addTo(def, stack.getCount()); ++ } ++ }); ++ iterator.afterPopulate(count.get()); ++ if (mergedResult != null && !iterator.isFinished()) { ++ updateCollectingProgress(mergedResult.getFirst()); ++ return mergedResult; ++ } ++ List partialResult = items.object2IntEntrySet().stream().limit(54).map(entry -> { ++ ItemDefinition def = entry.getKey(); ++ return def.toStack(entry.getIntValue()); ++ }).toList(); ++ List> groups = List.of(updateCollectingProgress(new ViewGroup<>(partialResult))); ++ if (iterator.isFinished()) { ++ mergedResult = groups; ++ version = currentVersion; ++ lastTimeFinished = gameTime; ++ items.clear(); ++ } ++ return groups; ++ } ++ ++ protected ViewGroup updateCollectingProgress(ViewGroup group) { ++ float progress = iterator.getCollectingProgress(); ++ CompoundTag data = group.getExtraData(); ++ if (Float.isNaN(progress)) { ++ progress = 0; ++ } ++ if (progress >= 1) { ++ data.remove("Collecting"); ++ } else { ++ data.putFloat("Collecting", progress); ++ } ++ return group; ++ } ++ ++ public record ItemDefinition(Item item, DataComponentPatch components) { ++ ItemDefinition(ItemStack stack) { ++ this(stack.getItem(), stack.getComponentsPatch()); ++ } ++ ++ public ItemStack toStack(int count) { ++ ItemStack itemStack = new ItemStack(item, count); ++ itemStack.applyComponents(components); ++ return itemStack; ++ } ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4d65e9a8b5224bd268b1bf18bc39a58dc0113850 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ItemIterator.java +@@ -0,0 +1,102 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import net.minecraft.world.Container; ++import net.minecraft.world.item.ItemStack; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Function; ++import java.util.stream.IntStream; ++import java.util.stream.Stream; ++ ++public abstract class ItemIterator { ++ public static final AtomicLong version = new AtomicLong(); ++ protected final Function containerFinder; ++ protected final int fromIndex; ++ protected boolean finished; ++ protected int currentIndex; ++ ++ protected ItemIterator(Function containerFinder, int fromIndex) { ++ this.containerFinder = containerFinder; ++ this.currentIndex = this.fromIndex = fromIndex; ++ } ++ ++ public @Nullable T find(Object target) { ++ return containerFinder.apply(target); ++ } ++ ++ public final boolean isFinished() { ++ return finished; ++ } ++ ++ public long getVersion(T container) { ++ return version.getAndIncrement(); ++ } ++ ++ public abstract Stream populate(T container); ++ ++ public void reset() { ++ currentIndex = fromIndex; ++ finished = false; ++ } ++ ++ public void afterPopulate(int count) { ++ currentIndex += count; ++ if (count == 0 || currentIndex >= 10000) { ++ finished = true; ++ } ++ } ++ ++ public float getCollectingProgress() { ++ return Float.NaN; ++ } ++ ++ public static abstract class SlottedItemIterator extends ItemIterator { ++ protected float progress; ++ ++ public SlottedItemIterator(Function containerFinder, int fromIndex) { ++ super(containerFinder, fromIndex); ++ } ++ ++ protected abstract int getSlotCount(T container); ++ ++ protected abstract ItemStack getItemInSlot(T container, int slot); ++ ++ @Override ++ public Stream populate(T container) { ++ int slotCount = getSlotCount(container); ++ int toIndex = currentIndex + ItemCollector.MAX_SIZE * 2; ++ if (toIndex >= slotCount) { ++ toIndex = slotCount; ++ finished = true; ++ } ++ progress = (float) (currentIndex - fromIndex) / (slotCount - fromIndex); ++ return IntStream.range(currentIndex, toIndex).mapToObj(slot -> getItemInSlot(container, slot)); ++ } ++ ++ @Override ++ public float getCollectingProgress() { ++ return progress; ++ } ++ } ++ ++ public static class ContainerItemIterator extends SlottedItemIterator { ++ public ContainerItemIterator(int fromIndex) { ++ this(Container.class::cast, fromIndex); ++ } ++ ++ public ContainerItemIterator(Function containerFinder, int fromIndex) { ++ super(containerFinder, fromIndex); ++ } ++ ++ @Override ++ protected int getSlotCount(Container container) { ++ return container.getContainerSize(); ++ } ++ ++ @Override ++ protected ItemStack getItemInSlot(Container container, int slot) { ++ return container.getItem(slot); ++ } ++ } ++} +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 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java +@@ -0,0 +1,101 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import com.google.common.cache.Cache; ++import com.google.common.cache.CacheBuilder; ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.Iterables; ++import net.minecraft.resources.ResourceLocation; ++import org.apache.commons.lang3.tuple.Pair; ++import org.leavesmc.leaves.LeavesLogger; ++import org.leavesmc.leaves.protocol.jade.JadeProtocol; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++ ++import java.util.Collection; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.concurrent.ExecutionException; ++import java.util.stream.Stream; ++ ++public class PairHierarchyLookup implements IHierarchyLookup { ++ ++ public final IHierarchyLookup first; ++ public final IHierarchyLookup second; ++ private final Cache, Class>, List> mergedCache = CacheBuilder.newBuilder().build(); ++ ++ public PairHierarchyLookup(IHierarchyLookup first, IHierarchyLookup second) { ++ this.first = first; ++ this.second = second; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public List getMerged(Object first, Object second) { ++ Objects.requireNonNull(first); ++ Objects.requireNonNull(second); ++ try { ++ return (List) mergedCache.get(Pair.of(first.getClass(), second.getClass()), () -> { ++ List firstList = this.first.get(first); ++ List secondList = this.second.get(second); ++ if (firstList.isEmpty()) { ++ return secondList; ++ } else if (secondList.isEmpty()) { ++ return firstList; ++ } ++ return ImmutableList.sortedCopyOf(Comparator.comparingInt(JadeProtocol.priorities::byValue), Iterables.concat(firstList, secondList)); ++ }); ++ } catch (ExecutionException e) { ++ LeavesLogger.LOGGER.severe(e.getMessage()); ++ } ++ return List.of(); ++ } ++ ++ @Override ++ public void register(Class clazz, T provider) { ++ if (first.isClassAcceptable(clazz)) { ++ first.register(clazz, provider); ++ } else if (second.isClassAcceptable(clazz)) { ++ second.register(clazz, provider); ++ } else { ++ throw new IllegalArgumentException("Class " + clazz + " is not acceptable"); ++ } ++ } ++ ++ @Override ++ public boolean isClassAcceptable(Class clazz) { ++ return first.isClassAcceptable(clazz) || second.isClassAcceptable(clazz); ++ } ++ ++ @Override ++ public List get(Class clazz) { ++ List result = first.get(clazz); ++ if (result.isEmpty()) { ++ result = second.get(clazz); ++ } ++ return result; ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return first.isEmpty() && second.isEmpty(); ++ } ++ ++ @Override ++ public Stream, Collection>> entries() { ++ return Stream.concat(first.entries(), second.entries()); ++ } ++ ++ @Override ++ public void invalidate() { ++ first.invalidate(); ++ second.invalidate(); ++ mergedCache.invalidateAll(); ++ } ++ ++ @Override ++ public void loadComplete(PriorityStore priorityStore) { ++ first.loadComplete(priorityStore); ++ second.loadComplete(priorityStore); ++ } ++} ++ +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e94e10e0feea1bc2f4e0495d4ed05810baa1466 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java +@@ -0,0 +1,73 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.Sets; ++import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++ ++import java.util.Collection; ++import java.util.Comparator; ++import java.util.List; ++import java.util.Objects; ++import java.util.Set; ++import java.util.function.BiFunction; ++import java.util.function.Function; ++import java.util.function.ToIntFunction; ++ ++public class PriorityStore { ++ ++ private final Object2IntMap priorities = new Object2IntLinkedOpenHashMap<>(); ++ private final Function keyGetter; ++ private final ToIntFunction defaultPriorityGetter; ++ private ImmutableList sortedList = ImmutableList.of(); ++ private BiFunction, Collection, List> sortingFunction = (store, allKeys) -> allKeys.stream() ++ .sorted(Comparator.comparingInt(store::byKey)) ++ .toList(); ++ ++ public PriorityStore(ToIntFunction defaultPriorityGetter, Function keyGetter) { ++ this.defaultPriorityGetter = defaultPriorityGetter; ++ this.keyGetter = keyGetter; ++ } ++ ++ public void setSortingFunction(BiFunction, Collection, List> sortingFunction) { ++ this.sortingFunction = sortingFunction; ++ } ++ ++ public void put(V provider) { ++ Objects.requireNonNull(provider); ++ put(provider, defaultPriorityGetter.applyAsInt(provider)); ++ } ++ ++ public void put(V provider, int priority) { ++ Objects.requireNonNull(provider); ++ K uid = keyGetter.apply(provider); ++ Objects.requireNonNull(uid); ++ priorities.put(uid, priority); ++ } ++ ++ public void putUnsafe(K key, int priority) { ++ Objects.requireNonNull(key); ++ priorities.put(key, priority); ++ } ++ ++ public void sort(Set extraKeys) { ++ Set allKeys = priorities.keySet(); ++ if (!extraKeys.isEmpty()) { ++ allKeys = Sets.union(priorities.keySet(), extraKeys); ++ } ++ ++ sortedList = ImmutableList.copyOf(sortingFunction.apply(this, allKeys)); ++ } ++ ++ public int byValue(V value) { ++ return byKey(keyGetter.apply(value)); ++ } ++ ++ public int byKey(K id) { ++ return priorities.getInt(id); ++ } ++ ++ public ImmutableList getSortedList() { ++ return sortedList; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41dff617e766d013e32a64a1b2b1c434623f65c8 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java +@@ -0,0 +1,63 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.List; ++import java.util.function.Function; ++ ++public class ViewGroup { ++ ++ public final List views; ++ @Nullable ++ public String id; ++ @Nullable ++ protected CompoundTag extraData; ++ ++ public ViewGroup(List views) { ++ this.views = views; ++ } ++ ++ public void save(CompoundTag tag, Function writer) { ++ ListTag list = new ListTag(); ++ for (var view : views) { ++ list.add(writer.apply(view)); ++ } ++ tag.put("Views", list); ++ if (id != null) { ++ tag.putString("Id", id); ++ } ++ if (extraData != null) { ++ tag.put("Data", extraData); ++ } ++ } ++ ++ public static boolean saveList(CompoundTag tag, String key, List> groups, Function writer) { ++ if (groups == null || groups.isEmpty()) { ++ return false; ++ } ++ ++ ListTag groupList = new ListTag(); ++ for (ViewGroup group : groups) { ++ if (group.views.isEmpty()) { ++ continue; ++ } ++ CompoundTag groupTag = new CompoundTag(); ++ group.save(groupTag, writer); ++ groupList.add(groupTag); ++ } ++ if (!groupList.isEmpty()) { ++ tag.put(key, groupList); ++ return true; ++ } ++ return false; ++ } ++ ++ public CompoundTag getExtraData() { ++ if (extraData == null) { ++ extraData = new CompoundTag(); ++ } ++ return extraData; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eec302b45bedb026e1d3a4595ed99b89b2a64655 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java +@@ -0,0 +1,98 @@ ++package org.leavesmc.leaves.protocol.jade.util; ++ ++import com.google.common.collect.Lists; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.block.Block; ++import org.apache.commons.lang3.tuple.Pair; ++import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; ++import org.leavesmc.leaves.protocol.jade.accessor.RequestAccessor; ++import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; ++ ++import java.util.Collection; ++import java.util.List; ++import java.util.Map; ++import java.util.function.Function; ++import java.util.stream.Stream; ++ ++public class WrappedHierarchyLookup extends HierarchyLookup { ++ ++ public final List, Function, @Nullable Object>>> overrides = Lists.newArrayList(); ++ private boolean empty = true; ++ ++ public WrappedHierarchyLookup() { ++ super(Object.class, true); ++ overrides.add(Pair.of(new HierarchyLookup<>(Block.class, true), accessor -> { ++ if (accessor instanceof BlockAccessor blockAccessor) { ++ return blockAccessor.block(); ++ } ++ return null; ++ })); ++ } ++ ++ public List get(RequestAccessor accessor) { ++ List list = Lists.newArrayList(); ++ for (var override : overrides) { ++ Object o = override.getRight().apply(accessor); ++ if (o != null) { ++ list.addAll(override.getLeft().get(o)); ++ } ++ } ++ list.addAll(get(accessor.target())); ++ return list; ++ } ++ ++ @Override ++ public void register(Class clazz, T provider) { ++ for (var override : overrides) { ++ if (override.getLeft().isClassAcceptable(clazz)) { ++ override.getLeft().register(clazz, provider); ++ empty = false; ++ return; ++ } ++ } ++ super.register(clazz, provider); ++ empty = false; ++ } ++ ++ @Override ++ public boolean isClassAcceptable(Class clazz) { ++ for (var override : overrides) { ++ if (override.getLeft().isClassAcceptable(clazz)) { ++ return true; ++ } ++ } ++ return super.isClassAcceptable(clazz); ++ } ++ ++ @Override ++ public void invalidate() { ++ for (var override : overrides) { ++ override.getLeft().invalidate(); ++ } ++ super.invalidate(); ++ } ++ ++ @Override ++ public void loadComplete(PriorityStore priorityStore) { ++ for (var override : overrides) { ++ override.getLeft().loadComplete(priorityStore); ++ } ++ super.loadComplete(priorityStore); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return empty; ++ } ++ ++ @Override ++ public Stream, Collection>> entries() { ++ Stream, Collection>> stream = super.entries(); ++ for (var override : overrides) { ++ stream = Stream.concat(stream, override.getLeft().entries()); ++ } ++ return stream; ++ } ++} ++ diff --git a/patches/server/0032-Leaves-Appleskin-Protocol.patch b/patches/server/0033-Leaves-Appleskin-Protocol.patch similarity index 89% rename from patches/server/0032-Leaves-Appleskin-Protocol.patch rename to patches/server/0033-Leaves-Appleskin-Protocol.patch index f457b840..50c9dd32 100644 --- a/patches/server/0032-Leaves-Appleskin-Protocol.patch +++ b/patches/server/0033-Leaves-Appleskin-Protocol.patch @@ -6,37 +6,22 @@ Subject: [PATCH] Leaves: Appleskin Protocol Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 This patch is Powered by AppleSkin (https://github.com/squeek502/AppleSkin) diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1181ffae40b86a4212e199814d3e07e641a5a1f2 ---- /dev/null +index b63d87d0593d116b0c5ee835dc4372f1b5542453..ef23577faaf7948d7a9b1def4d56506eab19cc80 100644 +--- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.network; -+ -+import org.dreeam.leaf.config.ConfigInfo; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.IConfigModule; -+ -+public class ProtocolSupport implements IConfigModule { -+ -+ @Override -+ public EnumConfigCategory getCategory() { -+ return EnumConfigCategory.NETWORK; -+ } -+ -+ @Override -+ public String getBaseName() { -+ return "protocol_support"; -+ } +@@ -18,4 +18,7 @@ public class ProtocolSupport implements IConfigModule { + + @ConfigInfo(baseName = "jade-protocol") + public static boolean jadeProtocol = false; + + @ConfigInfo(baseName = "appleskin-protocol") + public static boolean appleskinProtocol = false; -+} + } diff --git a/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/AppleSkinProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..c496c97c99cd352c2566731d3017cf1b14ee74ec diff --git a/patches/server/0033-Leaves-Xaero-Map-Protocol.patch b/patches/server/0034-Leaves-Xaero-Map-Protocol.patch similarity index 95% rename from patches/server/0033-Leaves-Xaero-Map-Protocol.patch rename to patches/server/0034-Leaves-Xaero-Map-Protocol.patch index b77b12fa..87d70eb8 100644 --- a/patches/server/0033-Leaves-Xaero-Map-Protocol.patch +++ b/patches/server/0034-Leaves-Xaero-Map-Protocol.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Leaves: Xaero Map Protocol Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 This patch is Powered by Xaero Map @@ -23,7 +23,7 @@ index 0d65a53b23c82cbc4539afd28c52b5fd2d2ff5b4..c410f781371a87ecc2849144c747a6ac // CraftBukkit start - handle player weather // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 1181ffae40b86a4212e199814d3e07e641a5a1f2..be8474a514e7b9bdacd7cc89d8d3e8b3bc3eb709 100644 +index ef23577faaf7948d7a9b1def4d56506eab19cc80..c9736e79387d785a67d6ebe71df6553eeaf2b41d 100644 --- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -4,6 +4,8 @@ import org.dreeam.leaf.config.ConfigInfo; @@ -35,7 +35,7 @@ index 1181ffae40b86a4212e199814d3e07e641a5a1f2..be8474a514e7b9bdacd7cc89d8d3e8b3 public class ProtocolSupport implements IConfigModule { @Override -@@ -18,4 +20,9 @@ public class ProtocolSupport implements IConfigModule { +@@ -21,4 +23,9 @@ public class ProtocolSupport implements IConfigModule { @ConfigInfo(baseName = "appleskin-protocol") public static boolean appleskinProtocol = false; diff --git a/patches/server/0034-Chat-Image-protocol.patch b/patches/server/0035-Chat-Image-protocol.patch similarity index 98% rename from patches/server/0034-Chat-Image-protocol.patch rename to patches/server/0035-Chat-Image-protocol.patch index a77df7b4..5446557e 100644 --- a/patches/server/0034-Chat-Image-protocol.patch +++ b/patches/server/0035-Chat-Image-protocol.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Chat Image protocol This patch is Powered by ChatImage (https://github.com/kitUIN/ChatImage) diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index be8474a514e7b9bdacd7cc89d8d3e8b3bc3eb709..9324ca1f1abd2343b2f1eaec019e84428f01c626 100644 +index c9736e79387d785a67d6ebe71df6553eeaf2b41d..53ae1ecb7acf8e9d8b9b1c50d5a483008b303a66 100644 --- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -21,6 +21,9 @@ public class ProtocolSupport implements IConfigModule { +@@ -24,6 +24,9 @@ public class ProtocolSupport implements IConfigModule { @ConfigInfo(baseName = "appleskin-protocol") public static boolean appleskinProtocol = false; diff --git a/patches/server/0035-Asteor-Bar-protocol.patch b/patches/server/0036-Asteor-Bar-protocol.patch similarity index 97% rename from patches/server/0035-Asteor-Bar-protocol.patch rename to patches/server/0036-Asteor-Bar-protocol.patch index 7fbf1c29..31a5b667 100644 --- a/patches/server/0035-Asteor-Bar-protocol.patch +++ b/patches/server/0036-Asteor-Bar-protocol.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Asteor Bar protocol This patch is Powered by AsteorBar (https://github.com/afoxxvi/AsteorBarMod) diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 9324ca1f1abd2343b2f1eaec019e84428f01c626..45c422b993e254fd892bfd6f47a074f1e9688714 100644 +index 53ae1ecb7acf8e9d8b9b1c50d5a483008b303a66..b91f0ae28f044ceed0b9db46669a4cca2d47cb2d 100644 --- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -21,6 +21,9 @@ public class ProtocolSupport implements IConfigModule { +@@ -24,6 +24,9 @@ public class ProtocolSupport implements IConfigModule { @ConfigInfo(baseName = "appleskin-protocol") public static boolean appleskinProtocol = false; diff --git a/patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch b/patches/server/0037-Leaves-Disable-moved-wrongly-threshold.patch similarity index 97% rename from patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch rename to patches/server/0037-Leaves-Disable-moved-wrongly-threshold.patch index b9f56177..613997bc 100644 --- a/patches/server/0036-Leaves-Disable-moved-wrongly-threshold.patch +++ b/patches/server/0037-Leaves-Disable-moved-wrongly-threshold.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Leaves: Disable moved wrongly threshold Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d107cd93a6d43f060a7bcfc4a3ee0c82c51bc9b9..cc9c30b8555e0509162a82c4a01de9fc51ba59af 100644 +index 2461fdfe02dc2252178e442f54ef4f584ba75cf8..a0abb0182aa6166b1d2702aa9964132889dc9d86 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -589,7 +589,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl diff --git a/patches/server/0037-Leaves-Fix-vehicle-teleport-by-end-gateway.patch b/patches/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch similarity index 98% rename from patches/server/0037-Leaves-Fix-vehicle-teleport-by-end-gateway.patch rename to patches/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch index b5b85f48..c0718f4f 100644 --- a/patches/server/0037-Leaves-Fix-vehicle-teleport-by-end-gateway.patch +++ b/patches/server/0038-Leaves-Fix-vehicle-teleport-by-end-gateway.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Leaves: Fix vehicle teleport by end gateway Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves -Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c +Commit: 6951bdc2153bcb14aa64787ab007fa39fee7c007 diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java index 0e58011d22536051a18388c2d45fd1a30c2f5ffd..2910af920a746f90529f94038922e2a9e6ad3bc1 100644 diff --git a/patches/server/0038-Faster-Random-for-xaeroMapServerID-generation.patch b/patches/server/0039-Faster-Random-for-xaeroMapServerID-generation.patch similarity index 88% rename from patches/server/0038-Faster-Random-for-xaeroMapServerID-generation.patch rename to patches/server/0039-Faster-Random-for-xaeroMapServerID-generation.patch index 5c78d74d..785c4fd8 100644 --- a/patches/server/0038-Faster-Random-for-xaeroMapServerID-generation.patch +++ b/patches/server/0039-Faster-Random-for-xaeroMapServerID-generation.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Faster Random for xaeroMapServerID generation diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -index 8378bab717088b832888be69618f13ba6dd44273..58c2745563b482e9147afa168de72c3ee7a79f0b 100644 +index b91f0ae28f044ceed0b9db46669a4cca2d47cb2d..8c71281004649e23adb6609a66ab4951b280c517 100644 --- a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -4,7 +4,7 @@ import org.dreeam.leaf.config.ConfigInfo; @@ -17,7 +17,7 @@ index 8378bab717088b832888be69618f13ba6dd44273..58c2745563b482e9147afa168de72c3e public class ProtocolSupport implements IConfigModule { -@@ -30,5 +30,5 @@ public class ProtocolSupport implements IConfigModule { +@@ -33,5 +33,5 @@ public class ProtocolSupport implements IConfigModule { @ConfigInfo(baseName = "xaero-map-protocol") public static boolean xaeroMapProtocol = false; @ConfigInfo(baseName = "xaero-map-server-id") diff --git a/patches/server/0039-Petal-Async-Pathfinding.patch b/patches/server/0040-Petal-Async-Pathfinding.patch similarity index 99% rename from patches/server/0039-Petal-Async-Pathfinding.patch rename to patches/server/0040-Petal-Async-Pathfinding.patch index 18c6bbda..1008b9e0 100644 --- a/patches/server/0039-Petal-Async-Pathfinding.patch +++ b/patches/server/0040-Petal-Async-Pathfinding.patch @@ -15,7 +15,7 @@ This patch was ported downstream from the Petal fork. Makes most pathfinding-related work happen asynchronously diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 3a153b7f1092c2d4ac6e0115f611d60907619a40..8ba2d0884114abd7dd3ff8d6e8a9a86e171bc2c0 100644 +index 965a92f9a8e8efada117d208290271bc94b23db7..f82c85a09004448350611e406f1a373b2a07f9ec 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -303,6 +303,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti diff --git a/patches/server/0040-Petal-Multithreaded-Tracker.patch b/patches/server/0041-Petal-Multithreaded-Tracker.patch similarity index 99% rename from patches/server/0040-Petal-Multithreaded-Tracker.patch rename to patches/server/0041-Petal-Multithreaded-Tracker.patch index e589820b..4e39c0f7 100644 --- a/patches/server/0040-Petal-Multithreaded-Tracker.patch +++ b/patches/server/0041-Petal-Multithreaded-Tracker.patch @@ -193,7 +193,7 @@ index 4f91107f9ae42f96c060c310596db9aa869a8dbc..faad96f04af2e368f0276ade417dd1ba public boolean visible = true; diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index f1e4ed6f62472737a534fc457aa483d0725e92e3..7c303c4f1996139a0a5401a66413a58a10edbf55 100644 +index cb2683c7e090a8d040b581bc95a0505998f17f43..539934a75df64e7accf4829d8d4b075833bb5bc9 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -295,7 +295,11 @@ public class ServerEntity { diff --git a/patches/server/0041-Petal-reduce-work-done-by-game-event-system.patch b/patches/server/0042-Petal-reduce-work-done-by-game-event-system.patch similarity index 99% rename from patches/server/0041-Petal-reduce-work-done-by-game-event-system.patch rename to patches/server/0042-Petal-reduce-work-done-by-game-event-system.patch index 5a176971..c5e2c152 100644 --- a/patches/server/0041-Petal-reduce-work-done-by-game-event-system.patch +++ b/patches/server/0042-Petal-reduce-work-done-by-game-event-system.patch @@ -38,7 +38,7 @@ index 81dd0aa6a90fd9dda9e7752f85b9cf4568e3b575..7d04ff88e6352147e0c00266bd5d251f LivingEntity entityliving1 = deadEntity.getLastHurtByMob(); diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 1a2ef85cd8a62824b23f4212a5e2a70ce89e344f..8c36a75dbd60b61337ba5c1b061f8c6c2648c4ca 100644 +index 184b69dc39749734f8176d3f3c2bf9f690a099f0..852cd20583d2c783c58ae19d0fa84ec834fe40e9 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -82,7 +82,18 @@ public class LevelChunk extends ChunkAccess { diff --git a/patches/server/0042-Fix-tracker-NPE.patch b/patches/server/0043-Fix-tracker-NPE.patch similarity index 100% rename from patches/server/0042-Fix-tracker-NPE.patch rename to patches/server/0043-Fix-tracker-NPE.patch diff --git a/patches/server/0043-Optimize-Minecart-collisions.patch b/patches/server/0044-Optimize-Minecart-collisions.patch similarity index 100% rename from patches/server/0043-Optimize-Minecart-collisions.patch rename to patches/server/0044-Optimize-Minecart-collisions.patch diff --git a/patches/server/0044-Reduce-canSee-work.patch b/patches/server/0045-Reduce-canSee-work.patch similarity index 100% rename from patches/server/0044-Reduce-canSee-work.patch rename to patches/server/0045-Reduce-canSee-work.patch diff --git a/patches/server/0045-Faster-Natural-Spawning.patch b/patches/server/0046-Faster-Natural-Spawning.patch similarity index 97% rename from patches/server/0045-Faster-Natural-Spawning.patch rename to patches/server/0046-Faster-Natural-Spawning.patch index fc6d61a7..38826e82 100644 --- a/patches/server/0045-Faster-Natural-Spawning.patch +++ b/patches/server/0046-Faster-Natural-Spawning.patch @@ -22,7 +22,7 @@ index 9c6f5b55b1f1376fa75e216cd366ee47c79fafc4..7762c8186035fdf60e11d9f1844516b6 static RandomSource createThreadSafe() { return new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed()); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e971ef13754402f3de118ea24cb9f83b564cd65e..749fa63c641c1cb54445c85721c795355ffae123 100644 +index 25e93a0a58b0ecf02f2669fc8f01ef2f2f938dc7..a50c6d59cfd6286b4ab2f70b2918ba845f04bf94 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -128,6 +128,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { diff --git a/patches/server/0046-Fix-sprint-glitch.patch b/patches/server/0047-Fix-sprint-glitch.patch similarity index 90% rename from patches/server/0046-Fix-sprint-glitch.patch rename to patches/server/0047-Fix-sprint-glitch.patch index d74717a3..24eff23a 100644 --- a/patches/server/0046-Fix-sprint-glitch.patch +++ b/patches/server/0047-Fix-sprint-glitch.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Fix sprint glitch diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d55d869f0a389d58d001e59e26b1b5912b5c94a8..919b71af99edfed2240bacfd288fcd210398f49a 100644 +index d4cee0133f8e7df5321b00cf97bb7f43b1d198bc..f1c0ca7baa6297813c5e5deeaf6ad698086ffddb 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1451,7 +1451,8 @@ public abstract class LivingEntity extends Entity implements Attackable { diff --git a/patches/server/0047-Fix-keepalive-kicked-name.patch b/patches/server/0048-Fix-keepalive-kicked-name.patch similarity index 100% rename from patches/server/0047-Fix-keepalive-kicked-name.patch rename to patches/server/0048-Fix-keepalive-kicked-name.patch diff --git a/patches/server/0048-Configurable-movement-speed-of-more-entities.patch b/patches/server/0049-Configurable-movement-speed-of-more-entities.patch similarity index 100% rename from patches/server/0048-Configurable-movement-speed-of-more-entities.patch rename to patches/server/0049-Configurable-movement-speed-of-more-entities.patch diff --git a/patches/server/0049-Faster-sequencing-of-futures-for-chunk-structure-gen.patch b/patches/server/0050-Faster-sequencing-of-futures-for-chunk-structure-gen.patch similarity index 100% rename from patches/server/0049-Faster-sequencing-of-futures-for-chunk-structure-gen.patch rename to patches/server/0050-Faster-sequencing-of-futures-for-chunk-structure-gen.patch diff --git a/patches/server/0050-Reduce-items-finding-hopper-nearby-check.patch b/patches/server/0051-Reduce-items-finding-hopper-nearby-check.patch similarity index 100% rename from patches/server/0050-Reduce-items-finding-hopper-nearby-check.patch rename to patches/server/0051-Reduce-items-finding-hopper-nearby-check.patch diff --git a/patches/server/0051-Plazma-Add-some-missing-Pufferfish-configurations.patch b/patches/server/0052-Plazma-Add-some-missing-Pufferfish-configurations.patch similarity index 97% rename from patches/server/0051-Plazma-Add-some-missing-Pufferfish-configurations.patch rename to patches/server/0052-Plazma-Add-some-missing-Pufferfish-configurations.patch index 57687337..61c88670 100644 --- a/patches/server/0051-Plazma-Add-some-missing-Pufferfish-configurations.patch +++ b/patches/server/0052-Plazma-Add-some-missing-Pufferfish-configurations.patch @@ -10,7 +10,7 @@ Add Pufferfish DAB support for Camel, Sniffer https://github.com/pufferfish-gg/Pufferfish/issues/83 diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index 063dde771ade593a29481f14b8f44a0f72f15953..e658456676aba19861d301d009f61a3b5cb078da 100644 +index 877fe6312051f2669360b85c0caded7b47f04866..17c0e7fc08621d30bc8398a5063f9dbb9d917f57 100644 --- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java +++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java @@ -128,8 +128,10 @@ public class Armadillo extends Animal { diff --git a/patches/server/0052-Plazma-Add-missing-purpur-configuration-options.patch b/patches/server/0053-Plazma-Add-missing-purpur-configuration-options.patch similarity index 99% rename from patches/server/0052-Plazma-Add-missing-purpur-configuration-options.patch rename to patches/server/0053-Plazma-Add-missing-purpur-configuration-options.patch index 5bef8c01..86836614 100644 --- a/patches/server/0052-Plazma-Add-missing-purpur-configuration-options.patch +++ b/patches/server/0053-Plazma-Add-missing-purpur-configuration-options.patch @@ -97,7 +97,7 @@ index da026533b15a1981000e73bba6c5209c13de28dc..96d2077abd0112f2b2b8c4678447afbf return this.level().purpurConfig.frogBreedingTicks; } diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index e6861fd5f9817ec54294976f0e93952baa387773..2fe21d0d8807365610ef4f7346874e08e7342886 100644 +index 45f92d8fd06027f1487e24987c3fb9a389f619a9..f484f399f2b93e86b24de7393d86eae900b5a98b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java @@ -95,6 +95,23 @@ public class Tadpole extends AbstractFish { diff --git a/patches/server/0053-Skip-event-if-no-listeners.patch b/patches/server/0054-Skip-event-if-no-listeners.patch similarity index 100% rename from patches/server/0053-Skip-event-if-no-listeners.patch rename to patches/server/0054-Skip-event-if-no-listeners.patch diff --git a/patches/server/0054-PaperPR-Rewrite-framed-map-tracker-ticking.patch b/patches/server/0055-PaperPR-Rewrite-framed-map-tracker-ticking.patch similarity index 99% rename from patches/server/0054-PaperPR-Rewrite-framed-map-tracker-ticking.patch rename to patches/server/0055-PaperPR-Rewrite-framed-map-tracker-ticking.patch index 24df4019..45c7f183 100644 --- a/patches/server/0054-PaperPR-Rewrite-framed-map-tracker-ticking.patch +++ b/patches/server/0055-PaperPR-Rewrite-framed-map-tracker-ticking.patch @@ -16,7 +16,7 @@ now is just updating dirty map/decoration data. When no bukkit renderers are added to the map, we also re-use the same packet for all players who are tracking it which avoids a lot of work. diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 7c303c4f1996139a0a5401a66413a58a10edbf55..bed271712d4040983340ad001c20eaf07b0838a0 100644 +index 539934a75df64e7accf4829d8d4b075833bb5bc9..1aba5405b4b452c9972288f941e861d60331d8b9 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -120,27 +120,40 @@ public class ServerEntity { diff --git a/patches/server/0055-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch b/patches/server/0056-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch similarity index 100% rename from patches/server/0055-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch rename to patches/server/0056-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch diff --git a/patches/server/0056-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch b/patches/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch similarity index 100% rename from patches/server/0056-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch rename to patches/server/0057-SparklyPaper-Cache-coordinate-key-used-for-nearby-pl.patch diff --git a/patches/server/0057-SparklyPaper-Optimize-canSee-checks.patch b/patches/server/0058-SparklyPaper-Optimize-canSee-checks.patch similarity index 100% rename from patches/server/0057-SparklyPaper-Optimize-canSee-checks.patch rename to patches/server/0058-SparklyPaper-Optimize-canSee-checks.patch diff --git a/patches/server/0058-Polpot-Make-egg-and-snowball-can-knockback-player.patch b/patches/server/0059-Polpot-Make-egg-and-snowball-can-knockback-player.patch similarity index 100% rename from patches/server/0058-Polpot-Make-egg-and-snowball-can-knockback-player.patch rename to patches/server/0059-Polpot-Make-egg-and-snowball-can-knockback-player.patch diff --git a/patches/server/0059-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch b/patches/server/0060-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch similarity index 100% rename from patches/server/0059-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch rename to patches/server/0060-Redirect-vanilla-getProfiler-in-PathNavigationRegion.patch diff --git a/patches/server/0060-Fix-MC-2025.patch b/patches/server/0061-Fix-MC-2025.patch similarity index 95% rename from patches/server/0060-Fix-MC-2025.patch rename to patches/server/0061-Fix-MC-2025.patch index ac83b20e..35796fbe 100644 --- a/patches/server/0060-Fix-MC-2025.patch +++ b/patches/server/0061-Fix-MC-2025.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Fix MC-2025 Mojang issues: https://bugs.mojang.com/browse/MC-2025 diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1da09a313f73c35be5b1b46da6b489a599f745f4..a1af147bdf54a5001a8c65c97f309a2a4ec71567 100644 +index fd937544baae6835e1826a686676dcbfa58aca33..add6e68926cda8fc94fb6b37c3da20046b8c7346 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -2628,6 +2628,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/patches/server/0061-Fix-MC-65198.patch b/patches/server/0062-Fix-MC-65198.patch similarity index 100% rename from patches/server/0061-Fix-MC-65198.patch rename to patches/server/0062-Fix-MC-65198.patch diff --git a/patches/server/0062-Including-5s-in-getTPS.patch b/patches/server/0063-Including-5s-in-getTPS.patch similarity index 100% rename from patches/server/0062-Including-5s-in-getTPS.patch rename to patches/server/0063-Including-5s-in-getTPS.patch diff --git a/patches/server/0063-Remove-useless-creating-stats-json-bases-on-player-n.patch b/patches/server/0064-Remove-useless-creating-stats-json-bases-on-player-n.patch similarity index 100% rename from patches/server/0063-Remove-useless-creating-stats-json-bases-on-player-n.patch rename to patches/server/0064-Remove-useless-creating-stats-json-bases-on-player-n.patch diff --git a/patches/server/0064-Fix-NPE-during-creating-GUI-graph.patch b/patches/server/0065-Fix-NPE-during-creating-GUI-graph.patch similarity index 100% rename from patches/server/0064-Fix-NPE-during-creating-GUI-graph.patch rename to patches/server/0065-Fix-NPE-during-creating-GUI-graph.patch diff --git a/patches/server/0065-Don-t-throw-exception-on-missing-ResourceKey-value.patch b/patches/server/0066-Don-t-throw-exception-on-missing-ResourceKey-value.patch similarity index 100% rename from patches/server/0065-Don-t-throw-exception-on-missing-ResourceKey-value.patch rename to patches/server/0066-Don-t-throw-exception-on-missing-ResourceKey-value.patch diff --git a/patches/server/0066-Improve-Purpur-AFK-system.patch b/patches/server/0067-Improve-Purpur-AFK-system.patch similarity index 98% rename from patches/server/0066-Improve-Purpur-AFK-system.patch rename to patches/server/0067-Improve-Purpur-AFK-system.patch index 36f3344a..97e843f6 100644 --- a/patches/server/0066-Improve-Purpur-AFK-system.patch +++ b/patches/server/0067-Improve-Purpur-AFK-system.patch @@ -9,7 +9,7 @@ AFK command & command cooldown AFK title message diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 00a9348b4585d9e69364e0d1ac88a59f43ebeee6..21f1b80794e2234c7fe34959ca03190617659bb0 100644 +index 95c84e0f83d056ee080061cd495c95a328814469..a5784aa215522c4666139a8eac3711c966cc7cb1 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -250,6 +250,7 @@ public class Commands { @@ -44,7 +44,7 @@ index 9b97d5ca67c0e53f318a54465708e21ae906e994..0e772d847d9a9b4000bcb9547f07663f } else { getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 01fe83e04ed8ce8d91dad5feffe164346fa8a13e..4300ba74bd91e3229c8721d4b2c160eaa9f8a456 100644 +index a0abb0182aa6166b1d2702aa9964132889dc9d86..2c630eddcacee445f44beeefa842e90aff75c0a6 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2254,8 +2254,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl diff --git a/patches/server/0067-Virtual-Thread-for-async-scheduler.patch b/patches/server/0068-Virtual-Thread-for-async-scheduler.patch similarity index 100% rename from patches/server/0067-Virtual-Thread-for-async-scheduler.patch rename to patches/server/0068-Virtual-Thread-for-async-scheduler.patch diff --git a/patches/server/0068-Mirai-Configurable-chat-message-signatures.patch b/patches/server/0069-Mirai-Configurable-chat-message-signatures.patch similarity index 100% rename from patches/server/0068-Mirai-Configurable-chat-message-signatures.patch rename to patches/server/0069-Mirai-Configurable-chat-message-signatures.patch diff --git a/patches/server/0069-Block-log4j-rce-exploit-in-chat.patch b/patches/server/0070-Block-log4j-rce-exploit-in-chat.patch similarity index 96% rename from patches/server/0069-Block-log4j-rce-exploit-in-chat.patch rename to patches/server/0070-Block-log4j-rce-exploit-in-chat.patch index a9ccc4e5..6a14dba4 100644 --- a/patches/server/0069-Block-log4j-rce-exploit-in-chat.patch +++ b/patches/server/0070-Block-log4j-rce-exploit-in-chat.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Block log4j rce exploit in chat diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7830e21dce33ad389441227d728750606dcd3c56..e49bd9ab6c77ecb9da60d973e97448a2f4c78492 100644 +index 2c630eddcacee445f44beeefa842e90aff75c0a6..ea1383691d57bfe9da958b6071bdb936fed322d6 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2444,6 +2444,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl diff --git a/patches/server/0070-Cache-player-profileResult.patch b/patches/server/0071-Cache-player-profileResult.patch similarity index 100% rename from patches/server/0070-Cache-player-profileResult.patch rename to patches/server/0071-Cache-player-profileResult.patch diff --git a/patches/server/0071-Prevent-change-non-editable-sign-warning-spam-in-con.patch b/patches/server/0072-Prevent-change-non-editable-sign-warning-spam-in-con.patch similarity index 100% rename from patches/server/0071-Prevent-change-non-editable-sign-warning-spam-in-con.patch rename to patches/server/0072-Prevent-change-non-editable-sign-warning-spam-in-con.patch diff --git a/patches/server/0072-Matter-Secure-Seed.patch b/patches/server/0073-Matter-Secure-Seed.patch similarity index 100% rename from patches/server/0072-Matter-Secure-Seed.patch rename to patches/server/0073-Matter-Secure-Seed.patch diff --git a/patches/server/0073-Matter-Seed-Command.patch b/patches/server/0074-Matter-Seed-Command.patch similarity index 100% rename from patches/server/0073-Matter-Seed-Command.patch rename to patches/server/0074-Matter-Seed-Command.patch diff --git a/patches/server/0074-Ignore-terminal-provider-warning.patch b/patches/server/0075-Ignore-terminal-provider-warning.patch similarity index 100% rename from patches/server/0074-Ignore-terminal-provider-warning.patch rename to patches/server/0075-Ignore-terminal-provider-warning.patch diff --git a/patches/server/0075-Fix-console-freeze-above-JAVA-22.patch b/patches/server/0076-Fix-console-freeze-above-JAVA-22.patch similarity index 100% rename from patches/server/0075-Fix-console-freeze-above-JAVA-22.patch rename to patches/server/0076-Fix-console-freeze-above-JAVA-22.patch diff --git a/patches/server/0076-Faster-Random-Generator.patch b/patches/server/0077-Faster-Random-Generator.patch similarity index 100% rename from patches/server/0076-Faster-Random-Generator.patch rename to patches/server/0077-Faster-Random-Generator.patch diff --git a/patches/server/0077-Don-t-save-primed-tnt-entity.patch b/patches/server/0078-Don-t-save-primed-tnt-entity.patch similarity index 100% rename from patches/server/0077-Don-t-save-primed-tnt-entity.patch rename to patches/server/0078-Don-t-save-primed-tnt-entity.patch diff --git a/patches/server/0078-Don-t-save-falling-block-entity.patch b/patches/server/0079-Don-t-save-falling-block-entity.patch similarity index 100% rename from patches/server/0078-Don-t-save-falling-block-entity.patch rename to patches/server/0079-Don-t-save-falling-block-entity.patch diff --git a/patches/server/0079-Configurable-connection-message.patch b/patches/server/0080-Configurable-connection-message.patch similarity index 100% rename from patches/server/0079-Configurable-connection-message.patch rename to patches/server/0080-Configurable-connection-message.patch diff --git a/patches/unapplied/server/0091-Leaves-Jade-Protocol.patch b/patches/unapplied/server/0091-Leaves-Jade-Protocol.patch deleted file mode 100644 index 1795f792..00000000 --- a/patches/unapplied/server/0091-Leaves-Jade-Protocol.patch +++ /dev/null @@ -1,978 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Sat, 3 Dec 2022 08:57:15 +0800 -Subject: [PATCH] Leaves: Jade Protocol - -Original license: GPLv3 -Original project: https://github.com/LeavesMC/Leaves - -This patch is Powered by Jade (https://github.com/Snownee/Jade) - -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index e6861fd5f9817ec54294976f0e93952baa387773..45f92d8fd06027f1487e24987c3fb9a389f619a9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -290,7 +290,7 @@ public class Tadpole extends AbstractFish { - - } - -- private int getTicksLeftUntilAdult() { -+ public int getTicksLeftUntilAdult() { // Leaves - private -> public - return Math.max(0, Tadpole.ticksToBeFrog - this.age); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -index fca0131b9a90ac026a24cf579b17928c19173f3f..f8d0a8ea39cd90a9b45ff97e32e0e7224ddd8808 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java -@@ -68,7 +68,7 @@ public class TrialSpawnerData { - }); - public final Set detectedPlayers; - public final Set currentMobs; -- protected long cooldownEndsAt; -+ public long cooldownEndsAt; // Leaves - protected -> public - protected long nextMobSpawnsAt; - protected int totalMobsSpawned; - public Optional nextSpawnData; -diff --git a/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b63d87d0593d116b0c5ee835dc4372f1b5542453 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java -@@ -0,0 +1,21 @@ -+package org.dreeam.leaf.config.modules.network; -+ -+import org.dreeam.leaf.config.ConfigInfo; -+import org.dreeam.leaf.config.EnumConfigCategory; -+import org.dreeam.leaf.config.IConfigModule; -+ -+public class ProtocolSupport implements IConfigModule { -+ -+ @Override -+ public EnumConfigCategory getCategory() { -+ return EnumConfigCategory.NETWORK; -+ } -+ -+ @Override -+ public String getBaseName() { -+ return "protocol_support"; -+ } -+ -+ @ConfigInfo(baseName = "jade-protocol") -+ public static boolean jadeProtocol = false; -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..561a8c33eaf1d961b4d8442bbab8c1ed9eef619c ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java -@@ -0,0 +1,909 @@ -+package top.leavesmc.leaves.protocol; -+ -+import com.google.common.cache.Cache; -+import com.google.common.cache.CacheBuilder; -+import com.google.common.collect.ArrayListMultimap; -+import com.google.common.collect.ImmutableList; -+import com.google.common.collect.ListMultimap; -+import com.google.common.collect.Lists; -+import com.google.common.collect.Multimap; -+import com.mojang.authlib.GameProfile; -+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.chat.CommonComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.network.chat.MutableComponent; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.players.GameProfileCache; -+import net.minecraft.world.Container; -+import net.minecraft.world.LockCode; -+import net.minecraft.world.MenuProvider; -+import net.minecraft.world.Nameable; -+import net.minecraft.world.RandomizableContainer; -+import net.minecraft.world.effect.MobEffectCategory; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.entity.AgeableMob; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.OwnableEntity; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Chicken; -+import net.minecraft.world.entity.animal.allay.Allay; -+import net.minecraft.world.entity.animal.frog.Tadpole; -+import net.minecraft.world.entity.animal.horse.AbstractHorse; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.entity.monster.ZombieVillager; -+import net.minecraft.world.entity.vehicle.ContainerEntity; -+import net.minecraft.world.inventory.PlayerEnderChestContainer; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.BaseCommandBlock; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.CalibratedSculkSensorBlock; -+import net.minecraft.world.level.block.ChestBlock; -+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; -+import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; -+import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; -+import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity; -+import net.minecraft.world.level.block.entity.CampfireBlockEntity; -+import net.minecraft.world.level.block.entity.ChestBlockEntity; -+import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity; -+import net.minecraft.world.level.block.entity.CommandBlockEntity; -+import net.minecraft.world.level.block.entity.ComparatorBlockEntity; -+import net.minecraft.world.level.block.entity.EnderChestBlockEntity; -+import net.minecraft.world.level.block.entity.HopperBlockEntity; -+import net.minecraft.world.level.block.entity.JukeboxBlockEntity; -+import net.minecraft.world.level.block.entity.LecternBlockEntity; -+import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; -+import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.block.state.properties.BlockStateProperties; -+import net.minecraft.world.phys.BlockHitResult; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.ProtocolHandler; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.Collection; -+import java.util.List; -+import java.util.Locale; -+import java.util.Objects; -+import java.util.UUID; -+import java.util.concurrent.ExecutionException; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Function; -+import java.util.function.Predicate; -+import java.util.stream.IntStream; -+import java.util.stream.Stream; -+ -+@LeavesProtocol(namespace = "jade") -+public class JadeProtocol { -+ -+ 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 HierarchyLookup> tileDataProviders = new HierarchyLookup<>(BlockEntity.class); -+ -+ private static final HierarchyLookup> entityItemProviders = new HierarchyLookup<>(Entity.class); -+ private static final HierarchyLookup> tileItemProviders = new HierarchyLookup<>(BlockEntity.class); -+ -+ public static final Cache> targetCache = CacheBuilder.newBuilder().weakKeys().expireAfterAccess(60, TimeUnit.SECONDS).build(); -+ -+ public static final int MAX_DISTANCE_SQR = 900; -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.Init -+ public static void init() { -+ entityItemProviders.register(Entity.class, (player, world, target) -> { -+ if (target instanceof ContainerEntity containerEntity && containerEntity.getLootTable() != null) { -+ return List.of(); -+ } -+ -+ ItemCollector itemCollector; -+ try { -+ itemCollector = targetCache.get(target, () -> { -+ if (target instanceof AbstractHorse) { -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { -+ if (o instanceof AbstractHorse horse) { -+ return horse.inventory; -+ } -+ return null; -+ }, 2)); -+ } -+ return ItemCollector.EMPTY; -+ }); -+ } catch (ExecutionException e) { -+ return null; -+ } -+ -+ if (itemCollector == ItemCollector.EMPTY) { -+ return null; -+ } -+ return itemCollector.update(target, world.getGameTime()); -+ }); -+ -+ tileItemProviders.register(CampfireBlockEntity.class, (player, world, target) -> { -+ CampfireBlockEntity campfire = (CampfireBlockEntity) target; -+ List list = Lists.newArrayList(); -+ for (int i = 0; i < campfire.cookingTime.length; i++) { -+ ItemStack stack = campfire.getItems().get(i); -+ if (stack.isEmpty()) { -+ continue; -+ } -+ stack = stack.copy(); -+ stack.getOrCreateTag().putInt("jade:cooking", campfire.cookingTime[i] - campfire.cookingProgress[i]); -+ list.add(stack); -+ } -+ return List.of(new ViewGroup<>(list)); -+ }); -+ tileItemProviders.register(BlockEntity.class, (player, world, target) -> { -+ if (target instanceof RandomizableContainer te && te.getLootTable() != null) { -+ return List.of(); -+ } -+ -+ if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { -+ if (te.lockKey != LockCode.NO_LOCK) { -+ return List.of(); -+ } -+ } -+ if (target instanceof EnderChestBlockEntity) { -+ PlayerEnderChestContainer inventory = player.getEnderChestInventory(); -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)).update(inventory, world.getGameTime()); -+ } -+ ItemCollector itemCollector; -+ try { -+ itemCollector = targetCache.get(target, () -> { -+ if (target instanceof Container) { -+ if (target instanceof ChestBlockEntity) { -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(o -> { -+ if (o instanceof ChestBlockEntity be) { -+ if (be.getBlockState().getBlock() instanceof ChestBlock chestBlock) { -+ Container compound = ChestBlock.getContainer(chestBlock, be.getBlockState(), world, be.getBlockPos(), false); -+ if (compound != null) { -+ return compound; -+ } -+ } -+ return be; -+ } -+ return null; -+ }, 0)); -+ } -+ return new ItemCollector<>(new ItemIterator.ContainerItemIterator(0)); -+ } -+ return ItemCollector.EMPTY; -+ }); -+ } catch (ExecutionException e) { -+ return null; -+ } -+ -+ if (itemCollector == ItemCollector.EMPTY) { -+ return null; -+ } -+ return itemCollector.update(target, world.getGameTime()); -+ }); -+ -+ entityDataProviders.register(OwnableEntity.class, ((data, player, world, entity, showDetails) -> { -+ UUID ownerUUID = ((OwnableEntity) entity).getOwnerUUID(); -+ if (ownerUUID != null) { -+ GameProfileCache cache = MinecraftServer.getServer().getProfileCache(); -+ if (cache != null) { -+ cache.get(ownerUUID).map(GameProfile::getName).ifPresent(name -> data.putString("OwnerName", name)); -+ } -+ } -+ })); -+ entityDataProviders.register(LivingEntity.class, ((data, player, world, entity, showDetails) -> { -+ LivingEntity living = (LivingEntity) entity; -+ Collection effects = living.getActiveEffects(); -+ if (effects.isEmpty()) { -+ return; -+ } -+ ListTag list = new ListTag(); -+ for (MobEffectInstance effect : effects) { -+ CompoundTag compound = new CompoundTag(); -+ compound.putString("Name", Component.Serializer.toJson(getEffectName(effect))); -+ if (effect.isInfiniteDuration()) { -+ compound.putBoolean("Infinite", true); -+ } else { -+ compound.putInt("Duration", effect.getDuration()); -+ } -+ compound.putBoolean("Bad", effect.getEffect().getCategory() == MobEffectCategory.HARMFUL); -+ list.add(compound); -+ } -+ data.put("StatusEffects", list); -+ })); -+ entityDataProviders.register(AgeableMob.class, ((data, player, world, entity, showDetails) -> { -+ int time = -((AgeableMob) entity).getAge(); -+ if (time > 0) { -+ data.putInt("GrowingTime", time); -+ } -+ })); -+ entityDataProviders.register(Tadpole.class, ((data, player, world, entity, showDetails) -> { -+ int time = ((Tadpole) entity).getTicksLeftUntilAdult(); -+ if (time > 0) { -+ data.putInt("GrowingTime", time); -+ } -+ })); -+ entityDataProviders.register(Animal.class, ((data, player, world, entity, showDetails) -> { -+ int time = ((Animal) entity).getAge(); -+ if (time > 0) { -+ data.putInt("BreedingCD", time); -+ } -+ })); -+ entityDataProviders.register(Allay.class, ((data, player, world, entity, showDetails) -> { -+ int time = 0; -+ Allay allay = (Allay) entity; -+ if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) { -+ time = (int) allay.duplicationCooldown; -+ } -+ if (time > 0) { -+ data.putInt("BreedingCD", time); -+ } -+ })); -+ entityDataProviders.register(Chicken.class, ((data, player, world, entity, showDetails) -> { -+ data.putInt("NextEggIn", ((Chicken) entity).eggTime); -+ })); -+ entityDataProviders.register(Entity.class, ((tag, player, world, object, showDetails) -> { -+ for (var provider : entityItemProviders.get(object)) { -+ var groups = provider.getGroups(player, world, object); -+ if (groups == null) { -+ continue; -+ } -+ if (ViewGroup.saveList(tag, "JadeItemStorage", groups, item -> { -+ CompoundTag itemTag = new CompoundTag(); -+ int count = item.getCount(); -+ if (count > 64) { -+ item.setCount(1); -+ } -+ item.save(itemTag); -+ if (count > 64) { -+ itemTag.putInt("NewCount", count); -+ item.setCount(count); -+ } -+ return itemTag; -+ })) { -+ tag.putString("JadeItemStorageUid", "minecraft:item_storage"); -+ } -+ break; -+ } -+ })); -+ entityDataProviders.register(ZombieVillager.class, (data, player, world, object, showDetails) -> { -+ ZombieVillager entity = (ZombieVillager) object; -+ if (entity.villagerConversionTime > 0) { -+ data.putInt("ConversionTime", entity.villagerConversionTime); -+ } -+ }); -+ -+ tileDataProviders.register(BrewingStandBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ if (object instanceof BrewingStandBlockEntity brewingStand) { -+ CompoundTag compound = new CompoundTag(); -+ compound.putInt("Time", brewingStand.brewTime); -+ compound.putInt("Fuel", brewingStand.fuel); -+ data.put("BrewingStand", compound); -+ } -+ })); -+ tileDataProviders.register(BeehiveBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ data.getAllKeys().clear(); -+ BeehiveBlockEntity beehive = (BeehiveBlockEntity) object; -+ data.putByte("Bees", (byte) beehive.getOccupantCount()); -+ data.putBoolean("Full", beehive.isFull()); -+ })); -+ tileDataProviders.register(CommandBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ if (!player.canUseGameMasterBlocks()) { -+ return; -+ } -+ BaseCommandBlock logic = ((CommandBlockEntity) object).getCommandBlock(); -+ String command = logic.getCommand(); -+ if (command.isEmpty()) { -+ return; -+ } -+ if (command.length() > 40) { -+ command = command.substring(0, 37) + "..."; -+ } -+ data.putString("Command", command); -+ })); -+ tileDataProviders.register(JukeboxBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ if (object instanceof JukeboxBlockEntity jukebox) { -+ ItemStack stack = jukebox.getTheItem(); -+ if (!stack.isEmpty()) { -+ data.put("Record", stack.save(new CompoundTag())); -+ } -+ } -+ })); -+ tileDataProviders.register(LecternBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ ItemStack stack = ((LecternBlockEntity) object).getBook(); -+ if (!stack.isEmpty()) { -+ if (stack.hasCustomHoverName() || stack.getItem() != Items.WRITABLE_BOOK) { -+ data.put("Book", stack.save(new CompoundTag())); -+ } -+ } -+ })); -+ tileDataProviders.register(ComparatorBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ data.putInt("Signal", ((ComparatorBlockEntity) object).getOutputSignal()); -+ })); -+ tileDataProviders.register(HopperBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ BlockState state = object.getBlockState(); -+ if (state.hasProperty(BlockStateProperties.ENABLED) && !state.getValue(BlockStateProperties.ENABLED)) { -+ data.putBoolean("HopperLocked", true); -+ } -+ })); -+ tileDataProviders.register(AbstractFurnaceBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ AbstractFurnaceBlockEntity furnace = (AbstractFurnaceBlockEntity) object; -+ ListTag items = new ListTag(); -+ for (int i = 0; i < 3; i++) { -+ items.add(furnace.getItem(i).save(new CompoundTag())); -+ } -+ data.put("furnace", items); -+ CompoundTag furnaceTag = furnace.saveWithoutMetadata(); -+ data.putInt("progress", furnaceTag.getInt("CookTime")); -+ data.putInt("total", furnaceTag.getInt("CookTimeTotal")); -+ })); -+ tileDataProviders.register(BlockEntity.class, ((data, player, world, object, showDetails) -> { -+ if (object instanceof Nameable nameable) { -+ Component name = null; -+ if (nameable instanceof ChestBlockEntity chestBlock) { -+ MenuProvider menuProvider = chestBlock.getBlockState().getMenuProvider(world, chestBlock.getBlockPos()); -+ if (menuProvider != null) { -+ name = menuProvider.getDisplayName(); -+ } -+ } else if (nameable.hasCustomName()) { -+ name = nameable.getDisplayName(); -+ } -+ -+ if (name != null) { -+ data.putString("givenName", Component.Serializer.toJson(name)); -+ } -+ } -+ })); -+ tileDataProviders.register(ChiseledBookShelfBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ ChiseledBookShelfBlockEntity bookShelf = (ChiseledBookShelfBlockEntity) object; -+ if (!bookShelf.isEmpty()) { -+ data.put("Bookshelf", bookShelf.saveWithoutMetadata()); -+ } -+ })); -+ tileDataProviders.register(BlockEntity.class, ((tag, player, world, object, showDetails) -> { -+ if (object instanceof AbstractFurnaceBlockEntity) { -+ return; -+ } -+ -+ for (var provider : tileItemProviders.get(object)) { -+ var groups = provider.getGroups(player, world, object); -+ if (groups == null) { -+ continue; -+ } -+ -+ if (ViewGroup.saveList(tag, "JadeItemStorage", groups, item -> { -+ CompoundTag itemTag = new CompoundTag(); -+ int count = item.getCount(); -+ if (count > 64) { -+ item.setCount(1); -+ } -+ item.save(itemTag); -+ if (count > 64) { -+ itemTag.putInt("NewCount", count); -+ item.setCount(count); -+ } -+ return itemTag; -+ })) { -+ tag.putString("JadeItemStorageUid", "minecraft:item_storage"); -+ } else if (object instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) { -+ tag.putBoolean("Loot", true); -+ } else if (!player.isCreative() && !player.isSpectator() && object instanceof BaseContainerBlockEntity te) { -+ if (te.lockKey != LockCode.NO_LOCK) { -+ tag.putBoolean("Locked", true); -+ } -+ } -+ break; -+ } -+ })); -+ tileDataProviders.register(TrialSpawnerBlockEntity.class, (data, player, world, object, showDetails) -> { -+ TrialSpawnerBlockEntity spawner = (TrialSpawnerBlockEntity) object; -+ TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); -+ if (spawner.getTrialSpawner().canSpawnInLevel(world) && world.getGameTime() < spawnerData.cooldownEndsAt) { -+ data.putInt("Cooldown", (int) (spawnerData.cooldownEndsAt - world.getGameTime())); -+ } -+ }); -+ tileDataProviders.register(CalibratedSculkSensorBlockEntity.class, ((data, player, world, object, showDetails) -> { -+ Direction direction = object.getBlockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite(); -+ int signal = world.getSignal(object.getBlockPos().relative(direction), direction); -+ data.putInt("Signal", signal); -+ })); -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerJoin(ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ ProtocolUtils.sendPayloadPacket(player, PACKET_SERVER_PING, buf -> buf.writeUtf("{}")); -+ } -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class, payloadId = "request_entity") -+ public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ server.execute(() -> { -+ Level world = player.level(); -+ boolean showDetails = payload.showDetails; -+ Entity entity = world.getEntity(payload.entityId); -+ if (entity == null || player.distanceToSqr(entity) > MAX_DISTANCE_SQR) { -+ return; -+ } -+ if (payload.partIndex >= 0 && entity instanceof EnderDragon dragon) { -+ EnderDragonPart[] parts = dragon.getSubEntities(); -+ if (payload.partIndex < parts.length) { -+ entity = parts[payload.partIndex]; -+ } -+ } -+ -+ var providers = entityDataProviders.get(entity); -+ if (providers.isEmpty()) { -+ return; -+ } -+ -+ CompoundTag tag = new CompoundTag(); -+ for (IJadeDataProvider provider : providers) { -+ try { -+ provider.saveData(tag, player, world, entity, showDetails); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } -+ tag.putInt("WailaEntityID", entity.getId()); -+ -+ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); -+ }); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = RequestTilePayload.class, payloadId = "request_tile") -+ public static void requestTileData(ServerPlayer player, RequestTilePayload payload) { -+ if (!org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ boolean showDetails = payload.showDetails; -+ BlockHitResult result = payload.hitResult; -+ BlockPos pos = result.getBlockPos(); -+ Level world = player.level(); -+ if (pos.distSqr(player.blockPosition()) > MAX_DISTANCE_SQR || !world.isLoaded(pos)) { -+ return; -+ } -+ -+ server.execute(() -> { -+ BlockEntity tile = world.getBlockEntity(pos); -+ if (tile == null) return; -+ -+ List> providers = tileDataProviders.get(tile); -+ if (providers.isEmpty()) { -+ return; -+ } -+ -+ CompoundTag tag = new CompoundTag(); -+ for (IJadeDataProvider provider : providers) { -+ try { -+ provider.saveData(tag, player, world, tile, showDetails); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ tag.putInt("x", pos.getX()); -+ tag.putInt("y", pos.getY()); -+ tag.putInt("z", pos.getZ()); -+ tag.putString("id", BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(tile.getType()).toString()); -+ -+ ProtocolUtils.sendPayloadPacket(player, PACKET_RECEIVE_DATA, buf -> buf.writeNbt(tag)); -+ }); -+ } -+ -+ @ProtocolHandler.ReloadServer -+ public static void onServerReload() { -+ if (org.dreeam.leaf.config.modules.network.ProtocolSupport.jadeProtocol) { -+ enableAllPlayer(); -+ } -+ } -+ -+ public static void enableAllPlayer() { -+ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) { -+ onPlayerJoin(player); -+ } -+ } -+ -+ public record RequestEntityPayload(boolean showDetails, int entityId, int partIndex, float hitX, float hitY, float hitZ) implements CustomPacketPayload { -+ -+ private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); -+ -+ public RequestEntityPayload(ResourceLocation id, FriendlyByteBuf buf) { -+ this(buf.readBoolean(), buf.readVarInt(), buf.readVarInt(), buf.readFloat(), buf.readFloat(), buf.readFloat()); -+ } -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ buf.writeBoolean(showDetails); -+ buf.writeVarInt(entityId); -+ buf.writeVarInt(partIndex); -+ buf.writeFloat(hitX); -+ buf.writeFloat(hitY); -+ buf.writeFloat(hitZ); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return PACKET_REQUEST_ENTITY; -+ } -+ } -+ -+ public record RequestTilePayload(boolean showDetails, BlockHitResult hitResult, int blockState, ItemStack fakeBlock) implements CustomPacketPayload { -+ -+ private static final ResourceLocation PACKET_REQUEST_TILE = JadeProtocol.id("request_tile"); -+ -+ public RequestTilePayload(ResourceLocation id, FriendlyByteBuf buf) { -+ this(buf.readBoolean(), buf.readBlockHitResult(), buf.readVarInt(), buf.readItem()); -+ } -+ -+ @Override -+ public void write(FriendlyByteBuf buf) { -+ buf.writeBoolean(showDetails); -+ buf.writeBlockHitResult(hitResult); -+ buf.writeVarInt(blockState); -+ buf.writeItem(fakeBlock); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return PACKET_REQUEST_TILE; -+ } -+ } -+ -+ // Power by Jade -+ -+ public static Component getEffectName(MobEffectInstance mobEffectInstance) { -+ MutableComponent mutableComponent = mobEffectInstance.getEffect().getDisplayName().copy(); -+ if (mobEffectInstance.getAmplifier() >= 1 && mobEffectInstance.getAmplifier() <= 9) { -+ mutableComponent.append(CommonComponents.SPACE).append(Component.translatable("enchantment.level." + (mobEffectInstance.getAmplifier() + 1))); -+ } -+ return mutableComponent; -+ } -+ -+ public interface IJadeProvider { -+ } -+ -+ public interface IJadeDataProvider { -+ void saveData(CompoundTag data, ServerPlayer player, Level world, T object, boolean showDetails); -+ } -+ -+ public interface IServerExtensionProvider { -+ List> getGroups(ServerPlayer player, Level world, IN target); -+ } -+ -+ public static class ItemCollector { -+ public static final int MAX_SIZE = 54; -+ public static final ItemCollector EMPTY = new ItemCollector<>(null); -+ private static final Predicate NON_EMPTY = stack -> { -+ if (stack.isEmpty()) { -+ return false; -+ } -+ CompoundTag tag = stack.getTag(); -+ if (tag != null && tag.contains("CustomModelData")) { -+ for (String key : stack.getTag().getAllKeys()) { -+ if (key.toLowerCase(Locale.ENGLISH).endsWith("clear") && stack.getTag().getBoolean(key)) { -+ return false; -+ } -+ } -+ } -+ return true; -+ }; -+ private final Object2IntLinkedOpenHashMap items = new Object2IntLinkedOpenHashMap<>(); -+ private final ItemIterator iterator; -+ public long version; -+ public long lastTimeFinished; -+ public List> mergedResult; -+ -+ public ItemCollector(ItemIterator iterator) { -+ this.iterator = iterator; -+ } -+ -+ public List> update(Object target, long gameTime) { -+ if (iterator == null) { -+ return null; -+ } -+ T container = iterator.find(target); -+ if (container == null) { -+ return null; -+ } -+ long currentVersion = iterator.getVersion(container); -+ if (mergedResult != null && iterator.isFinished()) { -+ if (version == currentVersion) { -+ return mergedResult; // content not changed -+ } -+ if (lastTimeFinished + 5 > gameTime) { -+ return mergedResult; // avoid update too frequently -+ } -+ iterator.reset(); -+ } -+ AtomicInteger count = new AtomicInteger(); -+ iterator.populate(container).forEach(stack -> { -+ count.incrementAndGet(); -+ if (NON_EMPTY.test(stack)) { -+ ItemDefinition def = new ItemDefinition(stack); -+ items.addTo(def, stack.getCount()); -+ } -+ }); -+ iterator.afterPopulate(count.get()); -+ if (mergedResult != null && !iterator.isFinished()) { -+ updateCollectingProgress(mergedResult.get(0)); -+ return mergedResult; -+ } -+ List partialResult = items.object2IntEntrySet().stream().limit(54).map(entry -> { -+ ItemDefinition def = entry.getKey(); -+ return def.toStack(entry.getIntValue()); -+ }).toList(); -+ List> groups = List.of(updateCollectingProgress(new ViewGroup<>(partialResult))); -+ if (iterator.isFinished()) { -+ mergedResult = groups; -+ version = currentVersion; -+ lastTimeFinished = gameTime; -+ items.clear(); -+ } -+ return groups; -+ } -+ -+ protected ViewGroup updateCollectingProgress(ViewGroup group) { -+ float progress = iterator.getCollectingProgress(); -+ CompoundTag data = group.getExtraData(); -+ if (Float.isNaN(progress)) { -+ progress = 0; -+ } -+ if (progress >= 1) { -+ data.remove("Collecting"); -+ } else { -+ data.putFloat("Collecting", progress); -+ } -+ return group; -+ } -+ -+ public record ItemDefinition(Item item, @Nullable CompoundTag tag) { -+ ItemDefinition(ItemStack stack) { -+ this(stack.getItem(), stack.getTag()); -+ } -+ -+ public ItemStack toStack(int count) { -+ ItemStack stack = new ItemStack(item); -+ stack.setCount(count); -+ stack.setTag(tag); -+ return stack; -+ } -+ } -+ } -+ -+ public static abstract class ItemIterator { -+ public static final AtomicLong version = new AtomicLong(); -+ protected final Function containerFinder; -+ protected final int fromIndex; -+ protected boolean finished; -+ protected int currentIndex; -+ -+ protected ItemIterator(Function containerFinder, int fromIndex) { -+ this.containerFinder = containerFinder; -+ this.currentIndex = this.fromIndex = fromIndex; -+ } -+ -+ public @Nullable T find(Object target) { -+ return containerFinder.apply(target); -+ } -+ -+ public final boolean isFinished() { -+ return finished; -+ } -+ -+ public long getVersion(T container) { -+ return version.getAndIncrement(); -+ } -+ -+ public abstract Stream populate(T container); -+ -+ public void reset() { -+ currentIndex = fromIndex; -+ finished = false; -+ } -+ -+ public void afterPopulate(int count) { -+ currentIndex += count; -+ if (count == 0 || currentIndex >= 10000) { -+ finished = true; -+ } -+ } -+ -+ public float getCollectingProgress() { -+ return Float.NaN; -+ } -+ -+ public static abstract class SlottedItemIterator extends ItemIterator { -+ protected float progress; -+ -+ public SlottedItemIterator(Function containerFinder, int fromIndex) { -+ super(containerFinder, fromIndex); -+ } -+ -+ protected abstract int getSlotCount(T container); -+ -+ protected abstract ItemStack getItemInSlot(T container, int slot); -+ -+ @Override -+ public Stream populate(T container) { -+ int slotCount = getSlotCount(container); -+ int toIndex = currentIndex + ItemCollector.MAX_SIZE * 2; -+ if (toIndex >= slotCount) { -+ toIndex = slotCount; -+ finished = true; -+ } -+ progress = (float) (currentIndex - fromIndex) / (slotCount - fromIndex); -+ return IntStream.range(currentIndex, toIndex).mapToObj(slot -> getItemInSlot(container, slot)); -+ } -+ -+ @Override -+ public float getCollectingProgress() { -+ return progress; -+ } -+ } -+ -+ public static class ContainerItemIterator extends SlottedItemIterator { -+ public ContainerItemIterator(int fromIndex) { -+ this(Container.class::cast, fromIndex); -+ } -+ -+ public ContainerItemIterator(Function containerFinder, int fromIndex) { -+ super(containerFinder, fromIndex); -+ } -+ -+ @Override -+ protected int getSlotCount(Container container) { -+ return container.getContainerSize(); -+ } -+ -+ @Override -+ protected ItemStack getItemInSlot(Container container, int slot) { -+ return container.getItem(slot); -+ } -+ } -+ } -+ -+ public static class ViewGroup { -+ -+ public final List views; -+ @Nullable -+ public String id; -+ @Nullable -+ protected CompoundTag extraData; -+ -+ public ViewGroup(List views) { -+ this.views = views; -+ } -+ -+ public void save(CompoundTag tag, Function writer) { -+ ListTag list = new ListTag(); -+ for (var view : views) { -+ list.add(writer.apply(view)); -+ } -+ tag.put("Views", list); -+ if (id != null) { -+ tag.putString("Id", id); -+ } -+ if (extraData != null) { -+ tag.put("Data", extraData); -+ } -+ } -+ -+ public static boolean saveList(CompoundTag tag, String key, List> groups, Function writer) { -+ if (groups == null || groups.isEmpty()) { -+ return false; -+ } -+ -+ ListTag groupList = new ListTag(); -+ for (ViewGroup group : groups) { -+ if (group.views.isEmpty()) { -+ continue; -+ } -+ CompoundTag groupTag = new CompoundTag(); -+ group.save(groupTag, writer); -+ groupList.add(groupTag); -+ } -+ if (!groupList.isEmpty()) { -+ tag.put(key, groupList); -+ return true; -+ } -+ return false; -+ } -+ -+ public CompoundTag getExtraData() { -+ if (extraData == null) { -+ extraData = new CompoundTag(); -+ } -+ return extraData; -+ } -+ } -+ -+ public static class HierarchyLookup { -+ -+ private final Class baseClass; -+ private final Cache, List> resultCache = CacheBuilder.newBuilder().build(); -+ private final boolean singleton; -+ private ListMultimap, T> objects = ArrayListMultimap.create(); -+ -+ public HierarchyLookup(Class baseClass) { -+ this(baseClass, false); -+ } -+ -+ public HierarchyLookup(Class baseClass, boolean singleton) { -+ this.baseClass = baseClass; -+ this.singleton = singleton; -+ } -+ -+ public void register(Class clazz, T provider) { -+ Objects.requireNonNull(clazz); -+ objects.put(clazz, provider); -+ } -+ -+ public List get(Object obj) { -+ if (obj == null) { -+ return List.of(); -+ } -+ return get(obj.getClass()); -+ } -+ -+ public List get(Class clazz) { -+ try { -+ return resultCache.get(clazz, () -> { -+ List list = Lists.newArrayList(); -+ getInternal(clazz, list); -+ if (singleton && !list.isEmpty()) { -+ return ImmutableList.of(list.get(0)); -+ } -+ return list; -+ }); -+ } catch (ExecutionException e) { -+ e.printStackTrace(); -+ } -+ return List.of(); -+ } -+ -+ private void getInternal(Class clazz, List list) { -+ if (clazz != baseClass && clazz != Object.class) { -+ getInternal(clazz.getSuperclass(), list); -+ } -+ list.addAll(objects.get(clazz)); -+ } -+ -+ public Multimap, T> getObjects() { -+ return objects; -+ } -+ } -+}