diff --git a/.gitignore b/.gitignore index 0b17ea5b10..f977e2edf7 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,9 @@ docs/ build/ .DS_Store +### Run Paper +run-folia/ +run/ # Ignore run folders run-[0-0].[0-9]/ run-[0-0].[0-9].[0-9]/ diff --git a/Bukkit/build.gradle.kts b/Bukkit/build.gradle.kts index 33d2335144..dc623d39a5 100644 --- a/Bukkit/build.gradle.kts +++ b/Bukkit/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { implementation("org.bstats:bstats-bukkit") // Paper - compileOnly("io.papermc.paper:paper-api") + compileOnly("dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT") implementation("io.papermc:paperlib") // Plugins diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java index 2b05b97429..7c33eb3e1a 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java @@ -18,57 +18,28 @@ */ package com.plotsquared.bukkit; -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import com.google.inject.Key; -import com.google.inject.Singleton; -import com.google.inject.Stage; -import com.google.inject.TypeLiteral; +import com.google.inject.*; import com.plotsquared.bukkit.generator.BukkitPlotGenerator; import com.plotsquared.bukkit.inject.BackupModule; import com.plotsquared.bukkit.inject.BukkitModule; import com.plotsquared.bukkit.inject.PermissionModule; import com.plotsquared.bukkit.inject.WorldManagerModule; -import com.plotsquared.bukkit.listener.BlockEventListener; -import com.plotsquared.bukkit.listener.BlockEventListener117; -import com.plotsquared.bukkit.listener.ChunkListener; -import com.plotsquared.bukkit.listener.EntityEventListener; -import com.plotsquared.bukkit.listener.EntitySpawnListener; -import com.plotsquared.bukkit.listener.PaperListener; -import com.plotsquared.bukkit.listener.PlayerEventListener; -import com.plotsquared.bukkit.listener.ProjectileEventListener; -import com.plotsquared.bukkit.listener.ServerListener; -import com.plotsquared.bukkit.listener.SingleWorldListener; -import com.plotsquared.bukkit.listener.SpigotListener; -import com.plotsquared.bukkit.listener.WorldEvents; +import com.plotsquared.bukkit.listener.*; import com.plotsquared.bukkit.placeholder.PAPIPlaceholders; import com.plotsquared.bukkit.placeholder.PlaceholderFormatter; import com.plotsquared.bukkit.player.BukkitPlayer; import com.plotsquared.bukkit.player.BukkitPlayerManager; -import com.plotsquared.bukkit.util.BukkitUtil; -import com.plotsquared.bukkit.util.BukkitWorld; -import com.plotsquared.bukkit.util.SetGenCB; -import com.plotsquared.bukkit.util.TranslationUpdateManager; -import com.plotsquared.bukkit.util.UpdateUtility; +import com.plotsquared.bukkit.util.*; import com.plotsquared.bukkit.util.task.BukkitTaskManager; +import com.plotsquared.bukkit.util.task.FoliaTaskManager; import com.plotsquared.bukkit.util.task.PaperTimeConverter; import com.plotsquared.bukkit.util.task.SpigotTimeConverter; -import com.plotsquared.bukkit.uuid.EssentialsUUIDService; -import com.plotsquared.bukkit.uuid.LuckPermsUUIDService; -import com.plotsquared.bukkit.uuid.OfflinePlayerUUIDService; -import com.plotsquared.bukkit.uuid.PaperUUIDService; -import com.plotsquared.bukkit.uuid.SQLiteUUIDService; -import com.plotsquared.bukkit.uuid.SquirrelIdUUIDService; +import com.plotsquared.bukkit.uuid.*; import com.plotsquared.core.PlotPlatform; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.backup.BackupManager; import com.plotsquared.core.components.ComponentPresetManager; -import com.plotsquared.core.configuration.ConfigurationNode; -import com.plotsquared.core.configuration.ConfigurationSection; -import com.plotsquared.core.configuration.ConfigurationUtil; -import com.plotsquared.core.configuration.Settings; -import com.plotsquared.core.configuration.Storage; +import com.plotsquared.core.configuration.*; import com.plotsquared.core.configuration.caption.ChatFormatter; import com.plotsquared.core.configuration.file.YamlConfiguration; import com.plotsquared.core.database.DBFunc; @@ -77,20 +48,12 @@ import com.plotsquared.core.generator.GeneratorWrapper; import com.plotsquared.core.generator.IndependentPlotGenerator; import com.plotsquared.core.generator.SingleWorldGenerator; -import com.plotsquared.core.inject.annotations.BackgroundPipeline; -import com.plotsquared.core.inject.annotations.DefaultGenerator; -import com.plotsquared.core.inject.annotations.ImpromptuPipeline; -import com.plotsquared.core.inject.annotations.WorldConfig; -import com.plotsquared.core.inject.annotations.WorldFile; +import com.plotsquared.core.inject.annotations.*; import com.plotsquared.core.inject.modules.PlotSquaredModule; import com.plotsquared.core.listener.PlotListener; import com.plotsquared.core.listener.WESubscriber; import com.plotsquared.core.player.PlotPlayer; -import com.plotsquared.core.plot.Plot; -import com.plotsquared.core.plot.PlotArea; -import com.plotsquared.core.plot.PlotAreaTerrainType; -import com.plotsquared.core.plot.PlotAreaType; -import com.plotsquared.core.plot.PlotId; +import com.plotsquared.core.plot.*; import com.plotsquared.core.plot.comment.CommentManager; import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; import com.plotsquared.core.plot.world.PlotAreaManager; @@ -98,14 +61,7 @@ import com.plotsquared.core.plot.world.SinglePlotAreaManager; import com.plotsquared.core.setup.PlotAreaBuilder; import com.plotsquared.core.setup.SettingsNodesWrapper; -import com.plotsquared.core.util.EventDispatcher; -import com.plotsquared.core.util.FileUtils; -import com.plotsquared.core.util.PlatformWorldManager; -import com.plotsquared.core.util.PlayerManager; -import com.plotsquared.core.util.PremiumVerification; -import com.plotsquared.core.util.ReflectionUtils; -import com.plotsquared.core.util.SetupUtils; -import com.plotsquared.core.util.WorldUtil; +import com.plotsquared.core.util.*; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; import com.plotsquared.core.uuid.CacheUUIDService; @@ -143,27 +99,13 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import static com.plotsquared.core.util.PremiumVerification.getDownloadID; -import static com.plotsquared.core.util.PremiumVerification.getResourceID; -import static com.plotsquared.core.util.PremiumVerification.getUserID; +import static com.plotsquared.core.util.PremiumVerification.*; import static com.plotsquared.core.util.ReflectionUtils.getRefClass; @SuppressWarnings("unused") @@ -264,7 +206,12 @@ public void onEnable() { // Stuff that needs to be created before the PlotSquared instance PlotPlayer.registerConverter(Player.class, BukkitUtil::adapt); - TaskManager.setPlatformImplementation(new BukkitTaskManager(this, timeConverter)); + if (FoliaSupport.isFolia()) { + TaskManager.setPlatformImplementation(new FoliaTaskManager(this, timeConverter)); + } else { + TaskManager.setPlatformImplementation(new BukkitTaskManager(this, timeConverter)); + } + final PlotSquared plotSquared = new PlotSquared(this, "Bukkit"); @@ -558,9 +505,7 @@ public void onEnable() { e.printStackTrace(); } } - - // Clean up potential memory leak - Bukkit.getScheduler().runTaskTimer(this, () -> { + Runnable cleanUp = () -> { try { for (final PlotPlayer player : this.playerManager().getPlayers()) { if (player.getPlatformPlayer() == null || !player.getPlatformPlayer().isOnline()) { @@ -570,7 +515,15 @@ public void onEnable() { } catch (final Exception e) { getLogger().warning("Failed to clean up players: " + e.getMessage()); } - }, 100L, 100L); + }; + if (FoliaSupport.isFolia()) { + Bukkit.getGlobalRegionScheduler().runAtFixedRate(this, scheduledTask -> { + cleanUp.run(); + }, 100, 100); + } else { + // Clean up potential memory leak + Bukkit.getScheduler().runTaskTimer(this, cleanUp, 100L, 100L); + } // Check if we are in a safe environment ServerLib.checkUnsafeForks(); @@ -728,7 +681,12 @@ private void startUuidCaching( @Override public void onDisable() { PlotSquared.get().disable(); - Bukkit.getScheduler().cancelTasks(this); + if (FoliaSupport.isFolia()) { + Bukkit.getAsyncScheduler().cancelTasks(this); + Bukkit.getGlobalRegionScheduler().cancelTasks(this); + } else { + Bukkit.getScheduler().cancelTasks(this); + } } @Override diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java index 767bcd3751..05ad8f6335 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java @@ -436,6 +436,9 @@ private final class BukkitPlotBiomeProvider extends BiomeProvider { static { ArrayList biomes = new ArrayList<>(List.of(Biome.values())); biomes.remove(Biome.CUSTOM); + if (PlotSquared.platform().serverVersion()[1] <= 19) { + biomes.remove(Biome.CHERRY_GROVE); + } BIOMES = List.copyOf(biomes); } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/managers/BukkitWorldManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/managers/BukkitWorldManager.java index 7397d7b5c4..3e8f032160 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/managers/BukkitWorldManager.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/managers/BukkitWorldManager.java @@ -19,6 +19,7 @@ package com.plotsquared.bukkit.managers; import com.google.inject.Singleton; +import com.plotsquared.bukkit.util.FoliaSupport; import com.plotsquared.core.configuration.file.YamlConfiguration; import com.plotsquared.core.util.PlatformWorldManager; import org.bukkit.Bukkit; @@ -48,13 +49,16 @@ public void initialize() { @Override public @Nullable World handleWorldCreation(@NonNull String worldName, @Nullable String generator) { this.setGenerator(worldName, generator); - final WorldCreator wc = new WorldCreator(worldName); - wc.environment(World.Environment.NORMAL); - if (generator != null) { - wc.generator(generator); - wc.type(WorldType.FLAT); + if (!FoliaSupport.isFolia()) { + final WorldCreator wc = new WorldCreator(worldName); + wc.environment(World.Environment.NORMAL); + if (generator != null) { + wc.generator(generator); + wc.type(WorldType.FLAT); + } + return Bukkit.createWorld(wc); } - return Bukkit.createWorld(wc); + return null; } protected void setGenerator(final @Nullable String worldName, final @Nullable String generator) { diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitChunkCoordinator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitChunkCoordinator.java index fc3ce73703..d0a2bcd2dc 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitChunkCoordinator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitChunkCoordinator.java @@ -21,6 +21,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.plotsquared.bukkit.BukkitPlatform; +import com.plotsquared.bukkit.util.FoliaSupport; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.queue.ChunkCoordinator; import com.plotsquared.core.queue.subscriber.ProgressSubscriber; @@ -160,6 +161,7 @@ public void run() { } Chunk chunk = this.availableChunks.poll(); + if (chunk == null) { if (this.availableChunks.isEmpty()) { if (this.requestedChunks.isEmpty() && loadingChunks.get() == 0) { @@ -175,7 +177,20 @@ public void run() { do { final long start = System.currentTimeMillis(); try { - this.chunkConsumer.accept(BlockVector2.at(chunk.getX(), chunk.getZ())); + if (FoliaSupport.isFolia()) { + var tempChunk = chunk; + Bukkit.getRegionScheduler().run( + this.plugin, + this.bukkitWorld, + chunk.getX(), + chunk.getZ(), + scheduledTask -> { + this.chunkConsumer.accept(BlockVector2.at(tempChunk.getX(), tempChunk.getZ())); + } + ); + } else { + this.chunkConsumer.accept(BlockVector2.at(chunk.getX(), chunk.getZ())); + } } catch (final Throwable throwable) { this.throwableConsumer.accept(throwable); } @@ -188,6 +203,8 @@ public void run() { iterationTime[0] = iterationTime[1]; iterationTime[1] = end - start; } while (iterationTime[0] + iterationTime[1] < this.maxIterationTime * 2 && (chunk = availableChunks.poll()) != null); + + if (processedChunks < this.batchSize) { // Adjust batch size based on the amount of processed chunks per tick this.batchSize = processedChunks; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/FoliaSupport.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/FoliaSupport.java new file mode 100644 index 0000000000..fc9d019f7d --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/FoliaSupport.java @@ -0,0 +1,39 @@ +package com.plotsquared.bukkit.util; + +public final class FoliaSupport { + private FoliaSupport() { + + } + + private static final boolean IS_FOLIA; + + private static final Class TICK_THREAD_CLASS; + static { + boolean isFolia = false; + try { + // Assume API is present + Class.forName("io.papermc.paper.threadedregions.scheduler.EntityScheduler"); + isFolia = true; + } catch (Exception ignored) { + + } + IS_FOLIA = isFolia; + Class tickThreadClass = String.class; // thread will never be instance of String + if (IS_FOLIA) { + try { + tickThreadClass = Class.forName("io.papermc.paper.util.TickThread"); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + TICK_THREAD_CLASS = tickThreadClass; + } + + public static boolean isFolia() { + return IS_FOLIA; + } + + public static boolean isTickThread() { + return TICK_THREAD_CLASS.isInstance(Thread.currentThread()); + } +} diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaPlotSquaredTask.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaPlotSquaredTask.java new file mode 100644 index 0000000000..6a44df8278 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaPlotSquaredTask.java @@ -0,0 +1,61 @@ +package com.plotsquared.bukkit.util.task; + +import com.plotsquared.core.util.task.PlotSquaredTask; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +public final class FoliaPlotSquaredTask implements PlotSquaredTask { + + @NonNull + private final Runnable runnable; + @NotNull + private final JavaPlugin javaPlugin; + + private ScheduledTask task; + + public FoliaPlotSquaredTask(final @NonNull Runnable runnable, final JavaPlugin javaPlugin) { + this.runnable = runnable; + this.javaPlugin = javaPlugin; + } + + @Override + public void runTask() { + this.task = Bukkit.getGlobalRegionScheduler().run(javaPlugin, scheduledTask -> this.runnable.run()); + } + + public void runTaskLater(long delay) { + this.task = Bukkit.getGlobalRegionScheduler().runDelayed(javaPlugin, scheduledTask -> this.runnable.run(), delay); + } + + public void runTaskLaterAsynchronously(long delay) { + this.task = Bukkit.getAsyncScheduler().runDelayed(javaPlugin, scheduledTask -> this.runnable.run(), delay, TimeUnit.MILLISECONDS); + } + + public void runTaskAsynchronously() { + this.task = Bukkit.getAsyncScheduler().runNow(javaPlugin, scheduledTask -> this.runnable.run()); + } + + public void runTaskTimerAsynchronously(long delay, long period) { + this.task = Bukkit.getAsyncScheduler().runAtFixedRate(javaPlugin, scheduledTask -> this.runnable.run(), delay, period, TimeUnit.MILLISECONDS); + } + + public void runTaskTimer(long delay, long period) { + this.task = Bukkit.getGlobalRegionScheduler().runAtFixedRate(javaPlugin, scheduledTask -> this.runnable.run(), delay, period); + } + + @Override + public boolean isCancelled() { + return this.task.isCancelled(); + } + + @Override + public void cancel() { + this.task.cancel(); + } + +} diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaTaskManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaTaskManager.java new file mode 100644 index 0000000000..77a61c76c6 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/task/FoliaTaskManager.java @@ -0,0 +1,111 @@ +package com.plotsquared.bukkit.util.task; + +import com.google.inject.Singleton; +import com.plotsquared.bukkit.BukkitPlatform; +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.util.task.PlotSquaredTask; +import com.plotsquared.core.util.task.TaskManager; +import com.plotsquared.core.util.task.TaskTime; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.util.Location; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static java.lang.invoke.MethodHandles.dropReturn; +import static java.lang.invoke.MethodHandles.explicitCastArguments; +import static java.lang.invoke.MethodHandles.filterArguments; +import static java.lang.invoke.MethodHandles.insertArguments; +import static java.lang.invoke.MethodType.methodType; + +/** + * Bukkit implementation of {@link TaskManager} using + * by {@link org.bukkit.scheduler.BukkitScheduler} and {@link BukkitPlotSquaredTask} + */ +@Singleton +public class FoliaTaskManager extends TaskManager { + + private final BukkitPlatform bukkitMain; + private final TaskTime.TimeConverter timeConverter; + + private final ScheduledExecutorService backgroundExecutor = Executors.newSingleThreadScheduledExecutor(); + + public FoliaTaskManager(final BukkitPlatform bukkitMain, final TaskTime.TimeConverter timeConverter) { + this.bukkitMain = bukkitMain; + this.timeConverter = timeConverter; + } + + @Override + public T sync(final @NonNull Callable function, final int timeout) throws Exception { + if (PlotSquared.get().isMainThread(Thread.currentThread())) { + return function.call(); + } + return this.callMethodSync(function).get(timeout, TimeUnit.MILLISECONDS); + } + + @Override + public Future callMethodSync(final @NonNull Callable method) { + return backgroundExecutor.submit(method); + } + + @Override + public PlotSquaredTask taskRepeat(@NonNull final Runnable runnable, @NonNull final TaskTime taskTime) { + final long ticks = this.timeConverter.toTicks(taskTime); + final FoliaPlotSquaredTask foliaPlotSquaredTask = new FoliaPlotSquaredTask(runnable, this.bukkitMain); + foliaPlotSquaredTask.runTaskTimer(ticks, ticks); + return foliaPlotSquaredTask; + } + + @Override + public PlotSquaredTask taskRepeatAsync(@NonNull final Runnable runnable, @NonNull final TaskTime taskTime) { + var time = switch (taskTime.getUnit()) { + case TICKS -> timeConverter.ticksToMs(taskTime.getTime()); + case MILLISECONDS -> taskTime.getTime(); + }; + final FoliaPlotSquaredTask foliaPlotSquaredTask = new FoliaPlotSquaredTask(runnable, this.bukkitMain); + foliaPlotSquaredTask.runTaskTimerAsynchronously(time, time); + return foliaPlotSquaredTask; + } + + @Override + public void taskAsync(@NonNull final Runnable runnable) { + if (this.bukkitMain.isEnabled()) { + new FoliaPlotSquaredTask(runnable, this.bukkitMain).runTaskAsynchronously(); + } else { + runnable.run(); + } + + } + + @Override + public void task(@NonNull final Runnable runnable) { + new FoliaPlotSquaredTask(runnable, this.bukkitMain).runTask(); + } + + @Override + public void taskLater(@NonNull final Runnable runnable, @NonNull final TaskTime taskTime) { + new FoliaPlotSquaredTask(runnable, this.bukkitMain).runTaskLater(this.timeConverter.toTicks(taskTime)); + } + + @Override + public void taskLaterAsync(@NonNull final Runnable runnable, @NonNull final TaskTime taskTime) { + var time = switch (taskTime.getUnit()) { + case TICKS -> timeConverter.ticksToMs(taskTime.getTime()); + case MILLISECONDS -> taskTime.getTime(); + }; + new FoliaPlotSquaredTask(runnable, this.bukkitMain).runTaskLaterAsynchronously(time); + } + +} diff --git a/Bukkit/src/main/resources/plugin.yml b/Bukkit/src/main/resources/plugin.yml index 10a9ae0192..a59406a694 100644 --- a/Bukkit/src/main/resources/plugin.yml +++ b/Bukkit/src/main/resources/plugin.yml @@ -420,3 +420,4 @@ permissions: plots.done: true plots.continue: true plots.middle: true +folia-supported: true diff --git a/build.gradle.kts b/build.gradle.kts index b7963f49af..7992da98a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -234,4 +234,12 @@ tasks { runDirectory.set(file("run-$it")) } } + register("runFolia") { + downloadsApiService.set(xyz.jpenilla.runtask.service.DownloadsAPIService.folia(project)) + minecraftVersion("1.19.4") + group = "run paper" + runDirectory.set(file("run-folia")) + pluginJars(*project(":PlotSquared-Bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile } + .toTypedArray()) + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index c35882d558..f7db267686 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,3 +6,12 @@ project(":Core").name = "PlotSquared-Core" project(":Bukkit").name = "PlotSquared-Bukkit" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = "jmp repository" + url = uri("https://repo.jpenilla.xyz/snapshots") + } + } +}