Compare commits

..

19 Commits

Author SHA1 Message Date
9127e472b8 Fix IndexOutOfBoundsException on BlockFertilizeEvent 2025-02-24 16:59:23 +01:00
a0a3d8828a Back to snapshot for development 2025-02-23 20:57:47 +00:00
8741bfcf88 Release 7.5.1 2025-02-23 20:46:30 +00:00
6a6c113e5b fix: use orTimeout instead of completing null in chunk coordinator 2025-02-23 20:25:17 +00:00
3f573b4d46 Update fawe to v2.13.0 (#4607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-23 14:35:55 +00:00
2f6db9c3db Back to snapshot for development
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-02-23 11:27:30 +01:00
2f050b7b47 Release 7.5.0
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-02-23 11:25:22 +01:00
eb0d854870 fix: allow queues to not generate chunks (#4599)
- initially apply to regenallroads
 - additionally add a 10s timeout for requesting a chunk before resubmitting it to the queue
 - addresses #4310
2025-02-23 11:21:18 +01:00
d4f10422e3 1.21.4 (#4582)
* chore: bump api to 1.21.4

* fix: replace (removed) constants with backwards compatible alternatives

* chore: cleanup import

* chore: update javadoc link for paper

* chore: i like this more

* fix: check for vehicle

* chore: compile against 1.20.4 again

* chore: add 1.21.4 to bug report issue template

* chore: add 1.21.4 to runServer supported versions array

* chore: update entity type enum name switch

* chore: remove 1.18 from supportedVersions for runServer task

* fix: attempt to update ReplicatingEntityWrapper

* fix/chore: missing boat handling on spawn

* chore: cleanup imports
2025-02-23 11:20:33 +01:00
4e2ea67992 fix: update error message of /p remove <player> if player does not need to be removed (#4592) 2025-02-22 19:25:49 +00:00
f533e194f4 Update junit5 monorepo (#4598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 14:18:09 +00:00
98bc2e7407 Update adventure to v4.19.0 (#4597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 03:18:04 +00:00
1b9d0d5317 Improve teleport behavior for home command (#4369) 2025-02-15 15:29:21 +00:00
8bb15d5c65 Back to snapshot for development
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-02-12 21:15:51 +01:00
e9baa802ec Release 7.4.2
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-02-12 21:09:27 +01:00
63e2d325cf Update dependency com.github.spotbugs:spotbugs-annotations to v4.9.1 (#4591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 21:50:50 +00:00
94abd69e22 Update dependency org.checkerframework:checker-qual to v3.49.0 (#4587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 22:23:37 +00:00
661e4ae8d3 Update dependency com.gradleup.shadow to v8.3.6 (#4585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-02 21:38:30 +00:00
974c639a51 fix: grow music inventory dynamically (#4583) 2025-01-26 12:51:54 +01:00
16 changed files with 124 additions and 33 deletions

View File

@ -134,7 +134,7 @@ public class BlockEventListener117 implements Listener {
public void onBlockFertilize(BlockFertilizeEvent event) { public void onBlockFertilize(BlockFertilizeEvent event) {
Block block = event.getBlock(); Block block = event.getBlock();
List<org.bukkit.block.BlockState> blocks = event.getBlocks(); List<org.bukkit.block.BlockState> blocks = event.getBlocks();
Location location = BukkitUtil.adapt(blocks.get(0).getLocation()); Location location = BukkitUtil.adapt(block.getLocation());
PlotArea area = location.getPlotArea(); PlotArea area = location.getPlotArea();
if (area == null) { if (area == null) {

View File

@ -30,6 +30,8 @@ import com.plotsquared.core.util.task.TaskTime;
import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import io.papermc.lib.PaperLib; import io.papermc.lib.PaperLib;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -41,6 +43,8 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -55,6 +59,8 @@ import java.util.function.Consumer;
**/ **/
public final class BukkitChunkCoordinator extends ChunkCoordinator { public final class BukkitChunkCoordinator extends ChunkCoordinator {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BukkitChunkCoordinator.class.getSimpleName());
private final List<ProgressSubscriber> progressSubscribers = new LinkedList<>(); private final List<ProgressSubscriber> progressSubscribers = new LinkedList<>();
private final Queue<BlockVector2> requestedChunks; private final Queue<BlockVector2> requestedChunks;
@ -70,6 +76,7 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator {
private final AtomicInteger expectedSize; private final AtomicInteger expectedSize;
private final AtomicInteger loadingChunks = new AtomicInteger(); private final AtomicInteger loadingChunks = new AtomicInteger();
private final boolean forceSync; private final boolean forceSync;
private final boolean shouldGen;
private int batchSize; private int batchSize;
private PlotSquaredTask task; private PlotSquaredTask task;
@ -87,7 +94,8 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator {
@Assisted final @NonNull Consumer<Throwable> throwableConsumer, @Assisted final @NonNull Consumer<Throwable> throwableConsumer,
@Assisted("unloadAfter") final boolean unloadAfter, @Assisted("unloadAfter") final boolean unloadAfter,
@Assisted final @NonNull Collection<ProgressSubscriber> progressSubscribers, @Assisted final @NonNull Collection<ProgressSubscriber> progressSubscribers,
@Assisted("forceSync") final boolean forceSync @Assisted("forceSync") final boolean forceSync,
@Assisted("shouldGen") final boolean shouldGen
) { ) {
this.requestedChunks = new LinkedBlockingQueue<>(requestedChunks); this.requestedChunks = new LinkedBlockingQueue<>(requestedChunks);
this.availableChunks = new LinkedBlockingQueue<>(); this.availableChunks = new LinkedBlockingQueue<>();
@ -103,6 +111,7 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator {
this.bukkitWorld = Bukkit.getWorld(world.getName()); this.bukkitWorld = Bukkit.getWorld(world.getName());
this.progressSubscribers.addAll(progressSubscribers); this.progressSubscribers.addAll(progressSubscribers);
this.forceSync = forceSync; this.forceSync = forceSync;
this.shouldGen = shouldGen;
} }
@Override @Override
@ -212,18 +221,28 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator {
* Requests a batch of chunks to be loaded * Requests a batch of chunks to be loaded
*/ */
private void requestBatch() { private void requestBatch() {
BlockVector2 chunk; for (int i = 0; i < this.batchSize && this.requestedChunks.peek() != null; i++) {
for (int i = 0; i < this.batchSize && (chunk = this.requestedChunks.poll()) != null; i++) {
// This required PaperLib to be bumped to version 1.0.4 to mark the request as urgent // This required PaperLib to be bumped to version 1.0.4 to mark the request as urgent
final BlockVector2 chunk = this.requestedChunks.poll();
loadingChunks.incrementAndGet(); loadingChunks.incrementAndGet();
PaperLib PaperLib
.getChunkAtAsync(this.bukkitWorld, chunk.getX(), chunk.getZ(), true, true) .getChunkAtAsync(this.bukkitWorld, chunk.getX(), chunk.getZ(), shouldGen, true)
.orTimeout(10L, TimeUnit.SECONDS)
.whenComplete((chunkObject, throwable) -> { .whenComplete((chunkObject, throwable) -> {
loadingChunks.decrementAndGet(); loadingChunks.decrementAndGet();
if (throwable != null) { if (throwable != null) {
throwable.printStackTrace(); if (throwable instanceof TimeoutException) {
// We want one less because this couldn't be processed LOGGER.warn("Timed out awaiting chunk load {}", chunk);
this.expectedSize.decrementAndGet(); this.requestedChunks.offer(chunk);
} else {
LOGGER.error("Failed to load chunk {}", chunk, throwable);
// We want one less because this couldn't be processed
this.expectedSize.decrementAndGet();
}
} else if (chunkObject == null) {
if (shouldGen) {
LOGGER.error("Null chunk returned for chunk at {}", chunk);
}
} else if (PlotSquared.get().isMainThread(Thread.currentThread())) { } else if (PlotSquared.get().isMainThread(Thread.currentThread())) {
this.processChunk(chunkObject); this.processChunk(chunkObject);
} else { } else {

View File

@ -238,6 +238,7 @@ public class BukkitQueueCoordinator extends BasicQueueCoordinator {
.unloadAfter(isUnloadAfter()) .unloadAfter(isUnloadAfter())
.withProgressSubscribers(getProgressSubscribers()) .withProgressSubscribers(getProgressSubscribers())
.forceSync(isForceSync()) .forceSync(isForceSync())
.shouldGen(isShouldGen())
.build(); .build();
return super.enqueue(); return super.enqueue();
} }

View File

@ -262,6 +262,11 @@ public class BukkitUtil extends WorldUtil {
}); });
} }
@Override
public boolean isSmallBlock(Location location) {
return adapt(location).getBlock().getBoundingBox().getHeight() < 0.25;
}
@Override @Override
@NonNegative @NonNegative
public int getHighestBlockSynchronous(final @NonNull String world, final int x, final int z) { public int getHighestBlockSynchronous(final @NonNull String world, final int x, final int z) {

View File

@ -60,6 +60,9 @@ public class Music extends SubCommand {
"music_disc_creator_music_box", "music_disc_precipice" "music_disc_creator_music_box", "music_disc_precipice"
); );
// make sure all discs and the bedrock ("cancel") fit into the inventory
private static final int INVENTORY_ROWS = (int) Math.ceil((DISCS.size() + 1) / 9.0);
private final InventoryUtil inventoryUtil; private final InventoryUtil inventoryUtil;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
@ -94,7 +97,7 @@ public class Music extends SubCommand {
PlotInventory inv = new PlotInventory( PlotInventory inv = new PlotInventory(
this.inventoryUtil, this.inventoryUtil,
player, player,
2, INVENTORY_ROWS,
TranslatableCaption.of("plotjukebox.jukebox_header").getComponent(player) TranslatableCaption.of("plotjukebox.jukebox_header").getComponent(player)
) { ) {
@Override @Override

View File

@ -117,8 +117,8 @@ public class Remove extends SubCommand {
} }
if (count == 0) { if (count == 0) {
player.sendMessage( player.sendMessage(
TranslatableCaption.of("errors.invalid_player"), TranslatableCaption.of("member.player_not_removed"),
TagResolver.resolver("value", Tag.inserting(Component.text(args[0]))) TagResolver.resolver("player", Tag.inserting(Component.text(args[0])))
); );
} else { } else {
player.sendMessage( player.sendMessage(

View File

@ -582,6 +582,8 @@ public class Settings extends Config {
public static boolean PER_WORLD_VISIT = false; public static boolean PER_WORLD_VISIT = false;
@Comment("Search merged plots for having multiple owners when using the visit command") @Comment("Search merged plots for having multiple owners when using the visit command")
public static boolean VISIT_MERGED_OWNERS = true; public static boolean VISIT_MERGED_OWNERS = true;
@Comment("Allows to teleport based on block size instead to spawn on the highest block at the home command")
public static boolean SIZED_BASED = true;
} }

View File

@ -432,6 +432,7 @@ public class HybridUtils {
if (!UPDATE) { if (!UPDATE) {
Iterator<BlockVector2> iter = chunks.iterator(); Iterator<BlockVector2> iter = chunks.iterator();
QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
queue.setShouldGen(false);
while (iter.hasNext()) { while (iter.hasNext()) {
BlockVector2 chunk = iter.next(); BlockVector2 chunk = iter.next();
iter.remove(); iter.remove();
@ -474,6 +475,7 @@ public class HybridUtils {
Iterator<BlockVector2> iterator = chunks.iterator(); Iterator<BlockVector2> iterator = chunks.iterator();
if (chunks.size() >= 32) { if (chunks.size() >= 32) {
QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
queue.setShouldGen(false);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
final BlockVector2 chunk = iterator.next(); final BlockVector2 chunk = iterator.next();
iterator.remove(); iterator.remove();
@ -487,6 +489,7 @@ public class HybridUtils {
return null; return null;
} }
QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
queue.setShouldGen(false);
while (!chunks.isEmpty()) { while (!chunks.isEmpty()) {
final BlockVector2 chunk = iterator.next(); final BlockVector2 chunk = iterator.next();
iterator.remove(); iterator.remove();
@ -502,7 +505,6 @@ public class HybridUtils {
return; return;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
Iterator<BlockVector2> iterator = HybridUtils.regions.iterator(); Iterator<BlockVector2> iterator = HybridUtils.regions.iterator();
BlockVector2 loc = iterator.next(); BlockVector2 loc = iterator.next();
iterator.remove(); iterator.remove();
@ -510,7 +512,8 @@ public class HybridUtils {
"Error! Could not update '{}/region/r.{}.{}.mca' (Corrupt chunk?)", "Error! Could not update '{}/region/r.{}.{}.mca' (Corrupt chunk?)",
area.getWorldHash(), area.getWorldHash(),
loc.getX(), loc.getX(),
loc.getZ() loc.getZ(),
e
); );
} }
TaskManager.runTaskLater(task, TaskTime.seconds(1L)); TaskManager.runTaskLater(task, TaskTime.seconds(1L));
@ -558,7 +561,7 @@ public class HybridUtils {
try { try {
plotworld.setupSchematics(); plotworld.setupSchematics();
} catch (SchematicHandler.UnsupportedFormatException e) { } catch (SchematicHandler.UnsupportedFormatException e) {
e.printStackTrace(); LOGGER.error(e);
} }
}); });
}); });

View File

@ -40,7 +40,8 @@ public interface ChunkCoordinatorFactory {
final @NonNull Consumer<Throwable> throwableConsumer, final @NonNull Consumer<Throwable> throwableConsumer,
@Assisted("unloadAfter") final boolean unloadAfter, @Assisted("unloadAfter") final boolean unloadAfter,
final @NonNull Collection<ProgressSubscriber> progressSubscribers, final @NonNull Collection<ProgressSubscriber> progressSubscribers,
@Assisted("forceSync") final boolean forceSync @Assisted("forceSync") final boolean forceSync,
@Assisted("shouldGen") final boolean shouldGen
); );
} }

View File

@ -1407,6 +1407,9 @@ public class Plot {
); );
} }
Location location = toHomeLocation(bottom, home); Location location = toHomeLocation(bottom, home);
if (Settings.Teleport.SIZED_BASED && this.worldUtil.isSmallBlock(location) && this.worldUtil.isSmallBlock(location.add(0,1,0))) {
return location;
}
if (!this.worldUtil.getBlockSynchronous(location).getBlockType().getMaterial().isAir()) { if (!this.worldUtil.getBlockSynchronous(location).getBlockType().getMaterial().isAir()) {
location = location.withY( location = location.withY(
Math.max(1 + this.worldUtil.getHighestBlockSynchronous( Math.max(1 + this.worldUtil.getHighestBlockSynchronous(
@ -1440,15 +1443,21 @@ public class Plot {
} }
Location bottom = this.getBottomAbs(); Location bottom = this.getBottomAbs();
Location location = toHomeLocation(bottom, home); Location location = toHomeLocation(bottom, home);
this.worldUtil.getBlock(location, block -> { if (Settings.Teleport.SIZED_BASED && this.worldUtil.isSmallBlock(location) && this.worldUtil.isSmallBlock(location.add(0,1,0))) {
if (!block.getBlockType().getMaterial().isAir()) { result.accept(location);
this.worldUtil.getHighestBlock(this.getWorldName(), location.getX(), location.getZ(), } else {
y -> result.accept(location.withY(Math.max(1 + y, bottom.getY()))) this.worldUtil.getBlock(location, block -> {
);
} else { if (!block.getBlockType().getMaterial().isAir()) {
result.accept(location); this.worldUtil.getHighestBlock(this.getWorldName(), location.getX(), location.getZ(),
} y -> result.accept(location.withY(Math.max(1 + y, bottom.getY())))
}); );
} else {
result.accept(location);
}
});
}
} }
} }

View File

@ -52,6 +52,7 @@ public class ChunkCoordinatorBuilder {
private int initialBatchSize = Settings.QUEUE.INITIAL_BATCH_SIZE; private int initialBatchSize = Settings.QUEUE.INITIAL_BATCH_SIZE;
private boolean unloadAfter = true; private boolean unloadAfter = true;
private boolean forceSync = false; private boolean forceSync = false;
private boolean shouldGen = true;
@Inject @Inject
public ChunkCoordinatorBuilder(@NonNull ChunkCoordinatorFactory chunkCoordinatorFactory) { public ChunkCoordinatorBuilder(@NonNull ChunkCoordinatorFactory chunkCoordinatorFactory) {
@ -203,6 +204,19 @@ public class ChunkCoordinatorBuilder {
return this; return this;
} }
/**
* Set whether chunks should be generated as part of this operation. Default is true. Disabling this may not be supported
* depending on server implementation. (i.e. setting to false may not actually disable generation as part of this operation
* - this is just a catch-all in case of future differing server implementations; the option will work on Spigot/Paper).
*
* @param shouldGen should generate new chunks or not
* @since 7.5.0
*/
public @NonNull ChunkCoordinatorBuilder shouldGen(final boolean shouldGen) {
this.shouldGen = shouldGen;
return this;
}
public @NonNull ChunkCoordinatorBuilder withProgressSubscriber(ProgressSubscriber progressSubscriber) { public @NonNull ChunkCoordinatorBuilder withProgressSubscriber(ProgressSubscriber progressSubscriber) {
this.progressSubscribers.add(progressSubscriber); this.progressSubscribers.add(progressSubscriber);
return this; return this;
@ -234,7 +248,8 @@ public class ChunkCoordinatorBuilder {
this.throwableConsumer, this.throwableConsumer,
this.unloadAfter, this.unloadAfter,
this.progressSubscribers, this.progressSubscribers,
this.forceSync this.forceSync,
this.shouldGen
); );
} }

View File

@ -51,6 +51,7 @@ public class DelegateQueueCoordinator extends QueueCoordinator {
if (parent != null) { if (parent != null) {
this.setForceSync(parent.isForceSync()); this.setForceSync(parent.isForceSync());
this.setShouldGen(parent.isShouldGen());
} }
} }

View File

@ -45,6 +45,7 @@ public abstract class QueueCoordinator {
private final AtomicBoolean enqueued = new AtomicBoolean(); private final AtomicBoolean enqueued = new AtomicBoolean();
private boolean forceSync = false; private boolean forceSync = false;
private boolean shouldGen = true;
@Nullable @Nullable
private Object chunkObject; private Object chunkObject;
@SuppressWarnings({"unused", "FieldCanBeLocal"}) @SuppressWarnings({"unused", "FieldCanBeLocal"})
@ -110,6 +111,30 @@ public abstract class QueueCoordinator {
this.forceSync = forceSync; this.forceSync = forceSync;
} }
/**
* Get whether chunks should be generated as part of this operation. Default is true. Disabling this may not be supported
* depending on server implementation. (i.e. setting to false may not actually disable generation as part of this operation
* - this is just a catch-all in case of future differing server implementations; the option will work on Spigot/Paper).
*
* @since 7.5.0
*/
public boolean isShouldGen() {
return shouldGen;
}
/**
* Set whether chunks should be generated as part of this operation. Default is true. Disabling this may not be supported
* depending on server implementation. (i.e. setting to false may not actually disable generation as part of this operation
* - this is just a catch-all in case of future differing server implementations; the option will work on Spigot/Paper).
*
* @param shouldGen should generate new chunks or not
* @since 7.5.0
*/
public void setShouldGen(boolean shouldGen) {
this.shouldGen = shouldGen;
}
/** /**
* Get the Chunk Object set to the queue * Get the Chunk Object set to the queue
* *

View File

@ -177,6 +177,13 @@ public abstract class WorldUtil {
*/ */
public abstract void getBlock(@NonNull Location location, @NonNull Consumer<BlockState> result); public abstract void getBlock(@NonNull Location location, @NonNull Consumer<BlockState> result);
/**
* Checks if the block smaller as a slab
* @param location Block location
* @return true if it smaller as a slab
*/
public abstract boolean isSmallBlock(@NonNull Location location);
/** /**
* Get the block at a given location (synchronously) * Get the block at a given location (synchronously)
* *

View File

@ -22,7 +22,7 @@ plugins {
} }
group = "com.intellectualsites.plotsquared" group = "com.intellectualsites.plotsquared"
version = "7.4.2-SNAPSHOT" version = "7.5.2-SNAPSHOT"
if (!File("$rootDir/.git").exists()) { if (!File("$rootDir/.git").exists()) {
logger.lifecycle(""" logger.lifecycle("""
@ -79,8 +79,8 @@ subprojects {
dependencies { dependencies {
// Tests // Tests
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") testImplementation("org.junit.jupiter:junit-jupiter:5.12.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.4") testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.0")
} }
plugins.withId("java") { plugins.withId("java") {

View File

@ -2,18 +2,18 @@
# Platform expectations # Platform expectations
paper = "1.20.4-R0.1-SNAPSHOT" paper = "1.20.4-R0.1-SNAPSHOT"
guice = "7.0.0" guice = "7.0.0"
spotbugs = "4.9.0" spotbugs = "4.9.1"
checkerqual = "3.48.4" checkerqual = "3.49.0"
gson = "2.10" gson = "2.10"
guava = "31.1-jre" guava = "31.1-jre"
snakeyaml = "2.0" snakeyaml = "2.0"
adventure = "4.18.0" adventure = "4.19.0"
adventure-bukkit = "4.3.4" adventure-bukkit = "4.3.4"
log4j = "2.19.0" log4j = "2.19.0"
# Plugins # Plugins
worldedit = "7.2.20" worldedit = "7.2.20"
fawe = "2.12.3" fawe = "2.13.0"
placeholderapi = "2.11.6" placeholderapi = "2.11.6"
luckperms = "5.4" luckperms = "5.4"
essentialsx = "2.20.1" essentialsx = "2.20.1"
@ -33,7 +33,7 @@ vault = "1.7.1"
serverlib = "2.3.6" serverlib = "2.3.6"
# Gradle plugins # Gradle plugins
shadow = "8.3.5" shadow = "8.3.6"
grgit = "4.1.1" grgit = "4.1.1"
spotless = "7.0.2" spotless = "7.0.2"
nexus = "2.0.0" nexus = "2.0.0"