diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 1629f8e432..d6ad1f93e5 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -46,6 +46,7 @@ import com.earth2me.essentials.updatecheck.UpdateChecker; import com.earth2me.essentials.userstorage.ModernUserMap; import com.earth2me.essentials.utils.FormatUtil; +import com.earth2me.essentials.utils.ModernPaperEnvironment; import com.earth2me.essentials.utils.PasteUtil; import com.earth2me.essentials.utils.VersionUtil; import io.papermc.lib.PaperLib; @@ -261,6 +262,10 @@ public void onEnable() { getLogger().severe(getAdventureFacet().miniToLegacy(tlLiteral("serverSnapshot"))); } + if (PaperLib.isPaper() && !PaperLib.isVersion(13) && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) { + PaperLib.setCustomEnvironment(new ModernPaperEnvironment()); + } + final PluginManager pm = getServer().getPluginManager(); for (final Plugin plugin : pm.getPlugins()) { if (plugin.getDescription().getName().startsWith("Essentials") && !plugin.getDescription().getVersion().equals(this.getDescription().getVersion()) && !plugin.getDescription().getName().equals("EssentialsAntiCheat")) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java b/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java new file mode 100644 index 0000000000..1311f223d9 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java @@ -0,0 +1,23 @@ +package com.earth2me.essentials.utils; + +import io.papermc.lib.environments.PaperEnvironment; +import io.papermc.lib.features.asyncchunks.AsyncChunksPaper_15; +import io.papermc.lib.features.asyncteleport.AsyncTeleportPaper_13; +import io.papermc.lib.features.bedspawnlocation.BedSpawnLocationPaper; +import io.papermc.lib.features.chunkisgenerated.ChunkIsGeneratedApiExists; + +/** + * A PaperLib environment for Paper servers whose version PaperLib cannot read. + *
+ * PaperLib 1.0.6 expects a single digit major version in {@code Bukkit#getVersion()}, so it reads 26.x as version 0 and
+ * selects its synchronous fallbacks for chunk loading, teleporting and bed lookups. All of those exist on every Paper
+ * version that reports a year based version.
+ */
+public class ModernPaperEnvironment extends PaperEnvironment {
+ public ModernPaperEnvironment() {
+ asyncChunksHandler = new AsyncChunksPaper_15();
+ asyncTeleportHandler = new AsyncTeleportPaper_13();
+ isGeneratedHandler = new ChunkIsGeneratedApiExists();
+ bedSpawnLocationHandler = new BedSpawnLocationPaper();
+ }
+}
diff --git a/Essentials/src/test/java/com/earth2me/essentials/utils/ModernPaperEnvironmentTest.java b/Essentials/src/test/java/com/earth2me/essentials/utils/ModernPaperEnvironmentTest.java
new file mode 100644
index 0000000000..7bb08cfb76
--- /dev/null
+++ b/Essentials/src/test/java/com/earth2me/essentials/utils/ModernPaperEnvironmentTest.java
@@ -0,0 +1,62 @@
+package com.earth2me.essentials.utils;
+
+import io.papermc.lib.environments.PaperEnvironment;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class ModernPaperEnvironmentTest {
+ private static final String YEAR_VERSION = "26.3-140-main@a1b2c3d (2026-09-30T10:00:00Z) (MC: 26.3)";
+
+ @Test
+ public void testPaperLibFallsBackToSynchronousTeleportOnYearVersions() {
+ // Documents why ModernPaperEnvironment exists: PaperLib itself picks the synchronous handlers here.
+ withVersion(YEAR_VERSION, PaperEnvironment::new, env -> {
+ final Entity entity = mock(Entity.class);
+ final Location location = new Location(mock(World.class), 0, 64, 0);
+ env.teleport(entity, location, TeleportCause.PLUGIN);
+ verify(entity).teleport(location, TeleportCause.PLUGIN);
+ verify(entity, never()).teleportAsync(any(Location.class), any(TeleportCause.class));
+ });
+ }
+
+ @Test
+ public void testTeleportsAsynchronouslyOnYearVersions() {
+ withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
+ final Entity entity = mock(Entity.class);
+ final Location location = new Location(mock(World.class), 0, 64, 0);
+ env.teleport(entity, location, TeleportCause.PLUGIN);
+ verify(entity).teleportAsync(location, TeleportCause.PLUGIN);
+ verify(entity, never()).teleport(any(Location.class), any(TeleportCause.class));
+ });
+ }
+
+ @Test
+ public void testLoadsChunksAsynchronouslyOnYearVersions() {
+ withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
+ final World world = mock(World.class);
+ env.getChunkAtAsync(world, 1, 2, true);
+ verify(world).getChunkAtAsync(1, 2, true, false);
+ });
+ }
+
+ private static