diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index 0cc6f894658..8e4878a14e5 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -144,7 +144,10 @@ public void onMessage(String message) { byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); - PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); + // Wait until the session is actually spawned before sending, otherwise + // the plugin message can land during the proxy's null-connection window + // on initial join and be silently dropped by Velocity. + sendSkinWhenSpawned(geyser, session, bytes, 0); } break; case LOG_MESSAGE: @@ -242,6 +245,19 @@ public void uploadSkin(GeyserSession session) { } } + private void sendSkinWhenSpawned(GeyserImpl geyser, GeyserSession session, byte[] bytes, int attempt) { + if (session.isClosed() || attempt >= 10) { + return; + } + if (session.isSpawned()) { + PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); + return; + } + geyser.getScheduledThread().schedule( + () -> sendSkinWhenSpawned(geyser, session, bytes, attempt + 1), + 500, TimeUnit.MILLISECONDS); + } + private void reconnectLater(GeyserImpl geyser) { // we can only reconnect when the thread pool is open if (geyser.getScheduledThread().isShutdown() || closed) {