From 6a51d8298f75cd17cfca66726a0d5e550c3e316c Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 14 Feb 2024 12:50:50 +0100 Subject: [PATCH] Feature: Rework startup logic (#4377) * This makes `geyser reload` work the same across all platforms. For example, it ensures that we reload the config to the greatest extent possible (with the exception of compression/injection settings). Additionally, this clears up issues where Extensions were previously disabled during reloading - instead, the new Pre and Post reload events allow extensions to reload whatever necessary on their own. --- .../lifecycle/GeyserPostReloadEvent.java | 42 +++ .../event/lifecycle/GeyserPreReloadEvent.java | 42 +++ .../bungeecord/GeyserBungeePlugin.java | 163 +++++++----- .../platform/fabric/GeyserFabricMod.java | 122 ++++----- .../command/GeyserFabricCommandExecutor.java | 4 - .../mixin/client/IntegratedServerMixin.java | 3 +- .../platform/spigot/GeyserSpigotPlugin.java | 248 +++++++++--------- .../standalone/GeyserStandaloneBootstrap.java | 51 ++-- .../standalone/GeyserStandaloneLogger.java | 2 +- .../velocity/GeyserVelocityPlugin.java | 119 +++++---- .../org/geysermc/geyser/GeyserBootstrap.java | 22 +- .../java/org/geysermc/geyser/GeyserImpl.java | 68 +++-- .../geyser/command/GeyserCommandManager.java | 2 +- .../command/defaults/ReloadCommand.java | 2 +- .../geyser/command/defaults/StopCommand.java | 2 +- .../geyser/network/UpstreamPacketHandler.java | 2 +- .../geyser/scoreboard/ScoreboardUpdater.java | 2 +- .../org/geysermc/geyser/util/EntityUtils.java | 1 + 18 files changed, 539 insertions(+), 358 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java new file mode 100644 index 000000000..c421cda37 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.extension.ExtensionManager; + +/** + * Called when Geyser finished reloading and is accepting Bedrock connections again. + * Equivalent to the {@link GeyserPostInitializeEvent} + * + * @param extensionManager the extension manager + * @param eventBus the event bus + */ +public record GeyserPostReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java new file mode 100644 index 000000000..16d5058da --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.EventRegistrar; +import org.geysermc.geyser.api.extension.ExtensionManager; + +/** + * Called when Geyser is about to reload. Primarily aimed at extensions, so they can decide on their own what to reload. + * After this event is fired, some lifecycle events can be fired again - such as the {@link GeyserLoadResourcePacksEvent}. + * + * @param extensionManager the extension manager + * @param eventBus the event bus + */ +public record GeyserPreReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event { +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index c5d5cdacc..4191c8578 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -70,11 +70,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private GeyserImpl geyser; - private static boolean INITIALIZED = false; - - @SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"}) @Override public void onLoad() { + onGeyserInitialize(); + } + + @Override + public void onGeyserInitialize() { GeyserLocale.init(this); // Copied from ViaVersion. @@ -91,29 +93,62 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { getLogger().warning("/_____________\\"); } - if (!getDataFolder().exists()) - getDataFolder().mkdir(); - - try { - if (!getDataFolder().exists()) - getDataFolder().mkdir(); - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), - "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); - } catch (IOException ex) { - getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); + if (!this.loadConfig()) { return; } - this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); + this.geyserInjector = new GeyserBungeeInjector(this); } @Override public void onEnable() { + // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating + // task that waits for a field to be filled which is set after the plugin enable + // process is complete + this.awaitStartupCompletion(0); + } + + @SuppressWarnings("unchecked") + private void awaitStartupCompletion(int tries) { + // After 20 tries give up waiting. This will happen just after 3 minutes approximately + if (tries >= 20) { + this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " + + "If all your plugins are loaded properly, this is a bug! " + + "If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times."); + this.onGeyserEnable(); + return; + } + + try { + Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners"); + listenersField.setAccessible(true); + + Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance()); + if (listeners.isEmpty()) { + this.getProxy().getScheduler().schedule(this, this::onGeyserEnable, tries, TimeUnit.SECONDS); + } else { + this.awaitStartupCompletion(++tries); + } + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + public void onGeyserEnable() { + if (GeyserImpl.getInstance().isReloading()) { + if (!loadConfig()) { + return; + } + this.geyserLogger.setDebug(geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + } else { + // For consistency with other platforms - create command manager before GeyserImpl#start() + // This ensures the command events are called before the item/block ones are + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); + } // Force-disable query if enabled, or else Geyser won't enable for (ListenerInfo info : getProxy().getConfig().getListeners()) { @@ -133,54 +168,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } - // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating - // task that waits for a field to be filled which is set after the plugin enable - // process is complete - if (!INITIALIZED) { - this.awaitStartupCompletion(0); - } else { - // No need to "wait" for startup completion, just start Geyser - we're reloading. - this.postStartup(); - } - } + GeyserImpl.start(); - @SuppressWarnings("unchecked") - private void awaitStartupCompletion(int tries) { - // After 20 tries give up waiting. This will happen - // just after 3 minutes approximately - if (tries >= 20) { - this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " + - "If all your plugins are loaded properly, this is a bug! " + - "If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times."); - this.postStartup(); + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); + } else { + this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); + } + + // No need to re-register commands or re-init injector when reloading + if (GeyserImpl.getInstance().isReloading()) { return; } - try { - Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners"); - listenersField.setAccessible(true); - - Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance()); - if (listeners.isEmpty()) { - this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS); - } else { - this.awaitStartupCompletion(++tries); - } - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - } - } - - private void postStartup() { - GeyserImpl.start(); - - if (!INITIALIZED) { - this.geyserInjector = new GeyserBungeeInjector(this); - this.geyserInjector.initializeLocalChannel(this); - } - - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); + this.geyserInjector.initializeLocalChannel(this); this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { @@ -191,18 +192,17 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands)); } - - if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - } else { - this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); - } - - INITIALIZED = true; } @Override - public void onDisable() { + public void onGeyserDisable() { + if (geyser != null) { + geyser.disable(); + } + } + + @Override + public void onGeyserShutdown() { if (geyser != null) { geyser.shutdown(); } @@ -211,6 +211,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } + @Override + public void onDisable() { + this.onGeyserShutdown(); + } + @Override public GeyserBungeeConfiguration getGeyserConfig() { return geyserConfig; @@ -278,4 +283,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { .map(info -> (InetSocketAddress) info.getSocketAddress()) .findFirst(); } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean loadConfig() { + try { + if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored + getDataFolder().mkdir(); + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + return false; + } + return true; + } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index 071409046..756063af7 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -27,6 +27,8 @@ package org.geysermc.geyser.platform.fabric; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import lombok.Getter; +import lombok.Setter; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -66,13 +68,14 @@ import java.util.Optional; import java.util.UUID; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { + + @Getter private static GeyserFabricMod instance; - - private boolean reloading; - private GeyserImpl geyser; private ModContainer mod; private Path dataFolder; + + @Setter private MinecraftServer server; private GeyserCommandManager geyserCommandManager; @@ -85,64 +88,45 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { public void onInitialize() { instance = this; mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); - - this.onEnable(); - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { - // Set as an event so we can get the proper IP and port if needed - ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser); - } + onGeyserInitialize(); } @Override - public void onEnable() { - dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); - if (!dataFolder.toFile().exists()) { - //noinspection ResultOfMethodCallIgnored - dataFolder.toFile().mkdir(); + public void onGeyserInitialize() { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { + // Set as an event, so we can get the proper IP and port if needed + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { + this.server = server; + onGeyserEnable(); + }); } - // Init dataFolder first as local language overrides call getConfigFolder() - GeyserLocale.init(this); + // These are only registered once + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown()); + ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); - try { - File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); - } catch (IOException ex) { - LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); + dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); + GeyserLocale.init(this); + if (!loadConfig()) { return; } - this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(PlatformType.FABRIC, this); - - if (server == null) { - // Server has yet to start - // Register onDisable so players are properly kicked - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); - - ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); - } else { - // Server has started and this is a reload - startGeyser(this.server); - reloading = false; - } } - /** - * Initialize core Geyser. - * A function, as it needs to be called in different places depending on if Geyser is being reloaded or not. - * - * @param server The minecraft server. - */ - public void startGeyser(MinecraftServer server) { - this.server = server; - - GeyserImpl.start(); + @Override + public void onGeyserEnable() { + if (GeyserImpl.getInstance().isReloading()) { + if (!loadConfig()) { + return; + } + this.geyserLogger.setDebug(geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + } else { + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); + } if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -150,8 +134,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); } - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); + GeyserImpl.start(); + + // No need to re-register commands, or re-recreate the world manager when reloading + if (GeyserImpl.getInstance().isReloading()) { + return; + } this.geyserWorldManager = new GeyserFabricWorldManager(server); @@ -201,14 +189,19 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } @Override - public void onDisable() { + public void onGeyserDisable() { + if (geyser != null) { + geyser.disable(); + } + } + + @Override + public void onGeyserShutdown() { if (geyser != null) { geyser.shutdown(); geyser = null; } - if (!reloading) { - this.server = null; - } + this.server = null; } @Override @@ -291,11 +284,22 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } } - public void setReloading(boolean reloading) { - this.reloading = reloading; - } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean loadConfig() { + try { + if (!dataFolder.toFile().exists()) { + //noinspection ResultOfMethodCallIgnored + dataFolder.toFile().mkdir(); + } - public static GeyserFabricMod getInstance() { - return instance; + File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); + return true; + } catch (IOException ex) { + LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + return false; + } } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java index 732b28ca7..86b50d431 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java @@ -32,7 +32,6 @@ import net.minecraft.commands.CommandSourceStack; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.platform.fabric.GeyserFabricMod; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; @@ -64,9 +63,6 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); return 0; } - if (this.command.name().equals("reload")) { - GeyserFabricMod.getInstance().setReloading(true); - } if (command.isBedrockOnly() && session == null) { sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java index af11174dc..999a077bb 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java @@ -57,7 +57,8 @@ public class IntegratedServerMixin implements GeyserServerPortGetter { private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) { if (cir.getReturnValueZ()) { // If the LAN is opened, starts Geyser. - GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this); + GeyserFabricMod.getInstance().setServer((MinecraftServer) (Object) this); + GeyserFabricMod.getInstance().onGeyserEnable(); // Ensure player locale has been loaded, in case it's different from Java system language GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); // Give indication that Geyser is loaded diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index a2a08c3bf..1bc1718d7 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -81,10 +81,6 @@ import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - /** - * Determines if the plugin has been ran once before, including before /geyser reload. - */ - private static boolean INITIALIZED = false; private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; @@ -102,6 +98,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onLoad() { + onGeyserInitialize(); + } + + @Override + public void onGeyserInitialize() { GeyserLocale.init(this); try { @@ -118,6 +119,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); getLogger().severe(""); getLogger().severe("*********************************************"); + Bukkit.getPluginManager().disablePlugin(this); return; } @@ -131,6 +133,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); getLogger().severe(""); getLogger().severe("*********************************************"); + Bukkit.getPluginManager().disablePlugin(this); return; } } @@ -143,86 +146,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!"); getLogger().severe(""); getLogger().severe("*********************************************"); - return; - } - - // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed - try { - if (!getDataFolder().exists()) { - //noinspection ResultOfMethodCallIgnored - getDataFolder().mkdir(); - } - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", - (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); - } catch (IOException ex) { - getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); Bukkit.getPluginManager().disablePlugin(this); return; } + if (!loadConfig()) { + return; + } this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Turn "(MC: 1.16.4)" into 1.16.4. + this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; + this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this); } @Override public void onEnable() { - if (this.geyserConfig == null) { - // We failed to initialize correctly - Bukkit.getPluginManager().disablePlugin(this); - return; - } - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); this.geyserCommandManager.init(); - if (!INITIALIZED) { - // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes - Bukkit.getPluginManager().registerEvents(new Listener() { + // Because Bukkit locks its command map upon startup, we need to + // add our plugin commands in onEnable, but populating the executor + // can happen at any time (later in #onGeyserEnable()) + CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); + for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) { + // Thanks again, Bukkit + try { + Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); + constructor.setAccessible(true); - @EventHandler - public void onServerLoaded(ServerLoadEvent event) { - // Wait until all plugins have loaded so Geyser can start - postStartup(); - } - }, this); + PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this); + pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!"); - // Because Bukkit locks its command map upon startup, we need to - // add our plugin commands in onEnable, but populating the executor - // can happen at any time - CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); - for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) { - // Thanks again, Bukkit - try { - Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); - constructor.setAccessible(true); - - PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this); - pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!"); - - commandMap.register(extension.description().id(), "geyserext", pluginCommand); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { - this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex); - } + commandMap.register(extension.description().id(), "geyserext", pluginCommand); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { + this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex); } } - if (INITIALIZED) { - // Reload; continue with post startup - postStartup(); - } + // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes + Bukkit.getPluginManager().registerEvents(new Listener() { + + @EventHandler + public void onServerLoaded(ServerLoadEvent event) { + if (event.getType() == ServerLoadEvent.LoadType.RELOAD) { + geyser.setShuttingDown(false); + } + onGeyserEnable(); + } + }, this); } - private void postStartup() { - GeyserImpl.start(); + public void onGeyserEnable() { + // Configs are loaded once early - so we can create the logger, then load extensions and finally register + // extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists + if (GeyserImpl.getInstance().isReloading()) { + if (!loadConfig()) { + return; + } + this.geyserLogger.setDebug(this.geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + } - // Turn "(MC: 1.16.4)" into 1.16.4. - this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; + GeyserImpl.start(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -238,20 +227,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; - if (isViaVersion) { - try { - // Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version - Class.forName("com.viaversion.viaversion.api.ViaManager"); - } catch (ClassNotFoundException e) { - GeyserSpigotVersionChecker.sendOutdatedViaVersionMessage(geyserLogger); - isViaVersion = false; - if (this.geyserConfig.isDebugMode()) { - e.printStackTrace(); - } - } + // Don't need to re-create the world manager/re-register commands/reinject when reloading + if (GeyserImpl.getInstance().isReloading()) { + return; } + boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; + + // Check to ensure the current setup can support the protocol version Geyser uses + GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); + // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib // To do their job injecting, then connect into *that* this.geyserInjector = new GeyserSpigotInjector(isViaVersion); @@ -278,6 +263,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } else { geyserLogger.debug("Not using NMS adapter as it is disabled via system property."); } + if (this.geyserWorldManager == null) { // No NMS adapter this.geyserWorldManager = new GeyserSpigotWorldManager(this); @@ -302,72 +288,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); } - if (!INITIALIZED) { - // Register permissions so they appear in, for example, LuckPerms' UI - // Re-registering permissions throws an error - for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { + // Register permissions so they appear in, for example, LuckPerms' UI + // Re-registering permissions throws an error + for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { + Command command = entry.getValue(); + if (command.aliases().contains(entry.getKey())) { + // Don't register aliases + continue; + } + + Bukkit.getPluginManager().addPermission(new Permission(command.permission(), + GeyserLocale.getLocaleStringLog(command.description()), + command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); + } + + // Register permissions for extension commands + for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) { + for (Map.Entry entry : commandEntry.getValue().entrySet()) { Command command = entry.getValue(); if (command.aliases().contains(entry.getKey())) { // Don't register aliases continue; } + if (command.permission().isBlank()) { + continue; + } + + // Avoid registering the same permission twice, e.g. for the extension help commands + if (Bukkit.getPluginManager().getPermission(command.permission()) != null) { + GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered"); + continue; + } + Bukkit.getPluginManager().addPermission(new Permission(command.permission(), GeyserLocale.getLocaleStringLog(command.description()), command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); } - - // Register permissions for extension commands - for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) { - for (Map.Entry entry : commandEntry.getValue().entrySet()) { - Command command = entry.getValue(); - if (command.aliases().contains(entry.getKey())) { - // Don't register aliases - continue; - } - - if (command.permission().isBlank()) { - continue; - } - - // Avoid registering the same permission twice, e.g. for the extension help commands - if (Bukkit.getPluginManager().getPermission(command.permission()) != null) { - GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered"); - continue; - } - - Bukkit.getPluginManager().addPermission(new Permission(command.permission(), - GeyserLocale.getLocaleStringLog(command.description()), - command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); - } - } - - Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION, - "Whether update notifications can be seen", PermissionDefault.OP)); - - // Events cannot be unregistered - re-registering results in duplicate firings - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); - - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); } + Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION, + "Whether update notifications can be seen", PermissionDefault.OP)); + + // Events cannot be unregistered - re-registering results in duplicate firings + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); + Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); + boolean brigadierSupported = CommodoreProvider.isSupported(); geyserLogger.debug("Brigadier supported? " + brigadierSupported); if (brigadierSupported) { GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); } - - // Check to ensure the current setup can support the protocol version Geyser uses - GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); - - INITIALIZED = true; } @Override - public void onDisable() { + public void onGeyserDisable() { + if (geyser != null) { + geyser.disable(); + } + } + + @Override + public void onGeyserShutdown() { if (geyser != null) { geyser.shutdown(); } @@ -376,6 +362,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } + @Override + public void onDisable() { + this.onGeyserShutdown(); + } + @Override public GeyserSpigotConfiguration getGeyserConfig() { return geyserConfig; @@ -470,4 +461,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } return false; } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean loadConfig() { + // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed + try { + if (!getDataFolder().exists()) { + //noinspection ResultOfMethodCallIgnored + getDataFolder().mkdir(); + } + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + Bukkit.getPluginManager().disablePlugin(this); + return false; + } + + return true; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 9f2208ea8..039004867 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -39,9 +39,9 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; @@ -59,7 +59,12 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -68,11 +73,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; - private GeyserStandaloneGUI gui; - @Getter private boolean useGui = System.console() == null && !isHeadless(); + private Logger log4jLogger; private String configFilename = "config.yml"; private GeyserImpl geyser; @@ -161,23 +165,19 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } } } - bootstrap.onEnable(useGuiOpts, configFilenameOpt); - } - - public void onEnable(boolean useGui, String configFilename) { - this.configFilename = configFilename; - this.useGui = useGui; - this.onEnable(); + bootstrap.useGui = useGuiOpts; + bootstrap.configFilename = configFilenameOpt; + bootstrap.onGeyserInitialize(); } @Override - public void onEnable() { - Logger logger = (Logger) LogManager.getRootLogger(); - for (Appender appender : logger.getAppenders().values()) { + public void onGeyserInitialize() { + log4jLogger = (Logger) LogManager.getRootLogger(); + for (Appender appender : log4jLogger.getAppenders().values()) { // Remove the appender that is not in use // Prevents multiple appenders/double logging and removes harmless errors if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) { - logger.removeAppender(appender); + log4jLogger.removeAppender(appender); } } @@ -190,7 +190,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } LoopbackUtil.checkAndApplyLoopback(geyserLogger); - + + this.onGeyserEnable(); + } + + @Override + public void onGeyserEnable() { try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); @@ -215,14 +220,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); // Allow libraries like Protocol to have their debug information passthrough - logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); + log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); geyser = GeyserImpl.load(PlatformType.STANDALONE, this); - GeyserImpl.start(); geyserCommandManager = new GeyserCommandManager(geyser); geyserCommandManager.init(); + GeyserImpl.start(); + if (gui != null) { gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager); } @@ -250,7 +256,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } @Override - public void onDisable() { + public void onGeyserDisable() { + // We can re-register commands on standalone, so why not + GeyserImpl.getInstance().commandManager().getCommands().clear(); + geyser.disable(); + } + + @Override + public void onGeyserShutdown() { geyser.shutdown(); System.exit(0); } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index 3c29bc648..3a34920ce 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -49,7 +49,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override protected void shutdown() { - GeyserImpl.getInstance().getBootstrap().onDisable(); + GeyserImpl.getInstance().getBootstrap().onGeyserShutdown(); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index bd3d6085a..347a47d63 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -32,10 +32,10 @@ import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.network.ListenerType; +import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; -import net.kyori.adventure.util.Codec; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; @@ -46,6 +46,7 @@ import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor; @@ -63,12 +64,6 @@ import java.util.UUID; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") public class GeyserVelocityPlugin implements GeyserBootstrap { - - /** - * Determines if the plugin has been ran once before, including before /geyser reload. - */ - private static boolean INITIALIZED = false; - @Inject private Logger logger; @@ -90,52 +85,54 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/"); @Override - public void onEnable() { - try { - Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class); - } catch (NoSuchMethodException e) { - // velocitypowered.com has a build that is very outdated - logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " + - "that has likely been downloaded is very outdated and does not support 1.19."); - return; - } - + public void onGeyserInitialize() { GeyserLocale.init(this); - try { - if (!configFolder.toFile().exists()) - //noinspection ResultOfMethodCallIgnored - configFolder.toFile().mkdirs(); - File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), - "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); - } catch (IOException ex) { - logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - return; + if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) { + logger.error(" / \\"); + logger.error(" / \\"); + logger.error(" / | \\"); + logger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName())); + logger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps")); + logger.error(" / o \\"); + logger.error("/_____________\\"); } + if (!loadConfig()) { + return; + } this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this); - - // Hack: Normally triggered by ListenerBoundEvent, but that doesn't fire on /geyser reload - if (INITIALIZED) { - this.postStartup(); - } + this.geyserInjector = new GeyserVelocityInjector(proxyServer); } - private void postStartup() { - GeyserImpl.start(); - - if (!INITIALIZED) { - this.geyserInjector = new GeyserVelocityInjector(proxyServer); - // Will be initialized after the proxy has been bound + @Override + public void onGeyserEnable() { + if (GeyserImpl.getInstance().isReloading()) { + if (!loadConfig()) { + return; + } + this.geyserLogger.setDebug(geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + } else { + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); } - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); + GeyserImpl.start(); + + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); + } else { + this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); + } + + // No need to re-register commands when reloading + if (GeyserImpl.getInstance().isReloading()) { + return; + } this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { @@ -147,17 +144,18 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); } - if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - } else { - this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); - } - proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override - public void onDisable() { + public void onGeyserDisable() { + if (geyser != null) { + geyser.disable(); + } + } + + @Override + public void onGeyserShutdown() { if (geyser != null) { geyser.shutdown(); } @@ -188,26 +186,24 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Subscribe public void onInit(ProxyInitializeEvent event) { - onEnable(); + this.onGeyserInitialize(); } @Subscribe public void onShutdown(ProxyShutdownEvent event) { - onDisable(); + this.onGeyserShutdown(); } @Subscribe public void onProxyBound(ListenerBoundEvent event) { if (event.getListenerType() == ListenerType.MINECRAFT) { // Once listener is bound, do our startup process - this.postStartup(); + this.onGeyserEnable(); if (geyserInjector != null) { // After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too geyserInjector.initializeLocalChannel(this); } - - INITIALIZED = true; } } @@ -242,4 +238,21 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } return false; } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean loadConfig() { + try { + if (!configFolder.toFile().exists()) + //noinspection ResultOfMethodCallIgnored + configFolder.toFile().mkdirs(); + File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); + } catch (IOException ex) { + logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + return false; + } + return true; + } } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index 4dbc1dca3..a9414d9d0 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -44,14 +44,28 @@ public interface GeyserBootstrap { GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager(); /** - * Called when the GeyserBootstrap is enabled + * Called when the GeyserBootstrap is initialized. + * This will only be called once, when Geyser is loading. Calling this must + * happen before {@link #onGeyserEnable()}, since this "sets up" Geyser. */ - void onEnable(); + void onGeyserInitialize(); /** - * Called when the GeyserBootstrap is disabled + * Called when the GeyserBootstrap is enabled/reloaded. + * This starts Geyser, after which, Geyser is in a player-accepting state. */ - void onDisable(); + void onGeyserEnable(); + + /** + * Called when the GeyserBootstrap is disabled - either before a reload, + * of before fully shutting down. + */ + void onGeyserDisable(); + + /** + * Called when the GeyserBootstrap is shutting down. + */ + void onGeyserShutdown(); /** * Returns the current GeyserConfiguration diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index e9ea08260..5ed0c3947 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -58,9 +58,7 @@ import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventRegistrar; -import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent; -import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent; -import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; +import org.geysermc.geyser.api.event.lifecycle.*; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; @@ -145,6 +143,7 @@ public class GeyserImpl implements GeyserApi { private UnixSocketClientListener erosionUnixListener; + @Setter private volatile boolean shuttingDown = false; private ScheduledExecutorService scheduledThread; @@ -162,8 +161,14 @@ public class GeyserImpl implements GeyserApi { @Getter(AccessLevel.NONE) private Map savedRefreshTokens; + @Getter private static GeyserImpl instance; + /** + * Determines if we're currently reloading. Replaces per-bootstrap reload checks + */ + private volatile boolean isReloading; + private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { instance = this; @@ -172,13 +177,16 @@ public class GeyserImpl implements GeyserApi { this.platformType = platformType; this.bootstrap = bootstrap; - GeyserLocale.finalizeDefaultLocale(this); - /* Initialize event bus */ this.eventBus = new GeyserEventBus(); - /* Load Extensions */ + /* Create Extension Manager */ this.extensionManager = new GeyserExtensionManager(); + + /* Finalize locale loading now that we know the default locale from the config */ + GeyserLocale.finalizeDefaultLocale(this); + + /* Load Extensions */ this.extensionManager.init(); this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus)); } @@ -236,11 +244,17 @@ public class GeyserImpl implements GeyserApi { } else if (config.getRemote().authType() == AuthType.FLOODGATE) { VersionCheckUtils.checkForOutdatedFloodgate(logger); } + + VersionCheckUtils.checkForOutdatedJava(logger); } private void startInstance() { this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); + if (isReloading) { + // If we're reloading, the default locale in the config might have changed. + GeyserLocale.finalizeDefaultLocale(this); + } GeyserLogger logger = bootstrap.getGeyserLogger(); GeyserConfiguration config = bootstrap.getGeyserConfig(); @@ -536,12 +550,15 @@ public class GeyserImpl implements GeyserApi { newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); - this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); + if (isReloading) { + this.eventBus.fire(new GeyserPostReloadEvent(this.extensionManager, this.eventBus)); + } else { + this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus)); + } + if (config.isNotifyOnNewBedrockUpdate()) { VersionCheckUtils.checkForGeyserUpdate(this::getLogger); } - - VersionCheckUtils.checkForOutdatedJava(logger); } @Override @@ -600,9 +617,8 @@ public class GeyserImpl implements GeyserApi { return session.transfer(address, port); } - public void shutdown() { + public void disable() { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown")); - shuttingDown = true; if (sessionManager.size() >= 1) { bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.log", sessionManager.size())); @@ -616,7 +632,6 @@ public class GeyserImpl implements GeyserApi { skinUploader.close(); } newsHandler.shutdown(); - this.commandManager().getCommands().clear(); if (this.erosionUnixListener != null) { this.erosionUnixListener.close(); @@ -624,16 +639,29 @@ public class GeyserImpl implements GeyserApi { Registries.RESOURCE_PACKS.get().clear(); + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); + } + + public void shutdown() { + shuttingDown = true; + this.disable(); + this.commandManager().getCommands().clear(); + + // Disable extensions, fire the shutdown event this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } - public void reload() { - shutdown(); - this.extensionManager.enableExtensions(); - bootstrap.onEnable(); + public void reloadGeyser() { + isReloading = true; + this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus)); + + bootstrap.onGeyserDisable(); + bootstrap.onGeyserEnable(); + + isReloading = false; } /** @@ -744,9 +772,7 @@ public class GeyserImpl implements GeyserApi { throw new RuntimeException("Geyser has not been loaded yet!"); } - // We've been reloaded - if (instance.isShuttingDown()) { - instance.shuttingDown = false; + if (getInstance().isReloading()) { instance.startInstance(); } else { instance.initialize(); @@ -797,8 +823,4 @@ public class GeyserImpl implements GeyserApi { } }); } - - public static GeyserImpl getInstance() { - return instance; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index d646845c7..72ed22381 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -86,7 +86,7 @@ public class GeyserCommandManager { registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } - if (this.geyser.extensionManager().extensions().size() > 0) { + if (!this.geyser.extensionManager().extensions().isEmpty()) { registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index a3cd8fa4c..987860238 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -55,7 +55,7 @@ public class ReloadCommand extends GeyserCommand { geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); //FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown - geyser.getScheduledThread().schedule(geyser::reload, 10, TimeUnit.MILLISECONDS); + geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 7db539cc5..1cd3050c9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -52,7 +52,7 @@ public class StopCommand extends GeyserCommand { return; } - geyser.getBootstrap().onDisable(); + geyser.getBootstrap().onGeyserShutdown(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index df5eb36f7..59485b2cd 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -166,7 +166,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(LoginPacket loginPacket) { - if (geyser.isShuttingDown()) { + if (geyser.isShuttingDown() || geyser.isReloading()) { // Don't allow new players in if we're no longer operating session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message")); return PacketSignal.HANDLED; diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java index 8c1ef5296..395eb9576 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java @@ -62,7 +62,7 @@ public final class ScoreboardUpdater extends Thread { @Override public void run() { - while (!geyser.isShuttingDown()) { + while (!geyser.isShuttingDown() && !geyser.isReloading()) { try { long timeTillAction = getTimeTillNextAction(); if (timeTillAction > 0) { diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 583e15c23..f040fd0ba 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -266,6 +266,7 @@ public final class EntityUtils { * Convert Java GameMode to Bedrock GameType * Needed to account for ordinal differences (spectator is 3 in Java, 6 in Bedrock) */ + @SuppressWarnings("deprecation") // Must use survival_viewer due to limitations on Bedrock's spectator gamemode public static GameType toBedrockGamemode(GameMode gamemode) { return switch (gamemode) { case CREATIVE -> GameType.CREATIVE;