diff --git a/src/main/java/org/geysermc/globallinkserver/java/JavaServer.java b/src/main/java/org/geysermc/globallinkserver/java/JavaServer.java index fdf45fc..ab3ec1d 100644 --- a/src/main/java/org/geysermc/globallinkserver/java/JavaServer.java +++ b/src/main/java/org/geysermc/globallinkserver/java/JavaServer.java @@ -1,36 +1,20 @@ /* * Copyright (c) 2021-2024 GeyserMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * @author GeyserMC + * Licensed under the MIT license * @link https://github.com/GeyserMC/GlobalLinkServer */ package org.geysermc.globallinkserver.java; import static org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec.CODEC; +import java.util.BitSet; import java.util.Collections; +import java.util.List; import java.util.OptionalInt; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.nbt.NbtMap; import org.geysermc.globallinkserver.config.Config; import org.geysermc.globallinkserver.link.LinkManager; import org.geysermc.globallinkserver.player.PlayerManager; @@ -43,30 +27,32 @@ import org.geysermc.mcprotocollib.network.tcp.TcpServer; import org.geysermc.mcprotocollib.protocol.MinecraftConstants; import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; -import org.geysermc.mcprotocollib.protocol.ServerLoginHandler; import org.geysermc.mcprotocollib.protocol.data.game.command.CommandNode; import org.geysermc.mcprotocollib.protocol.data.game.command.CommandParser; import org.geysermc.mcprotocollib.protocol.data.game.command.CommandType; import org.geysermc.mcprotocollib.protocol.data.game.command.properties.IntegerProperties; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo; +import org.geysermc.mcprotocollib.protocol.data.game.level.LightUpdateData; +import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityInfo; import org.geysermc.mcprotocollib.protocol.data.game.level.notify.GameEvent; import org.geysermc.mcprotocollib.protocol.data.status.PlayerInfo; import org.geysermc.mcprotocollib.protocol.data.status.ServerStatusInfo; import org.geysermc.mcprotocollib.protocol.data.status.VersionInfo; -import org.geysermc.mcprotocollib.protocol.data.status.handler.ServerInfoBuilder; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundCommandsPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerAbilitiesPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket; -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundSetHealthPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundGameEventPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLevelChunkWithLightPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundSetDefaultSpawnPositionPacket; public class JavaServer implements org.geysermc.globallinkserver.Server { private final PlayerManager playerManager; private final LinkManager linkManager; + private final ClientboundLevelChunkWithLightPacket cachedChunk = cachedChunk(); + private final ServerStatusInfo pong = new ServerStatusInfo( Component.text("Global Link Server"), new PlayerInfo(1, 0, Collections.emptyList()), @@ -91,8 +77,8 @@ public boolean startServer(Config config) { server.setGlobalFlag(MinecraftConstants.SESSION_SERVICE_KEY, new SessionService()); server.setGlobalFlag(MinecraftConstants.VERIFY_USERS_KEY, true); - server.setGlobalFlag(MinecraftConstants.SERVER_INFO_BUILDER_KEY, (ServerInfoBuilder) session -> pong); - server.setGlobalFlag(MinecraftConstants.SERVER_LOGIN_HANDLER_KEY, (ServerLoginHandler) session -> { + server.setGlobalFlag(MinecraftConstants.SERVER_INFO_BUILDER_KEY, session -> pong); + server.setGlobalFlag(MinecraftConstants.SERVER_LOGIN_HANDLER_KEY, session -> { session.send(new ClientboundCommandsPacket( new CommandNode[] { new CommandNode( @@ -151,11 +137,6 @@ public boolean startServer(Config config) { session.send(new ClientboundPlayerAbilitiesPacket(false, false, true, false, 0f, 0f)); - // without this the player will spawn only after waiting 30 seconds - // there are multiple options to fix that, - // but this is the best option as we don't want to send chunk and the player is in spectator anyway - session.send(new ClientboundSetHealthPacket(0, 0, 0)); - // this packet is also required to let our player spawn, but the location itself doesn't matter session.send(new ClientboundSetDefaultSpawnPositionPacket(Vector3i.ZERO, 0)); @@ -163,8 +144,9 @@ public boolean startServer(Config config) { // so send it after calling ConnectedEvent which adds the PacketHandler as listener session.send(new ClientboundPlayerPositionPacket(0, 0, 0, 0, 0, 0)); - // this packet is required since 1.20.3 + // these packets are required since 1.20.3 session.send(new ClientboundGameEventPacket(GameEvent.LEVEL_CHUNKS_LOAD_START, null)); + session.send(cachedChunk); // Manually call the connect event session.callEvent(new ConnectedEvent(session)); @@ -192,4 +174,22 @@ public void shutdown() { server.close(); server = null; } + + private ClientboundLevelChunkWithLightPacket cachedChunk() { + // 8 bytes for every section: + // short - block count + // for both blocks and biomes: + // byte - bits per entry + // varint(1) - block ID (0, air) + // varint(1) - data length + // times 16 for the 16 chunk sections that the end biome has + byte[] chunkData = new byte[8 * 16]; + + // just setting everything to empty seems to do the trick + var lightData = + new LightUpdateData(new BitSet(), new BitSet(), new BitSet(), new BitSet(), List.of(), List.of()); + // same applies for the heightmaps + return new ClientboundLevelChunkWithLightPacket( + 0, 0, chunkData, NbtMap.EMPTY, new BlockEntityInfo[0], lightData); + } }