From 7de780507cd25916a1921f3c17df301e00bc4a05 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 19 Dec 2025 22:19:30 -0500 Subject: [PATCH 01/16] Add an option to WorldCreator to avoid spawn location computation on world creation This reduces world creation for vanilla worlds from ~1 second to 20ms. --- .../main/java/org/bukkit/WorldCreator.java | 25 +++++++++++++++++++ .../server/MinecraftServer.java.patch | 6 ++--- .../org/bukkit/craftbukkit/CraftServer.java | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 4b312399eced..037802edb06e 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -24,6 +24,7 @@ public class WorldCreator { private String generatorSettings = ""; private boolean hardcore = false; private boolean bonusChest = false; + private boolean computeSpawnLocation = true; /** * Creates an empty WorldCreationOptions for the given world name @@ -229,6 +230,30 @@ public WorldCreator type(@NotNull WorldType type) { return this; } + /** + * Sets if this world should compute its spawn location using vanilla spawn + * location mechanics. This causes chunk loads on world creation and is enabled by default. + * + * @param computeSpawnLocation Should compute spawn location + * @return This object, for chaining + */ + @NotNull + public WorldCreator computeSpawnLocation(boolean computeSpawnLocation) { + this.computeSpawnLocation = computeSpawnLocation; + + return this; + } + + /** + * Gets if this world should compute its spawn location using vanilla spawn + * location mechanics. This causes chunk loads on world creation. + * + * @return if it computes spawn location + */ + public boolean computeSpawnLocation() { + return computeSpawnLocation; + } + /** * Gets the generator that will be used to create or load the world. *

diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index 334018c37150..651431fe36c6 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -366,9 +366,9 @@ + ); + } + this.addLevel(serverLevel); -+ this.initWorld(serverLevel, serverLevelData, worldOptions); ++ this.initWorld(serverLevel, serverLevelData, worldOptions, true); + } -+ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions) { ++ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, boolean computeSpawnLocation) { + final boolean isDebugWorld = this.worldData.isDebugWorld(); + if (serverLevel.generator != null) { + serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld())); @@ -379,7 +379,7 @@ if (!serverLevelData.isInitialized()) { try { - setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, this.levelLoadListener); -+ setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener ++ if (computeSpawnLocation) setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process serverLevelData.setInitialized(true); if (isDebugWorld) { this.setupDebugLevel(this.worldData); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index b2961752b50e..30cbebcc1754 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1306,7 +1306,7 @@ public World createWorld(WorldCreator creator) { } this.console.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up - this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions()); + this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.computeSpawnLocation()); serverLevel.setSpawnSettings(true); // Paper - Put world into worldlist before initing the world; move up From cf37a9631661b6d697da20bad0ab090558a771b5 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 19 Dec 2025 22:35:24 -0500 Subject: [PATCH 02/16] Update docs --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 037802edb06e..e2fae2d1da9f 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -232,7 +232,7 @@ public WorldCreator type(@NotNull WorldType type) { /** * Sets if this world should compute its spawn location using vanilla spawn - * location mechanics. This causes chunk loads on world creation and is enabled by default. + * location mechanics. This causes chunk loads on world creation on the main thread and is enabled by default. * * @param computeSpawnLocation Should compute spawn location * @return This object, for chaining From c02e2efc778d789d2edf0a47b1a84cc74b6f5bea Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:15:35 -0500 Subject: [PATCH 03/16] Update to set spawn loc --- .../main/java/org/bukkit/WorldCreator.java | 2 ++ .../0001-Moonrise-optimisation-patches.patch | 20 +++++++++---------- ...-Incremental-chunk-and-player-saving.patch | 6 +++--- ...025-Optimise-EntityScheduler-ticking.patch | 4 ++-- .../features/0028-Optimize-Hoppers.patch | 4 ++-- .../server/MinecraftServer.java.patch | 10 ++++++++-- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index e2fae2d1da9f..ecc8a34100bc 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -247,6 +247,8 @@ public WorldCreator computeSpawnLocation(boolean computeSpawnLocation) { /** * Gets if this world should compute its spawn location using vanilla spawn * location mechanics. This causes chunk loads on world creation. + *

+ * This also causes bonus chests to no longer be generated. * * @return if it computes spawn location */ diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 9de3cccbff3e..fcd376c35ce0 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -23165,7 +23165,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 public MinecraftServer( // CraftBukkit start joptsimple.OptionSet options, -@@ -827,7 +914,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop false : this::haveTime); @@ -23302,7 +23302,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 this.tickFrame.end(); this.recordEndOfTick(); // Paper - improve tick loop profilerFiller.pop(); -@@ -2559,6 +2678,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index 651431fe36c6..bf4723fd188a 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -245,7 +245,7 @@ if (profiledDuration != null) { profiledDuration.finish(true); } -@@ -391,31 +_,127 @@ +@@ -391,31 +_,133 @@ } } @@ -379,7 +379,13 @@ if (!serverLevelData.isInitialized()) { try { - setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, this.levelLoadListener); -+ if (computeSpawnLocation) setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process ++ // Paper start - Allow zeroing spawn location ++ if (computeSpawnLocation) { ++ setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process ++ } else { ++ serverLevelData.setSpawn(LevelData.RespawnData.of(serverLevel.dimension(), BlockPos.ZERO, 0.0F, 0.0F)); ++ } ++ // Paper end - Allow zeroing spawn location serverLevelData.setInitialized(true); if (isDebugWorld) { this.setupDebugLevel(this.worldData); From e1c9996efd45111f923456d175d0d330ff71bf01 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:24:50 -0500 Subject: [PATCH 04/16] Use override location instead --- .../main/java/org/bukkit/WorldCreator.java | 33 +++++++++++-------- .../0001-Moonrise-optimisation-patches.patch | 24 +++++++------- .../0002-Rewrite-dataconverter-system.patch | 4 +-- ...-Incremental-chunk-and-player-saving.patch | 6 ++-- ...025-Optimise-EntityScheduler-ticking.patch | 4 +-- .../features/0028-Optimize-Hoppers.patch | 4 +-- .../server/MinecraftServer.java.patch | 16 ++++++--- .../org/bukkit/craftbukkit/CraftServer.java | 2 +- 8 files changed, 52 insertions(+), 41 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index ecc8a34100bc..b2a7e1e1f2af 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -24,7 +24,8 @@ public class WorldCreator { private String generatorSettings = ""; private boolean hardcore = false; private boolean bonusChest = false; - private boolean computeSpawnLocation = true; + @Nullable + private Location spawnLocationOverride = null; /** * Creates an empty WorldCreationOptions for the given world name @@ -231,29 +232,33 @@ public WorldCreator type(@NotNull WorldType type) { } /** - * Sets if this world should compute its spawn location using vanilla spawn - * location mechanics. This causes chunk loads on world creation on the main thread and is enabled by default. + * Sets the spawn location that this world will have on creation. + * This overrides vanilla / custom generator behavior and will not cause any chunk loads. + * As a result, the bonus chest will not be spawned if this is set. + * * - * @param computeSpawnLocation Should compute spawn location + * @param computedSpawnLocation Spawn location, which can have a null world to indicate the world being created. + * or null, to use vanilla behavior. * @return This object, for chaining */ @NotNull - public WorldCreator computeSpawnLocation(boolean computeSpawnLocation) { - this.computeSpawnLocation = computeSpawnLocation; - + public WorldCreator forcedSpawnLocation(Location computedSpawnLocation) { + this.spawnLocationOverride = computedSpawnLocation == null ? null : computedSpawnLocation.clone(); return this; } /** - * Gets if this world should compute its spawn location using vanilla spawn - * location mechanics. This causes chunk loads on world creation. - *

- * This also causes bonus chests to no longer be generated. + * Gets the spawn location that will be set for this world on creation. * - * @return if it computes spawn location + * @return the force computed spawn location */ - public boolean computeSpawnLocation() { - return computeSpawnLocation; + @Nullable + public Location forcedSpawnLocation() { + if (this.spawnLocationOverride == null) { + return null; + } + + return this.spawnLocationOverride.clone(); } /** diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index fcd376c35ce0..56a02e3b7d01 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -23062,7 +23062,7 @@ diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/Mi index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430fe35d6bc 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -185,7 +185,7 @@ import net.minecraft.world.scores.ScoreboardSaveData; +@@ -186,7 +186,7 @@ import org.bukkit.craftbukkit.util.CraftLocation; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -23071,7 +23071,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 private static MinecraftServer SERVER; // Paper public static final Logger LOGGER = LogUtils.getLogger(); public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper -@@ -397,6 +397,93 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop false : this::haveTime); @@ -23302,7 +23302,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 this.tickFrame.end(); this.recordEndOfTick(); // Paper - improve tick loop profilerFiller.pop(); -@@ -2565,6 +2684,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { diff --git a/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch index e341ae1b69ba..7ecf90a758ac 100644 --- a/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch +++ b/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index a5cd73f5e97757c0e15941105b0e0a8a685ddb65..dfa671c28048d2c4c7730438b29a732af110ca59 100644 +index 59f2eef56aec9092df7f2b78a9485d7b37fc5105..6dfaed7e8a7d5624cc95fedac1bf772bd8adbca2 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -980,7 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index bf4723fd188a..eaed57c480d9 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -1,6 +1,11 @@ --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -186,11 +_,13 @@ +@@ -182,15 +_,18 @@ + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.ScoreboardSaveData; ++import org.bukkit.craftbukkit.util.CraftLocation; + import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, CommandSource, ChunkIOErrorReporter { @@ -245,7 +250,7 @@ if (profiledDuration != null) { profiledDuration.finish(true); } -@@ -391,31 +_,133 @@ +@@ -391,31 +_,134 @@ } } @@ -368,7 +373,7 @@ + this.addLevel(serverLevel); + this.initWorld(serverLevel, serverLevelData, worldOptions, true); + } -+ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, boolean computeSpawnLocation) { ++ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, org.bukkit.Location forcedSpawnLocation) { + final boolean isDebugWorld = this.worldData.isDebugWorld(); + if (serverLevel.generator != null) { + serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld())); @@ -380,10 +385,11 @@ try { - setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, this.levelLoadListener); + // Paper start - Allow zeroing spawn location -+ if (computeSpawnLocation) { ++ if (forcedSpawnLocation == null) { + setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process + } else { -+ serverLevelData.setSpawn(LevelData.RespawnData.of(serverLevel.dimension(), BlockPos.ZERO, 0.0F, 0.0F)); ++ ResourceKey dimension = forcedSpawnLocation.isWorldLoaded() ? ((org.bukkit.craftbukkit.CraftWorld) forcedSpawnLocation.getWorld()).getHandle().dimension() : serverLevel.dimension(); // Default to target world if possibl, else the current world ++ serverLevelData.setSpawn(LevelData.RespawnData.of(dimension, org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(forcedSpawnLocation), forcedSpawnLocation.getYaw(), forcedSpawnLocation.getPitch())); + } + // Paper end - Allow zeroing spawn location serverLevelData.setInitialized(true); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 30cbebcc1754..bffe5306cf6e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1306,7 +1306,7 @@ public World createWorld(WorldCreator creator) { } this.console.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up - this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.computeSpawnLocation()); + this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.forcedSpawnLocation()); serverLevel.setSpawnSettings(true); // Paper - Put world into worldlist before initing the world; move up From d437d1193a40aa3ce43a13298419f96ec7d8db98 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:52:29 -0500 Subject: [PATCH 05/16] Fix --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index b2a7e1e1f2af..54487406def0 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -242,7 +242,7 @@ public WorldCreator type(@NotNull WorldType type) { * @return This object, for chaining */ @NotNull - public WorldCreator forcedSpawnLocation(Location computedSpawnLocation) { + public WorldCreator forcedSpawnLocation(@Nullable Location computedSpawnLocation) { this.spawnLocationOverride = computedSpawnLocation == null ? null : computedSpawnLocation.clone(); return this; } From f2758f194f469c79d57d4f51d4f1c796f311b45c Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:01:53 -0500 Subject: [PATCH 06/16] Add api to override dimension / world directory --- .../main/java/org/bukkit/WorldCreator.java | 135 ++++++++++++++++-- .../server/MinecraftServer.java.patch | 11 +- .../org/bukkit/craftbukkit/CraftServer.java | 11 +- 3 files changed, 135 insertions(+), 22 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 54487406def0..03d35d085f8d 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -1,7 +1,10 @@ package org.bukkit; import com.google.common.base.Preconditions; +import java.io.File; import java.util.Random; +import io.papermc.paper.math.Position; +import net.kyori.adventure.key.Key; import org.bukkit.command.CommandSender; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; @@ -24,8 +27,14 @@ public class WorldCreator { private String generatorSettings = ""; private boolean hardcore = false; private boolean bonusChest = false; + @Nullable - private Location spawnLocationOverride = null; + private Position spawnPositionOverride; + private Float spawnYawOverride; + private Float spawnPitchOverride; + + private Key dimensionKey = null; + private File worldFileOverride = Bukkit.getWorldContainer(); /** * Creates an empty WorldCreationOptions for the given world name @@ -208,6 +217,62 @@ public WorldCreator environment(@NotNull World.Environment env) { return this; } + /** + * Sets the dimension key override for this world. + * + *

The dimension key determines the dimension type this world will use + * (for example: overworld, nether, end, or a custom dimension).

+ * + *

If set, this overrides the dimension type provided by vanilla or any + * custom world generator.

+ * + * @param key the dimension key to use for this world + * @return this object, for chaining + */ + @NotNull + public WorldCreator dimensionKeyOverride(@NotNull Key key) { + this.dimensionKey = key; + return this; + } + + /** + * Gets the dimension key override that will be applied when this world is created. + * + * @return the dimension key override, or {@code null} if vanilla behavior is used + */ + @Nullable + public Key getDimensionKeyOverride() { + return dimensionKey; + } + + /** + * Sets the directory that this world's data will be stored in. + * + *

The provided file represents the parent folder used for + * storing all world data (region files, player data, level data, etc.).

+ * + *

If not set, the default world container directory will be used.

+ * + * @param override the parent directory to store this world's data in + * @return this object, for chaining + */ + @NotNull + public WorldCreator worldFileStorage(@NotNull File override) { + this.worldFileOverride = override; + return this; + } + + /** + * Gets the directory used for storing this world's data. + * + * @return the parent directory used for world storage, or {@code null} + * if the default world container is used + */ + @Nullable + public File getWorldFileStorage() { + return worldFileOverride; + } + /** * Gets the type of the world that will be created or loaded * @@ -232,33 +297,75 @@ public WorldCreator type(@NotNull WorldType type) { } /** - * Sets the spawn location that this world will have on creation. + * Sets the spawn position that this world will have on creation. * This overrides vanilla / custom generator behavior and will not cause any chunk loads. * As a result, the bonus chest will not be spawned if this is set. * - * - * @param computedSpawnLocation Spawn location, which can have a null world to indicate the world being created. - * or null, to use vanilla behavior. + * @param position Spawn position (world may be null to indicate the world being created), + * or null to use vanilla behavior. + * @param yaw Yaw rotation + * @param pitch Pitch rotation * @return This object, for chaining */ @NotNull - public WorldCreator forcedSpawnLocation(@Nullable Location computedSpawnLocation) { - this.spawnLocationOverride = computedSpawnLocation == null ? null : computedSpawnLocation.clone(); + public WorldCreator forcedSpawnPosition(@Nullable Position position, float yaw, float pitch + ) { + if (position == null) { + this.spawnPositionOverride = null; + this.spawnYawOverride = yaw; + this.spawnPitchOverride = pitch; + return this; + } + + this.spawnPositionOverride = position; + this.spawnYawOverride = yaw; + this.spawnPitchOverride = pitch; return this; } /** - * Gets the spawn location that will be set for this world on creation. + * Gets the forced spawn position that will be applied when this world is created. + * + *

If this returns {@code null}, vanilla or custom generator behavior will be used + * to determine the spawn position.

+ * + *

The returned {@link Position} is a clone and may be modified safely.

* - * @return the force computed spawn location + * @return the forced spawn position, or {@code null} to use vanilla behavior */ @Nullable - public Location forcedSpawnLocation() { - if (this.spawnLocationOverride == null) { - return null; - } + public Position forcedSpawnPosition() { + return this.spawnPositionOverride == null ? null : this.spawnPositionOverride; + } + + /** + * Gets the forced spawn yaw that will be applied when this world is created. + * + *

If this returns {@code null}, the spawn yaw will be determined by vanilla behavior + * or the world generator.

+ * + *

This value is only meaningful if a forced spawn position is present.

+ * + * @return the forced spawn yaw, or {@code null} to use vanilla behavior + */ + @Nullable + public Float forcedSpawnYaw() { + return this.spawnYawOverride; + } - return this.spawnLocationOverride.clone(); + /** + * Gets the forced spawn pitch that will be applied when this world is created. + * + *

If this returns {@code null}, the spawn pitch will be determined by vanilla behavior + * or the world generator.

+ * + *

This value is only meaningful if a forced spawn position is present.

+ * + * @return the forced spawn pitch, or {@code null} to use vanilla behavior + */ + @Nullable + public Float forcedSpawnPitch() { + return this.spawnPitchOverride; } /** diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index eaed57c480d9..2c1f0650d77b 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -250,7 +250,7 @@ if (profiledDuration != null) { profiledDuration.finish(true); } -@@ -391,31 +_,134 @@ +@@ -391,31 +_,133 @@ } } @@ -371,9 +371,9 @@ + ); + } + this.addLevel(serverLevel); -+ this.initWorld(serverLevel, serverLevelData, worldOptions, true); ++ this.initWorld(serverLevel, serverLevelData, worldOptions, null, 0.0F, 0.0F); + } -+ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, org.bukkit.Location forcedSpawnLocation) { ++ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, io.papermc.paper.math.@Nullable Position forcedPosition, float forcedPitch, float forcedYaw) { + final boolean isDebugWorld = this.worldData.isDebugWorld(); + if (serverLevel.generator != null) { + serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld())); @@ -385,11 +385,10 @@ try { - setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, this.levelLoadListener); + // Paper start - Allow zeroing spawn location -+ if (forcedSpawnLocation == null) { ++ if (forcedPosition == null) { + setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process + } else { -+ ResourceKey dimension = forcedSpawnLocation.isWorldLoaded() ? ((org.bukkit.craftbukkit.CraftWorld) forcedSpawnLocation.getWorld()).getHandle().dimension() : serverLevel.dimension(); // Default to target world if possibl, else the current world -+ serverLevelData.setSpawn(LevelData.RespawnData.of(dimension, org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(forcedSpawnLocation), forcedSpawnLocation.getYaw(), forcedSpawnLocation.getPitch())); ++ serverLevelData.setSpawn(LevelData.RespawnData.of(serverLevel.dimension(), io.papermc.paper.util.MCUtil.toBlockPos(forcedPosition), forcedYaw, forcedPitch)); + } + // Paper end - Allow zeroing spawn location serverLevelData.setInitialized(true); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index bffe5306cf6e..55ef3b05cc59 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -12,6 +12,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Dynamic; import com.mojang.serialization.Lifecycle; +import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.configuration.GlobalConfiguration; import io.papermc.paper.configuration.PaperServerConfiguration; import io.papermc.paper.configuration.ServerConfiguration; @@ -1214,7 +1215,7 @@ public World createWorld(WorldCreator creator) { LevelStorageSource.LevelStorageAccess levelStorageAccess; try { - levelStorageAccess = LevelStorageSource.createDefault(this.getWorldContainer().toPath()).validateAndCreateAccess(name, actualDimension); + levelStorageAccess = LevelStorageSource.createDefault(creator.getWorldFileStorage().toPath()).validateAndCreateAccess(name, actualDimension); } catch (IOException | ContentValidationException ex) { throw new RuntimeException(ex); } @@ -1269,6 +1270,12 @@ public World createWorld(WorldCreator creator) { new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(primaryLevelData) ); LevelStem customStem = contextLevelStemRegistry.getValue(actualDimension); + if (creator.getDimensionKeyOverride() != null) { + customStem = new LevelStem( + this.console.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(PaperAdventure.asVanilla(Registries.DIMENSION_TYPE, creator.getDimensionKeyOverride())), + customStem.generator() + ); + } WorldInfo worldInfo = new CraftWorldInfo(primaryLevelData, levelStorageAccess, creator.environment(), customStem.type().value(), customStem.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo if (biomeProvider == null && chunkGenerator != null) { @@ -1306,7 +1313,7 @@ public World createWorld(WorldCreator creator) { } this.console.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up - this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.forcedSpawnLocation()); + this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.forcedSpawnPosition(), java.util.Objects.requireNonNullElse(creator.forcedSpawnPitch(), 0.0F), java.util.Objects.requireNonNullElse(creator.forcedSpawnYaw(), 0.0F)); serverLevel.setSpawnSettings(true); // Paper - Put world into worldlist before initing the world; move up From a5b9e5f58f30b5fbdd16ed3b6209477793fdf00a Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:22:07 -0500 Subject: [PATCH 07/16] Fix --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 03d35d085f8d..88a374740b47 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -251,8 +251,6 @@ public Key getDimensionKeyOverride() { *

The provided file represents the parent folder used for * storing all world data (region files, player data, level data, etc.).

* - *

If not set, the default world container directory will be used.

- * * @param override the parent directory to store this world's data in * @return this object, for chaining */ @@ -265,10 +263,9 @@ public WorldCreator worldFileStorage(@NotNull File override) { /** * Gets the directory used for storing this world's data. * - * @return the parent directory used for world storage, or {@code null} - * if the default world container is used + * @return the parent directory used for world storage */ - @Nullable + @NotNull public File getWorldFileStorage() { return worldFileOverride; } From 672fa59e4ced06a8156ef9c8aed39a7921ad418e Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:29:04 -0500 Subject: [PATCH 08/16] Remove dimension override --- .../main/java/org/bukkit/WorldCreator.java | 29 ------------------- .../org/bukkit/craftbukkit/CraftServer.java | 8 +---- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 88a374740b47..5fd74a79e866 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -33,7 +33,6 @@ public class WorldCreator { private Float spawnYawOverride; private Float spawnPitchOverride; - private Key dimensionKey = null; private File worldFileOverride = Bukkit.getWorldContainer(); /** @@ -217,34 +216,6 @@ public WorldCreator environment(@NotNull World.Environment env) { return this; } - /** - * Sets the dimension key override for this world. - * - *

The dimension key determines the dimension type this world will use - * (for example: overworld, nether, end, or a custom dimension).

- * - *

If set, this overrides the dimension type provided by vanilla or any - * custom world generator.

- * - * @param key the dimension key to use for this world - * @return this object, for chaining - */ - @NotNull - public WorldCreator dimensionKeyOverride(@NotNull Key key) { - this.dimensionKey = key; - return this; - } - - /** - * Gets the dimension key override that will be applied when this world is created. - * - * @return the dimension key override, or {@code null} if vanilla behavior is used - */ - @Nullable - public Key getDimensionKeyOverride() { - return dimensionKey; - } - /** * Sets the directory that this world's data will be stored in. * diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 55ef3b05cc59..5d8f8a2fb58f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1181,7 +1181,7 @@ public World createWorld(WorldCreator creator) { String name = creator.name(); ChunkGenerator chunkGenerator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); - File folder = new File(this.getWorldContainer(), name); + File folder = new File(creator.getWorldFileStorage(), name); World world = this.getWorld(name); // Paper start @@ -1270,12 +1270,6 @@ public World createWorld(WorldCreator creator) { new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(primaryLevelData) ); LevelStem customStem = contextLevelStemRegistry.getValue(actualDimension); - if (creator.getDimensionKeyOverride() != null) { - customStem = new LevelStem( - this.console.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(PaperAdventure.asVanilla(Registries.DIMENSION_TYPE, creator.getDimensionKeyOverride())), - customStem.generator() - ); - } WorldInfo worldInfo = new CraftWorldInfo(primaryLevelData, levelStorageAccess, creator.environment(), customStem.type().value(), customStem.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo if (biomeProvider == null && chunkGenerator != null) { From 227801005e1ea6c6e43bd2d50087ca741fb0468d Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:31:23 -0500 Subject: [PATCH 09/16] Fix path --- .../main/java/org/bukkit/WorldCreator.java | 9 +++--- .../0001-Moonrise-optimisation-patches.patch | 28 +++++++++---------- .../0002-Rewrite-dataconverter-system.patch | 4 +-- ...-Incremental-chunk-and-player-saving.patch | 8 +++--- ...025-Optimise-EntityScheduler-ticking.patch | 4 +-- .../features/0028-Optimize-Hoppers.patch | 4 +-- .../server/MinecraftServer.java.patch | 7 +---- .../org/bukkit/craftbukkit/CraftServer.java | 4 +-- 8 files changed, 32 insertions(+), 36 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 5fd74a79e866..8515298b7b5a 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -2,6 +2,7 @@ import com.google.common.base.Preconditions; import java.io.File; +import java.nio.file.Path; import java.util.Random; import io.papermc.paper.math.Position; import net.kyori.adventure.key.Key; @@ -33,7 +34,7 @@ public class WorldCreator { private Float spawnYawOverride; private Float spawnPitchOverride; - private File worldFileOverride = Bukkit.getWorldContainer(); + private Path worldFileOverride = Bukkit.getWorldContainer().toPath(); /** * Creates an empty WorldCreationOptions for the given world name @@ -226,7 +227,7 @@ public WorldCreator environment(@NotNull World.Environment env) { * @return this object, for chaining */ @NotNull - public WorldCreator worldFileStorage(@NotNull File override) { + public WorldCreator worldFileStorage(@NotNull Path override) { this.worldFileOverride = override; return this; } @@ -237,8 +238,8 @@ public WorldCreator worldFileStorage(@NotNull File override) { * @return the parent directory used for world storage */ @NotNull - public File getWorldFileStorage() { - return worldFileOverride; + public Path getWorldFileStorage() { + return this.worldFileOverride; } /** diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 56a02e3b7d01..3961874a2e5e 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -23059,10 +23059,10 @@ index cda915fcb4822689f42b25280eb99aee082ddb74..094d2d528cb74b8f1d277cd780bba7f4 thread1 -> { DedicatedServer dedicatedServer1 = new DedicatedServer( diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430fe35d6bc 100644 +index 35f2485b8d727695476531391f886c4a598e486e..c2ffe3c695911c706a257cfbf5c41166545f721d 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -186,7 +186,7 @@ import org.bukkit.craftbukkit.util.CraftLocation; +@@ -185,7 +185,7 @@ import net.minecraft.world.scores.ScoreboardSaveData; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -23071,7 +23071,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 private static MinecraftServer SERVER; // Paper public static final Logger LOGGER = LogUtils.getLogger(); public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper -@@ -398,6 +398,93 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop false : this::haveTime); @@ -23302,7 +23302,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 this.tickFrame.end(); this.recordEndOfTick(); // Paper - improve tick loop profilerFiller.pop(); -@@ -2567,6 +2686,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { diff --git a/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch index 7ecf90a758ac..1d16a57c7af6 100644 --- a/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch +++ b/paper-server/patches/features/0020-Incremental-chunk-and-player-saving.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 59f2eef56aec9092df7f2b78a9485d7b37fc5105..6dfaed7e8a7d5624cc95fedac1bf772bd8adbca2 100644 +index e77ef704ebd2830b0d7e5afcbe873cd19293453f..9555d4cff8bdcfa64293b5a001b82a9bce808fd4 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -982,7 +982,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index 2c1f0650d77b..14dd382b1e83 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -1,11 +1,6 @@ --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -182,15 +_,18 @@ - import net.minecraft.world.phys.Vec2; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.scores.ScoreboardSaveData; -+import org.bukkit.craftbukkit.util.CraftLocation; - import org.jspecify.annotations.Nullable; +@@ -186,11 +_,13 @@ import org.slf4j.Logger; public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, CommandSource, ChunkIOErrorReporter { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 5d8f8a2fb58f..8edcb909d048 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1181,7 +1181,7 @@ public World createWorld(WorldCreator creator) { String name = creator.name(); ChunkGenerator chunkGenerator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); - File folder = new File(creator.getWorldFileStorage(), name); + File folder = new File(creator.getWorldFileStorage().toFile(), name); World world = this.getWorld(name); // Paper start @@ -1215,7 +1215,7 @@ public World createWorld(WorldCreator creator) { LevelStorageSource.LevelStorageAccess levelStorageAccess; try { - levelStorageAccess = LevelStorageSource.createDefault(creator.getWorldFileStorage().toPath()).validateAndCreateAccess(name, actualDimension); + levelStorageAccess = LevelStorageSource.createDefault(creator.getWorldFileStorage()).validateAndCreateAccess(name, actualDimension); } catch (IOException | ContentValidationException ex) { throw new RuntimeException(ex); } From 6f9fd99a9e2b296b6b66d23630f6fd6e89be86d2 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:34:12 -0500 Subject: [PATCH 10/16] Cleanup api types --- .../main/java/org/bukkit/WorldCreator.java | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 8515298b7b5a..b6866d82446d 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -266,32 +266,39 @@ public WorldCreator type(@NotNull WorldType type) { } /** - * Sets the spawn position that this world will have on creation. - * This overrides vanilla / custom generator behavior and will not cause any chunk loads. - * As a result, the bonus chest will not be spawned if this is set. - * - * @param position Spawn position (world may be null to indicate the world being created), - * or null to use vanilla behavior. - * @param yaw Yaw rotation - * @param pitch Pitch rotation - * @return This object, for chaining + * Sets the forced spawn position for the world created by this {@link WorldCreator}. + *

+ * This overrides vanilla and custom generator behavior without loading any chunks. + * When a forced spawn is specified, the bonus chest will not be generated. + * + * @param position the spawn position + * @param yaw the yaw rotation at spawn + * @param pitch the pitch rotation at spawn + * @return this creator for chaining */ @NotNull - public WorldCreator forcedSpawnPosition(@Nullable Position position, float yaw, float pitch - ) { - if (position == null) { - this.spawnPositionOverride = null; - this.spawnYawOverride = yaw; - this.spawnPitchOverride = pitch; - return this; - } - + public WorldCreator forcedSpawnPosition(@NotNull Position position, float yaw, float pitch) { this.spawnPositionOverride = position; this.spawnYawOverride = yaw; this.spawnPitchOverride = pitch; return this; } + /** + * Clears any previously forced spawn position. + *

+ * After calling this, vanilla spawn selection behavior is used. + * + * @return this creator for chaining + */ + @NotNull + public WorldCreator clearForcedSpawnPosition() { + this.spawnPositionOverride = null; + this.spawnYawOverride = null; + this.spawnPitchOverride = null; + return this; + } + /** * Gets the forced spawn position that will be applied when this world is created. * From 80d0fc229e1134e396b15a6175a9c9ba8c70ffb4 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:44:00 -0500 Subject: [PATCH 11/16] Use base sym link directory --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 4 +--- .../java/org/bukkit/craftbukkit/CraftServer.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index b6866d82446d..0f89c248f3ba 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -1,11 +1,9 @@ package org.bukkit; import com.google.common.base.Preconditions; -import java.io.File; import java.nio.file.Path; import java.util.Random; import io.papermc.paper.math.Position; -import net.kyori.adventure.key.Key; import org.bukkit.command.CommandSender; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; @@ -238,7 +236,7 @@ public WorldCreator worldFileStorage(@NotNull Path override) { * @return the parent directory used for world storage */ @NotNull - public Path getWorldFileStorage() { + public Path getParentDirectory() { return this.worldFileOverride; } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 8edcb909d048..d7f0c77aef01 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -12,7 +12,6 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Dynamic; import com.mojang.serialization.Lifecycle; -import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.configuration.GlobalConfiguration; import io.papermc.paper.configuration.PaperServerConfiguration; import io.papermc.paper.configuration.ServerConfiguration; @@ -27,6 +26,7 @@ import java.io.InputStreamReader; import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -114,6 +114,7 @@ import net.minecraft.world.level.storage.PlayerDataStorage; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.level.validation.ContentValidationException; +import net.minecraft.world.level.validation.DirectoryValidator; import org.apache.commons.lang3.StringUtils; import org.bukkit.BanList; import org.bukkit.Bukkit; @@ -1181,7 +1182,7 @@ public World createWorld(WorldCreator creator) { String name = creator.name(); ChunkGenerator chunkGenerator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); - File folder = new File(creator.getWorldFileStorage().toFile(), name); + File folder = new File(creator.getParentDirectory().toFile(), name); World world = this.getWorld(name); // Paper start @@ -1215,7 +1216,12 @@ public World createWorld(WorldCreator creator) { LevelStorageSource.LevelStorageAccess levelStorageAccess; try { - levelStorageAccess = LevelStorageSource.createDefault(creator.getWorldFileStorage()).validateAndCreateAccess(name, actualDimension); + Path serverRoot = this.getWorldContainer().toPath(); + // Make sure parsing off server root for symlinks + DirectoryValidator directoryValidator = LevelStorageSource.parseValidator(serverRoot.resolve("allowed_symlinks.txt")); + LevelStorageSource levelStorageSource = new LevelStorageSource(creator.getParentDirectory(), serverRoot.resolve("../backups"), directoryValidator, DataFixers.getDataFixer()); + + levelStorageAccess = levelStorageSource.validateAndCreateAccess(name, actualDimension); } catch (IOException | ContentValidationException ex) { throw new RuntimeException(ex); } From e6cc2b31043023bb821dc726f66ef730d396c8ba Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:58:16 -0500 Subject: [PATCH 12/16] Smoll cleanup --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 0f89c248f3ba..58defb8bb50d 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -29,10 +29,12 @@ public class WorldCreator { @Nullable private Position spawnPositionOverride; + @Nullable private Float spawnYawOverride; + @Nullable private Float spawnPitchOverride; - private Path worldFileOverride = Bukkit.getWorldContainer().toPath(); + private Path parentDirectory = Bukkit.getWorldContainer().toPath(); /** * Creates an empty WorldCreationOptions for the given world name @@ -225,8 +227,8 @@ public WorldCreator environment(@NotNull World.Environment env) { * @return this object, for chaining */ @NotNull - public WorldCreator worldFileStorage(@NotNull Path override) { - this.worldFileOverride = override; + public WorldCreator parentDirectory(@NotNull Path override) { + this.parentDirectory = override; return this; } @@ -237,7 +239,7 @@ public WorldCreator worldFileStorage(@NotNull Path override) { */ @NotNull public Path getParentDirectory() { - return this.worldFileOverride; + return this.parentDirectory; } /** From ca8012d5a9554dc082fda7cb69767cbd839c4fd7 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:00:01 -0500 Subject: [PATCH 13/16] Cleanup --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 58defb8bb50d..3c229500d165 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -311,7 +311,7 @@ public WorldCreator clearForcedSpawnPosition() { */ @Nullable public Position forcedSpawnPosition() { - return this.spawnPositionOverride == null ? null : this.spawnPositionOverride; + return this.spawnPositionOverride; } /** From 05480c86e08bf64acd7bac7f11f1687de2d8b2fb Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:03:11 -0500 Subject: [PATCH 14/16] Also use fluent getter --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 2 +- .../src/main/java/org/bukkit/craftbukkit/CraftServer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index 3c229500d165..a9f9d12a782a 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -238,7 +238,7 @@ public WorldCreator parentDirectory(@NotNull Path override) { * @return the parent directory used for world storage */ @NotNull - public Path getParentDirectory() { + public Path parentDirectory() { return this.parentDirectory; } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index d7f0c77aef01..e724b3067154 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1182,7 +1182,7 @@ public World createWorld(WorldCreator creator) { String name = creator.name(); ChunkGenerator chunkGenerator = creator.generator(); BiomeProvider biomeProvider = creator.biomeProvider(); - File folder = new File(creator.getParentDirectory().toFile(), name); + File folder = new File(creator.parentDirectory().toFile(), name); World world = this.getWorld(name); // Paper start @@ -1219,7 +1219,7 @@ public World createWorld(WorldCreator creator) { Path serverRoot = this.getWorldContainer().toPath(); // Make sure parsing off server root for symlinks DirectoryValidator directoryValidator = LevelStorageSource.parseValidator(serverRoot.resolve("allowed_symlinks.txt")); - LevelStorageSource levelStorageSource = new LevelStorageSource(creator.getParentDirectory(), serverRoot.resolve("../backups"), directoryValidator, DataFixers.getDataFixer()); + LevelStorageSource levelStorageSource = new LevelStorageSource(creator.parentDirectory(), serverRoot.resolve("../backups"), directoryValidator, DataFixers.getDataFixer()); levelStorageAccess = levelStorageSource.validateAndCreateAccess(name, actualDimension); } catch (IOException | ContentValidationException ex) { From 0798c417320934866f300f1af537a74132554e5f Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:10:12 -0500 Subject: [PATCH 15/16] Remove cloned comment due to the type being immutable --- paper-api/src/main/java/org/bukkit/WorldCreator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/WorldCreator.java b/paper-api/src/main/java/org/bukkit/WorldCreator.java index a9f9d12a782a..5519513a480f 100644 --- a/paper-api/src/main/java/org/bukkit/WorldCreator.java +++ b/paper-api/src/main/java/org/bukkit/WorldCreator.java @@ -305,8 +305,6 @@ public WorldCreator clearForcedSpawnPosition() { *

If this returns {@code null}, vanilla or custom generator behavior will be used * to determine the spawn position.

* - *

The returned {@link Position} is a clone and may be modified safely.

- * * @return the forced spawn position, or {@code null} to use vanilla behavior */ @Nullable From 0f8f59acdaeb1e7e3f501a7234cbc6404f5a48bd Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:31:22 -0500 Subject: [PATCH 16/16] Cleanup args passed --- .../minecraft/server/MinecraftServer.java.patch | 14 +++++++++----- .../java/org/bukkit/craftbukkit/CraftServer.java | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index 14dd382b1e83..d2e49c63adae 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -245,7 +245,7 @@ if (profiledDuration != null) { profiledDuration.finish(true); } -@@ -391,31 +_,133 @@ +@@ -391,31 +_,137 @@ } } @@ -366,9 +366,9 @@ + ); + } + this.addLevel(serverLevel); -+ this.initWorld(serverLevel, serverLevelData, worldOptions, null, 0.0F, 0.0F); ++ this.initWorld(serverLevel, serverLevelData, worldOptions, null); + } -+ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, io.papermc.paper.math.@Nullable Position forcedPosition, float forcedPitch, float forcedYaw) { ++ public void initWorld(ServerLevel serverLevel, net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, WorldOptions worldOptions, org.bukkit.@Nullable WorldCreator worldCreator) { + final boolean isDebugWorld = this.worldData.isDebugWorld(); + if (serverLevel.generator != null) { + serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld())); @@ -380,10 +380,14 @@ try { - setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, this.levelLoadListener); + // Paper start - Allow zeroing spawn location -+ if (forcedPosition == null) { ++ if (worldCreator == null || worldCreator.forcedSpawnPosition() == null) { + setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld, serverLevel.levelLoadListener); // Paper - per world level load listener & rework world loading process + } else { -+ serverLevelData.setSpawn(LevelData.RespawnData.of(serverLevel.dimension(), io.papermc.paper.util.MCUtil.toBlockPos(forcedPosition), forcedYaw, forcedPitch)); ++ serverLevelData.setSpawn(LevelData.RespawnData.of(serverLevel.dimension(), ++ io.papermc.paper.util.MCUtil.toBlockPos(worldCreator.forcedSpawnPosition()), ++ java.util.Objects.requireNonNullElse(worldCreator.forcedSpawnYaw(), 0.0F), ++ java.util.Objects.requireNonNullElse(worldCreator.forcedSpawnPitch(), 0.0F) ++ )); + } + // Paper end - Allow zeroing spawn location serverLevelData.setInitialized(true); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index e724b3067154..d14b18d27214 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1313,7 +1313,7 @@ public World createWorld(WorldCreator creator) { } this.console.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up - this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator.forcedSpawnPosition(), java.util.Objects.requireNonNullElse(creator.forcedSpawnPitch(), 0.0F), java.util.Objects.requireNonNullElse(creator.forcedSpawnYaw(), 0.0F)); + this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData.worldGenOptions(), creator); serverLevel.setSpawnSettings(true); // Paper - Put world into worldlist before initing the world; move up