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 7655987d061bdb2839b30f926efb034046feaea3..7ae6b2bb868cc3d391bae87fa5e141cf64cc6c78 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/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java index 1967c43ee3a12e63365cc40ee6565307e2fd73cf..6e376d0db5321d8e9b6e0b54617ffd171bf4ee73 100644 --- a/src/main/java/net/minecraft/resources/ResourceLocation.java +++ b/src/main/java/net/minecraft/resources/ResourceLocation.java @@ -36,7 +36,7 @@ public final class ResourceLocation implements Comparable { private final String namespace; private final String path; - private ResourceLocation(String namespace, String path) { + public ResourceLocation(String namespace, String path) { // Leaves - private -> public assert isValidNamespace(namespace); assert isValidPath(path); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index e5dd0fdcf0e54b9d972880b336fea46d396a56e4..bac0a69424c0370055c2a0ebfbf711c5e8a3db4c 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1807,6 +1807,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(); @@ -166,6 +171,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 f9141e1997ef88ff5478d4a01b6bc719125631f2..8ccd40b562691e757c7b5efa1497d93a95040a9a 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 @@ -589,6 +591,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 a02aafe76e1bd09e0f62a4b0bed648553ef92d47..7bd4373071204f13985e92bd64f6533cf43f39b5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -476,6 +476,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 this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol } public boolean getCommandBlockOverride(String command) { @@ -1105,6 +1106,7 @@ public final class CraftServer implements Server { this.spark.registerCommandBeforePlugins(this); // Paper - spark 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..b09a7bfe4cbff89acfaf4aec498a5b4e5f911cf6 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java @@ -0,0 +1,29 @@ +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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public interface LeavesCustomPayload> extends CustomPacketPayload { + + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @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..4fd8e06c8d2d3016ccc0baf1c43d77d87ea1dca5 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java @@ -0,0 +1,386 @@ +package org.leavesmc.leaves.protocol.core; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +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.Executable; +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 Class[] PAYLOAD_PARAMETER_TYPES = {ResourceLocation.class, FriendlyByteBuf.class}; + + 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 { + boolean found = false; + for (Method payloadMethod : receiver.payload().getDeclaredMethods()) { + if (payloadMethod.isAnnotationPresent(LeavesCustomPayload.New.class)) { + if (Arrays.equals(payloadMethod.getParameterTypes(), PAYLOAD_PARAMETER_TYPES) && payloadMethod.getReturnType() == receiver.payload() && Modifier.isStatic(payloadMethod.getModifiers())) { + payloadMethod.setAccessible(true); + map.put(receiver, payloadMethod); + found = true; + break; + } + } + } + + if (!found) { + Constructor> constructor = receiver.payload().getConstructor(PAYLOAD_PARAMETER_TYPES); + if (constructor.isAnnotationPresent(LeavesCustomPayload.New.class)) { + constructor.setAccessible(true); + map.put(receiver, constructor); + } else { + throw new NoSuchMethodException(); + } + } + } 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 { + if (map.get(receiver) instanceof Constructor constructor) { + return (LeavesCustomPayload) constructor.newInstance(id, buf); + } else if (map.get(receiver) instanceof Method method) { + return (LeavesCustomPayload) method.invoke(null, 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(Component.literal("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 { + + @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 { + + @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..f941f095184cf4b7af284d1357ea1a85815e8a66 --- /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() default ""; + + 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); + } +}