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] Jade Protocol 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 b38281f963377cc82b360e8457da7cad033b8c36..59f54bf1e86860a9fa35c444edc64ba141f4defb 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 290d41136f5ec7671bc4990dfe50da0a770c124d..600e03bf2bfbde245d53afa9ed312ebcd7ca8f03 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 @@ -255,7 +255,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/leavesmc/leaves/protocol/jade/JadeProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..cd5dfd55b6554e47a336c7aa4cb24db3cb4919dd --- /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.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.core.LeavesProtocol; +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 (LeavesConfig.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 (!LeavesConfig.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) { + LeavesLogger.LOGGER.warning("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 (!LeavesConfig.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) { + LeavesLogger.LOGGER.warning("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 (LeavesConfig.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; + } +} +