diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index b9bb453ce..21f457a62 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -36,6 +36,7 @@ import com.nukkitx.protocol.bedrock.BedrockServer; import io.netty.channel.epoll.Epoll; import io.netty.channel.kqueue.KQueue; import io.netty.util.NettyRuntime; +import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.internal.SystemPropertyUtil; import lombok.Getter; import lombok.Setter; @@ -118,7 +119,7 @@ public class GeyserConnector { private volatile boolean shuttingDown = false; - private final ScheduledExecutorService generalThreadPool; + private final ScheduledExecutorService scheduledThread; private final BedrockServer bedrockServer; private final PlatformType platformType; @@ -144,7 +145,7 @@ public class GeyserConnector { logger.info(""); logger.info("******************************************"); - this.generalThreadPool = Executors.newScheduledThreadPool(config.getGeneralThreadPool()); + this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); logger.setDebug(config.isDebugMode()); @@ -404,7 +405,7 @@ public class GeyserConnector { bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.done")); } - generalThreadPool.shutdown(); + scheduledThread.shutdown(); bedrockServer.close(); if (timeSyncer != null) { timeSyncer.shutdown(); diff --git a/connector/src/main/java/org/geysermc/connector/common/connection/LocalSession.java b/connector/src/main/java/org/geysermc/connector/common/connection/LocalSession.java index 882bd3957..7bd08a732 100644 --- a/connector/src/main/java/org/geysermc/connector/common/connection/LocalSession.java +++ b/connector/src/main/java/org/geysermc/connector/common/connection/LocalSession.java @@ -97,7 +97,7 @@ public final class LocalSession extends TcpSession { exceptionCaught(null, future.cause()); } }); - } catch(Throwable t) { + } catch (Throwable t) { exceptionCaught(null, t); } } diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index a3fe89c1b..2e05b0c18 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -68,8 +68,6 @@ public interface GeyserConfiguration { boolean isDebugMode(); - int getGeneralThreadPool(); - boolean isAllowThirdPartyCapes(); boolean isAllowThirdPartyEars(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index da2840f76..c6967adf1 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -96,9 +96,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("debug-mode") private boolean debugMode = false; - @JsonProperty("general-thread-pool") - private int generalThreadPool = 32; - @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes = true; diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index 369b86a8e..025747940 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -133,9 +133,7 @@ public class BoatEntity extends Entity { // Get the entity by the first stored passenger and convey motion in this manner Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); if (entity != null) { - session.getConnector().getGeneralThreadPool().execute(() -> - updateLeftPaddle(session, entity) - ); + updateLeftPaddle(session, entity); } } } else { @@ -150,9 +148,7 @@ public class BoatEntity extends Entity { if (!this.passengers.isEmpty()) { Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); if (entity != null) { - session.getConnector().getGeneralThreadPool().execute(() -> - updateRightPaddle(session, entity) - ); + updateRightPaddle(session, entity); } } } else { @@ -180,7 +176,7 @@ public class BoatEntity extends Entity { paddleTimeLeft += ROWING_SPEED; sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft); - session.getConnector().getGeneralThreadPool().schedule(() -> + session.scheduleInEventLoop(() -> updateLeftPaddle(session, rower), 100, TimeUnit.MILLISECONDS @@ -193,7 +189,7 @@ public class BoatEntity extends Entity { paddleTimeRight += ROWING_SPEED; sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight); - session.getConnector().getGeneralThreadPool().schedule(() -> + session.scheduleInEventLoop(() -> updateRightPaddle(session, rower), 100, TimeUnit.MILLISECONDS diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index cf40a3df1..6411a4f55 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -42,8 +42,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.registry.type.ItemMapping; -import java.util.concurrent.TimeUnit; - /** * Item frames are an entity in Java but a block entity in Bedrock. */ @@ -99,11 +97,8 @@ public class ItemFrameEntity extends Entity { session.getItemFrameCache().put(bedrockPosition, this); - // Delay is required, or else loading in frames on chunk load is sketchy at best - session.getConnector().getGeneralThreadPool().schedule(() -> { - updateBlock(session); - session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); - }, 500, TimeUnit.MILLISECONDS); + updateBlock(session); + session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); valid = true; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java index 1d70d9d5f..08ffa0dbc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java @@ -29,13 +29,11 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -public class TNTEntity extends Entity { +public class TNTEntity extends Entity implements Tickable { private int currentTick; @@ -49,16 +47,26 @@ public class TNTEntity extends Entity { currentTick = (int) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.IGNITED, true); metadata.put(EntityData.FUSE_LENGTH, currentTick); - ScheduledFuture future = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - if (currentTick % 5 == 0) { - metadata.put(EntityData.FUSE_LENGTH, currentTick); - } - currentTick--; - super.updateBedrockMetadata(entityMetadata, session); - }, 50, 50, TimeUnit.MILLISECONDS); // 5 ticks - session.getConnector().getGeneralThreadPool().schedule(() -> future.cancel(true), (int) entityMetadata.getValue() / 20, TimeUnit.SECONDS); } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void tick(GeyserSession session) { + if (currentTick == 0) { + // No need to update the fuse when there is none + return; + } + + if (currentTick % 5 == 0) { + metadata.put(EntityData.FUSE_LENGTH, currentTick); + + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.FUSE_LENGTH, currentTick); + session.sendUpstreamPacket(packet); + } + currentTick--; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index 08f9f1bb4..5b948ef37 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -129,7 +129,7 @@ public class PlayerEntity extends LivingEntity { if (session.getEntityCache().getPlayerEntity(uuid) == null) return; - if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) { + if (session.getEntityCache().getEntityByGeyserId(geyserId) == null) { session.getEntityCache().spawnEntity(this); } else { spawnEntity(session); @@ -288,7 +288,7 @@ public class PlayerEntity extends LivingEntity { linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false, false)); // Delay, or else spawned-in players won't get the link // TODO: Find a better solution. This problem also exists with item frames - session.getConnector().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS); + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS); if (isLeft) { leftParrot = parrot; } else { diff --git a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java index 6ebca3f8c..b33afaec0 100644 --- a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java +++ b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java @@ -114,7 +114,7 @@ public class Metrics { * Starts the Scheduler which submits our data every 30 minutes. */ private void startSubmitting() { - connector.getGeneralThreadPool().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES); + connector.getScheduledThread().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES); // Submit the data every 30 minutes, first time after 1 minutes to give other plugins enough time to start // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! // WARNING: Just don't do it! diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 42e24458f..4a51c9ca5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -104,6 +104,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); + + LanguageUtils.loadGeyserLocale(session.getLocale()); return true; } @@ -111,7 +113,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - session.connect(); + if (connector.getConfig().getRemote().getAuthType() != AuthType.ONLINE) { + session.authenticate(session.getAuthData().getName()); + } else if (!couldLoginUserByName(session.getAuthData().getName())) { + // We must spawn the white world + session.connect(); + } connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); break; @@ -182,9 +189,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName())); session.setMicrosoftAccount(info.isMicrosoftAccount()); session.authenticate(info.getEmail(), info.getPassword()); - - // TODO send a message to bedrock user telling them they are connected (if nothing like a motd - // somes from the Java server w/in a few seconds) return true; } } @@ -192,20 +196,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return false; } - @Override - public boolean handle(SetLocalPlayerAsInitializedPacket packet) { - LanguageUtils.loadGeyserLocale(session.getLocale()); - - if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) { - // TODO it is safer to key authentication on something that won't change (UUID, not username) - if (!couldLoginUserByName(session.getAuthData().getName())) { - LoginEncryptionUtils.buildAndShowLoginWindow(session); - } - // else we were able to log the user in - } - return translateAndDefault(packet); - } - @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 949b0937a..c3836b4cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -229,6 +229,8 @@ public class GeyserSession implements CommandSender { private Vector2i lastChunkPosition = null; private int renderDistance; + private boolean sentSpawnPacket; + private boolean loggedIn; private boolean loggingIn; @@ -501,6 +503,10 @@ public class GeyserSession implements CommandSender { disconnect(disconnectReason.name()); connector.getSessionManager().removeSession(this); }); + + this.remoteAddress = connector.getConfig().getRemote().getAddress(); + this.remotePort = connector.getConfig().getRemote().getPort(); + this.remoteAuthType = connector.getConfig().getRemote().getAuthType(); } /** @@ -508,9 +514,7 @@ public class GeyserSession implements CommandSender { */ public void connect() { startGame(); - this.remoteAddress = connector.getConfig().getRemote().getAddress(); - this.remotePort = connector.getConfig().getRemote().getPort(); - this.remoteAuthType = connector.getConfig().getRemote().getAuthType(); + sentSpawnPacket = true; // Set the hardcoded shield ID to the ID we just defined in StartGamePacket upstream.getSession().getHardcodedBlockingId().set(this.itemMappings.getStoredItems().shield().getBedrockId()); @@ -685,27 +689,36 @@ public class GeyserSession implements CommandSender { if (loggedIn || closed) { return; } - try { - msaAuthenticationService.login(); - GameProfile profile = msaAuthenticationService.getSelectedProfile(); - if (profile == null) { - // Java account is offline - disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + CompletableFuture.supplyAsync(() -> { + try { + msaAuthenticationService.login(); + GameProfile profile = msaAuthenticationService.getSelectedProfile(); + if (profile == null) { + // Java account is offline + disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return null; + } + + return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken()); + } catch (RequestException e) { + throw new CompletionException(e); + } + }).whenComplete((response, ex) -> { + if (ex != null) { + if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) { + connector.getLogger().error("Failed to log in with Microsoft code!", ex); + disconnect(ex.toString()); + } else { + // Wait one second before trying again + connector.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); + } return; } - - protocol = new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken()); - - connectDownstream(); - } catch (RequestException e) { - if (!(e instanceof AuthPendingException)) { - connector.getLogger().error("Failed to log in with Microsoft code!", e); - disconnect(e.toString()); - } else { - // Wait one second before trying again - connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); + if (!closed) { + this.protocol = response; + connectDownstream(); } - } + }); } /** @@ -725,6 +738,7 @@ public class GeyserSession implements CommandSender { downstream = new TcpClientSession(this.remoteAddress, this.remotePort, this.protocol); disableSrvResolving(); } + if (connector.getConfig().getRemote().isUseProxyProtocol()) { downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); @@ -1133,7 +1147,11 @@ public class GeyserSession implements CommandSender { StartGamePacket startGamePacket = new StartGamePacket(); startGamePacket.setUniqueEntityId(playerEntity.getGeyserId()); startGamePacket.setRuntimeEntityId(playerEntity.getGeyserId()); - startGamePacket.setPlayerGameType(GameType.SURVIVAL); + startGamePacket.setPlayerGameType(switch (gameMode) { + case CREATIVE -> GameType.CREATIVE; + case ADVENTURE -> GameType.ADVENTURE; + default -> GameType.SURVIVAL; + }); startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); startGamePacket.setRotation(Vector2f.from(1, 1)); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 954a2b033..73475673c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -65,8 +65,7 @@ public class FormCache { NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); latencyPacket.setFromServer(true); latencyPacket.setTimestamp(-System.currentTimeMillis()); - session.getConnector().getGeneralThreadPool().schedule( - () -> session.sendUpstreamPacket(latencyPacket), + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(latencyPacket), 500, TimeUnit.MILLISECONDS); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java index 4b4c5b20d..50d5d1c8c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; import com.nukkitx.protocol.bedrock.packet.MapInfoRequestPacket; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -44,7 +43,7 @@ public class BedrockMapInfoRequestTranslator extends PacketTranslator session.sendUpstreamPacket(mapPacket), + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket), 100, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 26dd340f6..4fa35bb46 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -61,7 +61,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND)), + session.scheduleInEventLoop(() -> session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND)), 50, TimeUnit.MILLISECONDS); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index cf1c0a1b5..91d60ce66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -76,8 +76,7 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator session.sendUpstreamPacket(attributesPacket), + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(attributesPacket), 500, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 2b2ec2917..5fe778513 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -43,7 +43,7 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { + session.scheduleInEventLoop(() -> { ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); serverSettingsResponsePacket.setFormData(window.getJsonData()); serverSettingsResponsePacket.setFormId(windowId); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index c52513c4d..5288a62fa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java @@ -25,14 +25,12 @@ package org.geysermc.connector.network.translators.bedrock; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket; -import org.geysermc.connector.entity.player.PlayerEntity; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.skin.SkinManager; -import org.geysermc.connector.skin.SkullSkinManager; +import org.geysermc.connector.utils.LoginEncryptionUtils; @Translator(packet = SetLocalPlayerAsInitializedPacket.class) public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator { @@ -41,23 +39,12 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) { if (!session.getUpstream().isInitialized()) { session.getUpstream().setInitialized(true); - session.login(); - for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) { - if (!entity.isValid()) { - SkinManager.requestAndHandleSkinAndCape(entity, session, null); - entity.sendPlayer(session); + if (session.getRemoteAuthType() == AuthType.ONLINE) { + if (!session.isLoggedIn()) { + LoginEncryptionUtils.buildAndShowLoginWindow(session); } - } - - // Send Skulls - for (PlayerEntity entity : session.getSkullCache().values()) { - entity.spawnEntity(session); - - SkullSkinManager.requestAndHandleSkin(entity, session, (skin) -> { - entity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); - entity.updateBedrockMetadata(session); - }); + // else we were able to log the user in } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginTranslator.java index dc4678ae2..9384a3515 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginTranslator.java @@ -70,19 +70,25 @@ public class JavaLoginTranslator extends PacketTranslator { - try { - if (session.isClosed()) { - return; - } - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, column, yOffset); - ChunkSection[] sections = chunkData.sections(); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, column, yOffset); + ChunkSection[] sections = chunkData.sections(); - // Find highest section - int sectionCount = sections.length - 1; - while (sectionCount >= 0 && sections[sectionCount] == null) { - sectionCount--; - } - sectionCount++; + // Find highest section + int sectionCount = sections.length - 1; + while (sectionCount >= 0 && sections[sectionCount] == null) { + sectionCount--; + } + sectionCount++; - // Estimate chunk size - int size = 0; - for (int i = 0; i < sectionCount; i++) { - ChunkSection section = sections[i]; - size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize(); - } - size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data - size += 1; // Border blocks - size += 1; // Extra data length (always 0) - size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity + // Estimate chunk size + int size = 0; + for (int i = 0; i < sectionCount; i++) { + ChunkSection section = sections[i]; + size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize(); + } + size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data + size += 1; // Border blocks + size += 1; // Extra data length (always 0) + size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity - // Allocate output buffer - ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size); - byte[] payload; - try { - for (int i = 0; i < sectionCount; i++) { - ChunkSection section = sections[i]; - (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf); - } - - // At this point we're dealing with Bedrock chunk sections - boolean overworld = session.getChunkCache().isExtendedHeight(); - int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4; - for (int i = 0; i < sectionCount; i++) { - int biomeYOffset = dimensionOffset + i; - if (biomeYOffset < yOffset) { - // Ignore this biome section since it goes below the height of the Java world - byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA); - continue; - } - BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf); - } - - // As of 1.17.10, Bedrock hardcodes to always read 32 biome sections - int remainingEmptyBiomes = 32 - sectionCount; - for (int i = 0; i < remainingEmptyBiomes; i++) { - byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA); - } - - byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now - - // Encode tile entities into buffer - NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); - for (NbtMap blockEntity : chunkData.blockEntities()) { - nbtStream.writeTag(blockEntity); - } - - // Copy data into byte[], because the protocol lib really likes things that are s l o w - byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]); - } finally { - byteBuf.release(); // Release buffer to allow buffer pooling to be useful - } - - LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); - levelChunkPacket.setSubChunksLength(sectionCount); - levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setChunkX(column.getX()); - levelChunkPacket.setChunkZ(column.getZ()); - levelChunkPacket.setData(payload); - session.sendUpstreamPacket(levelChunkPacket); - } catch (Exception ex) { - ex.printStackTrace(); + // Allocate output buffer + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size); + byte[] payload; + try { + for (int i = 0; i < sectionCount; i++) { + ChunkSection section = sections[i]; + (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf); } - }); + + // At this point we're dealing with Bedrock chunk sections + boolean overworld = session.getChunkCache().isExtendedHeight(); + int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4; + for (int i = 0; i < sectionCount; i++) { + int biomeYOffset = dimensionOffset + i; + if (biomeYOffset < yOffset) { + // Ignore this biome section since it goes below the height of the Java world + byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA); + continue; + } + BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf); + } + + // As of 1.17.10, Bedrock hardcodes to always read 32 biome sections + int remainingEmptyBiomes = 32 - sectionCount; + for (int i = 0; i < remainingEmptyBiomes; i++) { + byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA); + } + + byteBuf.writeByte(0); // Border blocks - Edu edition only + VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now + + // Encode tile entities into buffer + NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); + for (NbtMap blockEntity : chunkData.blockEntities()) { + nbtStream.writeTag(blockEntity); + } + + // Copy data into byte[], because the protocol lib really likes things that are s l o w + byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]); + } catch (IOException e) { + session.getConnector().getLogger().error("IO error while encoding chunk", e); + return; + } finally { + byteBuf.release(); // Release buffer to allow buffer pooling to be useful + } + + LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); + levelChunkPacket.setSubChunksLength(sectionCount); + levelChunkPacket.setCachingEnabled(false); + levelChunkPacket.setChunkX(column.getX()); + levelChunkPacket.setChunkZ(column.getZ()); + levelChunkPacket.setData(payload); + session.sendUpstreamPacket(levelChunkPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 90458ca71..0306e65f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -141,15 +141,12 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements // Cache entity session.getSkullCache().put(blockPosition, player); - // Only send to session if we are initialized, otherwise it will happen then. - if (session.getUpstream().isInitialized()) { - player.spawnEntity(session); + player.spawnEntity(session); - SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> { - // Delay to minimize split-second "player" pop-in - player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); - player.updateBedrockMetadata(session); - }, 250, TimeUnit.MILLISECONDS))); - } + SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> { + // Delay to minimize split-second "player" pop-in + player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); + player.updateBedrockMetadata(session); + }, 250, TimeUnit.MILLISECONDS))); } } diff --git a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java index a1a2d474b..5781a60d4 100644 --- a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java +++ b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java @@ -62,7 +62,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn // Ensure delay is not zero int interval = (connector.getConfig().getPingPassthroughInterval() == 0) ? 1 : connector.getConfig().getPingPassthroughInterval(); connector.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); - connector.getGeneralThreadPool().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); + connector.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); return pingPassthrough; } return null; diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index cf76c529a..9dce59acc 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -212,14 +212,14 @@ public final class FloodgateSkinUploader { private void reconnectLater(GeyserConnector connector) { // we ca only reconnect when the thread pool is open - if (connector.getGeneralThreadPool().isShutdown() || closed) { + if (connector.getScheduledThread().isShutdown() || closed) { logger.info("The skin uploader has been closed"); return; } long additionalTime = ThreadLocalRandom.current().nextInt(7); // we don't have to check the result. onClose will handle that for us - connector.getGeneralThreadPool() + connector.getScheduledThread() .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); } diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java index 70f9f8ff5..6815d7550 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java @@ -167,31 +167,29 @@ public class SkinManager { } } - if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry - ); + PlayerListPacket.Entry updatedEntry = buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry + ); - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); - if (!entity.isPlayerList()) { - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); - } + if (!entity.isPlayerList()) { + PlayerListPacket playerRemovePacket = new PlayerListPacket(); + playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); + playerRemovePacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerRemovePacket); } } catch (Exception e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index 567e52ace..7e25fed6f 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -101,7 +101,7 @@ public class SkinProvider { // Schedule Daily Image Expiry if we are caching them if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) { - GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> { + GeyserConnector.getInstance().getScheduledThread().scheduleAtFixedRate(() -> { File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java index 40ef47f99..ca02afdbe 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java @@ -55,15 +55,13 @@ public class SkullSkinManager extends SkinManager { SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) .whenCompleteAsync((skin, throwable) -> { try { - if (session.getUpstream().isInitialized()) { - PlayerSkinPacket packet = new PlayerSkinPacket(); - packet.setUuid(entity.getUuid()); - packet.setOldSkinName(""); - packet.setNewSkinName(skin.getTextureUrl()); - packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); - packet.setTrustedSkin(true); - session.sendUpstreamPacket(packet); - } + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); } catch (Exception e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java index 583c2ddae..4b2704803 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java @@ -92,7 +92,8 @@ public class CooldownUtils { titlePacket.setPlatformOnlineId(""); session.sendUpstreamPacket(titlePacket); if (hasCooldown(session)) { - session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + session.scheduleInEventLoop(() -> + computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); if (sessionPreference == CooldownType.ACTIONBAR) { diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 1d5198e3f..f690b50ee 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -110,9 +110,6 @@ max-players: 100 # If debug messages should be sent through console debug-mode: false -# Thread pool size -general-thread-pool: 32 - # Allow third party capes to be visible. Currently allowing: # OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes allow-third-party-capes: true