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<MapInfoReq
         ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId);
         if (mapPacket != null) {
             // Delay the packet 100ms to prevent the client from ignoring the packet
-            GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> 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<MobEquipment
             // Activate shield since we are already sneaking
             // (No need to send a release item packet - Java doesn't do this when swapping items)
             // Required to do it a tick later or else it doesn't register
-            session.getConnector().getGeneralThreadPool().schedule(() -> 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<Netwo
             attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
         }
 
-        session.getConnector().getGeneralThreadPool().schedule(
-                () -> 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<Ser
         int windowId = session.getFormCache().addForm(window);
 
         // Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay
-        session.getConnector().getGeneralThreadPool().schedule(() -> {
+        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<SetLocalPlayerAsInitializedPacket> {
@@ -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<ClientboundLoginPacket
         BiomeTranslator.loadServerBiomes(session, packet.getDimensionCodec());
         session.getTagCache().clear();
 
+        session.setGameMode(packet.getGameMode());
+
+        boolean needsSpawnPacket = !session.isSentSpawnPacket();
+        if (needsSpawnPacket) {
+            // The player has yet to spawn so let's do that using some of the information in this Java packet
+            session.setDimension(newDimension);
+            session.connect();
+        }
+
         AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
         bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId());
         bedrockPacket.setPlayerPermission(PlayerPermission.MEMBER);
         session.sendUpstreamPacket(bedrockPacket);
 
-        PlayStatusPacket playStatus = new PlayStatusPacket();
-        playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
-        // session.sendPacket(playStatus);
-
-        SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
-        playerGameTypePacket.setGamemode(packet.getGameMode().ordinal());
-        session.sendUpstreamPacket(playerGameTypePacket);
-        session.setGameMode(packet.getGameMode());
+        if (!needsSpawnPacket) {
+            SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
+            playerGameTypePacket.setGamemode(packet.getGameMode().ordinal());
+            session.sendUpstreamPacket(playerGameTypePacket);
+        }
 
         SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
         entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerInfoTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerInfoTranslator.java
index 587481c18..46170076e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerInfoTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerInfoTranslator.java
@@ -109,7 +109,7 @@ public class JavaPlayerInfoTranslator extends PacketTranslator<ClientboundPlayer
             }
         }
 
-        if (!translate.getEntries().isEmpty() && (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized())) {
+        if (!translate.getEntries().isEmpty()) {
             session.sendUpstreamPacket(translate);
         }
     }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkTranslator.java
index 3673f48e0..7a13198a2 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkTranslator.java
@@ -43,6 +43,8 @@ import org.geysermc.connector.network.translators.world.BiomeTranslator;
 import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
 import org.geysermc.connector.utils.ChunkUtils;
 
+import java.io.IOException;
+
 import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT;
 import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT_OVERWORLD;
 
@@ -61,85 +63,79 @@ public class JavaLevelChunkTranslator extends PacketTranslator<ClientboundLevelC
         // Ensure that, if the player is using lower world heights, the position is not offset
         int yOffset = session.getChunkCache().getChunkMinY();
 
-        GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
-            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