Require Paper and drop Spigot support

Core now compiles against paper-api instead of spigot-api. PaperLib is
replaced by Paper's native async chunk and teleport API, and the Spigot
fallbacks (Medieval pregen method, sync chunk loading) are removed.

Adventure is no longer bundled and relocated: Paper ships it and every
CommandSender is an Audience, so the BukkitAudiences wrapper and ShadeFix
go away.

Iris now refuses to enable on servers without the Paper API, with a clear
message instead of failing later at runtime.

Model used: Claude Opus 5.5
This commit is contained in:
piexel committed 2026-10-11 15:06:37 +02:00
1 parent 31615dc052
commit f1c1869e43
19 files changed
+55 -337

No files matched your search

+15 -14
View File
@@ -86,7 +86,6 @@ public class Iris extends VolmitPlugin implements Listener {
private static final Queue<Runnable> syncJobs = new ShurikenQueue<>();
public static Iris instance;
public static Bindings.Adventure audiences;
public static MultiverseCoreLink linkMultiverseCore;
public static IrisCompat compat;
public static FileWatcher configWatcher;
@@ -435,7 +434,6 @@ public class Iris extends VolmitPlugin implements Listener {
private void enable() {
services = new KMap<>();
setupAudience();
Bindings.setupSentry();
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
IO.delete(new File("iris"));
@@ -532,29 +530,23 @@ public class Iris extends VolmitPlugin implements Listener {
}
}
private void setupAudience() {
try {
audiences = new Bindings.Adventure(this);
} catch (Throwable e) {
e.printStackTrace();
IrisSettings.get().getGeneral().setUseConsoleCustomColors(false);
IrisSettings.get().getGeneral().setUseCustomColorsIngame(false);
Iris.error("Failed to setup Adventure API... No custom colors :(");
}
}
public void postShutdown(Runnable r) {
postShutdown.add(r);
}
public void onEnable() {
if (!isPaperServer()) {
getLogger().severe("Iris requires Paper or a Paper fork (Purpur, Pufferfish, ...). Spigot and CraftBukkit are not supported.");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
enable();
super.onEnable();
Bukkit.getPluginManager().registerEvents(this, this);
}
public void onDisable() {
if (IrisSafeguard.isForceShutdown()) return;
if (services == null || IrisSafeguard.isForceShutdown()) return;
services.values().forEach(IrisService::onDisable);
Bukkit.getScheduler().cancelTasks(this);
HandlerList.unregisterAll((Plugin) this);
@@ -564,6 +556,15 @@ public class Iris extends VolmitPlugin implements Listener {
J.attempt(new JarScanner(instance.getJarFile(), "", false)::scanAll);
}
private static boolean isPaperServer() {
try {
World.class.getMethod("getChunkAtAsync", int.class, int.class, boolean.class, boolean.class);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
private void setupPapi() {
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new IrisPapiExpansion().register();
@@ -59,7 +59,6 @@ import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.O;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import com.volmit.iris.util.scheduling.jobs.ParallelRadiusJob;
import io.papermc.lib.PaperLib;
import org.bukkit.*;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
@@ -689,7 +688,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Sending you to the studio world!");
var player = player();
PaperLib.teleportAsync(player(), Iris.service(StudioSVC.class)
player.teleportAsync(Iris.service(StudioSVC.class)
.getActiveProject()
.getActiveProvider()
.getTarget()
@@ -748,7 +747,6 @@ public class CommandStudio implements DecreeExecutor {
pw.println("Iris Version: " + Iris.instance.getDescription().getVersion());
pw.println("Bukkit Version: " + Bukkit.getBukkitVersion());
pw.println("MC Version: " + Bukkit.getVersion());
pw.println("PaperSpigot: " + (PaperLib.isPaper() ? "Yup!" : "Nope!"));
pw.println("Report Captured At: " + new Date());
pw.println("Chunks: (" + chunks.size() + "): ");
@@ -13,7 +13,6 @@ import com.volmit.iris.util.math.RollingSequence;
import com.volmit.iris.util.plugin.chunk.TicketHolder;
import com.volmit.iris.util.profile.LoadBalancer;
import com.volmit.iris.util.scheduling.J;
import io.papermc.lib.PaperLib;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
@@ -216,7 +215,7 @@ public class ChunkUpdater {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) {
if (!world.isChunkGenerated(x + dx, z + dz)) {
return false;
}
}
@@ -228,7 +227,7 @@ public class ChunkUpdater {
for (int dz = -1; dz <= 1; dz++) {
int xx = x + dx;
int zz = z + dz;
PaperLib.getChunkAtAsync(world, xx, zz, false, true)
world.getChunkAtAsync(xx, zz, false, true)
.thenAccept(chunk -> {
if (chunk == null || !chunk.isGenerated()) {
latch.countDown();
@@ -11,7 +11,6 @@ import com.volmit.iris.util.math.RollingSequence;
import com.volmit.iris.util.math.Spiraler;
import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import io.papermc.lib.PaperLib;
import lombok.Data;
import lombok.Getter;
import org.bukkit.Bukkit;
@@ -150,19 +149,11 @@ public class LazyPregenerator extends Thread implements Listener {
private void tickGenerate(Position2 chunk) {
executorService.submit(() -> {
CountDownLatch latch = new CountDownLatch(1);
if (PaperLib.isPaper()) {
PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true)
.thenAccept((i) -> {
Iris.verbose("Generated Async " + chunk);
latch.countDown();
});
} else {
J.s(() -> {
world.getChunkAt(chunk.getX(), chunk.getZ());
Iris.verbose("Generated " + chunk);
latch.countDown();
});
}
world.getChunkAtAsync(chunk.getX(), chunk.getZ(), true)
.thenAccept((i) -> {
Iris.verbose("Generated Async " + chunk);
latch.countDown();
});
try {
latch.await();
} catch (InterruptedException ignored) {}
@@ -17,7 +17,6 @@ import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import io.papermc.lib.PaperLib;
import lombok.Data;
import lombok.Getter;
import org.apache.logging.log4j.core.util.ExecutorServices;
@@ -227,7 +226,7 @@ public class TurboPregenerator extends Thread implements Listener {
private void tickGenerate(Position2 chunk) {
executorService.submit(() -> {
CountDownLatch latch = new CountDownLatch(1);
PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true)
world.getChunkAtAsync(chunk.getX(), chunk.getZ(), true)
.thenAccept((i) -> {
latch.countDown();
});
@@ -1,73 +0,0 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.core.pregenerator.methods;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.util.mantle.Mantle;
import io.papermc.lib.PaperLib;
import org.bukkit.World;
public class AsyncOrMedievalPregenMethod implements PregeneratorMethod {
private final PregeneratorMethod method;
public AsyncOrMedievalPregenMethod(World world, int threads) {
method = PaperLib.isPaper() ? new AsyncPregenMethod(world, threads) : new MedievalPregenMethod(world);
}
@Override
public void init() {
method.init();
}
@Override
public void close() {
method.close();
}
@Override
public void save() {
method.save();
}
@Override
public String getMethod(int x, int z) {
return method.getMethod(x, z);
}
@Override
public boolean supportsRegions(int x, int z, PregenListener listener) {
return false;
}
@Override
public void generateRegion(int x, int z, PregenListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void generateChunk(int x, int z, PregenListener listener) {
method.generateChunk(x, z, listener);
}
@Override
public Mantle getMantle() {
return method.getMantle();
}
}
@@ -28,7 +28,6 @@ import com.volmit.iris.util.mantle.Mantle;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.scheduling.J;
import io.papermc.lib.PaperLib;
import org.bukkit.Chunk;
import org.bukkit.World;
@@ -49,10 +48,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final Map<Chunk, Long> lastUse;
public AsyncPregenMethod(World world, int unusedThreads) {
if (!PaperLib.isPaper()) {
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
}
this.world = world;
this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor();
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
@@ -197,7 +192,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
public void generate(int x, int z, PregenListener listener) {
service.submit(() -> {
try {
PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> {
world.getChunkAtAsync(x, z, true, urgent).thenAccept((i) -> {
listener.onChunkGenerated(x, z);
listener.onChunkCleaned(x, z);
if (i == null) return;
@@ -222,7 +217,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private class TicketExecutor implements Executor {
@Override
public void generate(int x, int z, PregenListener listener) {
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
world.getChunkAtAsync(x, z, true, urgent)
.exceptionally(e -> {
Iris.reportError(e);
e.printStackTrace();
@@ -29,7 +29,7 @@ public class HybridPregenMethod implements PregeneratorMethod {
public HybridPregenMethod(World world, int threads) {
this.world = world;
inWorld = new AsyncOrMedievalPregenMethod(world, threads);
inWorld = new AsyncPregenMethod(world, threads);
}
@Override
@@ -1,138 +0,0 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.core.pregenerator.methods;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.mantle.Mantle;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.scheduling.J;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class MedievalPregenMethod implements PregeneratorMethod {
private final World world;
private final KList<CompletableFuture<?>> futures;
private final Map<Chunk, Long> lastUse;
public MedievalPregenMethod(World world) {
this.world = world;
futures = new KList<>();
this.lastUse = new KMap<>();
}
private void waitForChunks() {
for (CompletableFuture<?> i : futures) {
try {
i.get();
} catch (Throwable e) {
e.printStackTrace();
}
}
futures.clear();
}
private void unloadAndSaveAllChunks() {
try {
J.sfut(() -> {
if (world == null) {
Iris.warn("World was null somehow...");
return;
}
for (Chunk i : new ArrayList<>(lastUse.keySet())) {
Long lastUseTime = lastUse.get(i);
if (lastUseTime != null && M.ms() - lastUseTime >= 10) {
i.unload();
lastUse.remove(i);
}
}
world.save();
}).get();
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void init() {
unloadAndSaveAllChunks();
}
@Override
public void close() {
unloadAndSaveAllChunks();
}
@Override
public void save() {
unloadAndSaveAllChunks();
}
@Override
public boolean supportsRegions(int x, int z, PregenListener listener) {
return false;
}
@Override
public void generateRegion(int x, int z, PregenListener listener) {
throw new UnsupportedOperationException();
}
@Override
public String getMethod(int x, int z) {
return "Medieval";
}
@Override
public void generateChunk(int x, int z, PregenListener listener) {
if (futures.size() > IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())) {
waitForChunks();
}
listener.onChunkGenerating(x, z);
futures.add(J.sfut(() -> {
world.getChunkAt(x, z);
Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z);
lastUse.put(c, M.ms());
listener.onChunkGenerated(x, z);
listener.onChunkCleaned(x, z);
}));
}
@Override
public Mantle getMantle() {
if (IrisToolbelt.isIrisWorld(world)) {
return IrisToolbelt.access(world).getEngine().getMantle().getMantle();
}
return null;
}
}
@@ -44,7 +44,6 @@ import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Looper;
import com.volmit.iris.util.scheduling.jobs.QueueJob;
import io.papermc.lib.PaperLib;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Chunk;
@@ -525,7 +524,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}
futures.add(MultiBurst.burst.completeValue(()
-> PaperLib.getChunkAtAsync(to.getWorld(),
-> to.getWorld().getChunkAtAsync(
(to.getBlockX() >> 4) + finalI,
(to.getBlockZ() >> 4) + finalJ,
true, IrisSettings.get().getWorld().getAsyncTeleport().isUrgent()).get()));
@@ -65,7 +65,6 @@ import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import com.volmit.iris.util.stream.ProceduralStream;
import io.papermc.lib.PaperLib;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
@@ -545,8 +544,8 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
if (IrisLootEvent.callLootEvent(items, inv, world, x, y, z))
return;
if (PaperLib.isPaper() && getWorld().hasRealWorld()) {
PaperLib.getChunkAtAsync(getWorld().realWorld(), x >> 4, z >> 4).thenAccept((c) -> {
if (getWorld().hasRealWorld()) {
getWorld().realWorld().getChunkAtAsync(x >> 4, z >> 4).thenAccept((c) -> {
Runnable r = () -> {
for (ItemStack i : items) {
inv.addItem(i);
@@ -41,7 +41,6 @@ import com.volmit.iris.util.io.ReactiveFolder;
import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Looper;
import io.papermc.lib.PaperLib;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
@@ -142,7 +141,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
@Override
public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) {
Location location = new Location(world, 0, 64, 0);
PaperLib.getChunkAtAsync(location)
world.getChunkAtAsync(location)
.thenAccept(c -> {
World w = c.getWorld();
if (!w.getSpawnLocation().equals(location))
@@ -205,7 +204,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
this.world.bind(world);
getEngine().generate(x << 4, z << 4, tc, IrisSettings.get().getGenerator().useMulticore);
Chunk c = PaperLib.getChunkAtAsync(world, x, z)
Chunk c = world.getChunkAtAsync(x, z)
.thenApply(d -> {
Iris.tickets.addTicket(d);
@@ -10,21 +10,15 @@ import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.context.IrisContext;
import com.volmit.iris.util.json.JSONException;
import com.volmit.iris.util.reflect.ShadeFix;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.sentry.Attachments;
import com.volmit.iris.util.sentry.IrisLogger;
import com.volmit.iris.util.sentry.ServerID;
import io.sentry.Sentry;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SingleLineChart;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
@@ -113,21 +107,4 @@ public class Bindings {
plugin.postShutdown(metrics::shutdown);
});
}
public static class Adventure {
private final BukkitAudiences audiences;
public Adventure(Iris plugin) {
ShadeFix.fix(ComponentSerializer.class);
this.audiences = BukkitAudiences.create(plugin);
}
public Audience player(Player player) {
return audiences.player(player);
}
public Audience sender(CommandSender sender) {
return audiences.sender(sender);
}
}
}
@@ -18,6 +18,7 @@
package com.volmit.iris.util.plugin;
import net.kyori.adventure.text.Component;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
@@ -63,6 +64,12 @@ public class CommandDummy implements CommandSender {
return null;
}
@NotNull
@Override
public Component name() {
return Component.empty();
}
@NotNull
@Override
public Spigot spigot() {
@@ -42,6 +42,7 @@ import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.Set;
@@ -225,7 +226,7 @@ public class VolmitSender implements CommandSender {
}
public void sendTitle(String title, String subtitle, int i, int s, int o) {
Iris.audiences.player(player()).showTitle(Title.title(
player().showTitle(Title.title(
createComponent(title),
createComponent(subtitle),
Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o))));
@@ -247,15 +248,15 @@ public class VolmitSender implements CommandSender {
}
public void sendAction(String action) {
Iris.audiences.player(player()).sendActionBar(createNoPrefixComponent(action));
player().sendActionBar(createNoPrefixComponent(action));
}
public void sendActionNoProcessing(String action) {
Iris.audiences.player(player()).sendActionBar(createNoPrefixComponentNoProcessing(action));
player().sendActionBar(createNoPrefixComponentNoProcessing(action));
}
public void sendTitle(String subtitle, int i, int s, int o) {
Iris.audiences.player(player()).showTitle(Title.title(
player().showTitle(Title.title(
createNoPrefixComponent(" "),
createNoPrefixComponent(subtitle),
Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o))));
@@ -338,7 +339,7 @@ public class VolmitSender implements CommandSender {
}
try {
Iris.audiences.sender(s).sendMessage(createComponent(message));
s.sendMessage(createComponent(message));
} catch (Throwable e) {
String t = C.translateAlternateColorCodes('&', getTag() + message);
String a = C.aura(t, IrisSettings.get().getGeneral().getSpinh(), IrisSettings.get().getGeneral().getSpins(), IrisSettings.get().getGeneral().getSpinb());
@@ -368,7 +369,7 @@ public class VolmitSender implements CommandSender {
}
try {
Iris.audiences.sender(s).sendMessage(createComponentRaw(message));
s.sendMessage(createComponentRaw(message));
} catch (Throwable e) {
String t = C.translateAlternateColorCodes('&', getTag() + message);
String a = C.aura(t, IrisSettings.get().getGeneral().getSpinh(), IrisSettings.get().getGeneral().getSpins(), IrisSettings.get().getGeneral().getSpinb());
@@ -404,6 +405,11 @@ public class VolmitSender implements CommandSender {
return s.getName();
}
@Override
public @NotNull Component name() {
return s.name();
}
@Override
public Spigot spigot() {
return s.spigot();
@@ -1,25 +0,0 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.reflect;
public class ShadeFix {
public static void fix(Class<?> c) {
c.getCanonicalName();
}
}
@@ -50,9 +50,7 @@ private val software by task {
val supported = setOf(
"purpur",
"pufferfish",
"paper",
"spigot",
"bukkit"
"paper"
)
if (supported.any { server.name.contains(it, true) }) STABLE.withDiagnostics()