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 729fd2d52dd48e25ee7a077a3ffafc80ecef7c9f..28d6b1d49045c125214c40895efd484e4ae20c2b 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 @@ -63,7 +63,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 43046f4a0cff620834ac4647efdcde227185b2ff..a08cd692e332a6caed33cd3db2373e847621ad6a 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 @@ -256,7 +256,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 6684ded7135f943f8cea954b417f596369215357..0621c6c026678cb4ac3626342d73290c0f2803d9 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 @@ -72,7 +72,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/net/minecraft/world/level/storage/loot/LootPool.java b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java index 38078c44b35e917d1d243a5f8599aa858d8611de..13dbadfb50278b79b33d9dce10413c93c9e4ff31 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/LootPool.java @@ -36,7 +36,7 @@ public class LootPool { ) .apply(instance, LootPool::new) ); - private final List entries; + public final List entries; // Leaves - private -> public private final List conditions; private final Predicate compositeCondition; private final List functions; diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java index edaf7f1692ae059581f3abc24bb228874e6d114b..f09cfc472d4dbdc8cb0b6a45ef240b25a865ffba 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java @@ -58,7 +58,7 @@ public class LootTable { public static final Codec> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, LootTable.DIRECT_CODEC); private final LootContextParamSet paramSet; private final Optional randomSequence; - private final List pools; + public final List pools; // Leaves - private -> public private final List functions; private final BiFunction compositeFunction; public CraftLootTable craftLootTable; // CraftBukkit diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java index 128792f76f02d74c1ccf84beb8e7973453424639..775fbddf3e3b133e33f54aaa8e8a07d131095e34 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/CompositeEntryBase.java @@ -9,7 +9,7 @@ import net.minecraft.world.level.storage.loot.ValidationContext; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; public abstract class CompositeEntryBase extends LootPoolEntryContainer { - protected final List children; + public final List children; // Leaves - private -> public private final ComposableEntryContainer composedChildren; protected CompositeEntryBase(List terms, List conditions) { diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java index 1d2f2bb352abf6772cd20293575fc79e8e64ce3b..b157dfaf1efffebd3f2ae8cb8fcf0972fe742258 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer.java @@ -13,7 +13,7 @@ import net.minecraft.world.level.storage.loot.predicates.ConditionUserBuilder; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; public abstract class LootPoolEntryContainer implements ComposableEntryContainer { - protected final List conditions; + public final List conditions; // Leaves - private -> public private final Predicate compositeCondition; protected LootPoolEntryContainer(List conditions) { diff --git a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java index 71989359192c8f30a1a8d343a2c6cb5b92330491..ec273bd4d0e61f54532df599f00695e8b9d44800 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/entries/NestedLootTable.java @@ -25,7 +25,7 @@ public class NestedLootTable extends LootPoolSingletonContainer { .and(singletonFields(instance)) .apply(instance, NestedLootTable::new) ); - private final Either, LootTable> contents; + public final Either, LootTable> contents; // Leaves - private -> public private NestedLootTable( Either, LootTable> value, int weight, int quality, List conditions, List functions diff --git a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java index 30d0133a42ce990352f5c492fcf9beb105364848..1ab2eab686b3a89d406f127a6036c0e2932db4a6 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/predicates/CompositeLootItemCondition.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.ValidationContext; public abstract class CompositeLootItemCondition implements LootItemCondition { - protected final List terms; + public final List terms; // Leaves - private -> public private final Predicate composedPredicate; protected CompositeLootItemCondition(List terms, Predicate predicate) { diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..eb26708fafa054ba32c5deed43c8496d5496f6a9 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java @@ -0,0 +1,311 @@ +package org.leavesmc.leaves.protocol.jade; + +import com.google.common.base.Suppliers; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.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.item.Items; +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.EntityHitResult; +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.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.BlockAccessorImpl; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessorImpl; +import org.leavesmc.leaves.protocol.jade.payload.ReceiveDataPayload; +import org.leavesmc.leaves.protocol.jade.payload.RequestBlockPayload; +import org.leavesmc.leaves.protocol.jade.payload.RequestEntityPayload; +import org.leavesmc.leaves.protocol.jade.payload.ServerPingPayload; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; +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.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.HopperLockProvider; +import org.leavesmc.leaves.protocol.jade.provider.block.ItemStorageProvider; +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.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.LootTableMineableCollector; +import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup; +import org.leavesmc.leaves.protocol.jade.util.PriorityStore; +import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup; + +import java.util.Collections; +import java.util.List; + +@LeavesProtocol(namespace = "jade") +public class JadeProtocol { + + public static PriorityStore priorities; + private static List shearableBlocks = null; + + public static final String PROTOCOL_ID = "jade"; + + public static final HierarchyLookup> entityDataProviders = new HierarchyLookup<>(Entity.class); + public 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 ResourceLocation.withDefaultNamespace(path); + } + + @ProtocolHandler.Init + public static void init() { + priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid); + + // core plugin + blockDataProviders.register(BlockEntity.class, ObjectNameProvider.ForBlock.INSTANCE); + + // universal plugin + entityDataProviders.register(Entity.class, ItemStorageProvider.getEntity()); + blockDataProviders.register(Block.class, ItemStorageProvider.getBlock()); + + 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, HopperLockProvider.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); + + blockDataProviders.idMapped(); + entityDataProviders.idMapped(); + itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE); + + blockDataProviders.loadComplete(priorities); + entityDataProviders.loadComplete(priorities); + itemStorageProviders.loadComplete(priorities); + + try { + shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute( + MinecraftServer.getServer().reloadableRegistries().get().registryOrThrow(Registries.LOOT_TABLE), + Items.SHEARS.getDefaultInstance())); + } catch (Throwable ignore) { + shearableBlocks = List.of(); + LeavesLogger.LOGGER.severe("Failed to collect shearable blocks"); + } + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerJoin(ServerPlayer player) { + if (!LeavesConfig.jadeProtocol) { + return; + } + + sendPingPacket(player); + } + + @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.data().showDetails(); + Entity entity = world.getEntity(payload.data().id()); + double maxDistance = Mth.square(player.entityInteractionRange() + 21); + + if (entity == null || player.distanceToSqr(entity) > maxDistance) { + return; + } + + if (payload.data().partIndex() >= 0 && entity instanceof EnderDragon dragon) { + EnderDragonPart[] parts = dragon.getSubEntities(); + if (payload.data().partIndex() < parts.length) { + entity = parts[payload.data().partIndex()]; + } + } + + var providers = entityDataProviders.get(entity); + if (providers.isEmpty()) { + return; + } + + final Entity finalEntity = entity; + CompoundTag tag = new CompoundTag(); + EntityAccessor accessor = new EntityAccessorImpl.Builder() + .level(world) + .player(player) + .showDetails(showDetails) + .entity(entity) + .hit(Suppliers.memoize(() -> new EntityHitResult(finalEntity, payload.data().hitVec()))) + .build(); + + for (IServerDataProvider provider : providers) { + try { + provider.appendServerData(tag, accessor); + } catch (Exception e) { + LeavesLogger.LOGGER.warning("Error while saving data for entity " + entity); + } + } + tag.putInt("EntityId", entity.getId()); + + ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(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.data().blockState(); + Block block = blockState.getBlock(); + BlockHitResult result = payload.data().hit(); + BlockPos pos = result.getBlockPos(); + boolean showDetails = payload.data().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()) { + player.getBukkitEntity().sendMessage("Provider is empty!"); + return; + } + + CompoundTag tag = new CompoundTag(); + BlockAccessor accessor = new BlockAccessorImpl.Builder() + .level(world) + .player(player) + .showDetails(showDetails) + .hit(result) + .blockState(blockState) + .blockEntity(blockEntity) + .build(); + + for (IServerDataProvider provider : providers) { + try { + provider.appendServerData(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, new ReceiveDataPayload(tag)); + }); + } + + @ProtocolHandler.ReloadServer + public static void onServerReload() { + if (LeavesConfig.jadeProtocol) { + enableAllPlayer(); + } + } + + public static void enableAllPlayer() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().players) { + sendPingPacket(player); + } + } + + public static void sendPingPacket(ServerPlayer player) { + ProtocolUtils.sendPayloadPacket(player, new ServerPingPayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds())); + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java new file mode 100644 index 0000000000000000000000000000000000000000..9c9b469f805196dd97e02d93a11232d043f5e854 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/Accessor.java @@ -0,0 +1,37 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamEncoder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface Accessor { + + Level getLevel(); + + Player getPlayer(); + + @NotNull + CompoundTag getServerData(); + + Tag encodeAsNbt(StreamEncoder codec, D value); + + T getHitResult(); + + /** + * @return {@code true} if the dedicated server has Jade installed. + */ + boolean isServerConnected(); + + boolean showDetails(); + + @Nullable + Object getTarget(); + + float tickRate(); +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..38d6b146d9135fb7c985f1f4e8a804bb490ff0df --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/AccessorImpl.java @@ -0,0 +1,92 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import java.util.function.Supplier; + +import org.apache.commons.lang3.ArrayUtils; +import org.jetbrains.annotations.NotNull; + +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamEncoder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; + +public abstract class AccessorImpl implements Accessor { + + private final Level level; + private final Player player; + private final CompoundTag serverData; + private final Supplier hit; + private final boolean serverConnected; + private final boolean showDetails; + protected boolean verify; + private RegistryFriendlyByteBuf buffer; + + public AccessorImpl(Level level, Player player, CompoundTag serverData, Supplier hit, boolean serverConnected, boolean showDetails) { + this.level = level; + this.player = player; + this.hit = hit; + this.serverConnected = serverConnected; + this.showDetails = showDetails; + this.serverData = serverData == null ? new CompoundTag() : serverData.copy(); + } + + @Override + public Level getLevel() { + return level; + } + + @Override + public Player getPlayer() { + return player; + } + + @Override + public final @NotNull CompoundTag getServerData() { + return serverData; + } + + private RegistryFriendlyByteBuf buffer() { + if (buffer == null) { + buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess()); + } + buffer.clear(); + return buffer; + } + + @Override + public Tag encodeAsNbt(StreamEncoder streamCodec, D value) { + RegistryFriendlyByteBuf buffer = buffer(); + streamCodec.encode(buffer, value); + ByteArrayTag tag = new ByteArrayTag(ArrayUtils.subarray(buffer.array(), 0, buffer.readableBytes())); + buffer.clear(); + return tag; + } + + @Override + public T getHitResult() { + return hit.get(); + } + + /** + * Returns true if dedicated server has Jade installed. + */ + @Override + public boolean isServerConnected() { + return serverConnected; + } + + @Override + public boolean showDetails() { + return showDetails; + } + + @Override + public float tickRate() { + return getLevel().tickRateManager().tickrate(); + } +} 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..12d689ca80887dcd5dbf68ea2c38a8adcc5ddee4 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessor.java @@ -0,0 +1,50 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +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; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Supplier; + +public interface BlockAccessor extends Accessor { + + Block getBlock(); + + BlockState getBlockState(); + + BlockEntity getBlockEntity(); + + BlockPos getPosition(); + + Direction getSide(); + + @ApiStatus.NonExtendable + interface Builder { + Builder level(Level level); + + Builder player(Player player); + + Builder showDetails(boolean showDetails); + + Builder hit(BlockHitResult hit); + + Builder blockState(BlockState state); + + default Builder blockEntity(BlockEntity blockEntity) { + return blockEntity(() -> blockEntity); + } + + Builder blockEntity(Supplier blockEntity); + + Builder from(BlockAccessor accessor); + + BlockAccessor build(); + } + +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4a2cd64c7911b756f737ed34f245f7366668f417 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/BlockAccessorImpl.java @@ -0,0 +1,166 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.base.Suppliers; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +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.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +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.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +/** + * Class to get information of block target and context. + */ +public class BlockAccessorImpl extends AccessorImpl implements BlockAccessor { + + private final BlockState blockState; + @Nullable + private final Supplier blockEntity; + + private BlockAccessorImpl(Builder builder) { + super(builder.level, builder.player, builder.serverData, Suppliers.ofInstance(builder.hit), builder.connected, builder.showDetails); + blockState = builder.blockState; + blockEntity = builder.blockEntity; + } + + @Override + public Block getBlock() { + return getBlockState().getBlock(); + } + + @Override + public BlockState getBlockState() { + return blockState; + } + + @Override + public BlockEntity getBlockEntity() { + return blockEntity == null ? null : blockEntity.get(); + } + + @Override + public BlockPos getPosition() { + return getHitResult().getBlockPos(); + } + + @Override + public Direction getSide() { + return getHitResult().getDirection(); + } + + @Nullable + @Override + public Object getTarget() { + return getBlockEntity(); + } + + public static class Builder implements BlockAccessor.Builder { + + private Level level; + private Player player; + private CompoundTag serverData; + private boolean connected; + private boolean showDetails; + private BlockHitResult hit; + private BlockState blockState = Blocks.AIR.defaultBlockState(); + private Supplier blockEntity; + + @Override + public Builder level(Level level) { + this.level = level; + return this; + } + + @Override + public Builder player(Player player) { + this.player = player; + return this; + } + + @Override + public Builder showDetails(boolean showDetails) { + this.showDetails = showDetails; + return this; + } + + @Override + public Builder hit(BlockHitResult hit) { + this.hit = hit; + return this; + } + + @Override + public Builder blockState(BlockState blockState) { + this.blockState = blockState; + return this; + } + + @Override + public Builder blockEntity(Supplier blockEntity) { + this.blockEntity = blockEntity; + return this; + } + + @Override + public Builder from(BlockAccessor accessor) { + level = accessor.getLevel(); + player = accessor.getPlayer(); + serverData = accessor.getServerData(); + connected = accessor.isServerConnected(); + showDetails = accessor.showDetails(); + hit = accessor.getHitResult(); + blockEntity = accessor::getBlockEntity; + blockState = accessor.getBlockState(); + return this; + } + + @Override + public BlockAccessor build() { + return new BlockAccessorImpl(this); + } + } + + public record SyncData(boolean showDetails, BlockHitResult hit, BlockState blockState, ItemStack fakeBlock) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + SyncData::showDetails, + StreamCodec.of(FriendlyByteBuf::writeBlockHitResult, FriendlyByteBuf::readBlockHitResult), + SyncData::hit, + ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY), + SyncData::blockState, + ItemStack.OPTIONAL_STREAM_CODEC, + SyncData::fakeBlock, + SyncData::new + ); + + public BlockAccessor unpack(ServerPlayer player) { + Supplier blockEntity = null; + if (blockState.hasBlockEntity()) { + blockEntity = Suppliers.memoize(() -> player.level().getBlockEntity(hit.getBlockPos())); + } + return new Builder() + .level(player.level()) + .player(player) + .showDetails(showDetails) + .hit(hit) + .blockState(blockState) + .blockEntity(blockEntity) + .build(); + } + } +} 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..454360d5e5c01cad3c197b078d536a9f34e2c6a2 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessor.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Supplier; + +public interface EntityAccessor extends Accessor { + + Entity getEntity(); + + /** + * For part entity like ender dragon's, getEntity() will return the parent entity. + */ + Entity getRawEntity(); + + @ApiStatus.NonExtendable + interface Builder { + Builder level(Level level); + + Builder player(Player player); + + Builder showDetails(boolean showDetails); + + default Builder hit(EntityHitResult hit) { + return hit(() -> hit); + } + + Builder hit(Supplier hit); + + default Builder entity(Entity entity) { + return entity(() -> entity); + } + + Builder entity(Supplier entity); + + Builder from(EntityAccessor accessor); + + EntityAccessor build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f670ac9b7ee72bbf3fa4f509cc2cdaeee238ccff --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/accessor/EntityAccessorImpl.java @@ -0,0 +1,126 @@ +package org.leavesmc.leaves.protocol.jade.accessor; + +import com.google.common.base.Suppliers; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; + +import java.util.function.Supplier; + +public class EntityAccessorImpl extends AccessorImpl implements EntityAccessor { + + private final Supplier entity; + + public EntityAccessorImpl(Builder builder) { + super(builder.level, builder.player, builder.serverData, builder.hit, builder.connected, builder.showDetails); + entity = builder.entity; + } + + @Override + public Entity getEntity() { + return CommonUtil.wrapPartEntityParent(getRawEntity()); + } + + @Override + public Entity getRawEntity() { + return entity.get(); + } + + @NotNull + @Override + public Object getTarget() { + return getEntity(); + } + + public static class Builder implements EntityAccessor.Builder { + + public boolean showDetails; + private Level level; + private Player player; + private CompoundTag serverData; + private boolean connected; + private Supplier hit; + private Supplier entity; + + @Override + public Builder level(Level level) { + this.level = level; + return this; + } + + @Override + public Builder player(Player player) { + this.player = player; + return this; + } + + @Override + public Builder showDetails(boolean showDetails) { + this.showDetails = showDetails; + return this; + } + + @Override + public Builder hit(Supplier hit) { + this.hit = hit; + return this; + } + + @Override + public Builder entity(Supplier entity) { + this.entity = entity; + return this; + } + + @Override + public Builder from(EntityAccessor accessor) { + level = accessor.getLevel(); + player = accessor.getPlayer(); + serverData = accessor.getServerData(); + connected = accessor.isServerConnected(); + showDetails = accessor.showDetails(); + hit = accessor::getHitResult; + entity = accessor::getEntity; + return this; + } + + @Override + public EntityAccessor build() { + return new EntityAccessorImpl(this); + } + } + + public record SyncData(boolean showDetails, int id, int partIndex, Vec3 hitVec) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + SyncData::showDetails, + ByteBufCodecs.VAR_INT, + SyncData::id, + ByteBufCodecs.VAR_INT, + SyncData::partIndex, + ByteBufCodecs.VECTOR3F.map(Vec3::new, Vec3::toVector3f), + SyncData::hitVec, + SyncData::new + ); + + public EntityAccessor unpack(ServerPlayer player) { + Supplier entity = Suppliers.memoize(() -> CommonUtil.getPartEntity(player.level().getEntity(id), partIndex)); + return new EntityAccessorImpl.Builder() + .level(player.level()) + .player(player) + .showDetails(showDetails) + .entity(entity) + .hit(Suppliers.memoize(() -> new EntityHitResult(entity.get(), hitVec))) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..1b474ea8c1075b3dbaa7cd27e5bd95aa904fbe97 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ReceiveDataPayload.java @@ -0,0 +1,28 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data"); + + @New + public ReceiveDataPayload(ResourceLocation id, FriendlyByteBuf buf) { + this(buf.readNbt()); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) { + buf.writeNbt(tag); + } + + @Override + public ResourceLocation id() { + return PACKET_RECEIVE_DATA; + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..480ec35f28c850bfbe4f787d080fd7bbd84ca24c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestBlockPayload.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import io.netty.buffer.ByteBuf; +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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessorImpl; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +import java.util.List; +import java.util.Objects; + +import static org.leavesmc.leaves.protocol.jade.JadeProtocol.blockDataProviders; + +public record RequestBlockPayload(BlockAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block"); + private static final StreamCodec CODEC = StreamCodec.composite( + BlockAccessorImpl.SyncData.STREAM_CODEC, + RequestBlockPayload::data, + ByteBufCodecs.>list() + .apply(ByteBufCodecs.idMapper( + $ -> Objects.requireNonNull(blockDataProviders.idMapper()).byId($), + $ -> Objects.requireNonNull(blockDataProviders.idMapper()).getIdOrThrow($))), + RequestBlockPayload::dataProviders, + RequestBlockPayload::new); + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @New + public static RequestBlockPayload create(ResourceLocation location, FriendlyByteBuf buf) { + return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); + } + + @Override + @NotNull + public ResourceLocation id() { + return PACKET_REQUEST_BLOCK; + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..ef7ee32f0f2fd78b7e7891d622f76cddb8cb0680 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/RequestEntityPayload.java @@ -0,0 +1,53 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import io.netty.buffer.ByteBuf; +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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessorImpl; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +import java.util.List; +import java.util.Objects; + +import static org.leavesmc.leaves.protocol.jade.JadeProtocol.entityDataProviders; + +public record RequestEntityPayload(EntityAccessorImpl.SyncData data, List<@Nullable IServerDataProvider> dataProviders) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_REQUEST_ENTITY = JadeProtocol.id("request_entity"); + private static final StreamCodec CODEC = StreamCodec.composite( + EntityAccessorImpl.SyncData.STREAM_CODEC, + RequestEntityPayload::data, + ByteBufCodecs.>list() + .apply(ByteBufCodecs.idMapper( + $ -> Objects.requireNonNull(entityDataProviders.idMapper()).byId($), + $ -> Objects.requireNonNull(entityDataProviders.idMapper()) + .getIdOrThrow($))), + RequestEntityPayload::dataProviders, + RequestEntityPayload::new); + + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @New + public static RequestEntityPayload create(ResourceLocation location, FriendlyByteBuf buf) { + return CODEC.decode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess())); + } + + @Override + @NotNull + public ResourceLocation id() { + return PACKET_REQUEST_ENTITY; + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..f4419962a7fedaea05140bbf6eaa01cc94c05049 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/payload/ServerPingPayload.java @@ -0,0 +1,49 @@ +package org.leavesmc.leaves.protocol.jade.payload; + +import com.google.common.collect.Maps; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.registries.Registries; +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.world.level.block.Block; +import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +import java.util.List; +import java.util.Map; + +import static org.leavesmc.leaves.protocol.jade.util.JadeCodec.PRIMITIVE_STREAM_CODEC; + +public record ServerPingPayload( + Map serverConfig, + List shearableBlocks, + List blockProviderIds, + List entityProviderIds) implements LeavesCustomPayload { + + private static final ResourceLocation PACKET_SERVER_HANDSHAKE = JadeProtocol.id("server_ping_v1"); + private static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.map(Maps::newHashMapWithExpectedSize, ResourceLocation.STREAM_CODEC, PRIMITIVE_STREAM_CODEC), + ServerPingPayload::serverConfig, + ByteBufCodecs.registry(Registries.BLOCK).apply(ByteBufCodecs.list()), + ServerPingPayload::shearableBlocks, + ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), + ServerPingPayload::blockProviderIds, + ByteBufCodecs.list().apply(ResourceLocation.STREAM_CODEC), + ServerPingPayload::entityProviderIds, + ServerPingPayload::new); + + @Override + public void write(FriendlyByteBuf buf) { + CODEC.encode(new RegistryFriendlyByteBuf(buf, MinecraftServer.getServer().registryAccess()), this); + } + + @Override + public ResourceLocation id() { + return PACKET_SERVER_HANDSHAKE; + } +} + 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/IServerDataProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..7d839f17601ca4d2b8717222989cf566a0eb6524 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/IServerDataProvider.java @@ -0,0 +1,8 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.nbt.CompoundTag; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; + +public interface IServerDataProvider> extends IJadeProvider { + void appendServerData(CompoundTag data, T accessor); +} 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..6e32eed15f028020223e2500849b4db3892f68c3 --- /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.Accessor; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; + +public interface IServerExtensionProvider extends IJadeProvider { + List> getGroups(Accessor request); +} \ No newline at end of file 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..7680ff97d99e15a9b3475ef83f7cfe348c895b14 --- /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.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +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(Accessor request) { + Object target = request.getTarget(); + if (target == null && request instanceof BlockAccessor blockAccessor && blockAccessor.getBlock() instanceof WorldlyContainerHolder holder) { + WorldlyContainer container = holder.getContainer(blockAccessor.getBlockState(), request.getLevel(), blockAccessor.getPosition()); + 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.getPlayer(); + 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.getLevel().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.getLevel().getGameTime()); + } + + @Override + public ResourceLocation getUid() { + return UNIVERSAL_ITEM_STORAGE; + } + + public static List> containerGroup(Container container, Accessor accessor) { + try { + return containerCache.get(container, () -> new ItemCollector<>(new ItemIterator.ContainerItemIterator(0))).update(container, accessor.getLevel().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/StreamServerDataProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..52887edb3359c5eb1900cd1eec912e52afef2c9f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/StreamServerDataProvider.java @@ -0,0 +1,26 @@ +package org.leavesmc.leaves.protocol.jade.provider; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; + +import java.util.Optional; + +public interface StreamServerDataProvider, D> extends IServerDataProvider { + + @Override + default void appendServerData(CompoundTag data, T accessor) { + D value = streamData(accessor); + if (value != null) { + data.put(getUid().toString(), accessor.encodeAsNbt(streamCodec(), value)); + } + } + + @Nullable + D streamData(T accessor); + + StreamCodec streamCodec(); +} 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..ee92d79bf4d328c95c51178a2ad43beb0a54cd29 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BeehiveProvider.java @@ -0,0 +1,34 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +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.world.level.block.entity.BeehiveBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum BeehiveProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_BEEHIVE = JadeProtocol.mc_id("beehive"); + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.BYTE.cast(); + } + + @Override + public Byte streamData(@NotNull BlockAccessor accessor) { + BeehiveBlockEntity beehive = (BeehiveBlockEntity) accessor.getBlockEntity(); + int bees = beehive.getOccupantCount(); + return (byte) (beehive.isFull() ? bees : -bees); + } + + @Override + public ResourceLocation getUid() { + return MC_BEEHIVE; + } +} \ No newline at end of file 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..e6f15f87819d4a7c4d4383db735d8318ea30a03c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/BrewingStandProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import io.netty.buffer.ByteBuf; +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.world.level.block.entity.BrewingStandBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum BrewingStandProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_BREWING_STAND = JadeProtocol.mc_id("brewing_stand"); + + @Override + public @NotNull Data streamData(@NotNull BlockAccessor accessor) { + BrewingStandBlockEntity brewingStand = (BrewingStandBlockEntity) accessor.getBlockEntity(); + return new Data(brewingStand.fuel, brewingStand.brewTime); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return Data.STREAM_CODEC.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_BREWING_STAND; + } + + public record Data(int fuel, int time) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + Data::fuel, + ByteBufCodecs.VAR_INT, + Data::time, + Data::new); + } +} 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..2deb3777a320d6a50168e06f234ba4c21da48e9a --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java @@ -0,0 +1,55 @@ +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.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +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 @Nullable @Unmodifiable List> getGroups(@NotNull Accessor request) { + if (request.getTarget() 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(NbtOps.INSTANCE, 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..bde872cc5ebd9d79af307c8a4b38acd385cec11b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ChiseledBookshelfProvider.java @@ -0,0 +1,39 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +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.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum ChiseledBookshelfProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_CHISELED_BOOKSHELF = JadeProtocol.mc_id("chiseled_bookshelf"); + + @Override + public @Nullable ItemStack streamData(@NotNull BlockAccessor accessor) { + int slot = ((ChiseledBookShelfBlock) accessor.getBlock()).getHitSlot(accessor.getHitResult(), accessor.getBlockState()).orElse(-1); + if (slot == -1) { + return null; + } + return ((ChiseledBookShelfBlockEntity) accessor.getBlockEntity()).getItem(slot); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + + @Override + public ResourceLocation getUid() { + return MC_CHISELED_BOOKSHELF; + } +} 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..5f71fadae8fe95e3386e3ee5465eb33f850a37b0 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CommandBlockProvider.java @@ -0,0 +1,40 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +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.world.level.block.entity.CommandBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum CommandBlockProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_COMMAND_BLOCK = JadeProtocol.mc_id("command_block"); + + @Nullable + public String streamData(@NotNull BlockAccessor accessor) { + if (!accessor.getPlayer().canUseGameMasterBlocks()) { + return null; + } + String command = ((CommandBlockEntity) accessor.getBlockEntity()).getCommandBlock().getCommand(); + if (command.length() > 40) { + command = command.substring(0, 37) + "..."; + } + return command; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.STRING_UTF8.cast(); + } + + @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..090e6a350a5c19c0204ecf9a2c2c42e8d012cb3d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/FurnaceProvider.java @@ -0,0 +1,60 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.nbt.CompoundTag; +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.world.item.ItemStack; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +import java.util.List; + +public enum FurnaceProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_FURNACE = JadeProtocol.mc_id("furnace"); + + @Override + public @Nullable Data streamData(@NotNull BlockAccessor accessor) { + if (!(accessor.getTarget() instanceof AbstractFurnaceBlockEntity furnace)) { + return null; + } + + if (furnace.isEmpty()) { + return null; + } + + CompoundTag furnaceTag = furnace.saveWithoutMetadata(accessor.getLevel().registryAccess()); + return new Data( + furnaceTag.getInt("CookTime"), + furnaceTag.getInt("CookTimeTotal"), + List.of(furnace.getItem(0), furnace.getItem(1), furnace.getItem(2))); + } + + @Override + public StreamCodec streamCodec() { + return Data.STREAM_CODEC; + } + + @Override + public ResourceLocation getUid() { + return MC_FURNACE; + } + + public record Data(int progress, int total, List inventory) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + Data::progress, + ByteBufCodecs.VAR_INT, + Data::total, + ItemStack.OPTIONAL_LIST_STREAM_CODEC, + Data::inventory, + Data::new); + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..a3937081bd923d3b6f2ee966dc95aa235c3eb57c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/HopperLockProvider.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +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.world.level.block.state.properties.BlockStateProperties; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum HopperLockProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_HOPPER_LOCK = JadeProtocol.mc_id("hopper_lock"); + + @Override + public Boolean streamData(@NotNull BlockAccessor accessor) { + return !accessor.getBlockState().getValue(BlockStateProperties.ENABLED); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.BOOL.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_HOPPER_LOCK; + } +} \ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ItemStorageProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ItemStorageProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..f4eb38d4b5d98a286964cdb68581bb9a2d836def --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ItemStorageProvider.java @@ -0,0 +1,92 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +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.item.ItemStack; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; +import org.leavesmc.leaves.protocol.jade.util.ItemCollector; +import org.leavesmc.leaves.protocol.jade.util.ViewGroup; + +import java.util.List; +import java.util.Map; + +public abstract class ItemStorageProvider> implements IServerDataProvider { + + private static final StreamCodec>>> STREAM_CODEC = ViewGroup.listCodec( + ItemStack.OPTIONAL_STREAM_CODEC); + + private static final ResourceLocation UNIVERSAL_ITEM_STORAGE = JadeProtocol.mc_id("item_storage.default"); + + public static ForBlock getBlock() { + return ForBlock.INSTANCE; + } + + public static ForEntity getEntity() { + return ForEntity.INSTANCE; + } + + public static class ForBlock extends ItemStorageProvider { + private static final ForBlock INSTANCE = new ForBlock(); + } + + public static class ForEntity extends ItemStorageProvider { + private static final ForEntity INSTANCE = new ForEntity(); + } + + public static void putData(Accessor accessor) { + CompoundTag tag = accessor.getServerData(); + Object target = accessor.getTarget(); + Player player = accessor.getPlayer(); + Map.Entry>> entry = CommonUtil.getServerExtensionData( + accessor, + JadeProtocol.itemStorageProviders); + if (entry != null) { + List> groups = entry.getValue(); + for (ViewGroup group : groups) { + if (group.views.size() > ItemCollector.MAX_SIZE) { + group.views = group.views.subList(0, ItemCollector.MAX_SIZE); + } + } + tag.put(UNIVERSAL_ITEM_STORAGE.toString(), accessor.encodeAsNbt(STREAM_CODEC, entry)); + return; + } + if (target instanceof RandomizableContainer containerEntity && containerEntity.getLootTable() != null) { + tag.putBoolean("Loot", true); + } else if (!player.isCreative() && !player.isSpectator() && target instanceof BaseContainerBlockEntity te) { + if (te.lockKey != LockCode.NO_LOCK) { + tag.putBoolean("Locked", true); + } + } + } + + @Override + public ResourceLocation getUid() { + return UNIVERSAL_ITEM_STORAGE; + } + + @Override + public void appendServerData(CompoundTag tag, @NotNull T accessor) { + if (accessor.getTarget() instanceof AbstractFurnaceBlockEntity) { + return; + } + putData(accessor); + } + + @Override + public int getDefaultPriority() { + return 9999; + } +} \ No newline at end of file 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..0b6e224ebc8d6acdc29abf51f7d98b667baf0984 --- /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 net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.JukeboxBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum JukeboxProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_JUKEBOX = JadeProtocol.mc_id("jukebox"); + + @Override + public @NotNull ItemStack streamData(BlockAccessor accessor) { + return ((JukeboxBlockEntity) accessor.getBlockEntity()).getTheItem(); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + @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..c363bd616fa41eca3266ccb485432cfd90ad7473 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/LecternProvider.java @@ -0,0 +1,33 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.LecternBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum LecternProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_LECTERN = JadeProtocol.mc_id("lectern"); + + @Override + public @NotNull ItemStack streamData(@NotNull BlockAccessor accessor) { + return ((LecternBlockEntity) accessor.getBlockEntity()).getBook(); + } + + @Override + public StreamCodec streamCodec() { + return ItemStack.OPTIONAL_STREAM_CODEC; + } + + + @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..a70f4a81166221ec1971b1fbf06e4c73efffcbe4 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +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.level.ServerLevel; +import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobSpawnerCooldownProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_SPAWNER_COOLDOWN = JadeProtocol.mc_id("mob_spawner.cooldown"); + + @Override + public @Nullable Integer streamData(@NotNull BlockAccessor accessor) { + TrialSpawnerBlockEntity spawner = (TrialSpawnerBlockEntity) accessor.getBlockEntity(); + TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); + ServerLevel level = ((ServerLevel) accessor.getLevel()); + if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) { + return (int) (spawnerData.cooldownEndsAt - level.getGameTime()); + } + return null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + + @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..e97fb13707365cceaf28f2624791d0472e56169c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/ObjectNameProvider.java @@ -0,0 +1,57 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.codec.StreamCodec; +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.ChestBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public abstract class ObjectNameProvider implements StreamServerDataProvider { + + private static final ResourceLocation CORE_OBJECT_NAME = JadeProtocol.id("object_name"); + + public static class ForBlock extends ObjectNameProvider implements StreamServerDataProvider { + public static final ForBlock INSTANCE = new ForBlock(); + + @Override + @Nullable + public Component streamData(@NotNull BlockAccessor accessor) { + if (!(accessor.getBlockEntity() instanceof Nameable nameable)) { + return null; + } + if (nameable instanceof ChestBlockEntity && accessor.getBlock() instanceof ChestBlock) { + MenuProvider menuProvider = accessor.getBlockState().getMenuProvider(accessor.getLevel(), accessor.getPosition()); + if (menuProvider != null) { + return menuProvider.getDisplayName(); + } + } else if (nameable.hasCustomName()) { + return nameable.getDisplayName(); + } + return null; + } + + @Override + public StreamCodec streamCodec() { + return ComponentSerialization.STREAM_CODEC; + } + } + + @Override + public ResourceLocation getUid() { + return CORE_OBJECT_NAME; + } + + @Override + public int getDefaultPriority() { + return -10100; + } +} \ No newline at end of file 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..1cdcf21ed69744f96f47673100d8bf0114850f4f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/RedstoneProvider.java @@ -0,0 +1,36 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +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 org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +public enum RedstoneProvider implements IServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_REDSTONE = JadeProtocol.mc_id("redstone"); + + @Override + public void appendServerData(CompoundTag data, @NotNull BlockAccessor accessor) { + BlockEntity blockEntity = accessor.getBlockEntity(); + if (blockEntity instanceof ComparatorBlockEntity comparator) { + data.putInt("Signal", comparator.getOutputSignal()); + } else if (blockEntity instanceof CalibratedSculkSensorBlockEntity) { + Direction direction = accessor.getBlockState().getValue(CalibratedSculkSensorBlock.FACING).getOpposite(); + int signal = accessor.getLevel().getSignal(accessor.getPosition().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..ff50ef60e3b37ab231161c870c432ef8e3018458 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/AnimalOwnerProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +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.world.entity.Entity; +import net.minecraft.world.entity.OwnableEntity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; +import org.leavesmc.leaves.protocol.jade.util.CommonUtil; + +import java.util.UUID; + +public enum AnimalOwnerProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_ANIMAL_OWNER = JadeProtocol.mc_id("animal_owner"); + + @Override + public String streamData(@NotNull EntityAccessor accessor) { + return CommonUtil.getLastKnownUsername(getOwnerUUID(accessor.getEntity())); + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.STRING_UTF8.cast(); + } + + public static UUID getOwnerUUID(Entity entity) { + if (entity instanceof OwnableEntity ownableEntity) { + return ownableEntity.getOwnerUUID(); + } + return null; + } + + @Override + public ResourceLocation getUid() { + return MC_ANIMAL_OWNER; + } +} \ No newline at end of file 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..0acba2f9700e4a65e764077b22e65e18d787be2a --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobBreedingProvider.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +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.world.entity.Entity; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.allay.Allay; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobBreedingProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_BREEDING = JadeProtocol.mc_id("mob_breeding"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = 0; + Entity entity = accessor.getEntity(); + if (entity instanceof Allay allay) { + if (allay.duplicationCooldown > 0 && allay.duplicationCooldown < Integer.MAX_VALUE) { + time = (int) allay.duplicationCooldown; + } + } else { + time = ((Animal) entity).getAge(); + } + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + @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..44f5f4b8bf3b9fe66b2f8b93b36284c3ae5d1d87 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/MobGrowthProvider.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +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.world.entity.AgeableMob; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.animal.frog.Tadpole; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum MobGrowthProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_MOB_GROWTH = JadeProtocol.mc_id("mob_growth"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = -1; + Entity entity = accessor.getEntity(); + if (entity instanceof AgeableMob ageable) { + time = -ageable.getAge(); + } else if (entity instanceof Tadpole tadpole) { + time = tadpole.getTicksLeftUntilAdult(); + } + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + + @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..892911a3d0087a5bf48b2df8326e3c5ce27835a0 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/NextEntityDropProvider.java @@ -0,0 +1,35 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.animal.Chicken; +import net.minecraft.world.entity.animal.armadillo.Armadillo; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider; + +public enum NextEntityDropProvider implements IServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_NEXT_ENTITY_DROP = JadeProtocol.mc_id("next_entity_drop"); + + @Override + public void appendServerData(CompoundTag tag, @NotNull EntityAccessor accessor) { + int max = 24000 * 2; + if (accessor.getEntity() instanceof Chicken chicken) { + if (!chicken.isBaby() && chicken.eggTime < max) { + tag.putInt("NextEggIn", chicken.eggTime); + } + } else if (accessor.getEntity() instanceof Armadillo armadillo) { + if (!armadillo.isBaby() && armadillo.scuteTime < max) { + tag.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..ffc571875f620fe53b2210583c246d6579c3df1f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/StatusEffectsProvider.java @@ -0,0 +1,45 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +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.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +import java.util.List; + +public enum StatusEffectsProvider implements StreamServerDataProvider> { + INSTANCE; + + + private static final StreamCodec> STREAM_CODEC = ByteBufCodecs.list() + .apply(MobEffectInstance.STREAM_CODEC); + private static final ResourceLocation MC_POTION_EFFECTS = JadeProtocol.mc_id("potion_effects"); + + @Override + @Nullable + public List streamData(@NotNull EntityAccessor accessor) { + List effects = ((LivingEntity) accessor.getEntity()).getActiveEffects() + .stream() + .filter(MobEffectInstance::isVisible) + .toList(); + return effects.isEmpty() ? null : effects; + } + + @Override + public StreamCodec> streamCodec() { + return STREAM_CODEC; + } + + + @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..b7c9afd29f3ddead6871c8f2b1f2b0815605cea5 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/entity/ZombieVillagerProvider.java @@ -0,0 +1,34 @@ +package org.leavesmc.leaves.protocol.jade.provider.entity; + +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.world.entity.monster.ZombieVillager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; +import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor; +import org.leavesmc.leaves.protocol.jade.provider.StreamServerDataProvider; + +public enum ZombieVillagerProvider implements StreamServerDataProvider { + INSTANCE; + + private static final ResourceLocation MC_ZOMBIE_VILLAGER = JadeProtocol.mc_id("zombie_villager"); + + @Override + public @Nullable Integer streamData(@NotNull EntityAccessor accessor) { + int time = ((ZombieVillager) accessor.getEntity()).villagerConversionTime; + return time > 0 ? time : null; + } + + @Override + public @NotNull StreamCodec streamCodec() { + return ByteBufCodecs.VAR_INT.cast(); + } + + @Override + public ResourceLocation getUid() { + return MC_ZOMBIE_VILLAGER; + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6a9cd9f0e71331c4fd3bef7bbeabebcc5d0e80cb --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ShearsToolHandler.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.leavesmc.leaves.protocol.jade.JadeProtocol; + +import java.util.List; + +public class ShearsToolHandler extends SimpleToolHandler { + + private static final ShearsToolHandler INSTANCE = new ShearsToolHandler(); + + public static ShearsToolHandler getInstance() { + return INSTANCE; + } + + public ShearsToolHandler() { + super(JadeProtocol.id("shears"), List.of(Items.SHEARS.getDefaultInstance()), true); + } + + @Override + public ItemStack test(BlockState state, Level world, BlockPos pos) { + if (state.is(Blocks.TRIPWIRE)) { + return tools.getFirst(); + } + return super.test(state, world, pos); + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d45ecdb17a78d7e0c5eb280ee584960761ced1d2 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/SimpleToolHandler.java @@ -0,0 +1,71 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Tool; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SimpleToolHandler implements ToolHandler { + + protected final List tools = Lists.newArrayList(); + private final ResourceLocation uid; + private final boolean skipInstaBreakingBlock; + + protected SimpleToolHandler(ResourceLocation uid, @NotNull List tools, boolean skipInstaBreakingBlock) { + this.uid = uid; + Preconditions.checkArgument(!tools.isEmpty(), "tools cannot be empty"); + this.tools.addAll(tools); + this.skipInstaBreakingBlock = skipInstaBreakingBlock; + } + + @Contract("_, _ -> new") + public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools) { + return create(uid, tools, true); + } + + @Contract("_, _, _ -> new") + public static @NotNull SimpleToolHandler create(ResourceLocation uid, List tools, boolean skipInstaBreakingBlock) { + return new SimpleToolHandler(uid, Lists.transform(tools, Item::getDefaultInstance), skipInstaBreakingBlock); + } + + @Override + public ItemStack test(BlockState state, Level world, BlockPos pos) { + if (skipInstaBreakingBlock && !state.requiresCorrectToolForDrops() && state.getDestroySpeed(world, pos) == 0) { + return ItemStack.EMPTY; + } + return test(state); + } + + public ItemStack test(BlockState state) { + for (ItemStack toolItem : tools) { + if (toolItem.isCorrectToolForDrops(state)) { + return toolItem; + } + Tool tool = toolItem.get(DataComponents.TOOL); + if (tool != null && tool.getMiningSpeed(state) > tool.defaultMiningSpeed()) { + return toolItem; + } + } + return ItemStack.EMPTY; + } + + @Override + public List getTools() { + return tools; + } + + @Override + public ResourceLocation getUid() { + return uid; + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..18f11e701189ce3615e08c631e31112d53ea5686 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/tool/ToolHandler.java @@ -0,0 +1,17 @@ +package org.leavesmc.leaves.protocol.jade.tool; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.List; + +public interface ToolHandler extends IJadeProvider { + + ItemStack test(BlockState state, Level world, BlockPos pos); + + List getTools(); + +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a85361693980eb5b0bf7f9d486475c77b646f7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/CommonUtil.java @@ -0,0 +1,79 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.boss.EnderDragonPart; +import net.minecraft.world.entity.boss.enderdragon.EnderDragon; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.SkullBlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.protocol.jade.accessor.Accessor; +import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class CommonUtil { + + public static @NotNull ResourceLocation getId(Block block) { + return BuiltInRegistries.BLOCK.getKey(block); + } + + public static Entity wrapPartEntityParent(Entity target) { + if (target instanceof EnderDragonPart part) { + return part.parentMob; + } + return target; + } + + public static Entity getPartEntity(Entity parent, int index) { + if (parent == null) { + return null; + } + if (index < 0) { + return parent; + } + if (parent instanceof EnderDragon dragon) { + EnderDragonPart[] parts = dragon.getSubEntities(); + if (index < parts.length) { + return parts[index]; + } + } + return parent; + } + + + @Nullable + public static String getLastKnownUsername(@Nullable UUID uuid) { + if (uuid == null) { + return null; + } + Optional optional = SkullBlockEntity.fetchGameProfile(String.valueOf(uuid)).getNow(Optional.empty()); + return optional.map(GameProfile::getName).orElse(null); + } + + + public static Map.Entry>> getServerExtensionData( + Accessor accessor, + WrappedHierarchyLookup> lookup) { + for (var provider : lookup.wrappedGet(accessor)) { + List> groups; + try { + groups = provider.getGroups(accessor); + } catch (Exception e) { + LeavesLogger.LOGGER.severe(e.toString()); + continue; + } + if (groups != null) { + return Map.entry(provider.getUid(), groups); + } + } + return null; + } +} 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..0070fd22b096281a094d1cd19c93fdbccc03a3cc --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/HierarchyLookup.java @@ -0,0 +1,139 @@ +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.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; +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; + protected boolean idMapped; + @Nullable + protected IdMapper idMapper; + 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 idMapped() { + this.idMapped = true; + } + + @Override + @Nullable + public IdMapper idMapper() { + return idMapper; + } + + @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.warning("HierarchyLookup error", e); + } + 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(); + + if (idMapped) { + idMapper = createIdMapper(); + } + } + +} \ No newline at end of file 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..137cdf619879390477b4fc8c4b7ecee5b762dc30 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/IHierarchyLookup.java @@ -0,0 +1,66 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Streams; +import net.minecraft.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public interface IHierarchyLookup { + default IHierarchyLookup cast() { + return this; + } + + void idMapped(); + + @Nullable + IdMapper idMapper(); + + default List mappedIds() { + return Streams.stream(Objects.requireNonNull(idMapper())) + .map(IJadeProvider::getUid) + .toList(); + } + + 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); + + default IdMapper createIdMapper() { + List list = entries().flatMap(entry -> entry.getValue().stream()).toList(); + IdMapper idMapper = idMapper(); + if (idMapper == null) { + idMapper = new IdMapper<>(list.size()); + } + for (T provider : list) { + if (idMapper.getId(provider) == -1) { + idMapper.add(provider); + } + } + return idMapper; + } +} + 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..769c331035e59408064b63a29d8bf2194b386aa0 --- /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.copyTag(); + 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/JadeCodec.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java new file mode 100644 index 0000000000000000000000000000000000000000..a046ae4e542efcadd0001b7225440c309a7a7570 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/JadeCodec.java @@ -0,0 +1,59 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.NotNull; + +public class JadeCodec { + public static final StreamCodec PRIMITIVE_STREAM_CODEC = new StreamCodec<>() { + @Override + public @NotNull Object decode(@NotNull ByteBuf buf) { + byte b = buf.readByte(); + if (b == 0) { + return false; + } else if (b == 1) { + return true; + } else if (b == 2) { + return ByteBufCodecs.VAR_INT.decode(buf); + } else if (b == 3) { + return ByteBufCodecs.FLOAT.decode(buf); + } else if (b == 4) { + return ByteBufCodecs.STRING_UTF8.decode(buf); + } else if (b > 20) { + return b - 20; + } + throw new IllegalArgumentException("Unknown primitive type: " + b); + } + + @Override + public void encode(@NotNull ByteBuf buf, @NotNull Object o) { + switch (o) { + case Boolean b -> buf.writeByte(b ? 1 : 0); + case Number n -> { + float f = n.floatValue(); + if (f != (int) f) { + buf.writeByte(3); + ByteBufCodecs.FLOAT.encode(buf, f); + } + int i = n.intValue(); + if (i <= Byte.MAX_VALUE - 20 && i >= 0) { + buf.writeByte(i + 20); + } else { + ByteBufCodecs.VAR_INT.encode(buf, i); + } + } + case String s -> { + buf.writeByte(4); + ByteBufCodecs.STRING_UTF8.encode(buf, s); + } + case Enum anEnum -> { + buf.writeByte(4); + ByteBufCodecs.STRING_UTF8.encode(buf, anEnum.name()); + } + case null -> throw new NullPointerException(); + default -> throw new IllegalArgumentException("Unknown primitive type: %s (%s)".formatted(o, o.getClass())); + } + } + }; +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java new file mode 100644 index 0000000000000000000000000000000000000000..63f4d0a31232525db3620095fc662f0225c5f306 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java @@ -0,0 +1,105 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Lists; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.AlternativesEntry; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.entries.NestedLootTable; +import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler; + +import java.util.List; +import java.util.function.Function; + +public class LootTableMineableCollector { + + private final Registry lootRegistry; + private final ItemStack toolItem; + + public LootTableMineableCollector(Registry lootRegistry, ItemStack toolItem) { + this.lootRegistry = lootRegistry; + this.toolItem = toolItem; + } + + public static @NotNull List execute(Registry lootRegistry, ItemStack toolItem) { + LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem); + List list = Lists.newArrayList(); + for (Block block : BuiltInRegistries.BLOCK) { + if (!ShearsToolHandler.getInstance().test(block.defaultBlockState()).isEmpty()) { + continue; + } + + LootTable lootTable = lootRegistry.get(block.getLootTable()); + if (collector.doLootTable(lootTable)) { + list.add(block); + } + } + return list; + } + + private boolean doLootTable(LootTable lootTable) { + if (lootTable == null || lootTable == LootTable.EMPTY) { + return false; + } + + for (LootPool pool : lootTable.pools) { + if (doLootPool(pool)) { + return true; + } + } + return false; + } + + private boolean doLootPool(@NotNull LootPool lootPool) { + for (LootPoolEntryContainer entry : lootPool.entries) { + if (doLootPoolEntry(entry)) { + return true; + } + } + return false; + } + + private boolean doLootPoolEntry(LootPoolEntryContainer entry) { + if (entry instanceof AlternativesEntry alternativesEntry) { + for (LootPoolEntryContainer child : alternativesEntry.children) { + if (doLootPoolEntry(child)) { + return true; + } + } + } else if (entry instanceof NestedLootTable nestedLootTable) { + LootTable lootTable = nestedLootTable.contents.map(lootRegistry::get, Function.identity()); + return doLootTable(lootTable); + } else { + return isCorrectConditions(entry.conditions, toolItem); + } + return false; + } + + public static boolean isCorrectConditions(@NotNull List conditions, ItemStack toolItem) { + if (conditions.size() != 1) { + return false; + } + + LootItemCondition condition = conditions.getFirst(); + if (condition instanceof MatchTool matchTool) { + ItemPredicate itemPredicate = matchTool.predicate().orElse(null); + return itemPredicate != null && itemPredicate.test(toolItem); + } else if (condition instanceof AnyOfCondition anyOfCondition) { + for (LootItemCondition child : anyOfCondition.terms) { + if (isCorrectConditions(List.of(child), toolItem)) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java new file mode 100644 index 0000000000000000000000000000000000000000..cb5c8201958b3f444d990082d7aac615090cc2a8 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java @@ -0,0 +1,120 @@ +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.core.IdMapper; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.Nullable; +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(); + protected boolean idMapped; + @Nullable + protected IdMapper idMapper; + + 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.toString()); + } + return List.of(); + } + + @Override + public void idMapped() { + idMapped = true; + } + + @Override + public @Nullable IdMapper idMapper() { + return idMapper; + } + + @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); + if (idMapped) { + idMapper = createIdMapper(); + } + } +} \ No newline at end of file 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..da4d5a7751b1076417e63b63dc1f91c0fcc73ff9 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PriorityStore.java @@ -0,0 +1,40 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import java.util.Objects; +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; + + public PriorityStore(ToIntFunction defaultPriorityGetter, Function keyGetter) { + this.defaultPriorityGetter = defaultPriorityGetter; + this.keyGetter = keyGetter; + } + + 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 int byValue(V value) { + return byKey(keyGetter.apply(value)); + } + + public int byKey(K id) { + return priorities.getInt(id); + } +} 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..520eadbf6de55141524741b4e4063cd542ef7128 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/ViewGroup.java @@ -0,0 +1,62 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import io.netty.buffer.ByteBuf; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ViewGroup { + public static StreamCodec> codec(StreamCodec viewCodec) { + return StreamCodec.composite( + ByteBufCodecs.list().apply(viewCodec), + $ -> $.views, + ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), + $ -> Optional.ofNullable($.id), + ByteBufCodecs.optional(ByteBufCodecs.COMPOUND_TAG), + $ -> Optional.ofNullable($.extraData), + ViewGroup::new); + } + + public static StreamCodec>>> listCodec(StreamCodec viewCodec) { + return StreamCodec.composite( + ResourceLocation.STREAM_CODEC, + Map.Entry::getKey, + ByteBufCodecs.>list().apply(codec(viewCodec)), + Map.Entry::getValue, + Map::entry); + } + + public List views; + @Nullable + public String id; + @Nullable + protected CompoundTag extraData; + + public ViewGroup(List views) { + this(views, Optional.empty(), Optional.empty()); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public ViewGroup(List views, Optional id, Optional extraData) { + this.views = views; + this.id = id.orElse(null); + this.extraData = extraData.orElse(null); + } + + public CompoundTag getExtraData() { + if (extraData == null) { + extraData = new CompoundTag(); + } + return extraData; + } + + public void setProgress(float progress) { + getExtraData().putFloat("Progress", progress); + } +} 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..11d66b88327a954c8d530a002aa87c9abae8da12 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/WrappedHierarchyLookup.java @@ -0,0 +1,96 @@ +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.Accessor; +import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor; +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.getBlock(); + } + return null; + })); + } + + public List wrappedGet(Accessor 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.getTarget())); + 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; + } +}