From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:34:15 +0800 Subject: [PATCH] PCA sync protocol diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index c910fe0ed602d2444c0695159961867f9472d66f..6c6032bbf09a47319e9bc2dcccbb9a4b54598f12 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -245,6 +245,7 @@ import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.SmithingInventory; +import top.leavesmc.leaves.util.ProtocolUtils; // CraftBukkit end public class ServerGamePacketListenerImpl implements ServerPlayerConnection, TickablePacketListener, ServerGamePacketListener { @@ -3698,6 +3699,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause } } else { + // Leaves start - leaves extra protocol + try { + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol && ProtocolUtils.isNamespacePacket(packet, top.leavesmc.leaves.protocol.PcaSyncProtocol.PROTOCOL_ID)) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.handlePacket(server, player, packet); + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); + this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); + } + // Leaves end - leaves extra protocol try { byte[] data = new byte[packet.data.readableBytes()]; packet.data.readBytes(data); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index d0dfbb0341a0d614a38975dec82eee4a5c950526..4e3e0f7bfe54616fb6733ef2f8f05959eee0f47e 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -387,6 +387,7 @@ public abstract class PlayerList { bot1.render(playerconnection, true,player.getBukkitEntity().getWorld() == bot1.getBukkitEntity().getWorld())); // Leaves - render bot } // Leaves end - bot support + top.leavesmc.leaves.protocol.PcaSyncProtocol.onJoin(player); // Leaves - pca final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java index a8da601b8342aa6e4902b452eb588c76c98a7adf..1fac7a290d22ca4becea60282dc5f4eea97d0015 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java @@ -324,6 +324,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, @Override public void containerChanged(Container sender) { + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncEntityToClient(this); + } + // Leaves end - pca boolean flag = this.isSaddled(); this.updateContainerEquipment(); diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java index 5eab7d50734551d96128dfebee126a1da4c51375..bf5b421cddfd2fe6cce3b9cf14415e4ec8cecd42 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -65,6 +65,15 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa super(type, world); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 16.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F); + // Leaves start - pca + if (!this.level.isClientSide()) { + this.inventory.addListener(inventory -> { + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncEntityToClient(this); + } + }); + } + // Leaves end - pca } @Override diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java index b8fb7b5a347298ada16bc8b818edf1863e3f6040..ca71b5b61d85df4a3e74c69823184b52cab98f67 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java @@ -129,7 +129,13 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme } @Override - public void setChanged() {} + public void setChanged() { + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncEntityToClient(this); + } + // Leaves end - pca + } @Override public boolean stillValid(Player player) { diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java index c6aeda6497cb59673b469588142f5f15a338389d..ed09bac5e6ddb1f134353af70615dcce707363bb 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java @@ -574,6 +574,16 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override public boolean stillValid(net.minecraft.world.entity.player.Player player) { return this.level.getBlockEntity(this.worldPosition) != this ? false : player.distanceToSqr((double) this.worldPosition.getX() + 0.5D, (double) this.worldPosition.getY() + 0.5D, (double) this.worldPosition.getZ() + 0.5D) <= 64.0D; diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java index f52487e1cfcfab1bf22ab2cb52f998283a86e340..82f883f678eae83b028f78861de9ff2fd3471440 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java @@ -131,6 +131,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { this.items = list; } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override protected Component getDefaultName() { return Component.translatable("container.barrel"); diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java index 41c9f074203915c31c1ae7a160ce509c13383f84..4c9e84dcd154a4306659ef41ab40127ea8452835 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -127,6 +128,11 @@ public class BeehiveBlockEntity extends BlockEntity { super.setChanged(); } + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + // Leaves end - pca return list; } @@ -188,6 +194,12 @@ public class BeehiveBlockEntity extends BlockEntity { this.level.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, this.getBlockState())); } + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + // Leaves end - pca + entity.discard(); super.setChanged(); } @@ -334,6 +346,11 @@ public class BeehiveBlockEntity extends BlockEntity { if (BeehiveBlockEntity.releaseOccupant(world, pos, state, tileentitybeehive_hivebee, (List) null, tileentitybeehive_releasestatus, flowerPos)) { flag = true; iterator.remove(); + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(Objects.requireNonNull(world.getBlockEntity(pos))); + } + // Leaves end - pca // CraftBukkit start } else { tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - use exitTickCounter to keep actual bee life @@ -385,6 +402,11 @@ public class BeehiveBlockEntity extends BlockEntity { this.maxBees = nbt.getInt("Bukkit.MaxEntities"); } // CraftBukkit end + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + // Leaves end - pca } @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java index 55006724ccec9f3de828ec18693728e9741ff65f..4391127a685fa16ce76e35f7c17b2d7056b13ea1 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java @@ -328,6 +328,16 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override public boolean stillValid(Player player) { return this.level.getBlockEntity(this.worldPosition) != this ? false : player.distanceToSqr((double) this.worldPosition.getX() + 0.5D, (double) this.worldPosition.getY() + 0.5D, (double) this.worldPosition.getZ() + 0.5D) <= 64.0D; diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java index a71414397bd45ee7bcacfeef0041d80dfa25f114..1d6c7a04b8246a60d80325e1615e50573b17a6bb 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java @@ -190,6 +190,16 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement this.items = list; } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override public float getOpenNess(float tickDelta) { return this.chestLidController.getOpenness(tickDelta); diff --git a/src/main/java/net/minecraft/world/level/block/entity/ComparatorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ComparatorBlockEntity.java index 9b2c162c362fcf6093a3bf6da715ae8f18176c82..84b877a712a883b868b139d29da7e09c9552a1e5 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ComparatorBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ComparatorBlockEntity.java @@ -23,6 +23,16 @@ public class ComparatorBlockEntity extends BlockEntity { this.output = nbt.getInt("OutputSignal"); } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + public int getOutputSignal() { return this.output; } diff --git a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java index 881379681c39230a00b3a1f11cd87498984396c7..e01eb2025458cf311348c58a1530854053af78f4 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/DispenserBlockEntity.java @@ -92,6 +92,16 @@ public class DispenserBlockEntity extends RandomizableContainerBlockEntity { return -1; } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override protected Component getDefaultName() { return Component.translatable("container.dispenser"); diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java index ccad692aba2ed77259f6814d88f01b91ed9d229b..01bf32fd730000bdf3a71abe0ad9546ce3775ee7 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java @@ -125,6 +125,16 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override protected Component getDefaultName() { return Component.translatable("container.hopper"); @@ -165,6 +175,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen if (flag) { blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Spigot setChanged(world, pos, state); + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(blockEntity); + } + // Leaves end - pca return true; } } diff --git a/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java index 137bdf1168b9e1d00164471e4e79f56c000c2696..47522524ca4e7105f5fa624fc7c438f0c4f9a8c5 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java @@ -259,6 +259,16 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl this.itemStacks = list; } + // Leaves start - pca + @Override + public void setChanged() { + super.setChanged(); + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.syncBlockEntityToClient(this); + } + } + // Leaves end - pca + @Override public int[] getSlotsForFace(Direction side) { return ShulkerBoxBlockEntity.SLOTS; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index eb7733699a417d1ee73bc0c9d08eba61a34d8b7b..23be24cfc89abb7474464be3bb56e47f1d2e971b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -372,6 +372,7 @@ public final class CraftServer implements Server { MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); } datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + top.leavesmc.leaves.protocol.PcaSyncProtocol.init(); // Leaves - pca } public boolean getCommandBlockOverride(String command) { @@ -967,6 +968,13 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); top.leavesmc.leaves.LeavesConfig.init((File) console.options.valueOf("leaves-settings")); // Leaves - Server Config + // Leaves start - pca + if (top.leavesmc.leaves.LeavesConfig.pcaSyncProtocol) { + top.leavesmc.leaves.protocol.PcaSyncProtocol.enablePcaSyncProtocolGlobal(); + } else { + top.leavesmc.leaves.protocol.PcaSyncProtocol.disablePcaSyncProtocolGlobal(); + } + // Leaves end - pca for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java index 40d1d67172a90123a23020e71e06ced9d859d752..51a576f75c065a896f4f2a223c3e87241c925720 100644 --- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java @@ -383,6 +383,21 @@ public final class LeavesConfig { simplerVanillaShapelessRecipes = getBoolean("settings.performance.simpler-vanilla-shapeless-recipes", simplerVanillaShapelessRecipes); } + public static boolean pcaSyncProtocol = false; + private static void pcaSyncProtocol() { + pcaSyncProtocol = getBoolean("settings.protocol.pca-sync-protocol", pcaSyncProtocol); + } + + public static String pcaSyncPlayerEntity = "OPS"; + private static final List pcaSyncPlayerEntityList = List.of("NOBODY", "BOT", "OPS", "OPS_AND_SELF", "EVERYONE"); + private static void pcaSyncPlayerEntity() { + pcaSyncPlayerEntity = getString("settings.protocol.pca-sync-player-entity", pcaSyncPlayerEntity); + if (!pcaSyncPlayerEntityList.contains(pcaSyncPlayerEntity)) { + pcaSyncPlayerEntity = "OPS"; + LeavesLogger.LOGGER.severe("pca-sync-player-entity value error, reset to OPS"); + } + } + public static final class WorldConfig { public final String worldName; diff --git a/src/main/java/top/leavesmc/leaves/protocol/PcaSyncProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/PcaSyncProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f84c38aaae14a093bfe8e901a3be802617fb12 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/PcaSyncProtocol.java @@ -0,0 +1,357 @@ +package top.leavesmc.leaves.protocol; + +import io.netty.buffer.Unpooled; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +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.level.block.ChestBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.ChestType; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.util.TriConsumer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.leavesmc.leaves.LeavesConfig; +import top.leavesmc.leaves.LeavesLogger; +import top.leavesmc.leaves.bot.ServerBot; +import top.leavesmc.leaves.util.ProtocolUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +public class PcaSyncProtocol { + + public static final String PROTOCOL_ID = "pca"; + + public static final ReentrantLock lock = new ReentrantLock(true); + public static final ReentrantLock pairLock = new ReentrantLock(true); + + // send + private static final ResourceLocation ENABLE_PCA_SYNC_PROTOCOL = id("enable_pca_sync_protocol"); + private static final ResourceLocation DISABLE_PCA_SYNC_PROTOCOL = id("disable_pca_sync_protocol"); + private static final ResourceLocation UPDATE_ENTITY = id("update_entity"); + private static final ResourceLocation UPDATE_BLOCK_ENTITY = id("update_block_entity"); + // call + private static final ResourceLocation SYNC_BLOCK_ENTITY = id("sync_block_entity"); + private static final ResourceLocation SYNC_ENTITY = id("sync_entity"); + private static final ResourceLocation CANCEL_SYNC_BLOCK_ENTITY = id("cancel_sync_block_entity"); + private static final ResourceLocation CANCEL_SYNC_ENTITY = id("cancel_sync_entity"); + private static final Map> playerWatchBlockPos = new HashMap<>(); + private static final Map> playerWatchEntity = new HashMap<>(); + private static final Map, Set> blockPosWatchPlayerSet = new HashMap<>(); + private static final Map, Set> entityWatchPlayerSet = new HashMap<>(); + private static final MutablePair ResourceLocationEntityPair = new MutablePair<>(); + private static final MutablePair ResourceLocationBlockPosPair = new MutablePair<>(); + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return new ResourceLocation(PROTOCOL_ID, path); + } + + public static void enablePcaSyncProtocol(@NotNull ServerPlayer player) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + ProtocolUtils.sendPayloadPacket(player, ENABLE_PCA_SYNC_PROTOCOL, buf); + lock.lock(); + lock.unlock(); + } + + public static void disablePcaSyncProtocol(@NotNull ServerPlayer player) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + ProtocolUtils.sendPayloadPacket(player, DISABLE_PCA_SYNC_PROTOCOL, buf); + } + + public static void updateEntity(@NotNull ServerPlayer player, @NotNull Entity entity) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeResourceLocation(entity.getLevel().dimension().location()); + buf.writeInt(entity.getId()); + buf.writeNbt(entity.saveWithoutId(new CompoundTag())); + ProtocolUtils.sendPayloadPacket(player, UPDATE_ENTITY, buf); + } + + public static void updateBlockEntity(@NotNull ServerPlayer player, @NotNull BlockEntity blockEntity) { + Level world = blockEntity.getLevel(); + + if (world == null) { + return; + } + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeResourceLocation(world.dimension().location()); + buf.writeBlockPos(blockEntity.getBlockPos()); + buf.writeNbt(blockEntity.saveWithId()); + ProtocolUtils.sendPayloadPacket(player, UPDATE_BLOCK_ENTITY, buf); + } + + private static final Map> globalReceivers = new HashMap<>(); + + private static void registerGlobalReceiver(ResourceLocation identifier, TriConsumer consumer) { + globalReceivers.put(identifier, consumer); + } + + public static void handlePacket(MinecraftServer server, ServerPlayer player, @NotNull ServerboundCustomPayloadPacket packet) { + globalReceivers.getOrDefault( + packet.identifier, ((server1, player1, buf) -> LeavesLogger.LOGGER.severe("NullPotion")) + ).accept(server, player, packet.data); + } + + public static void init() { + registerGlobalReceiver(SYNC_BLOCK_ENTITY, PcaSyncProtocol::syncBlockEntityHandler); + registerGlobalReceiver(SYNC_ENTITY, PcaSyncProtocol::syncEntityHandler); + registerGlobalReceiver(CANCEL_SYNC_BLOCK_ENTITY, PcaSyncProtocol::cancelSyncBlockEntityHandler); + registerGlobalReceiver(CANCEL_SYNC_ENTITY, PcaSyncProtocol::cancelSyncEntityHandler); + } + + public static void onJoin(ServerPlayer player) { + if (LeavesConfig.pcaSyncProtocol) { + enablePcaSyncProtocol(player); + } + } + + private static void cancelSyncBlockEntityHandler(MinecraftServer server, ServerPlayer player, + FriendlyByteBuf buf) { + if (!LeavesConfig.pcaSyncProtocol) { + return; + } + PcaSyncProtocol.clearPlayerWatchBlock(player); + } + + private static void cancelSyncEntityHandler(MinecraftServer server, ServerPlayer player, + FriendlyByteBuf buf) { + if (!LeavesConfig.pcaSyncProtocol) { + return; + } + PcaSyncProtocol.clearPlayerWatchEntity(player); + } + + private static void syncBlockEntityHandler(MinecraftServer server, ServerPlayer player, + FriendlyByteBuf buf) { + if (!LeavesConfig.pcaSyncProtocol) { + return; + } + BlockPos pos = buf.readBlockPos(); + ServerLevel world = player.getLevel(); + BlockState blockState = world.getBlockState(pos); + clearPlayerWatchData(player); + + BlockEntity blockEntityAdj = null; + if (blockState.getBlock() instanceof ChestBlock) { + if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { + BlockPos posAdj = pos.offset(ChestBlock.getConnectedDirection(blockState).getNormal()); + // The method in World now checks that the caller is from the same thread... + blockEntityAdj = world.getChunk(posAdj).getBlockEntity(posAdj); + } + } + + if (blockEntityAdj != null) { + updateBlockEntity(player, blockEntityAdj); + } + + // The method in World now checks that the caller is from the same thread... + BlockEntity blockEntity = world.getChunk(pos).getBlockEntity(pos); + if (blockEntity != null) { + updateBlockEntity(player, blockEntity); + } + + Pair pair = new ImmutablePair<>(player.getLevel().dimension().location(), pos); + lock.lock(); + playerWatchBlockPos.put(player, pair); + if (!blockPosWatchPlayerSet.containsKey(pair)) { + blockPosWatchPlayerSet.put(pair, new HashSet<>()); + } + blockPosWatchPlayerSet.get(pair).add(player); + lock.unlock(); + } + + private static void syncEntityHandler(MinecraftServer server, ServerPlayer player, + FriendlyByteBuf buf) { + if (!LeavesConfig.pcaSyncProtocol) { + return; + } + int entityId = buf.readInt(); + ServerLevel world = player.getLevel(); + Entity entity = world.getEntity(entityId); + if (entity != null) { + clearPlayerWatchData(player); + if (entity instanceof Player) { + if (LeavesConfig.pcaSyncPlayerEntity.equals("NOBODY")) { + return; + } else if (LeavesConfig.pcaSyncPlayerEntity.equals("BOT")) { + if (!(entity instanceof ServerBot)) { + return; + } + } else if (LeavesConfig.pcaSyncPlayerEntity.equals("OPS")) { + if (!(entity instanceof ServerBot) && server.getProfilePermissions(player.getGameProfile()) < 2) { + return; + } + } else if (LeavesConfig.pcaSyncPlayerEntity.equals("OPS_AND_SELF")) { + if (!(entity instanceof ServerBot) && + server.getProfilePermissions(player.getGameProfile()) < 2 && + entity != player) { + return; + } + } else if (!LeavesConfig.pcaSyncPlayerEntity.equals("EVERYONE")) { + // wtf???? + LeavesLogger.LOGGER.warning("pcaSyncPlayerEntity wtf???"); + return; + } + } + updateEntity(player, entity); + + Pair pair = new ImmutablePair<>(entity.getLevel().dimension().location(), entity); + lock.lock(); + playerWatchEntity.put(player, pair); + if (!entityWatchPlayerSet.containsKey(pair)) { + entityWatchPlayerSet.put(pair, new HashSet<>()); + } + entityWatchPlayerSet.get(pair).add(player); + lock.unlock(); + } + } + + private static MutablePair getResourceLocationEntityPair(ResourceLocation ResourceLocation, Entity entity) { + pairLock.lock(); + ResourceLocationEntityPair.setLeft(ResourceLocation); + ResourceLocationEntityPair.setRight(entity); + pairLock.unlock(); + return ResourceLocationEntityPair; + } + + private static MutablePair getResourceLocationBlockPosPair(ResourceLocation ResourceLocation, BlockPos pos) { + pairLock.lock(); + ResourceLocationBlockPosPair.setLeft(ResourceLocation); + ResourceLocationBlockPosPair.setRight(pos); + pairLock.unlock(); + return ResourceLocationBlockPosPair; + } + + private static @Nullable Set getWatchPlayerList(@NotNull Entity entity) { + return entityWatchPlayerSet.get(getResourceLocationEntityPair(entity.getLevel().dimension().location(), entity)); + } + + private static @Nullable Set getWatchPlayerList(@NotNull Level world, @NotNull BlockPos blockPos) { + return blockPosWatchPlayerSet.get(getResourceLocationBlockPosPair(world.dimension().location(), blockPos)); + } + + public static boolean syncEntityToClient(@NotNull Entity entity) { + if (entity.getLevel().isClientSide()) { + return false; + } + lock.lock(); + Set playerList = getWatchPlayerList(entity); + boolean ret = false; + if (playerList != null) { + for (ServerPlayer player : playerList) { + updateEntity(player, entity); + ret = true; + } + } + lock.unlock(); + return ret; + } + + public static boolean syncBlockEntityToClient(@NotNull BlockEntity blockEntity) { + boolean ret = false; + Level world = blockEntity.getLevel(); + BlockPos pos = blockEntity.getBlockPos(); + if (world != null) { + if (world.isClientSide()) { + return false; + } + BlockState blockState = world.getBlockState(pos); + lock.lock(); + Set playerList = getWatchPlayerList(world, blockEntity.getBlockPos()); + + Set playerListAdj = null; + + if (blockState.getBlock() instanceof ChestBlock) { + if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { + BlockPos posAdj = pos.offset(ChestBlock.getConnectedDirection(blockState).getNormal()); + playerListAdj = getWatchPlayerList(world, posAdj); + } + } + if (playerListAdj != null) { + if (playerList == null) { + playerList = playerListAdj; + } else { + playerList.addAll(playerListAdj); + } + } + + if (playerList != null) { + for (ServerPlayer player : playerList) { + updateBlockEntity(player, blockEntity); + ret = true; + } + } + lock.unlock(); + } + return ret; + } + + private static void clearPlayerWatchEntity(ServerPlayer player) { + lock.lock(); + Pair pair = playerWatchEntity.get(player); + if (pair != null) { + Set playerSet = entityWatchPlayerSet.get(pair); + playerSet.remove(player); + if (playerSet.isEmpty()) { + entityWatchPlayerSet.remove(pair); + } + playerWatchEntity.remove(player); + } + lock.unlock(); + } + + private static void clearPlayerWatchBlock(ServerPlayer player) { + lock.lock(); + Pair pair = playerWatchBlockPos.get(player); + if (pair != null) { + Set playerSet = blockPosWatchPlayerSet.get(pair); + playerSet.remove(player); + if (playerSet.isEmpty()) { + blockPosWatchPlayerSet.remove(pair); + } + playerWatchBlockPos.remove(player); + } + lock.unlock(); + } + + public static void disablePcaSyncProtocolGlobal() { + lock.lock(); + playerWatchBlockPos.clear(); + playerWatchEntity.clear(); + blockPosWatchPlayerSet.clear(); + entityWatchPlayerSet.clear(); + lock.unlock(); + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + disablePcaSyncProtocol(player); + } + } + + public static void enablePcaSyncProtocolGlobal() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + enablePcaSyncProtocol(player); + } + } + + + public static void clearPlayerWatchData(ServerPlayer player) { + PcaSyncProtocol.clearPlayerWatchBlock(player); + PcaSyncProtocol.clearPlayerWatchEntity(player); + } +}