From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:00:41 +0800 Subject: [PATCH] Leaves Protocol Core diff --git a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java index 0211311b3b63bcdea7ebf7bcb24629674c771402..c05a72f4928ee2cec28a61ed06a9079d52634900 100644 --- a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +++ b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java @@ -40,13 +40,23 @@ public interface CustomPacketPayload { @Override public void encode(B friendlyByteBuf, CustomPacketPayload customPacketPayload) { + // Leaves start - protocol core + if (customPacketPayload instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload leavesCustomPayload) { + friendlyByteBuf.writeResourceLocation(leavesCustomPayload.id()); + leavesCustomPayload.write(friendlyByteBuf); + return; + } + // Leaves end - protocol core this.writeCap(friendlyByteBuf, customPacketPayload.type(), customPacketPayload); } @Override public CustomPacketPayload decode(B friendlyByteBuf) { ResourceLocation resourceLocation = friendlyByteBuf.readResourceLocation(); - return (CustomPacketPayload)this.findCodec(resourceLocation).decode(friendlyByteBuf); + // Leaves start - protocol core + var leavesCustomPayload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, friendlyByteBuf); + return java.util.Objects.requireNonNullElseGet(leavesCustomPayload, () -> (CustomPacketPayload) this.findCodec(resourceLocation).decode(friendlyByteBuf)); + // Leaves end - protocol core } }; } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 421b67ab81330975f6404578ffcc57feb0c9c0be..f1f0e7adada3527ac666623d63376e6cbd590630 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1782,6 +1782,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop leavesPayload) { + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(player, leavesPayload); + } + // Leaves end - protocol // Paper start - Brand support if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { this.player.clientBrandName = brandPayload.brand(); @@ -165,6 +170,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack String channels = payload.toString(com.google.common.base.Charsets.UTF_8); for (String channel : channels.split("\0")) { this.getCraftPlayer().addChannel(channel); + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, player); // Leaves - protocol } } catch (Exception ex) { ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 6957cbfbea51d6a3b57e1c5bfcebd52a25cde8d5..11d73647d2d94c8131c5e3eeef490fb3472fe0a4 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -349,6 +349,8 @@ public abstract class PlayerList { return; } + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol + final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure @@ -595,6 +597,7 @@ public abstract class PlayerList { return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); } public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(entityplayer); // Leaves - protocol // Paper end - Fix kick event leave message not being sent ServerLevel worldserver = entityplayer.serverLevel(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 9ffd97be26f52f5ca69ec14a3f013c59b33d3704..196ab702e5413612632afe945253ed8d00817893 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -468,6 +468,7 @@ public final class CraftServer implements Server { } this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol } public boolean getCommandBlockOverride(String command) { @@ -1067,6 +1068,7 @@ public final class CraftServer implements Server { io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol int pollCount = 0; diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java new file mode 100644 index 0000000000000000000000000000000000000000..243a8d77d67b9634d6442b60e481d18831367a74 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java @@ -0,0 +1,26 @@ +package org.leavesmc.leaves.protocol.core; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public interface LeavesCustomPayload> extends CustomPacketPayload { + + @Retention(RetentionPolicy.RUNTIME) + @interface New { + } + + void write(FriendlyByteBuf buf); + + ResourceLocation id(); + + @Override + @NotNull + default Type type() { + return new CustomPacketPayload.Type<>(id()); + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..986d2a6641ff8017dddf3e5f2655adfc2866c119 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.protocol.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface LeavesProtocol { + String[] namespace(); +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java new file mode 100644 index 0000000000000000000000000000000000000000..3a33982b92bfb12ce1284fb9429672a698a7ce4b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java @@ -0,0 +1,365 @@ +package org.leavesmc.leaves.protocol.core; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang.ArrayUtils; +import org.bukkit.event.player.PlayerKickEvent; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesLogger; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class LeavesProtocolManager { + + private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; + + private static final Map>>> KNOWN_TYPES = new HashMap<>(); + private static final Map> KNOW_RECEIVERS = new HashMap<>(); + + private static final List TICKERS = new ArrayList<>(); + private static final List PLAYER_JOIN = new ArrayList<>(); + private static final List PLAYER_LEAVE = new ArrayList<>(); + private static final List RELOAD_SERVER = new ArrayList<>(); + private static final Map> MINECRAFT_REGISTER = new HashMap<>(); + + public static void init() { + for (Class clazz : getClasses("org.leavesmc.leaves.protocol")) { + final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class); + if (protocol != null) { + Set methods; + try { + Method[] publicMethods = clazz.getMethods(); + Method[] privateMethods = clazz.getDeclaredMethods(); + methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f); + Collections.addAll(methods, publicMethods); + Collections.addAll(methods, privateMethods); + } catch (NoClassDefFoundError error) { + LOGGER.severe("Failed to load class " + clazz.getName() + " due to missing dependencies, " + error.getCause() + ": " + error.getMessage()); + return; + } + + Map>> map = KNOWN_TYPES.getOrDefault(protocol, new HashMap<>()); + for (final Method method : methods) { + if (method.isBridge() || method.isSynthetic() || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + method.setAccessible(true); + + final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class); + if (init != null) { + try { + method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.severe("Failed to invoke init method " + method.getName() + " in " + clazz.getName() + ", " + exception.getCause() + ": " + exception.getMessage()); + } + continue; + } + + final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class); + if (receiver != null) { + try { + Constructor> constructor = receiver.payload().getConstructor(ResourceLocation.class, FriendlyByteBuf.class); + constructor.setAccessible(true); + + if (constructor.getAnnotation(LeavesCustomPayload.New.class) == null) { + LOGGER.warning("Failed to find new annotation for " + receiver.payload().getName()); + } + + map.put(receiver, constructor); + } catch (NoSuchMethodException exception) { + LOGGER.severe("Failed to find constructor for " + receiver.payload().getName() + ", " + exception.getCause() + ": " + exception.getMessage()); + continue; + } + + if (!KNOW_RECEIVERS.containsKey(protocol)) { + KNOW_RECEIVERS.put(protocol, new HashMap<>()); + } + + KNOW_RECEIVERS.get(protocol).put(receiver, method); + continue; + } + + final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class); + if (ticker != null) { + TICKERS.add(method); + continue; + } + + final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class); + if (playerJoin != null) { + PLAYER_JOIN.add(method); + continue; + } + + final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class); + if (playerLeave != null) { + PLAYER_LEAVE.add(method); + continue; + } + + final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class); + if (reloadServer != null) { + RELOAD_SERVER.add(method); + continue; + } + + final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class); + if (minecraftRegister != null) { + if (!MINECRAFT_REGISTER.containsKey(protocol)) { + MINECRAFT_REGISTER.put(protocol, new HashMap<>()); + } + + MINECRAFT_REGISTER.get(protocol).put(minecraftRegister, method); + } + } + KNOWN_TYPES.put(protocol, map); + } + } + } + + public static LeavesCustomPayload decode(ResourceLocation id, FriendlyByteBuf buf) { + for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) { + continue; + } + + Map>> map = KNOWN_TYPES.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), id.getPath())) { + try { + return map.get(receiver).newInstance(id, buf); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException exception) { + LOGGER.warning("Failed to create payload for " + id + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); + buf.readBytes(buf.readableBytes()); + return new ErrorPayload(id, protocol.namespace(), receiver.payloadId()); + } + } + } + } + return null; + } + + public static void handlePayload(ServerPlayer player, LeavesCustomPayload payload) { + if (payload instanceof ErrorPayload errorPayload) { + player.connection.disconnect("Payload " + Arrays.toString(errorPayload.packetID) + " from " + Arrays.toString(errorPayload.protocolID) + " error", PlayerKickEvent.Cause.INVALID_PAYLOAD); + return; + } + + for (LeavesProtocol protocol : KNOW_RECEIVERS.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), payload.type().id().getNamespace())) { + continue; + } + + Map map = KNOW_RECEIVERS.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (payload.getClass() == receiver.payload()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.type().id().getPath())) { + try { + map.get(receiver).invoke(null, player, payload); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle payload " + payload.type().id() + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + } + + public static void handleTick() { + if (!TICKERS.isEmpty()) { + try { + for (Method method : TICKERS) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to tick, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handlePlayerJoin(ServerPlayer player) { + if (!PLAYER_JOIN.isEmpty()) { + try { + for (Method method : PLAYER_JOIN) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle player join, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handlePlayerLeave(ServerPlayer player) { + if (!PLAYER_LEAVE.isEmpty()) { + try { + for (Method method : PLAYER_LEAVE) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle player leave, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handleServerReload() { + if (!RELOAD_SERVER.isEmpty()) { + try { + for (Method method : RELOAD_SERVER) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle server reload, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + + public static void handleMinecraftRegister(String channelId, ServerPlayer player) { + for (LeavesProtocol protocol : MINECRAFT_REGISTER.keySet()) { + String[] channel = channelId.split(":"); + if (!ArrayUtils.contains(protocol.namespace(), channel[0])) { + continue; + } + + Map map = MINECRAFT_REGISTER.get(protocol); + for (ProtocolHandler.MinecraftRegister register : map.keySet()) { + if (register.ignoreId() || register.channelId().equals(channel[1]) || + ArrayUtils.contains(register.channelIds(), channel[1])) { + try { + map.get(register).invoke(null, player); + } catch (InvocationTargetException | IllegalAccessException exception) { + LOGGER.warning("Failed to handle minecraft register, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + + public static Set> getClasses(String pack) { + Set> classes = new LinkedHashSet>(); + String packageDirName = pack.replace('.', '/'); + Enumeration dirs; + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); + findClassesInPackageByFile(pack, filePath, classes); + } else if ("jar".equals(protocol)) { + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + findClassesInPackageByJar(pack, entries, packageDirName, classes); + } catch (IOException exception) { + LOGGER.warning("Failed to load jar file, " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } catch (IOException exception) { + LOGGER.warning("Failed to load classes, " + exception.getCause() + ": " + exception.getMessage()); + } + return classes; + } + + private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { + File dir = new File(packagePath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); + if (dirfiles != null) { + for (File file : dirfiles) { + if (file.isDirectory()) { + findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException exception) { + LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + + private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.charAt(0) == '/') { + name = name.substring(1); + } + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + if (name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(packageName.length() + 1, name.length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException exception) { + LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage()); + } + } + } + } + } + + public record ErrorPayload(ResourceLocation id, String[] protocolID, String[] packetID) implements LeavesCustomPayload { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record EmptyPayload(ResourceLocation id) implements LeavesCustomPayload { + + @LeavesCustomPayload.New + public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(location); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record LeavesPayload(FriendlyByteBuf data, ResourceLocation id) implements LeavesCustomPayload { + + @LeavesCustomPayload.New + public LeavesPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(new FriendlyByteBuf(buf.readBytes(buf.readableBytes())), location); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeBytes(data); + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..f9dc0a60ca4287e5ec91dd3fc1ae315e2826da34 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java @@ -0,0 +1,61 @@ +package org.leavesmc.leaves.protocol.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class ProtocolHandler { + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Init { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PayloadReceiver { + + Class> payload(); + + String[] payloadId(); + + boolean ignoreId() default false; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Ticker { + int delay() default 0; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerJoin { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerLeave { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface ReloadServer { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface MinecraftRegister { + + String channelId() default ""; + + String[] channelIds() default {}; + + boolean ignoreId() default false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f54381eec7ec0ffde39e87b39b16e02d3ed1b419 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java @@ -0,0 +1,47 @@ +package org.leavesmc.leaves.protocol.core; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class ProtocolUtils { + + private static final Function bufDecorator = RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess()); + + public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id))); + } + + @SuppressWarnings("all") + public static void sendPayloadPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer consumer) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesCustomPayload() { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + consumer.accept(buf); + } + + @Override + @NotNull + public ResourceLocation id() { + return id; + } + })); + } + + public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) { + player.connection.send(new ClientboundCustomPayloadPacket(payload)); + } + + public static RegistryFriendlyByteBuf decorate(ByteBuf buf) { + return bufDecorator.apply(buf); + } +}