2015-05-25 12:37:24 +02:00
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
2024-12-14 04:12:33 +01:00
@@ -174,11 +_,13 @@
import org.slf4j.Logger;
2015-02-26 23:41:06 +01:00
2024-12-11 22:26:55 +01:00
public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, ChunkIOErrorReporter, CommandSource {
2016-04-28 06:57:27 +02:00
+ private static MinecraftServer SERVER; // Paper
2013-01-10 06:18:11 +01:00
public static final Logger LOGGER = LogUtils.getLogger();
2021-01-29 17:54:03 +01:00
+ public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
2023-12-05 17:40:00 +01:00
public static final String VANILLA_BRAND = "vanilla";
private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F;
private static final int TICK_STATS_SPAN = 100;
2024-12-11 22:26:55 +01:00
- private static final long OVERLOADED_THRESHOLD_NANOS = 20L * TimeUtil.NANOSECONDS_PER_SECOND / 20L;
+ private static final long OVERLOADED_THRESHOLD_NANOS = 30L * TimeUtil.NANOSECONDS_PER_SECOND / 20L; // CraftBukkit
2023-12-05 17:40:00 +01:00
private static final int OVERLOADED_TICKS_THRESHOLD = 20;
2024-12-11 22:26:55 +01:00
private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
2023-12-05 17:40:00 +01:00
private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
2024-12-14 04:24:28 +01:00
@@ -218,6 +_,7 @@
private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
private PlayerList playerList;
2024-12-14 04:12:33 +01:00
private volatile boolean running = true;
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
private boolean stopped;
private int tickCount;
2024-12-14 04:12:33 +01:00
private int ticksUntilAutosave = 6000;
@@ -226,11 +_,15 @@
2021-01-29 17:54:03 +01:00
private boolean preventProxyConnections;
private boolean pvp;
private boolean allowFlight;
- @Nullable
- private String motd;
+ private net.kyori.adventure.text.Component motd; // Paper - Adventure
private int playerIdleTimeout;
2024-12-14 04:12:33 +01:00
private final long[] tickTimesNanos = new long[100];
private long aggregatedTickTimesNanos = 0L;
2020-04-06 05:23:14 +02:00
+ // Paper start - Add tick times API and /mspt command
+ public final TickTimes tickTimes5s = new TickTimes(100);
+ public final TickTimes tickTimes10s = new TickTimes(200);
+ public final TickTimes tickTimes60s = new TickTimes(1200);
+ // Paper end - Add tick times API and /mspt command
@Nullable
private KeyPair keyPair;
@Nullable
2024-12-14 04:12:33 +01:00
@@ -271,10 +_,33 @@
private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
2024-10-22 17:15:00 +02:00
private final DiscontinuousFrame tickFrame;
2017-06-09 19:03:43 +02:00
2014-11-25 22:32:16 +01:00
+ // CraftBukkit start
2024-12-11 22:26:55 +01:00
+ public final WorldLoader.DataLoadContext worldLoader;
2014-11-25 22:32:16 +01:00
+ public org.bukkit.craftbukkit.CraftServer server;
2024-12-14 04:12:33 +01:00
+ public joptsimple.OptionSet options;
2014-11-25 22:32:16 +01:00
+ public org.bukkit.command.ConsoleCommandSender console;
2016-03-02 06:09:29 +01:00
+ public static int currentTick; // Paper - improve tick loop
2014-11-25 22:32:16 +01:00
+ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
+ public int autosavePeriod;
2022-08-02 04:50:34 +02:00
+ // Paper - don't store the vanilla dispatcher
2019-04-25 07:33:13 +02:00
+ private boolean forceTicks;
2014-11-25 22:32:16 +01:00
+ // CraftBukkit end
2017-01-26 22:50:51 +01:00
+ // Spigot start
+ public static final int TPS = 20;
+ public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
2016-03-02 06:09:29 +01:00
+ private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop
+ @Deprecated(forRemoval = true) // Paper
2017-01-26 22:50:51 +01:00
+ public final double[] recentTps = new double[ 3 ];
+ // Spigot end
2022-06-09 07:20:16 +02:00
+ public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
2022-03-22 20:44:30 +01:00
+ public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
2024-11-12 22:25:20 +01:00
+ private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
2017-06-09 19:03:43 +02:00
+
2024-12-14 04:12:33 +01:00
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
AtomicReference<S> atomicReference = new AtomicReference<>();
Thread thread = new Thread(() -> atomicReference.get().runServer(), "Server thread");
thread.setUncaughtExceptionHandler((thread1, exception) -> LOGGER.error("Uncaught exception in server thread", exception));
2018-10-24 05:14:38 +02:00
+ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority
if (Runtime.getRuntime().availableProcessors() > 4) {
2021-11-21 23:00:00 +01:00
thread.setPriority(8);
}
2024-12-14 04:12:33 +01:00
@@ -286,6 +_,10 @@
2020-06-25 02:00:00 +02:00
}
2024-12-14 04:12:33 +01:00
public MinecraftServer(
+ // CraftBukkit start
+ joptsimple.OptionSet options,
+ WorldLoader.DataLoadContext worldLoader,
+ // CraftBukkit end
Thread serverThread,
LevelStorageSource.LevelStorageAccess storageSource,
PackRepository packRepository,
2024-12-14 04:24:28 +01:00
@@ -296,9 +_,10 @@
ChunkProgressListenerFactory progressListenerFactory
2024-12-14 04:12:33 +01:00
) {
2019-04-23 04:00:00 +02:00
super("Server");
2016-04-28 06:57:27 +02:00
+ SERVER = this; // Paper - better singleton
2024-12-14 04:12:33 +01:00
this.registries = worldStem.registries();
this.worldData = worldStem.worldData();
2024-12-11 22:26:55 +01:00
- if (!this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) {
+ if (false && !this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { // CraftBukkit - initialised later
2022-06-07 18:00:00 +02:00
throw new IllegalStateException("Missing Overworld dimension data");
} else {
this.proxy = proxy;
2024-12-14 04:12:33 +01:00
@@ -309,7 +_,7 @@
services.profileCache().setExecutor(this);
2024-12-11 22:26:55 +01:00
}
2013-12-13 01:58:58 +01:00
- this.connection = new ServerConnectionListener(this);
2024-12-14 04:12:33 +01:00
+ // this.connection = new ServerConnectionListener(this); // Spigot
2024-12-11 22:26:55 +01:00
this.tickRateManager = new ServerTickRateManager(this);
2024-12-14 04:12:33 +01:00
this.progressListenerFactory = progressListenerFactory;
this.storageSource = storageSource;
@@ -328,6 +_,37 @@
2024-10-22 17:15:00 +02:00
this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
this.tickFrame = TracyClient.createDiscontinuousFrame("Server Tick");
2022-06-07 18:00:00 +02:00
}
2014-11-25 22:32:16 +01:00
+ // CraftBukkit start
+ this.options = options;
2022-12-07 17:00:00 +01:00
+ this.worldLoader = worldLoader;
2017-06-09 19:03:43 +02:00
+ // Paper start - Handled by TerminalConsoleAppender
2014-11-25 22:32:16 +01:00
+ // Try to see if we're actually running in a terminal, disable jline if not
2017-06-09 19:03:43 +02:00
+ /*
2015-06-11 13:59:36 +02:00
+ if (System.console() == null && System.getProperty("jline.terminal") == null) {
2014-11-25 22:32:16 +01:00
+ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
+ Main.useJline = false;
+ }
+
+ try {
2024-12-11 22:26:55 +01:00
+ this.reader = new ConsoleReader(System.in, System.out);
+ this.reader.setExpandEvents(false); // Avoid parsing exceptions for uncommonly used event designators
2014-11-25 22:32:16 +01:00
+ } catch (Throwable e) {
+ try {
+ // Try again with jline disabled for Windows users without C++ 2008 Redistributable
+ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
+ System.setProperty("user.language", "en");
+ Main.useJline = false;
2024-12-11 22:26:55 +01:00
+ this.reader = new ConsoleReader(System.in, System.out);
+ this.reader.setExpandEvents(false);
2014-11-25 22:32:16 +01:00
+ } catch (IOException ex) {
2024-12-11 22:26:55 +01:00
+ MinecraftServer.LOGGER.warn((String) null, ex);
2014-11-25 22:32:16 +01:00
+ }
+ }
2017-06-09 19:03:43 +02:00
+ */
+ // Paper end
2014-11-25 22:32:16 +01:00
+ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
2023-09-21 18:40:00 +02:00
+ // CraftBukkit end
2022-06-09 07:20:16 +02:00
+ this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
2014-11-25 22:32:16 +01:00
}
2018-07-15 02:00:00 +02:00
2024-12-14 04:12:33 +01:00
private void readScoreboard(DimensionDataStorage dataStorage) {
@@ -336,18 +_,13 @@
2018-07-22 04:00:00 +02:00
2021-11-21 23:00:00 +01:00
protected abstract boolean initServer() throws IOException;
- protected void loadLevel() {
+ protected void loadLevel(String s) { // CraftBukkit
if (!JvmProfiler.INSTANCE.isRunning()) {
}
2024-12-14 04:12:33 +01:00
boolean flag = false;
ProfiledDuration profiledDuration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
2021-11-21 23:00:00 +01:00
- this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
2024-12-14 04:12:33 +01:00
- ChunkProgressListener chunkProgressListener = this.progressListenerFactory
- .create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
- this.createLevels(chunkProgressListener);
2021-11-21 23:00:00 +01:00
- this.forceDifficulty();
2024-12-14 04:12:33 +01:00
- this.prepareLevels(chunkProgressListener);
+ this.loadWorld0(s); // CraftBukkit
if (profiledDuration != null) {
profiledDuration.finish(true);
2021-11-21 23:00:00 +01:00
}
2024-12-14 04:12:33 +01:00
@@ -364,25 +_,245 @@
protected void forceDifficulty() {
}
- protected void createLevels(ChunkProgressListener listener) {
- ServerLevelData serverLevelData = this.worldData.overworldData();
- boolean isDebugWorld = this.worldData.isDebugWorld();
- Registry<LevelStem> registry = this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM);
- WorldOptions worldOptions = this.worldData.worldGenOptions();
- long seed = worldOptions.seed();
- long l = BiomeManager.obfuscateSeed(seed);
- List<CustomSpawner> list = ImmutableList.of(
- new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverLevelData)
- );
- LevelStem levelStem = registry.getValue(LevelStem.OVERWORLD);
- ServerLevel serverLevel = new ServerLevel(
- this, this.executor, this.storageSource, serverLevelData, Level.OVERWORLD, levelStem, listener, isDebugWorld, l, list, true, null
- );
- this.levels.put(Level.OVERWORLD, serverLevel);
- DimensionDataStorage dataStorage = serverLevel.getDataStorage();
- this.readScoreboard(dataStorage);
- this.commandStorage = new CommandStorage(dataStorage);
- WorldBorder worldBorder = serverLevel.getWorldBorder();
2021-11-21 23:00:00 +01:00
+ // CraftBukkit start
+ private void loadWorld0(String s) {
2024-12-11 22:26:55 +01:00
+ LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
+ RegistryAccess.Frozen iregistrycustom_dimension = this.registries.compositeAccess();
+ Registry<LevelStem> dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
+ for (LevelStem worldDimension : dimensions) {
+ ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
2024-12-14 04:24:28 +01:00
+ ServerLevel world;
2016-03-01 21:32:43 +01:00
+ int dimension = 0;
+
2024-12-11 22:26:55 +01:00
+ if (dimensionKey == LevelStem.NETHER) {
+ if (this.server.getAllowNether()) {
2014-11-25 22:32:16 +01:00
+ dimension = -1;
+ } else {
+ continue;
+ }
2024-12-11 22:26:55 +01:00
+ } else if (dimensionKey == LevelStem.END) {
+ if (this.server.getAllowEnd()) {
2014-11-25 22:32:16 +01:00
+ dimension = 1;
+ } else {
+ continue;
+ }
2024-12-11 22:26:55 +01:00
+ } else if (dimensionKey != LevelStem.OVERWORLD) {
2021-04-16 02:36:05 +02:00
+ dimension = -999;
2018-08-26 04:00:00 +02:00
+ }
2023-09-21 18:40:00 +02:00
+
2024-06-02 23:18:17 +02:00
+ String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT);
2024-12-11 22:26:55 +01:00
+ String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
2021-04-16 02:36:05 +02:00
+ if (dimension != 0) {
2024-12-14 04:12:33 +01:00
+ java.io.File newWorld = LevelStorageSource.getStorageFolder(new java.io.File(name).toPath(), dimensionKey).toFile();
+ java.io.File oldWorld = LevelStorageSource.getStorageFolder(new java.io.File(s).toPath(), dimensionKey).toFile();
+ java.io.File oldLevelDat = new java.io.File(new java.io.File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
2020-06-26 01:49:40 +02:00
+
+ if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
+ MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
+ MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
+ MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
+
+ if (newWorld.exists()) {
+ MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!");
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
+ } else if (newWorld.getParentFile().mkdirs()) {
+ if (oldWorld.renameTo(newWorld)) {
+ MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
+ // Migrate world data too.
+ try {
2024-12-14 04:12:33 +01:00
+ com.google.common.io.Files.copy(oldLevelDat, new java.io.File(new java.io.File(name), "level.dat"));
+ org.apache.commons.io.FileUtils.copyDirectory(new java.io.File(new java.io.File(s), "data"), new java.io.File(new java.io.File(name), "data"));
2020-06-26 01:49:40 +02:00
+ } catch (IOException exception) {
+ MinecraftServer.LOGGER.warn("Unable to migrate world data.");
+ }
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----");
+ } else {
+ MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!");
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
+ }
+ } else {
+ MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!");
+ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
+ }
+ }
2023-03-14 17:30:00 +01:00
+
2020-06-25 02:00:00 +02:00
+ try {
2024-12-11 22:26:55 +01:00
+ worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
2024-12-14 04:12:33 +01:00
+ } catch (IOException | net.minecraft.world.level.validation.ContentValidationException ex) {
2020-06-25 02:00:00 +02:00
+ throw new RuntimeException(ex);
+ }
+ }
2023-09-21 18:57:13 +02:00
+
2024-12-14 04:12:33 +01:00
+ com.mojang.serialization.Dynamic<?> dynamic;
2023-12-05 17:40:00 +01:00
+ if (worldSession.hasWorldData()) {
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.storage.LevelSummary worldinfo;
2023-12-05 17:40:00 +01:00
+
+ try {
+ dynamic = worldSession.getDataTag();
+ worldinfo = worldSession.getSummary(dynamic);
2024-12-14 04:12:33 +01:00
+ } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception) {
2024-12-11 22:26:55 +01:00
+ LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
2023-12-05 17:40:00 +01:00
+
+ MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
+ MinecraftServer.LOGGER.info("Attempting to use fallback");
+
+ try {
+ dynamic = worldSession.getDataTagFallback();
+ worldinfo = worldSession.getSummary(dynamic);
2024-12-14 04:12:33 +01:00
+ } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception1) {
2023-12-05 17:40:00 +01:00
+ MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
+ MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
+ return;
+ }
+
+ worldSession.restoreLevelDataFromOld();
+ }
+
+ if (worldinfo.requiresManualConversion()) {
+ MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted");
+ return;
+ }
+
+ if (!worldinfo.isCompatible()) {
+ MinecraftServer.LOGGER.info("This world was created by an incompatible version.");
+ return;
+ }
+ } else {
+ dynamic = null;
+ }
+
2021-04-16 02:36:05 +02:00
+ org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
SPIGOT-5880, SPIGOT-5567: New ChunkGenerator API
## **Current API**
The current world generation API is very old and limited when you want to make more complex world generation. Resulting in some hard to fix bugs such as that you cannot modify blocks outside the chunk in the BlockPopulator (which should and was per the docs possible), or strange behavior such as SPIGOT-5880.
## **New API**
With the new API, the generation is more separate in multiple methods and is more in line with Vanilla chunk generation. The new API is designed to as future proof as possible. If for example a new generation step is added it can easily also be added as a step in API by simply creating the method for it. On the other side if a generation step gets removed, the method can easily be called after another, which is the case with surface and bedrock. The new API and changes are also fully backwards compatible with old chunk generators.
### **Changes in the new api**
**Extra generation steps:**
Noise, surface, bedrock and caves are added as steps. With those generation steps three extra methods for Vanilla generation are also added. Those new methods provide the ChunkData instead of returning one. The reason for this is, that the ChunkData is now backed by a ChunkAccess. With this, each step has the information of the step before and the Vanilla information (if chosen by setting a 'should' method to true). The old method is deprecated.
**New class BiomeProvider**
The BiomeProvider acts as Biome source and wrapper for the NMS class WorldChunkManager. With this the underlying Vanilla ChunkGeneration knows which Biome to use for the structure and decoration generation. (Fixes: SPIGOT-5880). Although the List of Biomes which is required in BiomeProvider, is currently not much in use in Vanilla, I decided to add it to future proof the API when it may be required in later versions of Minecraft.
The BiomeProvider is also separated from the ChunkGenerator for plugins which only want to change the biome map, such as single Biome worlds or if some biomes should be more present than others.
**Deprecated isParallelCapable**
Mojang has and is pushing to a more multi threaded chunk generation. This should also be the case for custom chunk generators. This is why the new API only supports multi threaded generation. This does not affect the old API, which is still checking this.
**Base height method added**
This method was added to also bring the Minecraft generator and Bukkit generator more in line. With this it is possible to return the max height of a location (before decorations). This is useful to let most structures know were to place them. This fixes SPIGOT-5567. (This fixes not all structures placement, desert pyramids for example are still way up at y-level 64, This however is more a vanilla bug and should be fixed at Mojangs end).
**WorldInfo Class**
The World object was swapped for a WorldInfo object. This is because many methods of the World object won't work during world generation and would mostly likely result in a deadlock. It contains any information a plugin should need to identify the world.
**BlockPopulator Changes**
Instead of directly manipulating a chunk, changes are now made to a new class LimitedRegion, this class provides methods to populated the chunk and its surrounding area. The wrapping is done so that the population can be moved into the place where Minecraft generates decorations. Where there is no chunk to access yet. By moving it into this place the generation is now async and the surrounding area of the chunk can also be used.
For common methods between the World and LimitedRegion a RegionAccessor was added.
By: DerFrZocker <derrieple@gmail.com>
2021-08-15 00:08:16 +02:00
+ org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name);
2023-09-21 18:57:13 +02:00
+
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.storage.PrimaryLevelData worlddata;
2024-12-11 22:26:55 +01:00
+ WorldLoader.DataLoadContext worldloader_a = this.worldLoader;
+ Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
2023-12-05 17:40:00 +01:00
+ if (dynamic != null) {
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.storage.LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
2022-12-07 17:00:00 +01:00
+
2024-12-14 04:12:33 +01:00
+ worlddata = (net.minecraft.world.level.storage.PrimaryLevelData) leveldataanddimensions.worldData();
2022-12-07 17:00:00 +01:00
+ } else {
2024-12-11 22:26:55 +01:00
+ LevelSettings worldsettings;
2022-12-07 17:00:00 +01:00
+ WorldOptions worldoptions;
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.levelgen.WorldDimensions worlddimensions;
2019-04-23 04:00:00 +02:00
+
2021-11-21 23:00:00 +01:00
+ if (this.isDemo()) {
2021-06-11 07:00:00 +02:00
+ worldsettings = MinecraftServer.DEMO_SETTINGS;
2022-12-07 17:00:00 +01:00
+ worldoptions = WorldOptions.DEMO_OPTIONS;
2024-12-14 04:12:33 +01:00
+ worlddimensions = net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
2020-06-25 02:00:00 +02:00
+ } else {
2024-12-14 04:12:33 +01:00
+ net.minecraft.server.dedicated.DedicatedServerProperties dedicatedserverproperties = ((net.minecraft.server.dedicated.DedicatedServer) this).getProperties();
2020-06-25 02:00:00 +02:00
+
2024-12-11 22:26:55 +01:00
+ worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
+ worldoptions = this.options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
2022-12-07 17:00:00 +01:00
+ worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
2018-08-26 04:00:00 +02:00
+ }
2019-04-23 04:00:00 +02:00
+
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.levelgen.WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
+ com.mojang.serialization.Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
2022-12-07 17:00:00 +01:00
+
2024-12-14 04:12:33 +01:00
+ worlddata = new net.minecraft.world.level.storage.PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
2020-06-25 02:00:00 +02:00
+ }
+ worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
2024-12-11 22:26:55 +01:00
+ if (this.options.has("forceUpgrade")) {
2024-12-15 05:15:49 +01:00
+ net.minecraft.server.Main.forceUpgrade(worldSession, net.minecraft.util.datafix.DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
2020-06-25 02:00:00 +02:00
+ return true;
2024-12-11 22:26:55 +01:00
+ }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
2020-06-25 02:00:00 +02:00
+ }
+
2024-12-14 04:12:33 +01:00
+ net.minecraft.world.level.storage.PrimaryLevelData iworlddataserver = worlddata;
2022-12-07 17:00:00 +01:00
+ boolean flag = worlddata.isDebugWorld();
+ WorldOptions worldoptions = worlddata.worldGenOptions();
+ long i = worldoptions.seed();
2021-11-21 23:00:00 +01:00
+ long j = BiomeManager.obfuscateSeed(i);
2024-12-11 22:26:55 +01:00
+ List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
+ LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey);
2020-06-25 02:00:00 +02:00
+
2022-01-07 00:59:06 +01:00
+ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
SPIGOT-5880, SPIGOT-5567: New ChunkGenerator API
## **Current API**
The current world generation API is very old and limited when you want to make more complex world generation. Resulting in some hard to fix bugs such as that you cannot modify blocks outside the chunk in the BlockPopulator (which should and was per the docs possible), or strange behavior such as SPIGOT-5880.
## **New API**
With the new API, the generation is more separate in multiple methods and is more in line with Vanilla chunk generation. The new API is designed to as future proof as possible. If for example a new generation step is added it can easily also be added as a step in API by simply creating the method for it. On the other side if a generation step gets removed, the method can easily be called after another, which is the case with surface and bedrock. The new API and changes are also fully backwards compatible with old chunk generators.
### **Changes in the new api**
**Extra generation steps:**
Noise, surface, bedrock and caves are added as steps. With those generation steps three extra methods for Vanilla generation are also added. Those new methods provide the ChunkData instead of returning one. The reason for this is, that the ChunkData is now backed by a ChunkAccess. With this, each step has the information of the step before and the Vanilla information (if chosen by setting a 'should' method to true). The old method is deprecated.
**New class BiomeProvider**
The BiomeProvider acts as Biome source and wrapper for the NMS class WorldChunkManager. With this the underlying Vanilla ChunkGeneration knows which Biome to use for the structure and decoration generation. (Fixes: SPIGOT-5880). Although the List of Biomes which is required in BiomeProvider, is currently not much in use in Vanilla, I decided to add it to future proof the API when it may be required in later versions of Minecraft.
The BiomeProvider is also separated from the ChunkGenerator for plugins which only want to change the biome map, such as single Biome worlds or if some biomes should be more present than others.
**Deprecated isParallelCapable**
Mojang has and is pushing to a more multi threaded chunk generation. This should also be the case for custom chunk generators. This is why the new API only supports multi threaded generation. This does not affect the old API, which is still checking this.
**Base height method added**
This method was added to also bring the Minecraft generator and Bukkit generator more in line. With this it is possible to return the max height of a location (before decorations). This is useful to let most structures know were to place them. This fixes SPIGOT-5567. (This fixes not all structures placement, desert pyramids for example are still way up at y-level 64, This however is more a vanilla bug and should be fixed at Mojangs end).
**WorldInfo Class**
The World object was swapped for a WorldInfo object. This is because many methods of the World object won't work during world generation and would mostly likely result in a deadlock. It contains any information a plugin should need to identify the world.
**BlockPopulator Changes**
Instead of directly manipulating a chunk, changes are now made to a new class LimitedRegion, this class provides methods to populated the chunk and its surrounding area. The wrapping is done so that the population can be moved into the place where Minecraft generates decorations. Where there is no chunk to access yet. By moving it into this place the generation is now async and the surrounding area of the chunk can also be used.
For common methods between the World and LimitedRegion a RegionAccessor was added.
By: DerFrZocker <derrieple@gmail.com>
2021-08-15 00:08:16 +02:00
+ if (biomeProvider == null && gen != null) {
+ biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
+ }
+
2024-12-11 22:26:55 +01:00
+ ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
2021-03-08 22:47:33 +01:00
+
2024-12-11 22:26:55 +01:00
+ if (dimensionKey == LevelStem.OVERWORLD) {
2021-06-11 07:00:00 +02:00
+ this.worldData = worlddata;
2024-12-14 04:12:33 +01:00
+ this.worldData.setGameType(((net.minecraft.server.dedicated.DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
2023-09-21 18:40:00 +02:00
+
2024-12-14 04:24:28 +01:00
+ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
2023-09-21 18:40:00 +02:00
+
2024-12-14 04:24:28 +01:00
+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
2024-12-11 22:26:55 +01:00
+ DimensionDataStorage worldpersistentdata = world.getDataStorage();
2021-11-21 23:00:00 +01:00
+ this.readScoreboard(worldpersistentdata);
2014-11-25 22:32:16 +01:00
+ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
2024-12-11 22:26:55 +01:00
+ this.commandStorage = new CommandStorage(worldpersistentdata);
2018-08-26 04:00:00 +02:00
+ } else {
2024-12-14 04:24:28 +01:00
+ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
2022-02-20 05:15:41 +01:00
+ // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
+ final List<CustomSpawner> spawners;
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
+ spawners = list;
+ } else {
+ spawners = Collections.emptyList();
+ }
2024-12-14 04:24:28 +01:00
+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
2022-02-20 05:15:41 +01:00
+ // Paper end - option to use the dimension_type to check if spawners should be added
2018-08-26 04:00:00 +02:00
+ }
2022-06-07 18:00:00 +02:00
+
2021-11-21 23:00:00 +01:00
+ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
2022-02-22 23:21:35 +01:00
+ this.addLevel(world); // Paper - Put world into worldlist before initing the world; move up
2024-12-11 22:26:55 +01:00
+ this.initWorld(world, worlddata, this.worldData, worldoptions);
2022-07-27 20:00:00 +02:00
+
2022-02-22 23:21:35 +01:00
+ // Paper - Put world into worldlist before initing the world; move up
2021-11-21 23:00:00 +01:00
+ this.getPlayerList().addWorldborderListener(world);
2022-07-27 20:00:00 +02:00
+
2019-04-23 04:00:00 +02:00
+ if (worlddata.getCustomBossEvents() != null) {
2024-04-23 17:15:00 +02:00
+ this.getCustomBossEvents().load(worlddata.getCustomBossEvents(), this.registryAccess());
2018-07-15 02:00:00 +02:00
+ }
2019-05-04 12:54:32 +02:00
+ }
2021-11-21 23:00:00 +01:00
+ this.forceDifficulty();
2024-12-14 04:24:28 +01:00
+ for (ServerLevel serverLevel : this.getAllLevels()) {
2024-12-14 04:12:33 +01:00
+ this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
+ serverLevel.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
+ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
2022-03-04 08:53:19 +01:00
+ }
2022-08-14 02:46:41 +02:00
+
2016-04-13 08:10:49 +02:00
+ // Paper start - Configurable player collision; Handle collideRule team for player collision toggle
+ final ServerScoreboard scoreboard = this.getScoreboard();
+ final java.util.Collection<String> toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList());
+ for (String teamName : toRemove) {
+ scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves
+ }
+
+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) {
+ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16);
+ net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName);
+ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all
+ }
+ // Paper end - Configurable player collision
+
2019-04-23 04:00:00 +02:00
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
2024-07-16 23:55:23 +02:00
+ this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
+ this.server.spark.enableAfterPlugins(this.server); // Paper - spark
2022-10-30 00:22:32 +02:00
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
2022-08-02 04:50:34 +02:00
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
+ ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands();
2024-12-14 04:12:33 +01:00
+ this.server.getPluginManager().callEvent(new org.bukkit.event.server.ServerLoadEvent(org.bukkit.event.server.ServerLoadEvent.LoadType.STARTUP));
2021-06-11 07:00:00 +02:00
+ this.connection.acceptConnections();
2021-11-21 23:00:00 +01:00
+ }
2022-12-07 17:00:00 +01:00
+
2024-12-14 04:24:28 +01:00
+ public void initWorld(ServerLevel serverLevel, ServerLevelData serverLevelData, WorldData saveData, WorldOptions worldOptions) {
2024-12-14 04:12:33 +01:00
+ boolean isDebugWorld = saveData.isDebugWorld();
+ if (serverLevel.generator != null) {
+ serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld()));
2021-11-21 23:00:00 +01:00
+ }
2024-12-14 04:12:33 +01:00
+ // CraftBukkit start
+ WorldBorder worldborder = serverLevel.getWorldBorder();
+ worldborder.applySettings(serverLevelData.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent
+ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(serverLevel.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated
+
if (!serverLevelData.isInitialized()) {
2022-01-31 22:13:13 +01:00
try {
2024-12-14 04:12:33 +01:00
setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld);
@@ -403,47 +_,30 @@
2024-03-10 20:10:41 +01:00
2024-12-14 04:12:33 +01:00
serverLevelData.setInitialized(true);
}
-
- this.getPlayerList().addWorldborderListener(serverLevel);
2021-06-11 07:00:00 +02:00
- if (this.worldData.getCustomBossEvents() != null) {
2024-04-23 17:15:00 +02:00
- this.getCustomBossEvents().load(this.worldData.getCustomBossEvents(), this.registryAccess());
2018-12-07 01:52:50 +01:00
- }
-
2024-12-14 04:12:33 +01:00
- RandomSequences randomSequences = serverLevel.getRandomSequences();
2019-04-23 04:00:00 +02:00
-
2024-12-14 04:12:33 +01:00
- for (Entry<ResourceKey<LevelStem>, LevelStem> entry : registry.entrySet()) {
- ResourceKey<LevelStem> resourceKey = entry.getKey();
- if (resourceKey != LevelStem.OVERWORLD) {
- ResourceKey<Level> resourceKey1 = ResourceKey.create(Registries.DIMENSION, resourceKey.location());
- DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, serverLevelData);
- ServerLevel serverLevel1 = new ServerLevel(
- this,
- this.executor,
- this.storageSource,
- derivedLevelData,
- resourceKey1,
- entry.getValue(),
- listener,
- isDebugWorld,
- l,
- ImmutableList.of(),
- false,
- randomSequences
- );
- worldBorder.addListener(new BorderChangeListener.DelegateBorderChangeListener(serverLevel1.getWorldBorder()));
- this.levels.put(resourceKey1, serverLevel1);
2019-04-23 04:00:00 +02:00
- }
2024-03-10 20:10:41 +01:00
- }
-
2024-12-14 04:12:33 +01:00
- worldBorder.applySettings(serverLevelData.getWorldBorder());
2016-03-29 02:55:47 +02:00
}
2019-04-23 04:00:00 +02:00
+ // CraftBukkit end
2018-08-26 04:00:00 +02:00
2024-12-14 04:24:28 +01:00
private static void setInitialSpawn(ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
2024-12-14 04:12:33 +01:00
if (debug) {
levelData.setSpawn(BlockPos.ZERO.above(80), 0.0F);
2021-11-21 23:00:00 +01:00
} else {
2024-12-14 04:24:28 +01:00
ServerChunkCache chunkSource = level.getChunkSource();
2024-12-14 04:12:33 +01:00
- ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition());
2020-06-25 02:00:00 +02:00
+ // CraftBukkit start
2024-12-14 04:12:33 +01:00
+ if (level.generator != null) {
+ java.util.Random rand = new java.util.Random(level.getSeed());
+ org.bukkit.Location spawn = level.generator.getFixedSpawnLocation(level.getWorld(), rand);
2020-06-25 02:00:00 +02:00
+
+ if (spawn != null) {
2024-12-14 04:12:33 +01:00
+ if (spawn.getWorld() != level.getWorld()) {
+ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")");
2020-06-25 02:00:00 +02:00
+ } else {
2024-12-14 04:12:33 +01:00
+ levelData.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw());
2020-06-25 02:00:00 +02:00
+ return;
+ }
+ }
+ }
+ // CraftBukkit end
2024-12-14 04:12:33 +01:00
+ ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition()); // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set
int spawnHeight = chunkSource.getGenerator().getSpawnHeight(level);
if (spawnHeight < level.getMinY()) {
BlockPos worldPosition = chunkPos.getWorldPosition();
@@ -495,26 +_,31 @@
serverLevelData.setGameType(GameType.SPECTATOR);
2019-04-23 04:00:00 +02:00
}
2018-08-26 04:00:00 +02:00
2024-12-14 04:12:33 +01:00
- public void prepareLevels(ChunkProgressListener listener) {
- ServerLevel serverLevel = this.overworld();
2019-04-23 04:00:00 +02:00
+ // CraftBukkit start
2024-12-14 04:24:28 +01:00
+ public void prepareLevels(ChunkProgressListener listener, ServerLevel serverLevel) {
2019-04-25 07:33:13 +02:00
+ this.forceTicks = true;
2019-04-23 04:00:00 +02:00
+ // CraftBukkit end
2024-12-14 04:12:33 +01:00
LOGGER.info("Preparing start region for dimension {}", serverLevel.dimension().location());
BlockPos sharedSpawnPos = serverLevel.getSharedSpawnPos();
listener.updateSpawnPos(new ChunkPos(sharedSpawnPos));
2024-12-14 04:24:28 +01:00
ServerChunkCache chunkSource = serverLevel.getChunkSource();
2024-12-11 22:26:55 +01:00
this.nextTickTimeNanos = Util.getNanos();
2024-12-14 04:12:33 +01:00
serverLevel.setDefaultSpawnPos(sharedSpawnPos, serverLevel.getSharedSpawnAngle());
- int _int = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS);
+ int _int = serverLevel.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
2024-12-14 04:24:28 +01:00
int i = _int > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(_int)) : 0;
2024-12-14 04:12:33 +01:00
while (chunkSource.getTickingGenerated() < i) {
- this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2021-11-21 23:00:00 +01:00
- this.waitUntilNextTick();
2024-04-23 17:15:00 +02:00
+ // CraftBukkit start
2024-12-14 04:12:33 +01:00
+ // this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2024-04-23 17:15:00 +02:00
+ this.executeModerately();
2019-04-25 07:33:13 +02:00
}
2014-11-25 22:32:16 +01:00
2024-12-14 04:12:33 +01:00
- this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2021-11-21 23:00:00 +01:00
- this.waitUntilNextTick();
2024-12-14 04:12:33 +01:00
+ // this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2019-04-25 07:33:13 +02:00
+ this.executeModerately();
2022-03-04 08:53:19 +01:00
2024-12-14 04:12:33 +01:00
- for (ServerLevel serverLevel1 : this.levels.values()) {
2019-04-23 04:00:00 +02:00
+ if (true) {
2024-12-14 04:24:28 +01:00
+ ServerLevel serverLevel1 = serverLevel;
2019-04-23 04:00:00 +02:00
+ // CraftBukkit end
2024-12-14 04:12:33 +01:00
ForcedChunksSavedData forcedChunksSavedData = serverLevel1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
if (forcedChunksSavedData != null) {
LongIterator longIterator = forcedChunksSavedData.getChunks().iterator();
@@ -527,10 +_,17 @@
2019-04-25 07:33:13 +02:00
}
}
2024-12-14 04:12:33 +01:00
- this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2021-11-21 23:00:00 +01:00
- this.waitUntilNextTick();
2019-04-25 07:33:13 +02:00
+ // CraftBukkit start
2023-12-05 17:40:00 +01:00
+ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
2019-04-25 07:33:13 +02:00
+ this.executeModerately();
+ // CraftBukkit end
2024-12-14 04:12:33 +01:00
listener.stop();
- this.updateMobSpawningFlags();
2019-04-25 07:33:13 +02:00
+ // CraftBukkit start
2021-11-21 23:00:00 +01:00
+ // this.updateMobSpawningFlags();
2024-12-14 04:12:33 +01:00
+ serverLevel.setSpawnSettings(serverLevel.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
2020-11-06 08:46:21 +01:00
+
2019-04-25 07:33:13 +02:00
+ this.forceTicks = false;
+ // CraftBukkit end
2014-11-25 22:32:16 +01:00
}
2024-12-11 22:26:55 +01:00
public GameType getDefaultGameType() {
2024-12-14 04:24:28 +01:00
@@ -559,11 +_,14 @@
2024-12-14 04:12:33 +01:00
flag = true;
}
2019-04-28 03:13:44 +02:00
2024-12-14 04:12:33 +01:00
+ /* // CraftBukkit start - moved to WorldServer.save
ServerLevel serverLevel1 = this.overworld();
ServerLevelData serverLevelData = this.worldData.overworldData();
serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
2024-04-23 17:15:00 +02:00
this.worldData.setCustomBossEvents(this.getCustomBossEvents().save(this.registryAccess()));
2022-02-28 16:00:00 +01:00
this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData());
2024-12-14 04:12:33 +01:00
+ */
2019-06-25 13:02:13 +02:00
+ // CraftBukkit end
2024-12-11 22:26:55 +01:00
if (flush) {
2024-12-14 04:24:28 +01:00
for (ServerLevel serverLevel2 : this.getAllLevels()) {
2024-12-14 04:12:33 +01:00
LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", serverLevel2.getChunkSource().chunkMap.getStorageName());
2024-12-14 04:24:28 +01:00
@@ -593,18 +_,46 @@
2021-11-21 23:00:00 +01:00
this.stopServer();
2024-11-10 16:32:34 +01:00
}
2015-02-07 11:39:00 +01:00
+ // CraftBukkit start
+ private boolean hasStopped = false;
2021-02-18 21:23:28 +01:00
+ private boolean hasLoggedStop = false; // Paper - Debugging
2015-02-07 11:39:00 +01:00
+ private final Object stopLock = new Object();
2019-04-23 04:00:00 +02:00
+ public final boolean hasStopped() {
2024-12-11 22:26:55 +01:00
+ synchronized (this.stopLock) {
+ return this.hasStopped;
2019-04-23 04:00:00 +02:00
+ }
2024-11-10 16:32:34 +01:00
+ }
2015-02-07 11:39:00 +01:00
+ // CraftBukkit end
2024-11-10 16:32:34 +01:00
+
2021-11-21 23:00:00 +01:00
public void stopServer() {
2015-02-07 11:39:00 +01:00
+ // CraftBukkit start - prevent double stopping on multiple threads
2024-12-11 22:26:55 +01:00
+ synchronized(this.stopLock) {
+ if (this.hasStopped) return;
+ this.hasStopped = true;
2015-02-07 11:39:00 +01:00
+ }
2021-02-18 21:23:28 +01:00
+ if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
2015-02-07 11:39:00 +01:00
+ // CraftBukkit end
2022-06-07 18:00:00 +02:00
if (this.metricsRecorder.isRecording()) {
this.cancelRecordingMetrics();
}
2024-12-14 04:12:33 +01:00
LOGGER.info("Stopping server");
2020-04-08 09:42:14 +02:00
+ Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
2016-03-01 20:33:41 +01:00
+ // CraftBukkit start
+ if (this.server != null) {
2024-07-16 23:55:23 +02:00
+ this.server.spark.disable(); // Paper - spark
2016-03-01 20:33:41 +01:00
+ this.server.disablePlugins();
2020-05-11 04:16:17 +02:00
+ this.server.waitForAsyncTasksShutdown(); // Paper - Wait for Async Tasks during shutdown
2016-03-01 20:33:41 +01:00
+ }
+ // CraftBukkit end
2022-10-30 00:22:32 +02:00
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.shutdown(); // Paper - Plugin remapping
2023-09-21 18:40:00 +02:00
this.getConnection().stop();
this.isSaving = true;
if (this.playerList != null) {
2024-12-14 04:12:33 +01:00
LOGGER.info("Saving players");
2021-11-21 23:00:00 +01:00
this.playerList.saveAll();
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
- this.playerList.removeAll();
+ this.playerList.removeAll(this.isRestarting); // Paper
2016-02-29 22:32:46 +01:00
+ try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
}
2015-09-15 11:52:51 +02:00
2024-12-14 04:12:33 +01:00
LOGGER.info("Saving worlds");
@@ -646,6 +_,15 @@
} catch (IOException var4) {
LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), var4);
2014-05-24 00:05:10 +02:00
}
+ // Spigot start
2016-03-29 02:55:47 +02:00
+ io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.shutdown(); // Paper
+ try { io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
+ } catch (java.lang.InterruptedException ignored) {} // Paper
2014-05-24 00:05:10 +02:00
+ if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
+ MinecraftServer.LOGGER.info("Saving usercache.json");
2016-05-17 02:47:41 +02:00
+ this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
2014-05-24 00:05:10 +02:00
+ }
+ // Spigot end
}
2024-12-14 04:12:33 +01:00
public String getLocalIp() {
@@ -661,6 +_,14 @@
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
}
2024-12-14 04:12:33 +01:00
public void halt(boolean waitForServer) {
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ // Paper start - allow passing of the intent to restart
2024-12-14 04:12:33 +01:00
+ this.safeShutdown(waitForServer, false);
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ }
2024-12-14 04:12:33 +01:00
+ public void safeShutdown(boolean waitForServer, boolean isRestarting) {
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ this.isRestarting = isRestarting;
2021-02-18 21:23:28 +01:00
+ this.hasLoggedStop = true; // Paper - Debugging
+ if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ // Paper end
this.running = false;
2024-12-14 04:12:33 +01:00
if (waitForServer) {
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
try {
2024-12-14 04:12:33 +01:00
@@ -671,6 +_,63 @@
}
2024-11-28 19:20:25 +01:00
}
2017-01-26 22:50:51 +01:00
+ // Spigot Start
2024-12-14 04:12:33 +01:00
+ private static double calcTps(double avg, double exp, double tps) {
+ return (avg * exp) + (tps * (1 - exp));
2022-10-30 00:22:32 +02:00
+ }
+
2016-03-02 06:09:29 +01:00
+ // Paper start - Further improve server tick loop
+ private static final long SEC_IN_NANO = 1000000000;
+ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
+ private long lastTick = 0;
+ private long catchupTime = 0;
+ public final RollingAverage tps1 = new RollingAverage(60);
+ public final RollingAverage tps5 = new RollingAverage(60 * 5);
+ public final RollingAverage tps15 = new RollingAverage(60 * 15);
+
+ public static class RollingAverage {
+ private final int size;
+ private long time;
+ private java.math.BigDecimal total;
+ private int index = 0;
+ private final java.math.BigDecimal[] samples;
+ private final long[] times;
+
+ RollingAverage(int size) {
+ this.size = size;
+ this.time = size * SEC_IN_NANO;
+ this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size));
+ this.samples = new java.math.BigDecimal[size];
+ this.times = new long[size];
+ for (int i = 0; i < size; i++) {
+ this.samples[i] = dec(TPS);
+ this.times[i] = SEC_IN_NANO;
2024-11-28 19:20:25 +01:00
+ }
+ }
+
2016-03-02 06:09:29 +01:00
+ private static java.math.BigDecimal dec(long t) {
+ return new java.math.BigDecimal(t);
+ }
+ public void add(java.math.BigDecimal x, long t) {
+ time -= times[index];
+ total = total.subtract(samples[index].multiply(dec(times[index])));
+ samples[index] = x;
+ times[index] = t;
+ time += t;
+ total = total.add(x.multiply(dec(t)));
+ if (++index == size) {
+ index = 0;
2019-03-28 03:48:45 +01:00
+ }
+ }
+
2016-03-02 06:09:29 +01:00
+ public double getAverage() {
+ return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue();
+ }
2024-11-28 19:20:25 +01:00
+ }
2016-03-02 06:09:29 +01:00
+ private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL));
+ // Paper end
+ // Spigot End
2024-11-28 19:20:25 +01:00
+
2017-01-26 22:50:51 +01:00
protected void runServer() {
try {
2024-11-28 19:20:25 +01:00
if (!this.initServer()) {
2024-12-14 04:12:33 +01:00
@@ -681,6 +_,24 @@
this.statusIcon = this.loadStatusIcon().orElse(null);
2023-03-14 17:30:00 +01:00
this.status = this.buildServerStatus();
2024-11-10 16:32:34 +01:00
2024-07-16 23:55:23 +02:00
+ this.server.spark.enableBeforePlugins(); // Paper - spark
2017-01-26 22:50:51 +01:00
+ // Spigot start
2018-08-08 21:30:52 +02:00
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper
2017-01-26 22:50:51 +01:00
+ Arrays.fill( this.recentTps, 20 );
2016-03-02 06:09:29 +01:00
+ // Paper start - further improve server tick loop
+ long tickSection = Util.getNanos();
+ long currentTime;
+ // Paper end - further improve server tick loop
2024-03-10 20:10:41 +01:00
+ // Paper start - Add onboarding message for initial server start
+ if (io.papermc.paper.configuration.GlobalConfiguration.isFirstStart) {
+ LOGGER.info("*************************************************************************************");
+ LOGGER.info("This is the first time you're starting this server.");
+ LOGGER.info("It's recommended you read our 'Getting Started' documentation for guidance.");
+ LOGGER.info("View this and more helpful information here: https://docs.papermc.io/paper/next-steps");
+ LOGGER.info("*************************************************************************************");
+ }
+ // Paper end - Add onboarding message for initial server start
2024-11-10 16:32:34 +01:00
+
2022-06-07 18:00:00 +02:00
while (this.running) {
2024-12-14 04:12:33 +01:00
long l;
if (!this.isPaused() && this.tickRateManager.isSprinting() && this.tickRateManager.checkShouldSprintThisTick()) {
@@ -693,11 +_,30 @@
if (l1 > OVERLOADED_THRESHOLD_NANOS + 20L * l
&& this.nextTickTimeNanos - this.lastOverloadWarningNanos >= OVERLOADED_WARNING_INTERVAL_NANOS + 100L * l) {
long l2 = l1 / l;
2024-12-11 22:26:55 +01:00
+ if (this.server.getWarnOnOverload()) // CraftBukkit
2024-12-14 04:12:33 +01:00
LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", l1 / TimeUtil.NANOSECONDS_PER_MILLISECOND, l2);
this.nextTickTimeNanos += l2 * l;
2023-12-05 17:40:00 +01:00
this.lastOverloadWarningNanos = this.nextTickTimeNanos;
2017-01-26 22:50:51 +01:00
}
2024-12-14 04:12:33 +01:00
}
2017-01-26 22:50:51 +01:00
+ // Spigot start
2016-03-02 06:09:29 +01:00
+ // Paper start - further improve server tick loop
+ currentTime = Util.getNanos();
+ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
+ final long diff = currentTime - tickSection;
+ final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
+ tps1.add(currentTps, diff);
+ tps5.add(currentTps, diff);
+ tps15.add(currentTps, diff);
2024-11-28 19:20:25 +01:00
+
2016-03-02 06:09:29 +01:00
+ // Backwards compat with bad plugins
+ this.recentTps[0] = tps1.getAverage();
+ this.recentTps[1] = tps5.getAverage();
+ this.recentTps[2] = tps15.getAverage();
+ tickSection = currentTime;
2024-12-14 04:12:33 +01:00
+ }
2016-03-02 06:09:29 +01:00
+ // Paper end - further improve server tick loop
2017-01-26 22:50:51 +01:00
+ // Spigot end
2024-11-28 19:20:25 +01:00
2024-12-14 04:12:33 +01:00
boolean flag = l == 0L;
if (this.debugCommandProfilerDelayStart) {
@@ -705,6 +_,8 @@
2024-12-11 22:26:55 +01:00
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
2022-06-07 18:00:00 +02:00
}
2016-03-02 06:09:29 +01:00
+ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
+ lastTick = currentTime;
2024-12-14 04:12:33 +01:00
this.nextTickTimeNanos += l;
2024-10-22 17:15:00 +02:00
2024-12-14 04:12:33 +01:00
try (Profiler.Scope scope = Profiler.use(this.createProfiler())) {
@@ -755,6 +_,14 @@
2022-06-07 18:00:00 +02:00
this.services.profileCache().clearExecutor();
2021-11-21 23:00:00 +01:00
}
2014-08-05 18:20:19 +02:00
+ org.spigotmc.WatchdogThread.doStop(); // Spigot
2014-11-25 22:32:16 +01:00
+ // CraftBukkit start - Restore terminal to original settings
+ try {
2017-06-09 19:03:43 +02:00
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
2014-11-25 22:32:16 +01:00
+ } catch (Exception ignored) {
+ }
+ // CraftBukkit end
2021-06-05 13:45:15 +02:00
+ io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
2021-11-21 23:00:00 +01:00
this.onServerExit();
2014-11-25 22:32:16 +01:00
}
2024-12-14 04:12:33 +01:00
}
@@ -807,7 +_,14 @@
2019-04-25 07:33:13 +02:00
}
2021-11-21 23:00:00 +01:00
private boolean haveTime() {
2024-12-11 22:26:55 +01:00
- return this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
2019-04-25 07:33:13 +02:00
+ // CraftBukkit start
2024-12-11 22:26:55 +01:00
+ return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
2024-12-14 04:12:33 +01:00
+ }
+
2019-04-25 07:33:13 +02:00
+ private void executeModerately() {
2021-11-21 23:00:00 +01:00
+ this.runAllTasks();
2019-04-25 07:33:13 +02:00
+ java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
2023-09-21 18:40:00 +02:00
+ // CraftBukkit end
2024-06-13 17:05:00 +02:00
}
2024-12-14 04:12:33 +01:00
public static boolean throwIfFatalException() {
@@ -871,15 +_,16 @@
2021-12-28 16:19:01 +01:00
if (super.pollTask()) {
return true;
} else {
+ boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
if (this.tickRateManager.isSprinting() || this.haveTime()) {
2024-12-14 04:24:28 +01:00
for (ServerLevel serverLevel : this.getAllLevels()) {
2024-12-14 04:12:33 +01:00
if (serverLevel.getChunkSource().pollTask()) {
2021-12-28 16:19:01 +01:00
- return true;
+ ret = true; // Paper - force execution of all worlds, do not just bias the first
}
}
}
- return false;
+ return ret; // Paper - force execution of all worlds, do not just bias the first
2021-11-21 23:00:00 +01:00
}
}
2024-12-14 04:12:33 +01:00
@@ -927,26 +_,44 @@
2014-08-05 18:20:19 +02:00
}
2024-12-14 04:12:33 +01:00
public void tickServer(BooleanSupplier hasTimeLeft) {
2014-08-05 18:20:19 +02:00
+ org.spigotmc.WatchdogThread.tick(); // Spigot
2024-12-14 04:12:33 +01:00
long nanos = Util.getNanos();
int i = this.pauseWhileEmptySeconds() * 20;
2024-11-12 22:25:20 +01:00
+ this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
2024-12-14 04:12:33 +01:00
if (i > 0) {
2024-11-12 22:25:20 +01:00
- if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting()) {
+ if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping
2024-12-14 04:12:33 +01:00
this.emptyTicks++;
2024-11-12 22:25:20 +01:00
} else {
this.emptyTicks = 0;
2024-07-16 23:55:23 +02:00
}
2024-12-14 04:12:33 +01:00
if (this.emptyTicks >= i) {
2024-07-16 23:55:23 +02:00
+ this.server.spark.tickStart(); // Paper - spark
2024-12-14 04:12:33 +01:00
if (this.emptyTicks == i) {
LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhileEmptySeconds());
2024-10-22 17:15:00 +02:00
this.autoSave();
}
+ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
2024-10-27 22:18:28 +01:00
+ // Paper start - avoid issues with certain tasks not processing during sleep
+ Runnable task;
+ while ((task = this.processQueue.poll()) != null) {
+ task.run();
+ }
2024-12-14 04:24:28 +01:00
+ for (final ServerLevel level : this.levels.values()) {
2024-10-27 22:18:28 +01:00
+ // process unloads
+ level.getChunkSource().tick(() -> true, false);
+ }
+ // Paper end - avoid issues with certain tasks not processing during sleep
2024-07-16 23:55:23 +02:00
+ this.server.spark.executeMainThreadTasks(); // Paper - spark
2024-10-22 17:15:00 +02:00
this.tickConnection();
2024-07-16 23:55:23 +02:00
+ this.server.spark.tickEnd(((double)(System.nanoTime() - lastTick) / 1000000D)); // Paper - spark
2024-10-22 17:15:00 +02:00
return;
}
2019-03-28 03:48:45 +01:00
}
2024-07-16 23:55:23 +02:00
+ this.server.spark.tickStart(); // Paper - spark
2019-03-28 03:48:45 +01:00
+ new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper - Server Tick Events
2024-12-14 04:12:33 +01:00
this.tickCount++;
2019-03-28 03:48:45 +01:00
this.tickRateManager.tick();
2024-12-14 04:12:33 +01:00
this.tickChildren(hasTimeLeft);
@@ -956,11 +_,19 @@
2014-11-25 22:32:16 +01:00
}
2024-12-14 04:12:33 +01:00
this.ticksUntilAutosave--;
2023-12-05 17:40:00 +01:00
- if (this.ticksUntilAutosave <= 0) {
2024-10-22 17:15:00 +02:00
+ if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit
this.autoSave();
}
2024-12-14 04:12:33 +01:00
ProfilerFiller profilerFiller = Profiler.get();
2016-03-03 11:00:11 +01:00
+ this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
2024-07-16 23:55:23 +02:00
+ this.server.spark.executeMainThreadTasks(); // Paper - spark
2019-03-28 03:48:45 +01:00
+ // Paper start - Server Tick Events
+ long endTime = System.nanoTime();
+ long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
+ new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
+ // Paper end - Server Tick Events
2024-07-16 23:55:23 +02:00
+ this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark
2024-12-14 04:12:33 +01:00
profilerFiller.push("tallying");
long l = Util.getNanos() - nanos;
int i1 = this.tickCount % 100;
@@ -968,12 +_,17 @@
this.aggregatedTickTimesNanos += l;
this.tickTimesNanos[i1] = l;
this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float)l / (float)TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
2020-04-06 05:23:14 +02:00
+ // Paper start - Add tick times API and /mspt command
2024-12-14 04:12:33 +01:00
+ this.tickTimes5s.add(this.tickCount, l);
+ this.tickTimes10s.add(this.tickCount, l);
+ this.tickTimes60s.add(this.tickCount, l);
2020-04-06 05:23:14 +02:00
+ // Paper end - Add tick times API and /mspt command
2024-12-14 04:12:33 +01:00
this.logTickMethodTime(nanos);
profilerFiller.pop();
2024-10-22 17:15:00 +02:00
}
private void autoSave() {
- this.ticksUntilAutosave = this.computeNextAutosaveInterval();
+ this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit
2024-12-14 04:12:33 +01:00
LOGGER.debug("Autosave started");
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("save");
@@ -1015,7 +_,7 @@
2021-01-29 17:54:03 +01:00
private ServerStatus buildServerStatus() {
2024-12-14 04:12:33 +01:00
ServerStatus.Players players = this.buildPlayerStatus();
return new ServerStatus(
- Component.nullToEmpty(this.motd),
+ io.papermc.paper.adventure.PaperAdventure.asVanilla(this.motd), // Paper - Adventure
Optional.of(players),
Optional.of(ServerStatus.Version.current()),
Optional.ofNullable(this.statusIcon),
2024-12-14 04:24:28 +01:00
@@ -1029,7 +_,7 @@
2017-10-11 15:56:26 +02:00
if (this.hidesOnlinePlayers()) {
2024-12-14 04:12:33 +01:00
return new ServerStatus.Players(maxPlayers, players.size(), List.of());
2017-10-11 15:56:26 +02:00
} else {
2024-12-14 04:12:33 +01:00
- int min = Math.min(players.size(), 12);
+ int min = Math.min(players.size(), org.spigotmc.SpigotConfig.playerSample); // Paper - PaperServerListPingEvent
ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
@@ -1046,17 +_,64 @@
protected void tickChildren(BooleanSupplier hasTimeLeft) {
ProfilerFiller profilerFiller = Profiler.get();
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
2024-10-22 17:15:00 +02:00
+ this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
2023-06-17 11:52:52 +02:00
+ // Paper start - Folia scheduler API
2024-12-14 04:12:33 +01:00
+ ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
2023-06-17 11:52:52 +02:00
+ getAllLevels().forEach(level -> {
2024-12-14 04:12:33 +01:00
+ for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) {
2023-06-17 11:52:52 +02:00
+ if (entity.isRemoved()) {
+ continue;
+ }
+ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
+ if (bukkit != null) {
+ bukkit.taskScheduler.executeTick();
+ }
+ }
+ });
+ // Paper end - Folia scheduler API
2021-01-29 17:54:03 +01:00
+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
2024-12-14 04:12:33 +01:00
profilerFiller.push("commandFunctions");
2021-11-21 23:00:00 +01:00
this.getFunctions().tick();
2024-12-14 04:12:33 +01:00
profilerFiller.popPush("levels");
2024-06-20 18:40:57 +02:00
2014-11-25 22:32:16 +01:00
+ // CraftBukkit start
+ // Run tasks that are waiting on processing
2024-12-11 22:26:55 +01:00
+ while (!this.processQueue.isEmpty()) {
+ this.processQueue.remove().run();
2014-11-25 22:32:16 +01:00
+ }
2024-11-10 16:32:34 +01:00
+
2014-11-25 22:32:16 +01:00
+ // Send time updates to everyone, it will get the right time from the world the player is in.
2018-11-03 04:11:51 +01:00
+ // Paper start - Perf: Optimize time updates
2024-12-14 04:24:28 +01:00
+ for (final ServerLevel level : this.getAllLevels()) {
2018-11-03 04:11:51 +01:00
+ final boolean doDaylight = level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT);
+ final long dayTime = level.getDayTime();
+ long worldTime = level.getGameTime();
+ final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
+ for (Player entityhuman : level.players()) {
2024-12-14 04:24:28 +01:00
+ if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
2018-11-03 04:11:51 +01:00
+ continue;
+ }
2024-12-14 04:24:28 +01:00
+ ServerPlayer entityplayer = (ServerPlayer) entityhuman;
2018-11-03 04:11:51 +01:00
+ long playerTime = entityplayer.getPlayerTime();
+ ClientboundSetTimePacket packet = (playerTime == dayTime) ? worldPacket :
+ new ClientboundSetTimePacket(worldTime, playerTime, doDaylight);
+ entityplayer.connection.send(packet); // Add support for per player time
+ // Paper end - Perf: Optimize time updates
2014-11-25 22:32:16 +01:00
+ }
+ }
2024-06-20 18:40:57 +02:00
+
2022-03-22 20:44:30 +01:00
+ this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
2024-12-14 04:24:28 +01:00
for (ServerLevel serverLevel : this.getAllLevels()) {
2024-12-14 04:12:33 +01:00
+ serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
+ serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
2020-06-25 02:00:00 +02:00
+ /* Drop global time updates
2021-06-11 07:00:00 +02:00
if (this.tickCount % 20 == 0) {
2024-12-14 04:12:33 +01:00
profilerFiller.push("timeSync");
this.synchronizeTime(serverLevel);
profilerFiller.pop();
2020-01-21 22:00:00 +01:00
}
2020-06-25 02:00:00 +02:00
+ // CraftBukkit end */
2020-01-21 22:00:00 +01:00
2024-12-14 04:12:33 +01:00
profilerFiller.push("tick");
2014-11-25 22:32:16 +01:00
2024-12-14 04:12:33 +01:00
@@ -1070,7 +_,9 @@
2016-03-02 18:59:48 +01:00
2024-12-14 04:12:33 +01:00
profilerFiller.pop();
profilerFiller.pop();
+ serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
2016-03-02 18:59:48 +01:00
}
2022-03-22 20:44:30 +01:00
+ this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
2016-03-02 18:59:48 +01:00
2024-12-14 04:12:33 +01:00
profilerFiller.popPush("connection");
2022-03-22 20:44:30 +01:00
this.tickConnection();
2024-12-14 04:24:28 +01:00
@@ -1148,6 +_,22 @@
2024-12-14 04:12:33 +01:00
return this.levels.get(dimension);
2021-02-18 21:23:28 +01:00
}
2022-08-14 02:46:41 +02:00
+ // CraftBukkit start
2024-12-14 04:24:28 +01:00
+ public void addLevel(ServerLevel level) {
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
2022-08-14 02:46:41 +02:00
+ newLevels.put(level.dimension(), level);
+ this.levels = Collections.unmodifiableMap(newLevels);
2021-02-18 21:23:28 +01:00
+ }
+
2024-12-14 04:24:28 +01:00
+ public void removeLevel(ServerLevel level) {
+ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
+ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
2022-08-14 02:46:41 +02:00
+ newLevels.remove(level.dimension());
+ this.levels = Collections.unmodifiableMap(newLevels);
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+ }
2022-08-14 02:46:41 +02:00
+ // CraftBukkit end
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
+
2024-12-11 22:26:55 +01:00
public Set<ResourceKey<Level>> levelKeys() {
2022-08-14 02:46:41 +02:00
return this.levels.keySet();
Properly handle async calls to restart the server
The watchdog thread calls the server restart function asynchronously. Prior to
this change, it attempted to do several non-safe operations from the watchdog
thread, rather than the main. Specifically, because of a separate upstream change,
it causes player entities to be ticked asynchronously, among other things.
This is dangerous.
This patch moves the old handling into a synchronous variant, for calls from the
restart command, and adds separate handling for async calls, such as those from
the watchdog thread.
When calling from the watchdog thread, we cannot assume the main thread is in a
tickable state; it may be completely deadlocked. In order to handle this, we mark
the server as stopping, in order to account for situations where the server should
complete a tick reasonbly soon, i.e. 99% of cases.
Should the server not enter a state where it is stopping within 10 seconds, We
will assume that the server has in fact deadlocked and will proceed to force
kill the server.
This modification does not force restart the server should we actually enter a
deadlocked state where the server is stopping, whereas this will in most cases
exit within a reasonable amount of time, to put a fixed limit on a process that
will have plugins and worlds saving to the disk has a high potential to result
in corruption/dataloss.
2017-05-13 06:34:11 +02:00
}
2024-12-14 04:12:33 +01:00
@@ -1177,7 +_,7 @@
2014-11-25 22:32:16 +01:00
2021-06-11 07:00:00 +02:00
@DontObfuscate
2014-11-25 22:32:16 +01:00
public String getServerModName() {
- return "vanilla";
2016-03-01 21:32:43 +01:00
+ return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper
2014-11-25 22:32:16 +01:00
}
2024-12-14 04:12:33 +01:00
public SystemReport fillSystemReport(SystemReport systemReport) {
@@ -1212,7 +_,7 @@
2017-06-09 19:03:43 +02:00
@Override
2024-12-14 04:12:33 +01:00
public void sendSystemMessage(Component component) {
- LOGGER.info(component.getString());
+ LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(component))); // Paper - Log message with colors
2017-06-09 19:03:43 +02:00
}
public KeyPair getKeyPair() {
2024-12-14 04:12:33 +01:00
@@ -1250,11 +_,14 @@
2020-06-28 09:59:10 +02:00
}
}
2024-12-14 04:12:33 +01:00
- public void setDifficulty(Difficulty difficulty, boolean forced) {
- if (forced || !this.worldData.isDifficultyLocked()) {
2020-06-28 09:59:10 +02:00
- this.worldData.setDifficulty(this.worldData.isHardcore() ? Difficulty.HARD : difficulty);
- this.updateMobSpawningFlags();
- this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
+ // Paper start - per level difficulty
2024-12-14 04:24:28 +01:00
+ public void setDifficulty(ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
2024-12-15 05:15:49 +01:00
+ net.minecraft.world.level.storage.PrimaryLevelData worldData = (net.minecraft.world.level.storage.PrimaryLevelData) level.serverLevelData;
2020-06-28 09:59:10 +02:00
+ if (forceUpdate || !worldData.isDifficultyLocked()) {
+ worldData.setDifficulty(worldData.isHardcore() ? Difficulty.HARD : difficulty);
2024-12-14 04:12:33 +01:00
+ level.setSpawnSettings(worldData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters);
2020-06-28 09:59:10 +02:00
+ // this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
+ // Paper end - per level difficulty
}
}
2024-12-14 04:24:28 +01:00
@@ -1264,7 +_,7 @@
2020-06-28 09:59:10 +02:00
2024-12-14 04:12:33 +01:00
private void updateMobSpawningFlags() {
2024-12-14 04:24:28 +01:00
for (ServerLevel serverLevel : this.getAllLevels()) {
2024-12-14 04:12:33 +01:00
- serverLevel.setSpawnSettings(this.isSpawningMonsters());
+ serverLevel.setSpawnSettings(serverLevel.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
2020-06-28 09:59:10 +02:00
}
2024-12-14 04:12:33 +01:00
}
2020-06-28 09:59:10 +02:00
2024-12-14 04:12:33 +01:00
@@ -1340,10 +_,20 @@
2021-01-29 17:54:03 +01:00
@Override
public String getMotd() {
- return this.motd;
+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.motd); // Paper - Adventure
}
public void setMotd(String motd) {
+ // Paper start - Adventure
+ this.motd = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOr(motd, net.kyori.adventure.text.Component.empty());
+ }
+
+ public net.kyori.adventure.text.Component motd() {
+ return this.motd;
+ }
+
+ public void motd(net.kyori.adventure.text.Component motd) {
+ // Paper end - Adventure
this.motd = motd;
}
2024-12-14 04:24:28 +01:00
@@ -1366,7 +_,7 @@
2013-12-13 01:58:58 +01:00
}
2024-12-14 04:24:28 +01:00
public ServerConnectionListener getConnection() {
2013-12-13 01:58:58 +01:00
- return this.connection;
2024-12-14 04:24:28 +01:00
+ return this.connection == null ? this.connection = new ServerConnectionListener(this) : this.connection; // Spigot
2013-12-13 01:58:58 +01:00
}
public boolean isReady() {
2024-12-14 04:12:33 +01:00
@@ -1452,7 +_,7 @@
2024-04-11 17:37:44 +02:00
@Override
2024-12-14 04:12:33 +01:00
public void executeIfPossible(Runnable task) {
2024-04-11 17:37:44 +02:00
if (this.isStopped()) {
- throw new RejectedExecutionException("Server already shutting down");
+ throw new io.papermc.paper.util.ServerStopRejectedExecutionException("Server already shutting down"); // Paper - do not prematurely disconnect players on stop
} else {
2024-12-14 04:12:33 +01:00
super.executeIfPossible(task);
2024-04-11 17:37:44 +02:00
}
2024-12-14 04:12:33 +01:00
@@ -1491,7 +_,13 @@
2020-12-03 05:04:01 +01:00
return this.functionManager;
}
2024-04-23 17:15:00 +02:00
2020-12-03 05:04:01 +01:00
+ // Paper start - Add ServerResourcesReloadedEvent
+ @Deprecated @io.papermc.paper.annotation.DoNotUse
2024-12-14 04:12:33 +01:00
public CompletableFuture<Void> reloadResources(Collection<String> selectedIds) {
+ return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
2020-12-03 05:04:01 +01:00
+ }
2024-12-14 04:12:33 +01:00
+ public CompletableFuture<Void> reloadResources(Collection<String> selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
2020-12-03 05:04:01 +01:00
+ // Paper end - Add ServerResourcesReloadedEvent
2024-12-14 04:12:33 +01:00
CompletableFuture<Void> completableFuture = CompletableFuture.<ImmutableList>supplyAsync(
() -> selectedIds.stream().map(this.packRepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()),
this
@@ -1499,7 +_,7 @@
.thenCompose(
list -> {
CloseableResourceManager closeableResourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, list);
- List<Registry.PendingTags<?>> list1 = TagLoader.loadTagsForExistingRegistries(closeableResourceManager, this.registries.compositeAccess());
+ List<Registry.PendingTags<?>> list1 = TagLoader.loadTagsForExistingRegistries(closeableResourceManager, this.registries.compositeAccess(), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag lifecycle - add cause
return ReloadableServerResources.loadResources(
closeableResourceManager,
this.registries,
@@ -1520,6 +_,7 @@
)
.thenAcceptAsync(
reloadableResources -> {
+ io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), reloadableResources.managers().commands); // Paper
this.resources.close();
this.resources = reloadableResources;
this.packRepository.setSelected(selectedIds);
@@ -1529,11 +_,23 @@
this.worldData.setDataConfiguration(worldDataConfiguration);
this.resources.managers.updateStaticRegistryTags();
this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
+ this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
this.getPlayerList().saveAll();
this.getPlayerList().reloadResources();
this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
+ org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
+ // Paper start - brigadier command API
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
+ final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap();
+ helpMap.clear();
+ helpMap.initializeGeneralTopics();
+ helpMap.initializeCommands();
+ this.server.syncCommands(); // Refresh commands after event
+ // Paper end
+ new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
},
this
);
@@ -1652,10 +_,11 @@
2020-10-04 05:00:27 +02:00
if (this.isEnforceWhitelist()) {
2024-12-14 04:12:33 +01:00
PlayerList playerList = commandSource.getServer().getPlayerList();
UserWhiteList whiteList = playerList.getWhiteList();
+ if (!((net.minecraft.server.dedicated.DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
2024-12-14 04:24:28 +01:00
for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
2024-12-14 04:12:33 +01:00
- if (!whiteList.isWhiteListed(serverPlayer.getGameProfile())) {
- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted"));
+ if (!whiteList.isWhiteListed(serverPlayer.getGameProfile()) && !this.getPlayerList().isOp(serverPlayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
+ serverPlayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
2020-10-04 05:00:27 +02:00
}
}
2024-12-14 04:12:33 +01:00
}
@@ -1859,6 +_,22 @@
2024-11-12 22:25:20 +01:00
}
2024-12-14 04:12:33 +01:00
}
2024-11-12 22:25:20 +01:00
+
2016-02-29 22:32:46 +01:00
+ // CraftBukkit start
2020-06-25 02:00:00 +02:00
+ public boolean isDebugging() {
+ return false;
2024-11-10 16:32:34 +01:00
+ }
+
2016-02-29 22:32:46 +01:00
+ public static MinecraftServer getServer() {
2016-04-28 06:57:27 +02:00
+ return SERVER; // Paper
2024-10-27 22:18:28 +01:00
+ }
2024-12-14 04:12:33 +01:00
+
2024-04-23 17:15:00 +02:00
+ @Deprecated
2024-12-11 22:26:55 +01:00
+ public static RegistryAccess getDefaultRegistryAccess() {
2024-12-14 04:12:33 +01:00
+ return org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry();
+ }
2016-02-29 22:32:46 +01:00
+ // CraftBukkit end
2024-12-14 04:12:33 +01:00
+
2024-12-11 22:26:55 +01:00
private ProfilerFiller createProfiler() {
2021-06-11 07:00:00 +02:00
if (this.willStartRecordingMetrics) {
2024-12-14 04:12:33 +01:00
this.metricsRecorder = ActiveMetricsRecorder.createStarted(
2024-12-14 04:24:28 +01:00
@@ -1941,7 +_,7 @@
2024-12-14 04:12:33 +01:00
}
2024-12-14 04:24:28 +01:00
public ServerPlayerGameMode createGameModeForPlayer(ServerPlayer player) {
2024-12-14 04:12:33 +01:00
- return (ServerPlayerGameMode)(this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
2024-12-14 04:24:28 +01:00
+ return (this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
2024-12-14 04:12:33 +01:00
}
2021-01-29 17:54:03 +01:00
2024-12-14 04:12:33 +01:00
@Nullable
2024-12-14 05:48:25 +01:00
@@ -1980,16 +_,22 @@
2024-12-14 04:12:33 +01:00
}
public void logChatMessage(Component content, ChatType.Bound boundChatType, @Nullable String header) {
- String string = boundChatType.decorate(content).getString();
+ // Paper start
+ net.kyori.adventure.text.Component string = io.papermc.paper.adventure.PaperAdventure.asAdventure(boundChatType.decorate(content));
if (header != null) {
- LOGGER.info("[{}] {}", header, string);
+ COMPONENT_LOGGER.info("[{}] {}", header, string);
2021-01-29 17:54:03 +01:00
} else {
2024-12-14 04:12:33 +01:00
- LOGGER.info("{}", string);
+ COMPONENT_LOGGER.info("{}", string);
2021-01-29 17:54:03 +01:00
+ // Paper end
}
2022-07-27 20:00:00 +02:00
}
2014-05-24 00:05:10 +02:00
+
2024-12-14 04:12:33 +01:00
+ public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
2021-01-29 17:54:03 +01:00
+ public final ChatDecorator improvedChatDecorator = new io.papermc.paper.adventure.ImprovedChatDecorator(this); // Paper - adventure
2024-12-14 04:12:33 +01:00
2022-07-27 20:00:00 +02:00
public ChatDecorator getChatDecorator() {
2021-01-29 17:54:03 +01:00
- return ChatDecorator.PLAIN;
+ return this.improvedChatDecorator; // Paper - support async chat decoration events
2014-05-24 00:05:10 +02:00
}
2021-01-29 17:54:03 +01:00
public boolean logIPs() {
2024-12-14 04:12:33 +01:00
@@ -2122,4 +_,53 @@
};
}
2024-11-12 22:25:20 +01:00
}
2024-03-10 20:10:41 +01:00
+
2020-04-06 05:23:14 +02:00
+ // Paper start - Add tick times API and /mspt command
+ public static class TickTimes {
+ private final long[] times;
2024-11-12 22:25:20 +01:00
+
2020-04-06 05:23:14 +02:00
+ public TickTimes(int length) {
+ times = new long[length];
+ }
+
+ void add(int index, long time) {
+ times[index % times.length] = time;
+ }
+
+ public long[] getTimes() {
+ return times.clone();
+ }
+
+ public double getAverage() {
+ long total = 0L;
+ for (long value : times) {
+ total += value;
+ }
+ return ((double) total / (double) times.length) * 1.0E-6D;
+ }
2024-11-12 22:25:20 +01:00
+ }
2020-04-06 05:23:14 +02:00
+ // Paper end - Add tick times API and /mspt command
2024-11-10 16:32:34 +01:00
+
+ // Paper start - API to check if the server is sleeping
+ public boolean isTickPaused() {
+ return this.emptyTicks > 0 && this.emptyTicks >= this.pauseWhileEmptySeconds() * 20;
+ }
2024-11-12 22:25:20 +01:00
+
+ public void addPluginAllowingSleep(final String pluginName, final boolean value) {
+ if (!value) {
+ this.pluginsBlockingSleep.add(pluginName);
+ } else {
+ this.pluginsBlockingSleep.remove(pluginName);
+ }
+ }
+
+ private void removeDisabledPluginsBlockingSleep() {
+ if (this.pluginsBlockingSleep.isEmpty()) {
+ return;
+ }
+ this.pluginsBlockingSleep.removeIf(plugin -> (
+ !io.papermc.paper.plugin.manager.PaperPluginManagerImpl.getInstance().isPluginEnabled(plugin)
+ ));
+ }
2024-11-10 16:32:34 +01:00
+ // Paper end - API to check if the server is sleeping
2020-04-06 05:23:14 +02:00
}