From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:31:20 +0800 Subject: [PATCH] Servux Protocol diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index 00989bd8ba9953353a213d57c5ff81f7d2f07773..914fb5deb5dbdab1cd8899810e08205270d78df2 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -939,6 +939,7 @@ public class LevelChunk extends ChunkAccess { // Leaves start - bbor if (loaded) { top.leavesmc.leaves.protocol.BBORProtocol.onChunkLoaded(this); + top.leavesmc.leaves.protocol.ServuxProtocol.onChunkLoaded(this); // and servux } // Leaves end - bbor } diff --git a/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..fc50a1c8f5114fab81f3fbce1117a48937170d5c --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java @@ -0,0 +1,165 @@ +package top.leavesmc.leaves.protocol; + +import io.netty.buffer.Unpooled; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.leavesmc.leaves.LeavesConfig; +import top.leavesmc.leaves.protocol.core.LeavesProtocol; +import top.leavesmc.leaves.protocol.core.ProtocolHandler; +import top.leavesmc.leaves.protocol.core.ProtocolUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@LeavesProtocol(namespace = "servux") +public class ServuxProtocol { + + public static final String PROTOCOL_ID = "servux"; + + public static final int PROTOCOL_VERSION = 1; + public static final int PACKET_METADATA = 1; + public static final int PACKET_STRUCTURE_DATA = 2; + + public static final ResourceLocation CHANNEL = id("structures"); + + private static final Map players = new ConcurrentHashMap<>(); + private static final Map> playerBoundingBoxesCache = new HashMap<>(); + private static final Map> dimensionCache = new ConcurrentHashMap<>(); + + @Contract("_ -> new") + public static @NotNull ResourceLocation id(String path) { + return new ResourceLocation(PROTOCOL_ID, path); + } + + @ProtocolHandler.MinecraftRegister(channelId = "structures") + public static void onPlayerSubscribed(@NotNull ServerPlayer player) { + if (LeavesConfig.servuxProtocol) { + players.put(player.getId(), player); + + CompoundTag tag = new CompoundTag(); + tag.putInt("version", PROTOCOL_VERSION); + tag.putString("id", CHANNEL.toString()); + tag.putInt("timeout", Integer.MAX_VALUE - 200); + sendNBTPacket(player, PACKET_METADATA, tag); + } + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { + if (LeavesConfig.servuxProtocol) { + players.remove(player.getId()); + playerBoundingBoxesCache.remove(player.getId()); + } + } + + @ProtocolHandler.Ticker + public static void tick() { + if (LeavesConfig.servuxProtocol) { + for (var playerEntry : players.entrySet()) { + sendBoundingToPlayer(playerEntry.getKey(), playerEntry.getValue()); + } + } + } + + public static void onChunkLoaded(@NotNull LevelChunk chunk) { + if (LeavesConfig.servuxProtocol) { + List structures = new ArrayList<>(); + final Registry structureFeatureRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.STRUCTURE); + for (var es : chunk.getAllStarts().entrySet()) { + final var optional = structureFeatureRegistry.getResourceKey(es.getKey()); + optional.ifPresent(key -> structures.add(es.getValue())); + } + + if (!structures.isEmpty()) { + onStructuresLoaded((ServerLevel) chunk.getLevel(), structures); + } + } + } + + public static void onStructuresLoaded(@NotNull ServerLevel level, @NotNull List structures) { + Map cache = getOrCreateCache(level.dimension().location()); + for (StructureStart structureStart : structures) { + if (structureStart == null) { + return; + } + + StructurePieceSerializationContext ctx = StructurePieceSerializationContext.fromLevel(level); + + BoundingBox boundingBox = structureStart.getBoundingBox(); + if (cache.containsKey(boundingBox)) { + return; + } + + cache.put(boundingBox, structureStart.createTag(ctx, structureStart.getChunkPos())); + } + } + + private static void sendBoundingToPlayer(int id, ServerPlayer player) { + Map boundingBoxMap = dimensionCache.get(player.level().dimension().location()); + if (boundingBoxMap == null) { + return; + } + + Set playerBoundingBoxes = playerBoundingBoxesCache.computeIfAbsent(id, k -> new HashSet<>()); + + ListTag listTag = new ListTag(); + for (BoundingBox key : boundingBoxMap.keySet()) { + if (listTag.size() >= 50) { + break; + } + if (playerBoundingBoxes.contains(key)) { + continue; + } + + CompoundTag boundingBoxes = boundingBoxMap.get(key); + if (boundingBoxes != null) { + listTag.add(boundingBoxes); + } + playerBoundingBoxes.add(key); + } + + if (!listTag.isEmpty()) { + CompoundTag tag = new CompoundTag(); + tag.put("Structures", listTag); + sendNBTPacket(player, PACKET_STRUCTURE_DATA, tag); + } + } + + private static Map getOrCreateCache(ResourceLocation dimensionId) { + return dimensionCache.computeIfAbsent(dimensionId, dt -> new ConcurrentHashMap<>()); + } + + public static void sendNBTPacket(ServerPlayer player, int packetType, CompoundTag data) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeVarInt(packetType); + buf.writeNbt(data); + + int len = buf.writerIndex(); + buf.readerIndex(0); + + ProtocolUtils.sendPayloadPacket(player, CHANNEL, buf1 -> { + buf1.writeVarInt(len); + buf1.writeBytes(buf, len); + }); + + buf.release(); + } +}