From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Tue, 29 Nov 2022 09:51:16 +0800 Subject: [PATCH] BBOR Protocol diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 8ea03896e1d7f247d484628ec787d8a4f4488664..db71de592d157a2fd84ab821dd5ca54a60418769 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -1586,6 +1586,7 @@ public abstract class PlayerList { entityplayer.getRecipeBook().sendInitialRecipeBook(entityplayer); } + org.leavesmc.leaves.protocol.BBORProtocol.onDataPackReload(); // Leaves - bbor } public boolean isAllowCommandsForAllPlayers() { 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 c0ce5d4c3189337b06476c16558e12d3f8127797..382ca565fe1a680ab49b14ac90e4ec3bd903450d 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -784,6 +784,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p public void setLoaded(boolean loadedToWorld) { this.loaded = loadedToWorld; + // Leaves start - bbor + if (loaded) { + org.leavesmc.leaves.protocol.BBORProtocol.onChunkLoaded(this); + } + // Leaves end - bbor } public Level getLevel() { diff --git a/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java new file mode 100644 index 0000000000000000000000000000000000000000..16ef00dde5a1c502449378829b1b6b85d6d145fd --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java @@ -0,0 +1,227 @@ +package org.leavesmc.leaves.protocol; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +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.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.StructurePiece; +import net.minecraft.world.level.levelgen.structure.StructureStart; +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.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static org.leavesmc.leaves.protocol.core.LeavesProtocolManager.EmptyPayload; + +@LeavesProtocol(namespace = "bbor") +public class BBORProtocol { + + public static final String PROTOCOL_ID = "bbor"; + + // send + private static final ResourceLocation INITIALIZE_CLIENT = id("initialize"); + private static final ResourceLocation ADD_BOUNDING_BOX = id("add_bounding_box_v2"); + private static final ResourceLocation STRUCTURE_LIST_SYNC = id("structure_list_sync_v1"); + // call + 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.Ticker + public static void tick() { + if (LeavesConfig.bborProtocol) { + for (var playerEntry : players.entrySet()) { + sendBoundingToPlayer(playerEntry.getKey(), playerEntry.getValue()); + } + } + } + + @ProtocolHandler.ReloadServer + public static void onServerReload() { + if (LeavesConfig.bborProtocol) { + initAllPlayer(); + } else { + loggedOutAllPlayer(); + } + } + + @ProtocolHandler.PlayerJoin + public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { + if (LeavesConfig.bborProtocol) { + ServerLevel overworld = MinecraftServer.getServer().overworld(); + ProtocolUtils.sendPayloadPacket(player, INITIALIZE_CLIENT, buf -> { + buf.writeLong(overworld.getSeed()); + buf.writeInt(overworld.levelData.getSpawnPos().getX()); + buf.writeInt(overworld.levelData.getSpawnPos().getZ()); + }); + sendStructureList(player); + } + } + + @ProtocolHandler.PlayerLeave + public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { + if (LeavesConfig.bborProtocol) { + players.remove(player.getId()); + playerBoundingBoxesCache.remove(player.getId()); + } + } + + @ProtocolHandler.PayloadReceiver(payload = EmptyPayload.class, payloadId = "subscribe") + public static void onPlayerSubscribed(@NotNull ServerPlayer player, EmptyPayload payload) { + if (LeavesConfig.bborProtocol) { + players.put(player.getId(), player); + sendBoundingToPlayer(player.getId(), player); + } + } + + public static void onDataPackReload() { + if (LeavesConfig.bborProtocol) { + players.values().forEach(BBORProtocol::sendStructureList); + } + } + + public static void onChunkLoaded(@NotNull LevelChunk chunk) { + if (LeavesConfig.bborProtocol) { + Map structures = new HashMap<>(); + 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.put(key.location().toString(), es.getValue())); + } + if (!structures.isEmpty()) { + onStructuresLoaded(chunk.getLevel().dimension().location(), structures); + } + } + } + + public static void onStructuresLoaded(@NotNull ResourceLocation dimensionID, @NotNull Map structures) { + Map> cache = getOrCreateCache(dimensionID); + for (var entry : structures.entrySet()) { + StructureStart structureStart = entry.getValue(); + if (structureStart == null) { + return; + } + + String type = "structure:" + entry.getKey(); + BoundingBox bb = structureStart.getBoundingBox(); + BBoundingBox boundingBox = buildStructure(bb, type); + if (cache.containsKey(boundingBox)) { + return; + } + + Set structureBoundingBoxes = new HashSet<>(); + for (StructurePiece structureComponent : structureStart.getPieces()) { + structureBoundingBoxes.add(buildStructure(structureComponent.getBoundingBox(), type)); + } + cache.put(boundingBox, structureBoundingBoxes); + } + } + + private static @NotNull BBoundingBox buildStructure(@NotNull BoundingBox bb, String type) { + BlockPos min = new BlockPos(bb.minX(), bb.minY(), bb.minZ()); + BlockPos max = new BlockPos(bb.maxX(), bb.maxY(), bb.maxZ()); + return new BBoundingBox(type, min, max); + } + + private static void sendStructureList(ServerPlayer player) { + final Registry structureRegistry = player.server.registryAccess().registryOrThrow(Registries.STRUCTURE); + final Set structureIds = structureRegistry.entrySet().stream() + .map(e -> e.getKey().location().toString()).collect(Collectors.toSet()); + ProtocolUtils.sendPayloadPacket(player, STRUCTURE_LIST_SYNC, buf -> { + buf.writeVarInt(structureIds.size()); + structureIds.forEach(buf::writeUtf); + }); + } + + private static void sendBoundingToPlayer(int id, ServerPlayer player) { + for (var entry : dimensionCache.entrySet()) { + if (entry.getValue() == null) { + return; + } + + Set playerBoundingBoxes = playerBoundingBoxesCache.computeIfAbsent(id, k -> new HashSet<>()); + Map> boundingBoxMap = entry.getValue(); + for (BBoundingBox key : boundingBoxMap.keySet()) { + if (playerBoundingBoxes.contains(key)) { + continue; + } + + Set boundingBoxes = boundingBoxMap.get(key); + ProtocolUtils.sendPayloadPacket(player, ADD_BOUNDING_BOX, buf -> { + buf.writeResourceLocation(entry.getKey()); + key.serialize(buf); + if (boundingBoxes != null && boundingBoxes.size() > 1) { + for (BBoundingBox box : boundingBoxes) { + box.serialize(buf); + } + } + }); + playerBoundingBoxes.add(key); + } + } + } + + public static void initAllPlayer() { + for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { + onPlayerLoggedIn(player); + } + } + + public static void loggedOutAllPlayer() { + players.clear(); + playerBoundingBoxesCache.clear(); + for (var cache : dimensionCache.values()) { + cache.clear(); + } + dimensionCache.clear(); + } + + private static Map> getOrCreateCache(ResourceLocation dimensionId) { + return dimensionCache.computeIfAbsent(dimensionId, dt -> new ConcurrentHashMap<>()); + } + + private record BBoundingBox(String type, BlockPos min, BlockPos max) { + + public void serialize(@NotNull FriendlyByteBuf buf) { + buf.writeChar('S'); + buf.writeInt(type.hashCode()); + buf.writeVarInt(min.getX()).writeVarInt(min.getY()).writeVarInt(min.getZ()); + buf.writeVarInt(max.getX()).writeVarInt(max.getY()).writeVarInt(max.getZ()); + } + + @Override + public int hashCode() { + return combineHashCodes(min.hashCode(), max.hashCode()); + } + + private static int combineHashCodes(int @NotNull ... hashCodes) { + final int prime = 31; + int result = 0; + for (int hashCode : hashCodes) { + result = prime * result + hashCode; + } + return result; + } + } +}