From 703589910b81763595f714b1e6506d7cff4f01b2 Mon Sep 17 00:00:00 2001 From: CerealAxis <2741798712@qq.com> Date: Mon, 17 Jun 2024 21:39:43 +0800 Subject: [PATCH] add ChatImage Protocol and AsteorBar Protocol --- .../server/0136-Leaf-ChatImage-Protocol.patch | 208 ++++++++++++++++++ .../server/0137-Leaf-AsteorBar-Protocol.patch | 129 +++++++++++ 2 files changed, 337 insertions(+) create mode 100644 patches/server/0136-Leaf-ChatImage-Protocol.patch create mode 100644 patches/server/0137-Leaf-AsteorBar-Protocol.patch diff --git a/patches/server/0136-Leaf-ChatImage-Protocol.patch b/patches/server/0136-Leaf-ChatImage-Protocol.patch new file mode 100644 index 00000000..ee0f7200 --- /dev/null +++ b/patches/server/0136-Leaf-ChatImage-Protocol.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CerealAxis <2741798712@qq.com> +Date: Mon, 17 Jun 2024 21:38:25 +0800 +Subject: [PATCH] Leaf-ChatImage-Protocol + + +diff --git a/src/main/java/org/leavesmc/leaves/LeavesConfig.java b/src/main/java/org/leavesmc/leaves/LeavesConfig.java +index f74e84d68b043de8c9180d58f7f60da7fb19beaf..25949297734351ef566d6769da4b76bc790cbd65 100644 +--- a/src/main/java/org/leavesmc/leaves/LeavesConfig.java ++++ b/src/main/java/org/leavesmc/leaves/LeavesConfig.java +@@ -766,6 +766,9 @@ public final class LeavesConfig { + @GlobalConfig(name = "leaves-carpet-support", category = "protocol") + public static boolean leavesCarpetSupport = false; + ++ @GlobalConfig(name = "Chat-Image-support", category = "protocol") ++ public static boolean chatimageSupport = false; ++ + // Leaves end - protocol + + // Leaves start - misc +diff --git a/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java +new file mode 100644 +index 0000000000000000000000000000000000000000..93ecd42a3140ed23187b7475797a5a6f9dffce3c +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/ChatImageProtocol.java +@@ -0,0 +1,160 @@ ++package org.leavesmc.leaves.protocol; ++ ++import com.google.common.collect.Lists; ++import com.google.gson.Gson; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.common.custom.CustomPacketPayload; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerPlayer; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.protocol.chatimage.ChatImageIndex; ++import org.leavesmc.leaves.protocol.core.LeavesCustomPayload; ++import org.leavesmc.leaves.protocol.core.LeavesProtocol; ++import org.leavesmc.leaves.protocol.core.LeavesProtocolManager; ++import org.leavesmc.leaves.protocol.core.ProtocolHandler; ++ ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.UUID; ++ ++@LeavesProtocol(namespace = "chatimage") ++public class ChatImageProtocol { ++ public static final String PROTOCOL_ID = "chatimage"; ++ private static final Map> SERVER_BLOCK_CACHE = new HashMap<>(); ++ private static final Map FILE_COUNT_MAP = new HashMap<>(); ++ private static final Map> USER_CACHE_MAP = new HashMap<>(); ++ public static int MAX_STRING = 532767; ++ private static final Gson gson = new Gson(); ++ ++ public record FileInfoChannelPacket(String message) implements LeavesCustomPayload { ++ private static final ResourceLocation FILE_INFO = ChatImageProtocol.id("file_info"); ++ ++ @New ++ public FileInfoChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { ++ this(buffer.readUtf(MAX_STRING)); ++ } ++ ++ @Override ++ public void write(final FriendlyByteBuf buffer) { ++ buffer.writeUtf(message(), MAX_STRING); ++ } ++ ++ @Override ++ public @NotNull ResourceLocation id() { ++ return FILE_INFO; ++ } ++ } ++ ++ public record DownloadFileChannelPacket(String message) implements LeavesCustomPayload { ++ /** ++ * 发送文件分块到客户端通道(Map) ++ */ ++ private static final ResourceLocation DOWNLOAD_FILE_CHANNEL = ChatImageProtocol.id("download_file_channel"); ++ ++ @New ++ public DownloadFileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { ++ this(buffer.readUtf(MAX_STRING)); ++ } ++ ++ @Override ++ public void write(final FriendlyByteBuf buffer) { ++ buffer.writeUtf(message(), MAX_STRING); ++ } ++ ++ @Override ++ public @NotNull ResourceLocation id() { ++ return DOWNLOAD_FILE_CHANNEL; ++ } ++ ++ } ++ ++ public record FileChannelPacket(String message) implements LeavesCustomPayload { ++ /** ++ * 客户端发送文件分块到服务器通道(Map) ++ */ ++ private static final ResourceLocation FILE_CHANNEL = ChatImageProtocol.id("file_channel"); ++ ++ @New ++ public FileChannelPacket(ResourceLocation id, FriendlyByteBuf buffer) { ++ this(buffer.readUtf(MAX_STRING)); ++ } ++ ++ @Override ++ public void write(final FriendlyByteBuf buffer) { ++ buffer.writeUtf(message(), MAX_STRING); ++ } ++ ++ @Override ++ public @NotNull ResourceLocation id() { ++ return FILE_CHANNEL; ++ } ++ ++ } ++ ++ @ProtocolHandler.PayloadReceiver(payload = FileChannelPacket.class, payloadId = "file_channel") ++ public static void serverFileChannelReceived(ServerPlayer player, String res) { ++ if (!LeavesConfig.chatimageSupport) return; ++ ChatImageIndex title = gson.fromJson(res, ChatImageIndex.class); ++ HashMap blocks = SERVER_BLOCK_CACHE.containsKey(title.url) ? SERVER_BLOCK_CACHE.get(title.url) : new HashMap<>(); ++ blocks.put(title.index, res); ++ SERVER_BLOCK_CACHE.put(title.url, blocks); ++ FILE_COUNT_MAP.put(title.url, title.total); ++ //System.out.println("[FileChannel->Server:" + title.index + "/" + title.total + "]" + title.url); ++ if (title.total == blocks.size()) { ++ if (USER_CACHE_MAP.containsKey(title.url)) { ++ // 通知之前请求但是没图片的客户端 ++ List names = USER_CACHE_MAP.get(title.url); ++ for (String uuid : names) { ++ ServerPlayer serverPlayer = player.server.getPlayerList().getPlayer(UUID.fromString(uuid)); ++ if (serverPlayer != null) { ++ sendToPlayer(new FileInfoChannelPacket("true->" + title.url), serverPlayer); ++ } ++ //System.out.println("[echo to client(" + uuid + ")]" + title.url); ++ } ++ USER_CACHE_MAP.put(title.url, Lists.newArrayList()); ++ } ++ //System.out.println("[FileChannel->Server]" + title.url); ++ } ++ } ++ ++ @ProtocolHandler.PayloadReceiver(payload = FileInfoChannelPacket.class, payloadId = "file_info") ++ public static void serverFileInfoChannelReceived(ServerPlayer player, String url) { ++ if (!LeavesConfig.chatimageSupport) return; ++ if (SERVER_BLOCK_CACHE.containsKey(url) && FILE_COUNT_MAP.containsKey(url)) { ++ HashMap list = SERVER_BLOCK_CACHE.get(url); ++ Integer total = FILE_COUNT_MAP.get(url); ++ if (total == list.size()) { ++ // 服务器存在缓存图片,直接发送给客户端 ++ for (Map.Entry entry : list.entrySet()) { ++ //System.out.println("[GetFileChannel->Client:" + entry.getKey() + "/" + (list.size() - 1) + "]" + url); ++ sendToPlayer(new DownloadFileChannelPacket(entry.getValue()), player); ++ } ++ //System.out.println("[GetFileChannel->Client]" + url); ++ return; ++ } ++ } ++ //通知客户端无文件 ++ sendToPlayer(new FileInfoChannelPacket("null->" + url), player); ++ //System.out.println("[GetFileChannel]not found in server:" + url); ++ // 记录uuid,后续有文件了推送 ++ List names = USER_CACHE_MAP.containsKey(url) ? USER_CACHE_MAP.get(url) : Lists.newArrayList(); ++ names.add(player.getStringUUID()); ++ USER_CACHE_MAP.put(url, names); ++ //System.out.println("[GetFileChannel]记录uuid:" + player.getStringUUID()); ++ //System.out.println("[not found in server]" + url); ++ } ++ ++ @Contract("_ -> new") ++ public static @NotNull ResourceLocation id(String path) { ++ return new ResourceLocation(PROTOCOL_ID, path); ++ } ++ ++ public static void sendToPlayer(CustomPacketPayload payload, ServerPlayer player) { ++ player.connection.send((Packet) payload); ++ } ++ ++} +diff --git a/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java b/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java +new file mode 100644 +index 0000000000000000000000000000000000000000..369917ae4b82b6c1d480a5ac5ebd00e0c55f1fb5 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/chatimage/ChatImageIndex.java +@@ -0,0 +1,15 @@ ++package org.leavesmc.leaves.protocol.chatimage; ++ ++public class ChatImageIndex { ++ public int index; ++ public int total; ++ public String url; ++ public String bytes; ++ ++ public ChatImageIndex(int index, int total, String url, String bytes) { ++ this.index = index; ++ this.total = total; ++ this.url = url; ++ this.bytes = bytes; ++ } ++} +\ No newline at end of file diff --git a/patches/server/0137-Leaf-AsteorBar-Protocol.patch b/patches/server/0137-Leaf-AsteorBar-Protocol.patch new file mode 100644 index 00000000..f3fbe648 --- /dev/null +++ b/patches/server/0137-Leaf-AsteorBar-Protocol.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: CerealAxis <2741798712@qq.com> +Date: Mon, 17 Jun 2024 21:38:42 +0800 +Subject: [PATCH] Leaf-AsteorBar-Protocol + + +diff --git a/src/main/java/org/leavesmc/leaves/LeavesConfig.java b/src/main/java/org/leavesmc/leaves/LeavesConfig.java +index 25949297734351ef566d6769da4b76bc790cbd65..ac5ae104bd29af77ac251038ec40f977815a45db 100644 +--- a/src/main/java/org/leavesmc/leaves/LeavesConfig.java ++++ b/src/main/java/org/leavesmc/leaves/LeavesConfig.java +@@ -766,6 +766,9 @@ public final class LeavesConfig { + @GlobalConfig(name = "leaves-carpet-support", category = "protocol") + public static boolean leavesCarpetSupport = false; + ++ @GlobalConfig(name = "asteor-bar-protocol", category = "protocol") ++ public static boolean asteorbarProtocol = false; ++ + @GlobalConfig(name = "Chat-Image-support", category = "protocol") + public static boolean chatimageSupport = false; + +diff --git a/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3db86d1710cc474d13cd1f2f6f91919f6067b198 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/protocol/AsteorBarProtocol.java +@@ -0,0 +1,103 @@ ++package org.leavesmc.leaves.protocol; ++ ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.food.FoodData; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.protocol.core.LeavesProtocol; ++import org.leavesmc.leaves.protocol.core.ProtocolHandler; ++import org.leavesmc.leaves.protocol.core.ProtocolUtils; ++ ++import java.util.*; ++ ++@LeavesProtocol(namespace = "asteorbar") ++public class AsteorBarProtocol { ++ ++ public static final String PROTOCOL_ID = "asteorbar"; ++ ++ private static final ResourceLocation NETWORK_KEY = id("network"); ++ ++ private static final Map previousSaturationLevels = new HashMap<>(); ++ private static final Map previousExhaustionLevels = new HashMap<>(); ++ ++ private static final float THRESHOLD = 0.01F; ++ ++ private static final Set players = new HashSet<>(); ++ ++ @Contract("_ -> new") ++ public static @NotNull ResourceLocation id(String path) { ++ return new ResourceLocation(PROTOCOL_ID, path); ++ } ++ ++ @ProtocolHandler.PlayerJoin ++ public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { ++ if (LeavesConfig.asteorbarProtocol) { ++ resetPlayerData(player); ++ } ++ } ++ ++ @ProtocolHandler.PlayerLeave ++ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { ++ if (LeavesConfig.asteorbarProtocol) { ++ players.remove(player); ++ resetPlayerData(player); ++ } ++ } ++ ++ @ProtocolHandler.MinecraftRegister(ignoreId = true) ++ public static void onPlayerSubscribed(@NotNull ServerPlayer player) { ++ if (LeavesConfig.asteorbarProtocol) { ++ players.add(player); ++ } ++ } ++ ++ @ProtocolHandler.Ticker ++ public static void tick() { ++ if (LeavesConfig.asteorbarProtocol) { ++ for (ServerPlayer player : players) { ++ FoodData data = player.getFoodData(); ++ ++ float saturation = data.getSaturationLevel(); ++ Float previousSaturation = previousSaturationLevels.get(player.getUUID()); ++ if (previousSaturation == null || saturation != previousSaturation) { ++ ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { ++ buf.writeByte(1); ++ buf.writeFloat(saturation); ++ }); ++ previousSaturationLevels.put(player.getUUID(), saturation); ++ } ++ ++ float exhaustion = data.getExhaustionLevel(); ++ Float previousExhaustion = previousExhaustionLevels.get(player.getUUID()); ++ if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= THRESHOLD) { ++ ProtocolUtils.sendPayloadPacket(player, NETWORK_KEY, buf -> { ++ buf.writeByte(0); ++ buf.writeFloat(exhaustion); ++ }); ++ previousExhaustionLevels.put(player.getUUID(), exhaustion); ++ } ++ } ++ } ++ } ++ ++ @ProtocolHandler.ReloadServer ++ public static void onServerReload() { ++ if (LeavesConfig.asteorbarProtocol) { ++ disableAllPlayer(); ++ } ++ } ++ ++ public static void disableAllPlayer() { ++ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { ++ onPlayerLoggedOut(player); ++ } ++ } ++ ++ private static void resetPlayerData(@NotNull ServerPlayer player) { ++ previousExhaustionLevels.remove(player.getUUID()); ++ previousSaturationLevels.remove(player.getUUID()); ++ } ++}