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