--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -5,9 +5,9 @@
 import com.mojang.datafixers.DataFixer;
 import com.mojang.logging.LogUtils;
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.Writer;
 import java.net.InetAddress;
 import java.net.Proxy;
 import java.net.URI;
@@ -55,19 +55,33 @@
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.level.GameRules;
 import net.minecraft.world.level.GameType;
-import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.entity.SkullBlockEntity;
 import net.minecraft.world.level.storage.LevelStorageSource;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import net.minecraft.server.WorldLoader;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.io.IoBuilder;
+import org.bukkit.command.CommandSender;
+import co.aikar.timings.MinecraftTimings; // Paper
+import org.bukkit.craftbukkit.util.TerminalCompletionHandler;
+import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread;
+import org.bukkit.event.server.ServerCommandEvent;
+import org.bukkit.craftbukkit.util.Waitable; // Paper
+import org.bukkit.event.server.RemoteServerCommandEvent;
+// CraftBukkit end
+
 public class DedicatedServer extends MinecraftServer implements ServerInterface {
+
     static final Logger LOGGER = LogUtils.getLogger();
     private static final int CONVERSION_RETRY_DELAY_MS = 5000;
     private static final int CONVERSION_RETRIES = 2;
-    private final List<ConsoleInput> consoleInput = Collections.synchronizedList(Lists.newArrayList());
+    private final java.util.Queue<ConsoleInput> serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue
     @Nullable
     private QueryThreadGs4 queryThreadGs4;
-    private final RconConsoleSource rconConsoleSource;
+    // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field
     @Nullable
     private RconThread rconThread;
     public DedicatedServerSettings settings;
@@ -81,146 +95,280 @@
     private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker;
     public ServerLinks serverLinks;
 
-    public DedicatedServer(
-        Thread serverThread,
-        LevelStorageSource.LevelStorageAccess session,
-        PackRepository dataPackManager,
-        WorldStem saveLoader,
-        DedicatedServerSettings propertiesLoader,
-        DataFixer dataFixer,
-        Services apiServices,
-        ChunkProgressListenerFactory worldGenerationProgressListenerFactory
-    ) {
-        super(serverThread, session, dataPackManager, saveLoader, Proxy.NO_PROXY, dataFixer, apiServices, worldGenerationProgressListenerFactory);
-        this.settings = propertiesLoader;
-        this.rconConsoleSource = new RconConsoleSource(this);
-        this.textFilterClient = TextFilterClient.createFromConfig(propertiesLoader.getProperties().textFilteringConfig);
-        this.serverLinks = createServerLinks(propertiesLoader);
+    // CraftBukkit start - Signature changed
+    public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
+        super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
+        // CraftBukkit end
+        this.settings = dedicatedserversettings;
+        // this.rconConsoleSource = new RemoteControlCommandListener(this); // CraftBukkit - remove field
+        this.textFilterClient = TextFilterClient.createFromConfig(dedicatedserversettings.getProperties().textFilteringConfig);
+        this.serverLinks = DedicatedServer.createServerLinks(dedicatedserversettings);
     }
 
     @Override
     public boolean initServer() throws IOException {
         Thread thread = new Thread("Server console handler") {
-            @Override
             public void run() {
-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+                // CraftBukkit start
+                if (!org.bukkit.craftbukkit.Main.useConsole) {
+                    return;
+                }
+                // Paper start - Use TerminalConsoleAppender
+                new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
+                /*
+                jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
 
-                String string;
+                // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
                 try {
-                    while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (string = bufferedReader.readLine()) != null) {
-                        DedicatedServer.this.handleConsoleInput(string, DedicatedServer.this.createCommandSourceStack());
+                    System.in.available();
+                } catch (IOException ex) {
+                    return;
+                }
+                // CraftBukkit end
+
+                String s;
+
+                try {
+                    // CraftBukkit start - JLine disabling compatibility
+                    while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) {
+                        if (org.bukkit.craftbukkit.Main.useJline) {
+                            s = bufferedreader.readLine(">", null);
+                        } else {
+                            s = bufferedreader.readLine();
+                        }
+
+                        // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null
+                        if (s == null) {
+                            try {
+                                Thread.sleep(50L);
+                            } catch (InterruptedException ex) {
+                                Thread.currentThread().interrupt();
+                            }
+                            continue;
+                        }
+                        if (s.trim().length() > 0) { // Trim to filter lines which are just spaces
+                            DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener());
+                        }
+                        // CraftBukkit end
                     }
-                } catch (IOException var4) {
-                    DedicatedServer.LOGGER.error("Exception handling console input", (Throwable)var4);
+                } catch (IOException ioexception) {
+                    DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
                 }
+
+                */
+                // Paper end
             }
         };
+
+        // CraftBukkit start - TODO: handle command-line logging arguments
+        java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
+        global.setUseParentHandlers(false);
+        for (java.util.logging.Handler handler : global.getHandlers()) {
+            global.removeHandler(handler);
+        }
+        global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
+
+        // Paper start - Not needed with TerminalConsoleAppender
+        final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger();
+        /*
+        final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
+        for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
+            if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
+                logger.removeAppender(appender);
+            }
+        }
+
+        TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader);
+        this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler()));
+        writerThread.start();
+        */
+        // Paper end - Not needed with TerminalConsoleAppender
+
+        System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream());
+        System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream());
+        // CraftBukkit end
+
         thread.setDaemon(true);
-        thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
-        thread.start();
-        LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
+        thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
+        // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down
+        DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
         if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
-            LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
+            DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
         }
 
-        LOGGER.info("Loading properties");
-        DedicatedServerProperties dedicatedServerProperties = this.settings.getProperties();
+        // Paper start - detect running as root
+        if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) {
+            DedicatedServer.LOGGER.warn("****************************");
+            DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED.");
+            DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS.");
+            DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/");
+            DedicatedServer.LOGGER.warn("****************************");
+        }
+        // Paper end - detect running as root
+
+        DedicatedServer.LOGGER.info("Loading properties");
+        DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties();
+
         if (this.isSingleplayer()) {
             this.setLocalIp("127.0.0.1");
         } else {
-            this.setUsesAuthentication(dedicatedServerProperties.onlineMode);
-            this.setPreventProxyConnections(dedicatedServerProperties.preventProxyConnections);
-            this.setLocalIp(dedicatedServerProperties.serverIp);
+            this.setUsesAuthentication(dedicatedserverproperties.onlineMode);
+            this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
+            this.setLocalIp(dedicatedserverproperties.serverIp);
         }
+        // Spigot start
+        this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
+        org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
+        org.spigotmc.SpigotConfig.registerCommands();
+        // Spigot end
+        io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
+        // Paper start - initialize global and world-defaults configuration
+        this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
+        this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
+        // Paper end - initialize global and world-defaults configuration
+        this.server.spark.enableEarlyIfRequested(); // Paper - spark
+        // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
+        if (this.convertOldUsers()) {
+            this.getProfileCache().save(false); // Paper
+        }
+        this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames
+        // Paper end - fix converting txt to json file
+        org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
+        thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
+        io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+        this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
+        com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+        com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
+
+        this.setPvpAllowed(dedicatedserverproperties.pvp);
+        this.setFlightAllowed(dedicatedserverproperties.allowFlight);
+        this.setMotd(dedicatedserverproperties.motd);
+        super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get());
+        this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
+        // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
+        DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
+        // Paper start - Unix domain socket support
+        java.net.SocketAddress bindAddress;
+        if (this.getLocalIp().startsWith("unix:")) {
+            if (!io.netty.channel.epoll.Epoll.isAvailable()) {
+                DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
+                DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS.");
+                return false;
+            } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) {
+                DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
+                DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy.");
+                return false;
+            }
+            bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length()));
+        } else {
+        InetAddress inetaddress = null;
 
-        this.setPvpAllowed(dedicatedServerProperties.pvp);
-        this.setFlightAllowed(dedicatedServerProperties.allowFlight);
-        this.setMotd(dedicatedServerProperties.motd);
-        super.setPlayerIdleTimeout(dedicatedServerProperties.playerIdleTimeout.get());
-        this.setEnforceWhitelist(dedicatedServerProperties.enforceWhitelist);
-        this.worldData.setGameType(dedicatedServerProperties.gamemode);
-        LOGGER.info("Default game type: {}", dedicatedServerProperties.gamemode);
-        InetAddress inetAddress = null;
         if (!this.getLocalIp().isEmpty()) {
-            inetAddress = InetAddress.getByName(this.getLocalIp());
+            inetaddress = InetAddress.getByName(this.getLocalIp());
         }
 
         if (this.getPort() < 0) {
-            this.setPort(dedicatedServerProperties.serverPort);
+            this.setPort(dedicatedserverproperties.serverPort);
         }
+        bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort());
+        }
+        // Paper end - Unix domain socket support
 
         this.initializeKeyPair();
-        LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
+        DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
 
         try {
-            this.getConnection().startTcpServerListener(inetAddress, this.getPort());
-        } catch (IOException var10) {
-            LOGGER.warn("**** FAILED TO BIND TO PORT!");
-            LOGGER.warn("The exception was: {}", var10.toString());
-            LOGGER.warn("Perhaps a server is already running on that port?");
+            this.getConnection().bind(bindAddress); // Paper - Unix domain socket support
+        } catch (IOException ioexception) {
+            DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!");
+            DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString());
+            DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
+            if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error
             return false;
         }
 
+        // CraftBukkit start
+        // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
+        this.server.loadPlugins();
+        this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP);
+        // CraftBukkit end
+
+        // Paper start - Add Velocity IP Forwarding Support
+        boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled;
+        String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord";
+        String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/";
+        // Paper end - Add Velocity IP Forwarding Support
         if (!this.usesAuthentication()) {
-            LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
-            LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
-            LOGGER.warn(
-                "While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."
-            );
-            LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
+            DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
+            DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
+            // Spigot start
+            // Paper start - Add Velocity IP Forwarding Support
+            if (usingProxy) {
+                DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose.");
+                DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information.");
+            // Paper end - Add Velocity IP Forwarding Support
+            } else {
+                DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
+            }
+            // Spigot end
+            DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
         }
 
-        if (this.convertOldUsers()) {
-            this.getProfileCache().save();
-        }
 
         if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
             return false;
         } else {
-            this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
+            // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // CraftBukkit - moved up
             this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
-            this.tickTimeLogger = new RemoteSampleLogger(
-                TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME
-            );
-            long l = Util.getNanos();
+            this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
+            long i = Util.getNanos();
+
             SkullBlockEntity.setup(this.services, this);
             GameProfileCache.setUsesAuthentication(this.usesAuthentication());
-            LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
-            this.loadLevel();
-            long m = Util.getNanos() - l;
-            String string = String.format(Locale.ROOT, "%.3fs", (double)m / 1.0E9);
-            LOGGER.info("Done ({})! For help, type \"help\"", string);
-            if (dedicatedServerProperties.announcePlayerAchievements != null) {
-                this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(dedicatedServerProperties.announcePlayerAchievements, this);
+            DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
+            this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
+            long j = Util.getNanos() - i;
+            String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
+
+            DedicatedServer.LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), s); // Paper - clarify startup log messages & add total time
+            if (dedicatedserverproperties.announcePlayerAchievements != null) {
+                ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
             }
 
-            if (dedicatedServerProperties.enableQuery) {
-                LOGGER.info("Starting GS4 status listener");
+            if (dedicatedserverproperties.enableQuery) {
+                DedicatedServer.LOGGER.info("Starting GS4 status listener");
                 this.queryThreadGs4 = QueryThreadGs4.create(this);
             }
 
-            if (dedicatedServerProperties.enableRcon) {
-                LOGGER.info("Starting remote control listener");
+            if (dedicatedserverproperties.enableRcon) {
+                DedicatedServer.LOGGER.info("Starting remote control listener");
                 this.rconThread = RconThread.create(this);
             }
 
-            if (this.getMaxTickLength() > 0L) {
-                Thread thread2 = new Thread(new ServerWatchdog(this));
-                thread2.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER));
-                thread2.setName("Server Watchdog");
-                thread2.setDaemon(true);
-                thread2.start();
+            if (false && this.getMaxTickLength() > 0L) {  // Spigot - disable
+                Thread thread1 = new Thread(new ServerWatchdog(this));
+
+                thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
+                thread1.setName("Server Watchdog");
+                thread1.setDaemon(true);
+                thread1.start();
             }
 
-            if (dedicatedServerProperties.enableJmxMonitoring) {
+            if (dedicatedserverproperties.enableJmxMonitoring) {
                 MinecraftServerStatistics.registerJmxMonitoring(this);
-                LOGGER.info("JMX monitoring enabled");
+                DedicatedServer.LOGGER.info("JMX monitoring enabled");
             }
 
             return true;
         }
     }
 
+    // Paper start
+    public java.io.File getPluginsFolder() {
+        return (java.io.File) this.options.valueOf("plugins");
+    }
+    // Paper end
+
     @Override
     public boolean isSpawningAnimals() {
         return this.getProperties().spawnAnimals && super.isSpawningAnimals();
@@ -243,7 +391,7 @@
 
     @Override
     public void forceDifficulty() {
-        this.setDifficulty(this.getProperties().difficulty, true);
+        // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current
     }
 
     @Override
@@ -253,29 +401,49 @@
 
     @Override
     public SystemReport fillServerSystemReport(SystemReport details) {
-        details.setDetail("Is Modded", () -> this.getModdedStatus().fullDescription());
-        details.setDetail("Type", () -> "Dedicated Server (map_server.txt)");
+        details.setDetail("Is Modded", () -> {
+            return this.getModdedStatus().fullDescription();
+        });
+        details.setDetail("Type", () -> {
+            return "Dedicated Server (map_server.txt)";
+        });
         return details;
     }
 
     @Override
     public void dumpServerProperties(Path file) throws IOException {
-        DedicatedServerProperties dedicatedServerProperties = this.getProperties();
+        DedicatedServerProperties dedicatedserverproperties = this.getProperties();
+        BufferedWriter bufferedwriter = Files.newBufferedWriter(file);
 
-        try (Writer writer = Files.newBufferedWriter(file)) {
-            writer.write(String.format(Locale.ROOT, "sync-chunk-writes=%s%n", dedicatedServerProperties.syncChunkWrites));
-            writer.write(String.format(Locale.ROOT, "gamemode=%s%n", dedicatedServerProperties.gamemode));
-            writer.write(String.format(Locale.ROOT, "spawn-monsters=%s%n", dedicatedServerProperties.spawnMonsters));
-            writer.write(String.format(Locale.ROOT, "entity-broadcast-range-percentage=%d%n", dedicatedServerProperties.entityBroadcastRangePercentage));
-            writer.write(String.format(Locale.ROOT, "max-world-size=%d%n", dedicatedServerProperties.maxWorldSize));
-            writer.write(String.format(Locale.ROOT, "spawn-npcs=%s%n", dedicatedServerProperties.spawnNpcs));
-            writer.write(String.format(Locale.ROOT, "view-distance=%d%n", dedicatedServerProperties.viewDistance));
-            writer.write(String.format(Locale.ROOT, "simulation-distance=%d%n", dedicatedServerProperties.simulationDistance));
-            writer.write(String.format(Locale.ROOT, "spawn-animals=%s%n", dedicatedServerProperties.spawnAnimals));
-            writer.write(String.format(Locale.ROOT, "generate-structures=%s%n", dedicatedServerProperties.worldOptions.generateStructures()));
-            writer.write(String.format(Locale.ROOT, "use-native=%s%n", dedicatedServerProperties.useNativeTransport));
-            writer.write(String.format(Locale.ROOT, "rate-limit=%d%n", dedicatedServerProperties.rateLimitPacketsPerSecond));
+        try {
+            bufferedwriter.write(String.format(Locale.ROOT, "sync-chunk-writes=%s%n", dedicatedserverproperties.syncChunkWrites));
+            bufferedwriter.write(String.format(Locale.ROOT, "gamemode=%s%n", dedicatedserverproperties.gamemode));
+            bufferedwriter.write(String.format(Locale.ROOT, "spawn-monsters=%s%n", dedicatedserverproperties.spawnMonsters));
+            bufferedwriter.write(String.format(Locale.ROOT, "entity-broadcast-range-percentage=%d%n", dedicatedserverproperties.entityBroadcastRangePercentage));
+            bufferedwriter.write(String.format(Locale.ROOT, "max-world-size=%d%n", dedicatedserverproperties.maxWorldSize));
+            bufferedwriter.write(String.format(Locale.ROOT, "spawn-npcs=%s%n", dedicatedserverproperties.spawnNpcs));
+            bufferedwriter.write(String.format(Locale.ROOT, "view-distance=%d%n", dedicatedserverproperties.viewDistance));
+            bufferedwriter.write(String.format(Locale.ROOT, "simulation-distance=%d%n", dedicatedserverproperties.simulationDistance));
+            bufferedwriter.write(String.format(Locale.ROOT, "spawn-animals=%s%n", dedicatedserverproperties.spawnAnimals));
+            bufferedwriter.write(String.format(Locale.ROOT, "generate-structures=%s%n", dedicatedserverproperties.worldOptions.generateStructures()));
+            bufferedwriter.write(String.format(Locale.ROOT, "use-native=%s%n", dedicatedserverproperties.useNativeTransport));
+            bufferedwriter.write(String.format(Locale.ROOT, "rate-limit=%d%n", dedicatedserverproperties.rateLimitPacketsPerSecond));
+        } catch (Throwable throwable) {
+            if (bufferedwriter != null) {
+                try {
+                    bufferedwriter.close();
+                } catch (Throwable throwable1) {
+                    throwable.addSuppressed(throwable1);
+                }
+            }
+
+            throw throwable;
         }
+
+        if (bufferedwriter != null) {
+            bufferedwriter.close();
+        }
+
     }
 
     @Override
@@ -289,12 +457,15 @@
         }
 
         if (this.rconThread != null) {
-            this.rconThread.stop();
+            this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections
         }
 
         if (this.queryThreadGs4 != null) {
-            this.queryThreadGs4.stop();
+            // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
         }
+
+        hasFullyShutdown = true; // Paper
+        System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
     }
 
     @Override
@@ -304,19 +475,59 @@
     }
 
     @Override
-    public boolean isLevelEnabled(Level world) {
-        return world.dimension() != Level.NETHER || this.getProperties().allowNether;
+    public boolean isLevelEnabled(net.minecraft.world.level.Level world) {
+        return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
     }
 
+    private static final java.util.concurrent.atomic.AtomicInteger ASYNC_DEBUG_CHUNKS_COUNT = new java.util.concurrent.atomic.AtomicInteger(); // Paper - rewrite chunk system
+
     public void handleConsoleInput(String command, CommandSourceStack commandSource) {
-        this.consoleInput.add(new ConsoleInput(command, commandSource));
+        // Paper start - rewrite chunk system
+        if (command.equalsIgnoreCase("paper debug chunks --async")) {
+            LOGGER.info("Scheduling async debug chunks");
+            Runnable run = () -> {
+                LOGGER.info("Async debug chunks executing");
+                ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(this, false);
+                CommandSender sender = MinecraftServer.getServer().console;
+                java.io.File file = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getChunkDebugFile();
+                sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN));
+                try {
+                    ca.spottedleaf.moonrise.common.util.JsonUtil.writeJson(ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.debugAllWorlds(this), file);
+                    sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN));
+                } catch (Throwable thr) {
+                    MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
+                    sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED));
+                }
+            };
+            Thread t = new Thread(run);
+            t.setName("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement());
+            t.setDaemon(true);
+            t.start();
+            return;
+        }
+        // Paper end - rewrite chunk system
+        this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue
     }
 
     public void handleConsoleInputs() {
-        while (!this.consoleInput.isEmpty()) {
-            ConsoleInput consoleInput = this.consoleInput.remove(0);
-            this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg);
+        MinecraftTimings.serverCommandTimer.startTiming(); // Spigot
+        // Paper start - Perf: use proper queue
+        ConsoleInput servercommand;
+        while ((servercommand = this.serverCommandQueue.poll()) != null) {
+            // Paper end - Perf: use proper queue
+
+            // CraftBukkit start - ServerCommand for preprocessing
+            ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg);
+            this.server.getPluginManager().callEvent(event);
+            if (event.isCancelled()) continue;
+            servercommand = new ConsoleInput(event.getCommand(), servercommand.source);
+
+            // this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); // Called in dispatchServerCommand
+            this.server.dispatchServerCommand(this.console, servercommand);
+            // CraftBukkit end
         }
+
+        MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot
     }
 
     @Override
@@ -336,7 +547,7 @@
 
     @Override
     public DedicatedPlayerList getPlayerList() {
-        return (DedicatedPlayerList)super.getPlayerList();
+        return (DedicatedPlayerList) super.getPlayerList();
     }
 
     @Override
@@ -363,6 +574,7 @@
         if (this.gui == null) {
             this.gui = MinecraftServerGui.showFrameFor(this);
         }
+
     }
 
     @Override
@@ -382,7 +594,7 @@
 
     @Override
     public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
-        if (world.dimension() != Level.OVERWORLD) {
+        if (world.dimension() != net.minecraft.world.level.Level.OVERWORLD) {
             return false;
         } else if (this.getPlayerList().getOps().isEmpty()) {
             return false;
@@ -391,10 +603,11 @@
         } else if (this.getSpawnProtectionRadius() <= 0) {
             return false;
         } else {
-            BlockPos blockPos = world.getSharedSpawnPos();
-            int i = Mth.abs(pos.getX() - blockPos.getX());
-            int j = Mth.abs(pos.getZ() - blockPos.getZ());
+            BlockPos blockposition1 = world.getSharedSpawnPos();
+            int i = Mth.abs(pos.getX() - blockposition1.getX());
+            int j = Mth.abs(pos.getZ() - blockposition1.getZ());
             int k = Math.max(i, j);
+
             return k <= this.getSpawnProtectionRadius();
         }
     }
@@ -422,7 +635,9 @@
     @Override
     public void setPlayerIdleTimeout(int playerIdleTimeout) {
         super.setPlayerIdleTimeout(playerIdleTimeout);
-        this.settings.update(serverPropertiesHandler -> serverPropertiesHandler.playerIdleTimeout.update(this.registryAccess(), playerIdleTimeout));
+        this.settings.update((dedicatedserverproperties) -> {
+            return (DedicatedServerProperties) dedicatedserverproperties.playerIdleTimeout.update(this.registryAccess(), playerIdleTimeout);
+        });
     }
 
     @Override
@@ -447,8 +662,13 @@
 
     @Override
     public boolean enforceSecureProfile() {
-        DedicatedServerProperties dedicatedServerProperties = this.getProperties();
-        return dedicatedServerProperties.enforceSecureProfile && dedicatedServerProperties.onlineMode && this.services.canValidateProfileKeys();
+        DedicatedServerProperties dedicatedserverproperties = this.getProperties();
+
+        // Paper start - Add setting for proxy online mode status
+        return dedicatedserverproperties.enforceSecureProfile
+            && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
+            && this.services.canValidateProfileKeys();
+        // Paper end - Add setting for proxy online mode status
     }
 
     @Override
@@ -457,68 +677,71 @@
     }
 
     protected boolean convertOldUsers() {
-        boolean bl = false;
+        boolean flag = false;
 
-        for (int i = 0; !bl && i <= 2; i++) {
+        int i;
+
+        for (i = 0; !flag && i <= 2; ++i) {
             if (i > 0) {
-                LOGGER.warn("Encountered a problem while converting the user banlist, retrying in a few seconds");
+                DedicatedServer.LOGGER.warn("Encountered a problem while converting the user banlist, retrying in a few seconds");
                 this.waitForRetry();
             }
 
-            bl = OldUsersConverter.convertUserBanlist(this);
+            flag = OldUsersConverter.convertUserBanlist(this);
         }
 
-        boolean bl2 = false;
+        boolean flag1 = false;
 
-        for (int var7 = 0; !bl2 && var7 <= 2; var7++) {
-            if (var7 > 0) {
-                LOGGER.warn("Encountered a problem while converting the ip banlist, retrying in a few seconds");
+        for (i = 0; !flag1 && i <= 2; ++i) {
+            if (i > 0) {
+                DedicatedServer.LOGGER.warn("Encountered a problem while converting the ip banlist, retrying in a few seconds");
                 this.waitForRetry();
             }
 
-            bl2 = OldUsersConverter.convertIpBanlist(this);
+            flag1 = OldUsersConverter.convertIpBanlist(this);
         }
 
-        boolean bl3 = false;
+        boolean flag2 = false;
 
-        for (int var8 = 0; !bl3 && var8 <= 2; var8++) {
-            if (var8 > 0) {
-                LOGGER.warn("Encountered a problem while converting the op list, retrying in a few seconds");
+        for (i = 0; !flag2 && i <= 2; ++i) {
+            if (i > 0) {
+                DedicatedServer.LOGGER.warn("Encountered a problem while converting the op list, retrying in a few seconds");
                 this.waitForRetry();
             }
 
-            bl3 = OldUsersConverter.convertOpsList(this);
+            flag2 = OldUsersConverter.convertOpsList(this);
         }
 
-        boolean bl4 = false;
+        boolean flag3 = false;
 
-        for (int var9 = 0; !bl4 && var9 <= 2; var9++) {
-            if (var9 > 0) {
-                LOGGER.warn("Encountered a problem while converting the whitelist, retrying in a few seconds");
+        for (i = 0; !flag3 && i <= 2; ++i) {
+            if (i > 0) {
+                DedicatedServer.LOGGER.warn("Encountered a problem while converting the whitelist, retrying in a few seconds");
                 this.waitForRetry();
             }
 
-            bl4 = OldUsersConverter.convertWhiteList(this);
+            flag3 = OldUsersConverter.convertWhiteList(this);
         }
 
-        boolean bl5 = false;
+        boolean flag4 = false;
 
-        for (int var10 = 0; !bl5 && var10 <= 2; var10++) {
-            if (var10 > 0) {
-                LOGGER.warn("Encountered a problem while converting the player save files, retrying in a few seconds");
+        for (i = 0; !flag4 && i <= 2; ++i) {
+            if (i > 0) {
+                DedicatedServer.LOGGER.warn("Encountered a problem while converting the player save files, retrying in a few seconds");
                 this.waitForRetry();
             }
 
-            bl5 = OldUsersConverter.convertPlayers(this);
+            flag4 = OldUsersConverter.convertPlayers(this);
         }
 
-        return bl || bl2 || bl3 || bl4 || bl5;
+        return flag || flag1 || flag2 || flag3 || flag4;
     }
 
     private void waitForRetry() {
         try {
             Thread.sleep(5000L);
-        } catch (InterruptedException var2) {
+        } catch (InterruptedException interruptedexception) {
+            ;
         }
     }
 
@@ -533,24 +756,96 @@
 
     @Override
     public String getPluginNames() {
-        return "";
+        // CraftBukkit start - Whole method
+        StringBuilder result = new StringBuilder();
+        org.bukkit.plugin.Plugin[] plugins = this.server.getPluginManager().getPlugins();
+
+        result.append(this.server.getName());
+        result.append(" on Bukkit ");
+        result.append(this.server.getBukkitVersion());
+
+        if (plugins.length > 0 && this.server.getQueryPlugins()) {
+            result.append(": ");
+
+            for (int i = 0; i < plugins.length; i++) {
+                if (i > 0) {
+                    result.append("; ");
+                }
+
+                result.append(plugins[i].getDescription().getName());
+                result.append(" ");
+                result.append(plugins[i].getDescription().getVersion().replaceAll(";", ","));
+            }
+        }
+
+        return result.toString();
+        // CraftBukkit end
     }
 
     @Override
     public String runCommand(String command) {
-        this.rconConsoleSource.prepareForCommand();
-        this.executeBlocking(() -> this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command));
-        return this.rconConsoleSource.getCommandResponse();
+        // CraftBukkit start - fire RemoteServerCommandEvent
+        throw new UnsupportedOperationException("Not supported - remote source required.");
+    }
+
+    public String runCommand(RconConsoleSource rconConsoleSource, String s) {
+        Waitable[] waitableArray = new Waitable[1]; // Paper
+        rconConsoleSource.prepareForCommand();
+        final java.util.concurrent.atomic.AtomicReference<String> command = new java.util.concurrent.atomic.AtomicReference<>(s); // Paper
+        this.executeBlocking(() -> {
+            CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
+            RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
+            this.server.getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                return;
+            }
+            // Paper start
+            command.set(event.getCommand());
+            if (event.getCommand().toLowerCase(java.util.Locale.ROOT).startsWith("timings") && event.getCommand().toLowerCase(java.util.Locale.ROOT).matches("timings (report|paste|get|merged|seperate)")) {
+                org.bukkit.command.BufferedCommandSender sender = new org.bukkit.command.BufferedCommandSender();
+                Waitable<String> waitable = new Waitable<>() {
+                    @Override
+                    protected String evaluate() {
+                        return sender.getBuffer();
+                    }
+                };
+                waitableArray[0] = waitable;
+                co.aikar.timings.Timings.generateReport(new co.aikar.timings.TimingsReportListener(sender, waitable));
+            } else {
+            // Paper end
+            ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
+            this.server.dispatchServerCommand(event.getSender(), serverCommand);
+            } // Paper
+        });
+        // Paper start
+        if (waitableArray[0] != null) {
+            //noinspection unchecked
+            Waitable<String> waitable = waitableArray[0];
+            try {
+                return waitable.get();
+            } catch (java.util.concurrent.ExecutionException e) {
+                throw new RuntimeException("Exception processing rcon command " + command.get(), e.getCause());
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt(); // Maintain interrupted state
+                throw new RuntimeException("Interrupted processing rcon command " + command.get(), e);
+            }
+
+        }
+        // Paper end
+        return rconConsoleSource.getCommandResponse();
+        // CraftBukkit end
     }
 
     public void storeUsingWhiteList(boolean useWhitelist) {
-        this.settings.update(serverPropertiesHandler -> serverPropertiesHandler.whiteList.update(this.registryAccess(), useWhitelist));
+        this.settings.update((dedicatedserverproperties) -> {
+            return (DedicatedServerProperties) dedicatedserverproperties.whiteList.update(this.registryAccess(), useWhitelist);
+        });
     }
 
     @Override
     public void stopServer() {
         super.stopServer();
-        Util.shutdownExecutors();
+        //Util.shutdownExecutors(); // Paper - moved into super
         SkullBlockEntity.clear();
     }
 
@@ -622,21 +917,36 @@
     }
 
     private static ServerLinks createServerLinks(DedicatedServerSettings propertiesLoader) {
-        Optional<URI> optional = parseBugReportLink(propertiesLoader.getProperties());
-        return optional.<ServerLinks>map(uri -> new ServerLinks(List.of(ServerLinks.KnownLinkType.BUG_REPORT.create(uri)))).orElse(ServerLinks.EMPTY);
+        Optional<URI> optional = DedicatedServer.parseBugReportLink(propertiesLoader.getProperties());
+
+        return (ServerLinks) optional.map((uri) -> {
+            return new ServerLinks(List.of(ServerLinks.KnownLinkType.BUG_REPORT.create(uri)));
+        }).orElse(ServerLinks.EMPTY);
     }
 
     private static Optional<URI> parseBugReportLink(DedicatedServerProperties propertiesHandler) {
-        String string = propertiesHandler.bugReportLink;
-        if (string.isEmpty()) {
+        String s = propertiesHandler.bugReportLink;
+
+        if (s.isEmpty()) {
             return Optional.empty();
         } else {
             try {
-                return Optional.of(Util.parseAndValidateUntrustedUri(string));
-            } catch (Exception var3) {
-                LOGGER.warn("Failed to parse bug link {}", string, var3);
+                return Optional.of(Util.parseAndValidateUntrustedUri(s));
+            } catch (Exception exception) {
+                DedicatedServer.LOGGER.warn("Failed to parse bug link {}", s, exception);
                 return Optional.empty();
             }
         }
     }
+
+    // CraftBukkit start
+    public boolean isDebugging() {
+        return this.getProperties().debug;
+    }
+
+    @Override
+    public CommandSender getBukkitSender(CommandSourceStack wrapper) {
+        return this.console;
+    }
+    // CraftBukkit end
 }
