diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0241cbba73..fab76375f4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -71,7 +71,7 @@ body: - type: input attributes: label: Error log (if applicable) - description: If you are reporting a console error, upload any relevant log excerpts to either https://paste.gg/ or https://gist.github.com, save and the paste the link in this box. + description: If you are reporting a console error, upload any relevant log excerpts to https://gist.github.com, save and the paste the link in this box. - type: input attributes: diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index b07c467e5b..e048ba2711 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v6 - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v5 + uses: gradle/actions/wrapper-validation@v6 - name: Setup Java uses: actions/setup-java@v5 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 63ad6c8988..bd80947353 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v6 - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v5 + uses: gradle/actions/wrapper-validation@v6 - name: Setup Java uses: actions/setup-java@v5 with: diff --git a/.github/workflows/label-merge-conflicts.yaml b/.github/workflows/label-merge-conflicts.yaml index d3692f063a..f57ea5aaf2 100644 --- a/.github/workflows/label-merge-conflicts.yaml +++ b/.github/workflows/label-merge-conflicts.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Label conflicting PRs - uses: eps1lon/actions-label-merge-conflict@v3.0.3 + uses: eps1lon/actions-label-merge-conflict@v3.1.0 with: dirtyLabel: "unresolved-merge-conflict" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index b2bc4a8dd0..d8ff1527e0 100644 --- a/.gitignore +++ b/.gitignore @@ -138,5 +138,4 @@ build/ .DS_Store # Ignore run folders -run-[0-9].[0-9][0-9]/ -run-[0-9].[0-9][0-9].[0-9]/ +run-*/ diff --git a/Bukkit/build.gradle.kts b/Bukkit/build.gradle.kts index f00a912bd0..befec26416 100644 --- a/Bukkit/build.gradle.kts +++ b/Bukkit/build.gradle.kts @@ -38,7 +38,6 @@ dependencies { // Paper compileOnly(libs.paper) - implementation(libs.paperlib) // Plugins compileOnly(libs.worldeditBukkit) @@ -78,7 +77,6 @@ tasks.named("shadowJar") { relocate("net.kyori.option", "com.plotsquared.core.configuration.option") relocate("net.kyori.adventure", "com.plotsquared.core.configuration.adventure") relocate("net.kyori.examination", "com.plotsquared.core.configuration.examination") - relocate("io.papermc.lib", "com.plotsquared.bukkit.paperlib") relocate("org.bstats", "com.plotsquared.metrics") relocate("org.enginehub", "com.plotsquared.squirrelid") relocate("org.khelekore.prtree", "com.plotsquared.prtree") diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java index d60bbe3879..36fc07c266 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java @@ -48,6 +48,7 @@ import com.plotsquared.bukkit.player.BukkitPlayerManager; import com.plotsquared.bukkit.util.BukkitUtil; import com.plotsquared.bukkit.util.BukkitWorld; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.bukkit.util.SetGenCB; import com.plotsquared.bukkit.util.TranslationUpdateManager; import com.plotsquared.bukkit.util.UpdateUtility; @@ -100,6 +101,7 @@ import com.plotsquared.core.setup.SettingsNodesWrapper; import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.FileUtils; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.PlatformWorldManager; import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.PremiumVerification; @@ -113,7 +115,6 @@ import com.plotsquared.core.uuid.offline.OfflineModeUUIDService; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import io.papermc.lib.PaperLib; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -237,14 +238,19 @@ public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPl return this.version; } + @Override + public MinecraftVersion minecraftVersion() { + return MinecraftVersion.current(); + } + @Override public int versionMinHeight() { - return serverVersion()[1] >= 18 ? -64 : 0; + return minecraftVersion().isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS_2) ? -64 : 0; } @Override public int versionMaxHeight() { - return serverVersion()[1] >= 18 ? 319 : 255; + return minecraftVersion().isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS_2) ? 319 : 255; } @Override @@ -263,7 +269,7 @@ public void onEnable() { this.pluginName = getDescription().getName(); final TaskTime.TimeConverter timeConverter; - if (PaperLib.isPaper()) { + if (PaperSupport.isPaper()) { timeConverter = new PaperTimeConverter(); } else { timeConverter = new SpigotTimeConverter(); @@ -337,7 +343,7 @@ public void onEnable() { try { plotSquared.setConfigurationVersion("v5"); } catch (final Exception e) { - e.printStackTrace(); + LOGGER.error("Failed to update configuration version", e); } } } @@ -364,21 +370,21 @@ public void onEnable() { if (Settings.Enabled_Components.EVENTS) { getServer().getPluginManager().registerEvents(injector().getInstance(PlayerEventListener.class), this); - if ((serverVersion()[1] == 20 && serverVersion()[2] >= 1) || serverVersion()[1] > 20) { + if (minecraftVersion().isNewerOrEqualThan(20, 1)) { getServer().getPluginManager().registerEvents(injector().getInstance(PlayerEventListener1201.class), this); } getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener.class), this); if (Settings.HIGH_FREQUENCY_LISTENER) { getServer().getPluginManager().registerEvents(injector().getInstance(HighFreqBlockEventListener.class), this); } - if (serverVersion()[1] >= 17) { + if (minecraftVersion().isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS)) { getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener117.class), this); } getServer().getPluginManager().registerEvents(injector().getInstance(EntityEventListener.class), this); getServer().getPluginManager().registerEvents(injector().getInstance(ProjectileEventListener.class), this); getServer().getPluginManager().registerEvents(injector().getInstance(ServerListener.class), this); getServer().getPluginManager().registerEvents(injector().getInstance(EntitySpawnListener.class), this); - if (PaperLib.isPaper() && Settings.Paper_Components.PAPER_LISTENERS) { + if (PaperSupport.isPaper() && Settings.Paper_Components.PAPER_LISTENERS) { getServer().getPluginManager().registerEvents(injector().getInstance(PaperListener.class), this); } else { getServer().getPluginManager().registerEvents(injector().getInstance(SpigotListener.class), this); @@ -501,7 +507,7 @@ public void onEnable() { if (!Settings.UUID.OFFLINE) { // If running Paper we'll also try to use their profiles - if (Bukkit.getOnlineMode() && PaperLib.isPaper() && Settings.UUID.SERVICE_PAPER) { + if (Bukkit.getOnlineMode() && PaperSupport.isPaper() && Settings.UUID.SERVICE_PAPER) { final PaperUUIDService paperUUIDService = new PaperUUIDService(); this.impromptuPipeline.registerService(paperUUIDService); this.backgroundPipeline.registerService(paperUUIDService); @@ -787,7 +793,7 @@ private void runEntityTask() { while (iterator.hasNext()) { Entity entity = iterator.next(); //noinspection ConstantValue - getEntitySpawnReason annotated as NotNull, but is not NotNull. lol. - if (PaperLib.isPaper() && entity.getEntitySpawnReason() != null && "CUSTOM".equals(entity.getEntitySpawnReason().name())) { + if (PaperSupport.isPaper() && entity.getEntitySpawnReason() != null && "CUSTOM".equals(entity.getEntitySpawnReason().name())) { continue; } // Fallback for Spigot not having Entity#getEntitySpawnReason diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitAugmentedGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitAugmentedGenerator.java index b5d9e661c1..7ba6dd9d7a 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitAugmentedGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitAugmentedGenerator.java @@ -50,7 +50,7 @@ public static BukkitAugmentedGenerator get(World world) { @Override public void populate(@NonNull World world, @NonNull Random random, @NonNull Chunk source) { QueueCoordinator queue = PlotSquared.platform().globalBlockQueue().getNewQueue(BukkitAdapter.adapt(world)); - // The chunk is already loaded and we do not want to load the chunk in "fully" by using any PaperLib methods. + // The chunk is already loaded and we do not want to load the chunk in "fully" by using any Paper methods. queue.setForceSync(true); queue.setSideEffectSet(SideEffectSet.none()); queue.setBiomesEnabled(false); 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 f713ed3506..2a763c4c67 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java @@ -32,6 +32,7 @@ import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator; import com.plotsquared.core.util.ChunkManager; +import com.plotsquared.core.util.MinecraftVersion; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; @@ -90,14 +91,13 @@ public BukkitPlotGenerator( this.plotGenerator = generator; this.platformGenerator = this; this.populators = new ArrayList<>(); - int minecraftMinorVersion = PlotSquared.platform().serverVersion()[1]; - if (minecraftMinorVersion >= 17) { + if (MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS)) { this.populators.add(new BlockStatePopulator(this.plotGenerator)); } else { this.populators.add(new LegacyBlockStatePopulator(this.plotGenerator)); } this.full = true; - this.useNewGenerationMethods = PlotSquared.platform().serverVersion()[1] >= 19; + this.useNewGenerationMethods = MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.THE_WILD_UPDATE); this.biomeProvider = new BukkitPlotBiomeProvider(); } @@ -112,7 +112,7 @@ public BukkitPlotGenerator(final String world, final ChunkGenerator cg, final @N this.full = false; this.platformGenerator = cg; this.plotGenerator = new DelegatePlotGenerator(cg, world); - this.useNewGenerationMethods = PlotSquared.platform().serverVersion()[1] >= 19; + this.useNewGenerationMethods = MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.THE_WILD_UPDATE); this.biomeProvider = null; } @@ -446,7 +446,7 @@ private final class BukkitPlotBiomeProvider extends BiomeProvider { static { Set disabledBiomes = new HashSet<>(List.of(Biome.CUSTOM)); - if (PlotSquared.platform().serverVersion()[1] <= 19) { + if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.THE_WILD_UPDATE)) { final Biome cherryGrove = Registry.BIOME.get(NamespacedKey.minecraft("cherry_grove")); if (cherryGrove != null) { disabledBiomes.add(cherryGrove); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java index 4888911068..6119224b7e 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java @@ -163,7 +163,8 @@ public void onBlockForm(BlockFormEvent event) { if (plot == null) { return; } - if (event.getNewState().getType().name().contains("COPPER")) { + String newTypeName = event.getNewState().getType().name(); + if (newTypeName.contains("COPPER") || newTypeName.contains("LIGHTNING_ROD")) { if (!plot.getFlag(CopperOxideFlag.class)) { plot.debug("Copper could not oxide because copper-oxide = false"); event.setCancelled(true); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java index 763263e991..feeb54342f 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java @@ -19,13 +19,14 @@ package com.plotsquared.bukkit.listener; import com.google.inject.Inject; -import com.plotsquared.core.PlotSquared; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.location.Location; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.plot.world.SinglePlotArea; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.ReflectionUtils; import com.plotsquared.core.util.ReflectionUtils.RefClass; import com.plotsquared.core.util.ReflectionUtils.RefField; @@ -33,28 +34,26 @@ import com.plotsquared.core.util.task.PlotSquaredTask; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; -import io.papermc.lib.PaperLib; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPhysicsEvent; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntitySpawnEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkUnloadEvent; import org.checkerframework.checker.nullness.qual.NonNull; -import java.lang.reflect.Method; -import java.util.HashSet; import java.util.Objects; import static com.plotsquared.core.util.ReflectionUtils.getRefClass; @@ -62,31 +61,21 @@ @SuppressWarnings("unused") public class ChunkListener implements Listener { + private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + ChunkListener.class.getSimpleName()); + private final PlotAreaManager plotAreaManager; - private final int version; private RefMethod methodSetUnsaved; private RefMethod methodGetHandleChunk; private RefMethod methodGetHandleWorld; private RefField mustNotSave; private Object objChunkStatusFull = null; - /* - private RefMethod methodGetFullChunk; - private RefMethod methodGetBukkitChunk; - private RefMethod methodGetChunkProvider; - private RefMethod methodGetVisibleMap; - private RefField worldServer; - private RefField playerChunkMap; - private RefField updatingChunks; - private RefField visibleChunks; - */ private Chunk lastChunk; private boolean ignoreUnload = false; @Inject public ChunkListener(final @NonNull PlotAreaManager plotAreaManager) { this.plotAreaManager = plotAreaManager; - version = PlotSquared.platform().serverVersion()[1]; if (!Settings.Chunk_Processor.AUTO_TRIM) { return; } @@ -98,76 +87,24 @@ public ChunkListener(final @NonNull PlotAreaManager plotAreaManager) { try { this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle"); } catch (NoSuchMethodException ignored) { - try { - RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus"); - this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null); - this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass()); - } catch (NoSuchMethodException ex) { - throw new RuntimeException(ex); - } + RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus"); + this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null); + this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass()); } - try { - if (version < 17) { - RefClass classChunk = getRefClass("{nms}.Chunk"); - this.mustNotSave = classChunk.getField("mustNotSave"); - } else { - RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk"); - this.mustNotSave = classChunk.getField("mustNotSave"); - } - } catch (NoSuchFieldException e) { - e.printStackTrace(); + if (MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS)) { + RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk"); + this.mustNotSave = classChunk.getField("mustNotSave"); + } else { + RefClass classChunk = getRefClass("{nms}.Chunk"); + this.mustNotSave = classChunk.getField("mustNotSave"); } - } catch (Throwable ignored) { + } catch (Throwable t) { Settings.Chunk_Processor.AUTO_TRIM = false; + LOGGER.error("Failed to initialize NMS access for auto-trimming capabilities. Disabling auto trim", t); } for (World world : Bukkit.getWorlds()) { world.setAutoSave(false); } - if (version > 13) { - return; - } - TaskManager.runTaskRepeat(() -> { - try { - HashSet toUnload = new HashSet<>(); - for (World world : Bukkit.getWorlds()) { - String worldName = world.getName(); - if (!this.plotAreaManager.hasPlotArea(worldName)) { - continue; - } - Object craftWorld = methodGetHandleWorld.of(world).call(); - if (version == 13) { - Object chunkMap = craftWorld.getClass().getDeclaredMethod("getPlayerChunkMap").invoke(craftWorld); - Method methodIsChunkInUse = - chunkMap.getClass().getDeclaredMethod("isChunkInUse", int.class, int.class); - Chunk[] chunks = world.getLoadedChunks(); - for (Chunk chunk : chunks) { - if ((boolean) methodIsChunkInUse.invoke(chunkMap, chunk.getX(), chunk.getZ())) { - continue; - } - int x = chunk.getX(); - int z = chunk.getZ(); - if (!shouldSave(worldName, x, z)) { - unloadChunk(worldName, chunk, false); - continue; - } - toUnload.add(chunk); - } - } - } - if (toUnload.isEmpty()) { - return; - } - long start = System.currentTimeMillis(); - for (Chunk chunk : toUnload) { - if (System.currentTimeMillis() - start > 5) { - return; - } - chunk.unload(true); - } - } catch (Throwable e) { - e.printStackTrace(); - } - }, TaskTime.ticks(1L)); } public boolean unloadChunk(String world, Chunk chunk, boolean safe) { @@ -263,25 +200,7 @@ public void onChunkLoad(ChunkLoadEvent event) { @EventHandler(priority = EventPriority.LOWEST) public void onItemSpawn(ItemSpawnEvent event) { - Item entity = event.getEntity(); - PaperLib.getChunkAtAsync(event.getLocation()).thenAccept(chunk -> { - if (chunk == this.lastChunk) { - event.getEntity().remove(); - event.setCancelled(true); - return; - } - if (!this.plotAreaManager.hasPlotArea(chunk.getWorld().getName())) { - return; - } - Entity[] entities = chunk.getEntities(); - if (entities.length > Settings.Chunk_Processor.MAX_ENTITIES) { - event.getEntity().remove(); - event.setCancelled(true); - this.lastChunk = chunk; - } else { - this.lastChunk = null; - } - }); + this.onInternalEntitySpawn(event); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @@ -293,8 +212,11 @@ public void onBlockPhysics(BlockPhysicsEvent event) { @EventHandler(priority = EventPriority.LOWEST) public void onEntitySpawn(CreatureSpawnEvent event) { - LivingEntity entity = event.getEntity(); - PaperLib.getChunkAtAsync(event.getLocation()).thenAccept(chunk -> { + this.onInternalEntitySpawn(event); + } + + private void onInternalEntitySpawn(EntitySpawnEvent event) { + PaperSupport.getChunkAtAsync(event.getLocation()).thenAccept(chunk -> { if (chunk == this.lastChunk) { event.getEntity().remove(); event.setCancelled(true); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java index 15220af2d2..8af129313e 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java @@ -23,6 +23,7 @@ import com.plotsquared.bukkit.player.BukkitPlayer; import com.plotsquared.bukkit.util.BukkitEntityUtil; import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.listener.PlayerBlockEventType; @@ -44,7 +45,6 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.util.Enums; import com.sk89q.worldedit.world.block.BlockType; -import io.papermc.lib.PaperLib; import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.World; @@ -180,7 +180,7 @@ public void creatureSpawnEvent(CreatureSpawnEvent event) { return; } // No need to clutter metadata if running paper - if (!PaperLib.isPaper()) { + if (!PaperSupport.isPaper()) { entity.setMetadata("ps_custom_spawned", new FixedMetadataValue(this.platform, true)); } return; // Don't cancel if mob spawning is disabled diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntitySpawnListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntitySpawnListener.java index 432b13b019..6e234355a6 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntitySpawnListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntitySpawnListener.java @@ -20,13 +20,13 @@ import com.plotsquared.bukkit.util.BukkitEntityUtil; import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.location.Location; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.flag.implementations.DoneFlag; -import io.papermc.lib.PaperLib; import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.block.Block; @@ -99,7 +99,7 @@ public static void test(Entity entity) { } try { ignoreTP = true; - PaperLib.teleportAsync(entity, origin); + PaperSupport.teleportAsync(entity, origin); } finally { ignoreTP = false; } @@ -125,7 +125,7 @@ public void creatureSpawnEvent(EntitySpawnEvent event) { if (!location.isPlotArea() || area == null) { return; } - if (PaperLib.isPaper()) { + if (PaperSupport.isPaper()) { //noinspection ConstantValue - getEntitySpawnReason annotated as NotNull, but is not NotNull. lol. if (area.isSpawnCustom() && entity.getEntitySpawnReason() != null && "CUSTOM".equals(entity.getEntitySpawnReason().name())) { return; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java index f7a2623caa..445097470a 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java @@ -24,6 +24,7 @@ import com.plotsquared.bukkit.player.BukkitPlayer; import com.plotsquared.bukkit.util.BukkitEntityUtil; import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.bukkit.util.UpdateUtility; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; @@ -72,6 +73,7 @@ import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.MathMan; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.PlotFlagUtil; import com.plotsquared.core.util.PremiumVerification; import com.plotsquared.core.util.entity.EntityCategories; @@ -82,7 +84,6 @@ import com.sk89q.worldedit.util.Enums; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; -import io.papermc.lib.PaperLib; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.Tag; @@ -216,8 +217,7 @@ public class PlayerEventListener implements Listener { "PINK_DYE", "GLOW_INK_SAC" )); - int[] version = PlotSquared.platform().serverVersion(); - if (version[1] >= 20) { + if (MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.TRAILS_AND_TALES)) { mutableDyes.add("HONEYCOMB"); } DYES = Set.copyOf(mutableDyes); @@ -692,7 +692,7 @@ public void vehicleMove(VehicleMoveEvent event) if (dest != null) { vehicle.eject(); vehicle.setVelocity(new Vector(0d, 0d, 0d)); - PaperLib.teleportAsync(vehicle, dest); + PaperSupport.teleportAsync(vehicle, dest); passengers.forEach(vehicle::addPassenger); return; } @@ -1310,7 +1310,7 @@ public void onInteract(PlayerInteractEvent event) { } } } - if (PaperLib.isPaper()) { + if (PaperSupport.isPaper()) { if (MaterialTags.SPAWN_EGGS.isTagged(type) || Material.EGG.equals(type)) { eventType = PlayerBlockEventType.SPAWN_MOB; break; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java index 8036b94e25..9c506b03c0 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java @@ -22,6 +22,7 @@ import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.plot.world.SinglePlotArea; import com.plotsquared.core.plot.world.SinglePlotAreaManager; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.ReflectionUtils; import org.bukkit.Chunk; import org.bukkit.World; @@ -49,7 +50,7 @@ public SingleWorldListener() throws Exception { this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle").getRealMethod(); } catch (NoSuchMethodException ignored) { try { - String chunkStatus = PlotSquared.platform().serverVersion()[1] < 21 + String chunkStatus = MinecraftVersion.current().isOlderThan(MinecraftVersion.TRICKY_TRIALS) ? "net.minecraft.world.level.chunk" + ".ChunkStatus" : "net.minecraft.world.level.chunk.status.ChunkStatus"; ReflectionUtils.RefClass classChunkStatus = getRefClass(chunkStatus); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java index bd9c992874..2e4986c335 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java @@ -20,6 +20,7 @@ import com.google.common.base.Charsets; import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.bukkit.util.PaperSupport; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.events.TeleportCause; @@ -32,12 +33,12 @@ import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.MathMan; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.WorldUtil; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemTypes; -import io.papermc.lib.PaperLib; import net.kyori.adventure.audience.Audience; import org.bukkit.GameMode; import org.bukkit.Sound; @@ -232,7 +233,7 @@ public void teleport(final @NonNull Location location, final @NonNull TeleportCa new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX() + 0.5, location.getY(), location.getZ() + 0.5, location.getYaw(), location.getPitch() ); - PaperLib.teleportAsync(player, bukkitLocation, getTeleportCause(cause)); + PaperSupport.teleportAsync(player, bukkitLocation, getTeleportCause(cause)); } @Override @@ -313,7 +314,7 @@ public void setFlight(boolean fly) { @Override public void playMusic(final @NonNull Location location, final @NonNull ItemType id) { if (id == ItemTypes.AIR) { - if (PlotSquared.platform().serverVersion()[1] >= 19) { + if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.THE_WILD_UPDATE)) { player.stopSound(SoundCategory.MUSIC); return; } 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 759cbbb4a6..d5097ec0f4 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.PaperSupport; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.queue.ChunkCoordinator; import com.plotsquared.core.queue.subscriber.ProgressSubscriber; @@ -29,7 +30,6 @@ import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.world.World; -import io.papermc.lib.PaperLib; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; @@ -222,10 +222,9 @@ public void run() { */ private void requestBatch() { for (int i = 0; i < this.batchSize && this.requestedChunks.peek() != null; i++) { - // 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(); - PaperLib + PaperSupport .getChunkAtAsync(this.bukkitWorld, chunk.getX(), chunk.getZ(), shouldGen, true) .orTimeout(10L, TimeUnit.SECONDS) .whenComplete((chunkObject, throwable) -> { @@ -258,11 +257,6 @@ private void requestBatch() { * server's main thread. */ private void processChunk(final @NonNull Chunk chunk) { - /* Chunk#isLoaded does not necessarily return true shortly after PaperLib#getChunkAtAsync completes, but the chunk is - still loaded. - if (!chunk.isLoaded()) { - throw new IllegalArgumentException(String.format("Chunk %d;%d is is not loaded", chunk.getX(), chunk.getZ()); - }*/ if (finished) { return; } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java b/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java index d4aacb1113..6051539cb5 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java @@ -21,6 +21,7 @@ import com.destroystokyo.paper.profile.PlayerProfile; import com.destroystokyo.paper.profile.ProfileProperty; import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.bukkit.util.PaperSupport; import com.sk89q.jnbt.ByteTag; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.ListTag; @@ -30,7 +31,6 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.world.item.ItemType; -import io.papermc.lib.PaperLib; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; @@ -270,7 +270,7 @@ public boolean restoreTag(@NonNull Block block) { if (textureValue == null) { return false; } - if (!PaperLib.isPaper()) { + if (!PaperSupport.isPaper()) { if (!paperErrorTextureSent) { paperErrorTextureSent = true; LOGGER.error("Failed to populate skull data in your road schematic - This is a Spigot limitation."); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitChunkManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitChunkManager.java index bb529a4617..1bbc6a355d 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitChunkManager.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitChunkManager.java @@ -22,7 +22,6 @@ import com.plotsquared.core.util.ChunkManager; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.regions.CuboidRegion; -import io.papermc.lib.PaperLib; import java.util.concurrent.CompletableFuture; @@ -38,7 +37,7 @@ public static boolean isIn(CuboidRegion region, int x, int z) { @Override public CompletableFuture loadChunk(String world, BlockVector2 chunkLoc, boolean force) { - return PaperLib.getChunkAtAsync(BukkitUtil.getWorld(world), chunkLoc.getX(), chunkLoc.getZ(), force); + return PaperSupport.getChunkAtAsync(BukkitUtil.getWorld(world), chunkLoc.getX(), chunkLoc.getZ(), force); } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitEntityUtil.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitEntityUtil.java index 5bb8988fa1..e901a0bdf4 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitEntityUtil.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitEntityUtil.java @@ -38,8 +38,10 @@ import com.plotsquared.core.plot.flag.implementations.PveFlag; import com.plotsquared.core.plot.flag.implementations.PvpFlag; import com.plotsquared.core.plot.flag.implementations.TamedAttackFlag; +import com.plotsquared.core.plot.flag.implementations.VehicleBreakFlag; import com.plotsquared.core.plot.flag.implementations.VehicleCapFlag; import com.plotsquared.core.util.EntityUtil; +import com.plotsquared.core.util.PlotFlagUtil; import com.plotsquared.core.util.entity.EntityCategories; import com.sk89q.worldedit.bukkit.BukkitAdapter; import net.kyori.adventure.text.Component; @@ -311,6 +313,51 @@ public static boolean entityDamage(Entity damager, Entity victim, EntityDamageEv } } else if (EntityCategories.VEHICLE .contains(entityType)) { // Vehicles are managed in vehicle destroy event + // Fire damage (e.g. from Fire Aspect swords) needs to be checked here as it bypasses VehicleDestroyEvent + if (cause == EntityDamageEvent.DamageCause.FIRE_TICK) { + if (isPlot) { + if (!plot.hasOwner()) { + if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED)) { + plotPlayer.sendMessage( + TranslatableCaption.of("permission.no_permission_event"), + TagResolver.resolver( + "node", + Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_UNOWNED) + ) + ); + return false; + } + return true; + } + if (plot.getFlag(VehicleBreakFlag.class) || plot.isAdded(plotPlayer.getUUID())) { + return true; + } + if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER)) { + plotPlayer.sendMessage( + TranslatableCaption.of("permission.no_permission_event"), + TagResolver.resolver( + "node", + Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_OTHER) + ) + ); + plot.debug(player.getName() + " could not set vehicle on fire because vehicle-break = false"); + return false; + } + } else { + // Road + if (!PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, VehicleBreakFlag.class, true) + && !plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD)) { + plotPlayer.sendMessage( + TranslatableCaption.of("permission.no_permission_event"), + TagResolver.resolver( + "node", + Tag.inserting(Permission.PERMISSION_ADMIN_DESTROY_VEHICLE_ROAD) + ) + ); + return false; + } + } + } return true; } else { // victim is something else if (isPlot) { @@ -372,7 +419,8 @@ public static boolean checkEntity(EntityType type, Plot plot) { if (EntityCategories.PROJECTILE.contains(entityType) || EntityCategories.OTHER .contains(entityType) || EntityCategories.HANGING.contains(entityType)) { - return EntityUtil.checkEntity(plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, + return EntityUtil.checkEntity( + plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MiscCapFlag.MISC_CAP_UNLIMITED ); } @@ -382,20 +430,23 @@ public static boolean checkEntity(EntityType type, Plot plot) { if (EntityCategories.ANIMAL.contains(entityType) || EntityCategories.VILLAGER .contains(entityType) || EntityCategories.TAMEABLE.contains(entityType)) { return EntityUtil - .checkEntity(plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED, + .checkEntity( + plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED, AnimalCapFlag.ANIMAL_CAP_UNLIMITED ); } if (EntityCategories.HOSTILE.contains(entityType)) { return EntityUtil - .checkEntity(plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED, + .checkEntity( + plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, MobCapFlag.MOB_CAP_UNLIMITED, HostileCapFlag.HOSTILE_CAP_UNLIMITED ); } if (EntityCategories.VEHICLE.contains(entityType)) { - return EntityUtil.checkEntity(plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, + return EntityUtil.checkEntity( + plot, EntityCapFlag.ENTITY_CAP_UNLIMITED, VehicleCapFlag.VEHICLE_CAP_UNLIMITED ); } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java index d78991c892..19125dd1e0 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitRegionManager.java @@ -41,7 +41,6 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockTypes; -import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; @@ -126,7 +125,7 @@ public int[] countEntities(@NonNull Plot plot) { if (doWhole) { for (Entity entity : entities) { org.bukkit.Location location = entity.getLocation(); - PaperLib.getChunkAtAsync(location).thenAccept(chunk -> { + PaperSupport.getChunkAtAsync(location).thenAccept(chunk -> { if (chunks.contains(chunk)) { int X = chunk.getX(); int Z = chunk.getZ(); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java index fd1ce055ef..7ec238f995 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitUtil.java @@ -30,6 +30,7 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.util.BlockUtil; import com.plotsquared.core.util.MathMan; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.StringComparison; import com.plotsquared.core.util.WorldUtil; @@ -42,7 +43,6 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; -import io.papermc.lib.PaperLib; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; @@ -56,7 +56,6 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Sign; -import org.bukkit.block.data.Directional; import org.bukkit.block.data.type.WallSign; import org.bukkit.entity.Allay; import org.bukkit.entity.Ambient; @@ -192,12 +191,12 @@ private static void ensureLoaded( final int z, final @NonNull Consumer chunkConsumer ) { - PaperLib.getChunkAtAsync(Objects.requireNonNull(getWorld(world)), x >> 4, z >> 4, true) + PaperSupport.getChunkAtAsync(Objects.requireNonNull(getWorld(world)), x >> 4, z >> 4, true) .thenAccept(chunk -> ensureMainThread(chunkConsumer, chunk)); } private static void ensureLoaded(final @NonNull Location location, final @NonNull Consumer chunkConsumer) { - PaperLib.getChunkAtAsync(adapt(location), true).thenAccept(chunk -> ensureMainThread(chunkConsumer, chunk)); + PaperSupport.getChunkAtAsync(adapt(location)).thenAccept(chunk -> ensureMainThread(chunkConsumer, chunk)); } private static void ensureMainThread(final @NonNull Consumer consumer, final @NonNull T value) { @@ -357,15 +356,14 @@ public void setSign( facing = BlockFace.SOUTH; } } - if (PlotSquared.platform().serverVersion()[1] == 13) { + if (MinecraftVersion.current().isOlderOrEqualThan(13)) { block.setType(Material.valueOf(area.legacySignMaterial()), false); } else { block.setType(Material.valueOf(area.signMaterial()), false); } - if (!(block.getBlockData() instanceof WallSign)) { + if (!(block.getBlockData() instanceof WallSign sign)) { throw new RuntimeException("Something went wrong generating a sign"); } - final Directional sign = (Directional) block.getBlockData(); sign.setFacing(facing); block.setBlockData(sign, false); } @@ -438,7 +436,6 @@ public void setFoodLevel(final @NonNull PlotPlayer player, @NonNegative final @Override public @NonNull Set getTypesInCategory(final @NonNull String category) { final Collection> allowedInterfaces = new HashSet<>(); - final int[] version = PlotSquared.platform().serverVersion(); switch (category) { case "animal" -> { allowedInterfaces.add(IronGolem.class); @@ -446,7 +443,7 @@ public void setFoodLevel(final @NonNull PlotPlayer player, @NonNegative final allowedInterfaces.add(Animals.class); allowedInterfaces.add(WaterMob.class); allowedInterfaces.add(Ambient.class); - if (version[1] >= 19) { + if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.THE_WILD_UPDATE)) { allowedInterfaces.add(Allay.class); } } @@ -478,7 +475,7 @@ public void setFoodLevel(final @NonNull PlotPlayer player, @NonNegative final } case "player" -> allowedInterfaces.add(Player.class); case "interaction" -> { - if ((version[1] > 19) || (version[1] == 19 && version[2] >= 4)) { + if (MinecraftVersion.current().isNewerOrEqualThan(19, 4)) { allowedInterfaces.add(Interaction.class); } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/PaperSupport.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/PaperSupport.java new file mode 100644 index 0000000000..d4b7ac82c3 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/PaperSupport.java @@ -0,0 +1,85 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * 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 . + */ +package com.plotsquared.bukkit.util; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.ApiStatus; + +import java.util.concurrent.CompletableFuture; + +/** + * Not for external use. May change at any point. + */ +@ApiStatus.Internal +public enum PaperSupport { + ; + public static final boolean PAPER; + + static { + PAPER = hasClass("com.destroystokyo.paper.PaperConfig") + || hasClass("io.papermc.paper.configuration.Configuration"); + } + + public static boolean isPaper() { + return PAPER; + } + + public static CompletableFuture teleportAsync(Entity entity, Location location) { + if (isPaper()) { + return entity.teleportAsync(location); + } + return CompletableFuture.completedFuture(entity.teleport(location)); + } + + public static CompletableFuture teleportAsync(Entity entity, Location location, PlayerTeleportEvent.TeleportCause cause) { + if (isPaper()) { + return entity.teleportAsync(location, cause); + } + return CompletableFuture.completedFuture(entity.teleport(location, cause)); + } + + public static CompletableFuture getChunkAtAsync(Location location) { + return getChunkAtAsync(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, true); + } + + public static CompletableFuture getChunkAtAsync(World world, int cx, int cz, boolean gen) { + return getChunkAtAsync(world, cx, cz, gen, false); + } + + public static CompletableFuture getChunkAtAsync(World world, int cx, int cz, boolean gen, boolean urgent) { + if (isPaper()) { + return world.getChunkAtAsync(cx, cz, gen, urgent); + } else { + return CompletableFuture.completedFuture(world.getChunkAt(cx, cz)); + } + } + + private static boolean hasClass(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java index 844d553b63..ab1d825cbc 100644 --- a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java +++ b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java @@ -37,6 +37,7 @@ import com.plotsquared.core.queue.GlobalBlockQueue; import com.plotsquared.core.util.ChunkManager; import com.plotsquared.core.util.EconHandler; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.PlatformWorldManager; import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.RegionManager; @@ -94,9 +95,18 @@ public interface PlotPlatform

extends LocaleHolder { * Gets the version of Minecraft that is running * * @return server version as array of numbers + * @deprecated non-standardized format, use {@link #minecraftVersion()} */ + @Deprecated(since = "TODO") int[] serverVersion(); + /** + * Gets the version of Minecraft that this server is running + * + * @return minecraft version + */ + MinecraftVersion minecraftVersion(); + /** * Gets the default minimum world height for the version of Minecraft that the server is running. * diff --git a/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java b/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java index 4406bf1765..9fceae6230 100644 --- a/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java +++ b/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java @@ -173,11 +173,11 @@ private static boolean checkPermValue( } /** - * Checks if the player is allowed to modify the flags at their current location + * Checks if the player is allowed to interact with flags at their current location * * @return {@code true} if the player is allowed to modify the flags at their current location */ - private static boolean checkRequirements(final @NonNull PlotPlayer player) { + private static boolean checkRequirements(final @NonNull PlotPlayer player, final @NonNull Permission permissionIfNotOwner) { final Plot plot = player.getCurrentPlot(); if (plot == null) { player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); @@ -187,10 +187,10 @@ private static boolean checkRequirements(final @NonNull PlotPlayer player) { player.sendMessage(TranslatableCaption.of("working.plot_not_claimed")); return false; } - if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_SET_FLAG_OTHER)) { + if (!plot.isOwner(player.getUUID()) && !player.hasPermission(permissionIfNotOwner)) { player.sendMessage( TranslatableCaption.of("permission.no_permission"), - TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_SET_FLAG_OTHER)) + TagResolver.resolver("node", Tag.inserting(permissionIfNotOwner)) ); return false; } @@ -328,7 +328,7 @@ public void set( final RunnableVal3 confirm, final RunnableVal2 whenDone ) { - if (!checkRequirements(player)) { + if (!checkRequirements(player, Permission.PERMISSION_SET_FLAG_OTHER)) { return; } if (args.length < 2) { @@ -393,7 +393,7 @@ public void add( final RunnableVal3 confirm, final RunnableVal2 whenDone ) { - if (!checkRequirements(player)) { + if (!checkRequirements(player, Permission.PERMISSION_SET_FLAG_OTHER)) { return; } if (args.length < 2) { @@ -468,7 +468,7 @@ public void remove( final RunnableVal3 confirm, final RunnableVal2 whenDone ) { - if (!checkRequirements(player)) { + if (!checkRequirements(player, Permission.PERMISSION_SET_FLAG_OTHER)) { return; } if (args.length != 1 && args.length != 2) { @@ -595,7 +595,7 @@ public void list( final RunnableVal3 confirm, final RunnableVal2 whenDone ) { - if (!checkRequirements(player)) { + if (!checkRequirements(player, Permission.PERMISSION_FLAG_LIST_OTHER)) { return; } @@ -645,7 +645,7 @@ public void info( final RunnableVal3 confirm, final RunnableVal2 whenDone ) { - if (!checkRequirements(player)) { + if (!checkRequirements(player, Permission.PERMISSION_SET_FLAG_OTHER)) { return; } if (args.length < 1) { diff --git a/Core/src/main/java/com/plotsquared/core/command/Merge.java b/Core/src/main/java/com/plotsquared/core/command/Merge.java index 0e06508364..447f3c841d 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Merge.java +++ b/Core/src/main/java/com/plotsquared/core/command/Merge.java @@ -40,6 +40,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.UUID; +import java.util.function.Supplier; @CommandDeclaration(command = "merge", aliases = "m", @@ -116,9 +117,11 @@ public boolean onCommand(final PlotPlayer player, String[] args) { if (direction == null) { player.sendMessage( TranslatableCaption.of("commandconfig.command_syntax"), - TagResolver.resolver("value", Tag.inserting(Component.text( - "/plot merge <" + StringMan.join(values, " | ") + "> [removeroads]" - ))) + TagResolver.resolver( + "value", Tag.inserting(Component.text( + "/plot merge <" + StringMan.join(values, " | ") + "> [removeroads]" + )) + ) ); player.sendMessage( TranslatableCaption.of("help.direction"), @@ -154,14 +157,26 @@ public boolean onCommand(final PlotPlayer player, String[] args) { UUID uuid = player.getUUID(); - if (!force && !plot.isOwner(uuid)) { - if (!player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_MERGE)) { - player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); + if (!force) { + if (!plot.isOwner(uuid)) { + if (!player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_MERGE)) { + player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); + return false; + } else { + uuid = plot.getOwnerAbs(); + } + } + + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d && this.econHandler.getMoney( + player) < price) { + player.sendMessage( + TranslatableCaption.of("economy.cannot_afford_merge"), + TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) + ); return false; - } else { - uuid = plot.getOwnerAbs(); } } + if (direction == Direction.ALL) { boolean terrain = true; if (args.length == 2) { @@ -178,17 +193,15 @@ public boolean onCommand(final PlotPlayer player, String[] args) { return true; } if (plot.getPlotModificationManager().autoMerge(Direction.ALL, maxSize, uuid, player, terrain)) { - if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { - this.econHandler.withdrawMoney(player, price); - player.sendMessage( - TranslatableCaption.of("economy.removed_balance"), - TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))), - TagResolver.resolver( - "balance", - Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(player)))) - ) - ); - } + this.econHandler.withdrawMoney(player, price); + player.sendMessage( + TranslatableCaption.of("economy.removed_balance"), + TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))), + TagResolver.resolver( + "balance", + Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(player)))) + ) + ); player.sendMessage(TranslatableCaption.of("merge.success_merge")); eventDispatcher.callPostMerge(player, plot); return true; @@ -196,14 +209,6 @@ public boolean onCommand(final PlotPlayer player, String[] args) { player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); return false; } - if (!force && this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d && this.econHandler.getMoney( - player) < price) { - player.sendMessage( - TranslatableCaption.of("economy.cannot_afford_merge"), - TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) - ); - return false; - } final boolean terrain; if (args.length == 2) { terrain = "true".equalsIgnoreCase(args[1]); @@ -244,54 +249,66 @@ public boolean onCommand(final PlotPlayer player, String[] args) { } java.util.Set uuids = adjacent.getOwners(); boolean isOnline = false; - for (final UUID owner : uuids) { - final PlotPlayer accepter = PlotSquared.platform().playerManager().getPlayerIfExists(owner); - if (!force && accepter == null) { - continue; - } - isOnline = true; - final Direction dir = direction; - Runnable run = () -> { - accepter.sendMessage(TranslatableCaption.of("merge.merge_accepted")); - plot.getPlotModificationManager().autoMerge(dir, maxSize - size, owner, player, terrain); - PlotPlayer plotPlayer = PlotSquared.platform().playerManager().getPlayerIfExists(player.getUUID()); - if (plotPlayer == null) { - accepter.sendMessage(TranslatableCaption.of("merge.merge_not_valid")); - return; + if (!force) { + for (final UUID owner : uuids) { + final PlotPlayer accepter = PlotSquared.platform().playerManager().getPlayerIfExists(owner); + if (accepter == null) { + continue; } - if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { - if (!force && this.econHandler.getMoney(player) < price) { - player.sendMessage( - TranslatableCaption.of("economy.cannot_afford_merge"), - TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) - ); - return; + isOnline = true; + final Direction dir = direction; + Supplier run = () -> { + accepter.sendMessage(TranslatableCaption.of("merge.merge_accepted")); + if (plot.getPlotModificationManager().autoMerge(dir, maxSize - size, owner, player, terrain)) { + PlotPlayer plotPlayer = PlotSquared.platform().playerManager().getPlayerIfExists(player.getUUID()); + if (plotPlayer == null) { + accepter.sendMessage(TranslatableCaption.of("merge.merge_not_valid")); + return false; + } + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { + if (this.econHandler.getMoney(player) < price) { + player.sendMessage( + TranslatableCaption.of("economy.cannot_afford_merge"), + TagResolver.resolver( + "money", + Tag.inserting(Component.text(this.econHandler.format(price))) + ) + ); + return false; + } + this.econHandler.withdrawMoney(player, price); + player.sendMessage( + TranslatableCaption.of("economy.removed_balance"), + TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) + ); + } + player.sendMessage(TranslatableCaption.of("merge.success_merge")); + eventDispatcher.callPostMerge(player, plot); + return true; } - this.econHandler.withdrawMoney(player, price); - player.sendMessage( - TranslatableCaption.of("economy.removed_balance"), - TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) + player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); + return false; + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending( + accepter, MINI_MESSAGE.serialize(MINI_MESSAGE + .deserialize( + TranslatableCaption.of("merge.merge_request_confirm").getComponent(player), + TagResolver.builder() + .tag("player", Tag.inserting(Component.text(player.getName()))) + .tag( + "location", + Tag.inserting(Component.text(plot.getWorldName() + " " + plot.getId())) + ) + .build() + )), + run::get ); + } else { + return run.get(); } - player.sendMessage(TranslatableCaption.of("merge.success_merge")); - eventDispatcher.callPostMerge(player, plot); - }; - if (!force && hasConfirmation(player)) { - CmdConfirm.addPending(accepter, MINI_MESSAGE.serialize(MINI_MESSAGE - .deserialize( - TranslatableCaption.of("merge.merge_request_confirm").getComponent(player), - TagResolver.builder() - .tag("player", Tag.inserting(Component.text(player.getName()))) - .tag( - "location", - Tag.inserting(Component.text(plot.getWorldName() + " " + plot.getId())) - ) - .build() - )), - run - ); - } else { - run.run(); + // find first + break; } } if (force || !isOnline) { diff --git a/Core/src/main/java/com/plotsquared/core/database/SQLManager.java b/Core/src/main/java/com/plotsquared/core/database/SQLManager.java index bd017184fc..f58a26984a 100644 --- a/Core/src/main/java/com/plotsquared/core/database/SQLManager.java +++ b/Core/src/main/java/com/plotsquared/core/database/SQLManager.java @@ -1728,6 +1728,18 @@ public void setSQL(PreparedStatement stmt, Integer obj) throws SQLException { @Override public boolean convertFlags() { final Map> flagMap = new HashMap<>(); + try { + // only migrate flags, if plot_settings table has flags column + DatabaseMetaData metaData = this.connection.getMetaData(); + try (ResultSet rs = metaData.getColumns(null, null, this.prefix + "plot_settings", "flags")) { + if (!rs.next()) { + return true; + } + } + } catch (SQLException e) { + LOGGER.error("Failed to query table metadata", e); + return false; + } try (Statement statement = this.connection.createStatement()) { try (ResultSet resultSet = statement .executeQuery("SELECT * FROM `" + this.prefix + "plot_settings`")) { @@ -1742,11 +1754,10 @@ public boolean convertFlags() { if (element.contains(":")) { String[] split = element.split(":"); // splits flag:value try { - String flag_str = - split[1].replaceAll("¯", ":").replaceAll("\u00B4", ","); + String flag_str = split[1].replace("¯", ":").replace("´", ","); flagMap.get(id).put(split[0], flag_str); } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("Failed to migrate flag value", e); } } } @@ -1759,8 +1770,7 @@ public boolean convertFlags() { LOGGER.info("Loaded {} plot flag collections...", flagMap.size()); LOGGER.info("Attempting to store these flags in the new table..."); try (final PreparedStatement preparedStatement = this.connection.prepareStatement( - "INSERT INTO `" + SQLManager.this.prefix - + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?)")) { + "INSERT INTO `" + this.prefix + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?)")) { long timeStarted = System.currentTimeMillis(); int flagsProcessed = 0; @@ -1785,23 +1795,18 @@ public boolean convertFlags() { try { preparedStatement.executeBatch(); } catch (final Exception e) { - LOGGER.error("Failed to store flag values for plot with entry ID: {}", plotFlagEntry.getKey()); - e.printStackTrace(); + LOGGER.error("Failed to store flag values for plot with entry ID: {}", plotFlagEntry.getKey(), e); continue; } - if (System.currentTimeMillis() - timeStarted >= 1000L || plotsProcessed >= flagMap - .size()) { + if (System.currentTimeMillis() - timeStarted >= 1000L || plotsProcessed >= flagMap.size()) { timeStarted = System.currentTimeMillis(); LOGGER.info( "... Flag conversion in progress. {}% done", String.format("%.1f", ((float) flagsProcessed / totalFlags) * 100) ); } - LOGGER.info( - "- Finished converting flags for plot with entry ID: {}", - plotFlagEntry.getKey() - ); + LOGGER.info("- Finished converting flags for plot with entry ID: {}", plotFlagEntry.getKey()); } } catch (final Exception e) { LOGGER.error("Failed to store flag values", e); diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java index 7b35f6f971..cb2681d38c 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java @@ -436,9 +436,19 @@ public void run() { while (iter.hasNext()) { BlockVector2 chunk = iter.next(); iter.remove(); - boolean regenedRoad = regenerateRoad(area, chunk, extend, queue); - if (!regenedRoad) { - LOGGER.info("Failed to regenerate roads in chunk {}", chunk); + RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); + if (result != RoadRegenerateResult.SUCCESS) { + LOGGER.info( + "Failed to regenerate roads in chunk {}: {}", chunk, + result.getMessage() + ); + if (result.irrecoverable()) { + LOGGER.error("Cancelling road regen"); + chunks.clear(); + queue.cancel(); + HybridUtils.regions.clear(); + return; + } } } queue.enqueue(); @@ -479,9 +489,20 @@ public void run() { for (int i = 0; i < 32; i++) { final BlockVector2 chunk = iterator.next(); iterator.remove(); - boolean regenedRoads = regenerateRoad(area, chunk, extend, queue); - if (!regenedRoads) { - LOGGER.info("Failed to regenerate the road in chunk {}", chunk); + RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); + if (result != RoadRegenerateResult.SUCCESS) { + LOGGER.info( + "Failed to regenerate the road in chunk {}: {}", chunk, + result.getMessage() + ); + if (result.irrecoverable()) { + LOGGER.error("Cancelling road regen"); + chunks.clear(); + queue.cancel(); + HybridUtils.regions.clear(); + TaskManager.runTaskLater(task, TaskTime.seconds(1L)); + return null; + } } } queue.setCompleteTask(task); @@ -493,9 +514,20 @@ public void run() { while (!chunks.isEmpty()) { final BlockVector2 chunk = iterator.next(); iterator.remove(); - boolean regenedRoads = regenerateRoad(area, chunk, extend, queue); - if (!regenedRoads) { - LOGGER.info("Failed to regenerate road in chunk {}", chunk); + RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); + if (result != RoadRegenerateResult.SUCCESS) { + LOGGER.info( + "Failed to regenerate road in chunk {}: {}", chunk, + result.getMessage() + ); + if (result.irrecoverable()) { + LOGGER.error("Cancelling road regen"); + chunks.clear(); + queue.cancel(); + HybridUtils.regions.clear(); + TaskManager.runTaskLater(task, TaskTime.seconds(1L)); + return null; + } } } queue.setCompleteTask(task); @@ -592,10 +624,10 @@ private int get_ey(final HybridPlotWorld hpw, QueueCoordinator queue, int sx, in * @param chunk Chunk location to regenerate * @param extend How far to extend setting air above the road * @param queueCoordinator {@link QueueCoordinator} to use to set the blocks. Null if one should be created and enqueued - * @return if successful + * @return {@link RoadRegenerateResult} result * @since 6.6.0 */ - public boolean regenerateRoad( + public RoadRegenerateResult regenerateRoad( final PlotArea area, final BlockVector2 chunk, int extend, @@ -607,14 +639,14 @@ public boolean regenerateRoad( int ez = z + 15; HybridPlotWorld plotWorld = (HybridPlotWorld) area; if (!plotWorld.ROAD_SCHEMATIC_ENABLED) { - return false; + return RoadRegenerateResult.FAIL_ROAD_SCHEMATICS_NOT_ENABLED; } AtomicBoolean toCheck = new AtomicBoolean(false); if (plotWorld.getType() == PlotAreaType.PARTIAL) { boolean chunk1 = area.contains(x, z); boolean chunk2 = area.contains(ex, ez); if (!chunk1 && !chunk2) { - return false; + return RoadRegenerateResult.SUCCESS; } else { toCheck.set(chunk1 ^ chunk2); } @@ -624,10 +656,10 @@ public boolean regenerateRoad( PlotId id2 = manager.getPlotId(ex, 0, ez); x = x - plotWorld.ROAD_OFFSET_X; z -= plotWorld.ROAD_OFFSET_Z; - final int finalX = x; - final int finalZ = z; - final boolean enqueue; - final QueueCoordinator queue; + int finalX = x; + int finalZ = z; + boolean enqueue; + QueueCoordinator queue; if (queueCoordinator == null) { queue = this.blockQueue.getNewQueue(worldUtil.getWeWorld(plotWorld.getWorldName())); enqueue = true; @@ -635,7 +667,7 @@ public boolean regenerateRoad( queue = queueCoordinator; enqueue = false; } - if (id1 == null || id2 == null || id1 != id2) { + if (id1 == null || !id1.equals(id2)) { if (id1 != null) { Plot p1 = area.getPlotAbs(id1); if (p1 != null && p1.hasOwner() && p1.isMerged()) { @@ -720,9 +752,31 @@ public boolean regenerateRoad( if (enqueue) { queue.enqueue(); } - return true; } - return false; + return RoadRegenerateResult.SUCCESS; + } + + public enum RoadRegenerateResult { + + SUCCESS(true, null), + FAIL_ROAD_SCHEMATICS_NOT_ENABLED(false, "Plot world does not have road schematics enabled - are they correctly set up?"); + + private final boolean recoverable; + private final String message; + + RoadRegenerateResult(boolean recoverable, String message) { + this.recoverable = recoverable; + this.message = message; + } + + public boolean irrecoverable() { + return !recoverable; + } + + public String getMessage() { + return message; + } + } } diff --git a/Core/src/main/java/com/plotsquared/core/permissions/Permission.java b/Core/src/main/java/com/plotsquared/core/permissions/Permission.java index 4afbda3d3d..1828e1de91 100644 --- a/Core/src/main/java/com/plotsquared/core/permissions/Permission.java +++ b/Core/src/main/java/com/plotsquared/core/permissions/Permission.java @@ -145,6 +145,7 @@ public enum Permission implements ComponentLike { PERMISSION_FLAG_REMOVE("plots.flag.remove"), PERMISSION_FLAG_ADD("plots.flag.add"), PERMISSION_FLAG_LIST("plots.flag.list"), + PERMISSION_FLAG_LIST_OTHER("plots.flag.list.other"), PERMISSION_ADMIN_COMMAND_KICK("plots.admin.command.kick"), PERMISSION_GRANT_SINGLE("plots.grant"), PERMISSION_GRANT("plots.grant.%s"), diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java b/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java index d121aee8c6..aa8b1f2673 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java @@ -48,6 +48,7 @@ import com.plotsquared.core.queue.GlobalBlockQueue; import com.plotsquared.core.queue.QueueCoordinator; import com.plotsquared.core.util.MathMan; +import com.plotsquared.core.util.MinecraftVersion; import com.plotsquared.core.util.PlotExpression; import com.plotsquared.core.util.RegionUtil; import com.plotsquared.core.util.StringMan; @@ -325,7 +326,7 @@ public void loadDefaultConfiguration(ConfigurationSection config) { this.mobSpawnerSpawning = config.getBoolean("mob_spawner_spawning"); this.autoMerge = config.getBoolean("plot.auto_merge"); this.allowSigns = config.getBoolean("plot.create_signs"); - if (PlotSquared.platform().serverVersion()[1] == 13) { + if (MinecraftVersion.current().isOlderOrEqualThan(13)) { this.legacySignMaterial = config.getString("plot.legacy_sign_material"); } else { this.signMaterial = config.getString("plot.sign_material"); @@ -470,7 +471,7 @@ public void saveConfiguration(ConfigurationSection config) { options.put("mob_spawner_spawning", this.isMobSpawnerSpawning()); options.put("plot.auto_merge", this.isAutoMerge()); options.put("plot.create_signs", this.allowSigns()); - if (PlotSquared.platform().serverVersion()[1] == 13) { + if (MinecraftVersion.current().isOlderOrEqualThan(13)) { options.put("plot.legacy_sign_material", this.legacySignMaterial); } else { options.put("plot.sign_material", this.signMaterial()); diff --git a/Core/src/main/java/com/plotsquared/core/util/MinecraftVersion.java b/Core/src/main/java/com/plotsquared/core/util/MinecraftVersion.java new file mode 100644 index 0000000000..6dcd1450ba --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/util/MinecraftVersion.java @@ -0,0 +1,138 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * 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 . + */ +package com.plotsquared.core.util; + +import com.google.common.base.Preconditions; +import com.plotsquared.core.PlotSquared; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Represents a Minecraft version. For simplicity, and compatibility with modern versions, the `1.` prefix of older versions is + * ignored and the minor version part becomes the major version part. + *

+ *

    + *
  • `1.17.0` -> `(17, 0, 0)`
  • + *
  • `26.2` -> `(26, 2, 0)`
  • + *
  • `26.1.2` -> `(26, 1, 2)`
  • + *
+ * + * @param major the major part of the version string. For versions pre 26 this is the minor version part + * @param minor the minor part of the version string. For versions pre 26 this is the patch version part + * @param patch the patch part of the version string. For versions pre 26 this is always {@code 0} + */ +public record MinecraftVersion(int major, int minor, int patch) implements Comparable { + + /** + * Minecraft 1.17 release version + */ + public static final MinecraftVersion CAVES_AND_CLIFFS = new MinecraftVersion(17, 0, 0); + /** + * Minecraft 1.18 release version + */ + public static final MinecraftVersion CAVES_AND_CLIFFS_2 = new MinecraftVersion(18, 0, 0); + /** + * Minecraft 1.19 release version + */ + public static final MinecraftVersion THE_WILD_UPDATE = new MinecraftVersion(19, 0, 0); + /** + * Minecraft 1.20 release version + */ + public static final MinecraftVersion TRAILS_AND_TALES = new MinecraftVersion(20, 0, 0); + /** + * Minecraft 1.21 release version + */ + public static final MinecraftVersion TRICKY_TRIALS = new MinecraftVersion(21, 0, 0); + /** + * Minecraft 26.1 release version + */ + public static final MinecraftVersion TINY_TAKEOVER = new MinecraftVersion(26, 1, 0); + /** + * Minecraft 26.2 release version + */ + public static final MinecraftVersion CHAOS_CUBED = new MinecraftVersion(26, 2, 0); + + private static final Comparator COMPARATOR = Comparator + .comparingInt(MinecraftVersion::major) + .thenComparingInt(MinecraftVersion::minor) + .thenComparingInt(MinecraftVersion::patch); + + private static MinecraftVersion current; + + public static MinecraftVersion current() { + if (current == null) { + //noinspection deprecation + int[] parts = PlotSquared.platform().serverVersion(); + if (parts.length < 2) { + throw new IllegalStateException("Version string provided by platform is malformed: " + Arrays.toString(parts)); + } + // if real major version part is `1`, we are running on legacy versions + if (parts[0] == 1) { + // legacy versions don't have real patch versions (as their major part is basically a dummy) + current = new MinecraftVersion(parts[1], parts.length > 2 ? parts[2] : 0, 0); + } else { + // modern versions do have a optional patch version (e.g. 26.1.2) + current = new MinecraftVersion(parts[0], parts[1], parts.length > 2 ? parts[2] : 0); + } + } + return current; + } + + public MinecraftVersion { + Preconditions.checkArgument(this.major() > -1, "major version part must be positive or 0"); + Preconditions.checkArgument(this.minor() > -1, "major version part must be positive or 0"); + Preconditions.checkArgument(this.patch() > -1, "major version part must be positive or 0"); + } + + @Override + public int compareTo(@NotNull final MinecraftVersion o) { + return COMPARATOR.compare(this, o); + } + + public boolean isNewerOrEqualThan(MinecraftVersion other) { + return this.compareTo(other) >= 0; + } + + public boolean isOlderOrEqualThan(MinecraftVersion other) { + return this.compareTo(other) <= 0; + } + + public boolean isOlderThan(MinecraftVersion other) { + return this.compareTo(other) < 0; + } + + public boolean isNewerOrEqualThan(int otherMajor) { + return this.major() >= otherMajor; + } + + public boolean isNewerOrEqualThan(int otherMajor, int otherMinor) { + return this.major() > otherMajor || (this.major() == otherMajor && this.minor() >= otherMinor); + } + + public boolean isOlderOrEqualThan(int otherMajor) { + return this.major() <= otherMajor; + } + + public boolean isOlderOrEqualThan(int otherMajor, int otherMinor) { + return this.major() < otherMajor || (this.major() == otherMajor && this.minor() <= otherMinor); + } + +} diff --git a/Core/src/test/java/com/plotsquared/core/util/MinecraftVersionTest.java b/Core/src/test/java/com/plotsquared/core/util/MinecraftVersionTest.java new file mode 100644 index 0000000000..a0a7a5b2de --- /dev/null +++ b/Core/src/test/java/com/plotsquared/core/util/MinecraftVersionTest.java @@ -0,0 +1,88 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * 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 . + */ +package com.plotsquared.core.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MinecraftVersionTest { + + @Test + @DisplayName("isNewerOrEqualThan(MinecraftVersion)") + void isNewerOrEqualThan() { + assertTrue(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(MinecraftVersion.TRICKY_TRIALS)); + assertTrue(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(MinecraftVersion.TINY_TAKEOVER)); + assertTrue(new MinecraftVersion(1, 2, 3).isNewerOrEqualThan(new MinecraftVersion(1, 2, 2))); + assertFalse(MinecraftVersion.CAVES_AND_CLIFFS.isNewerOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS_2)); + } + + @Test + @DisplayName("isOlderOrEqualThan(MinecraftVersion)") + void isOlderOrEqualThan() { + assertTrue(MinecraftVersion.TRICKY_TRIALS.isOlderOrEqualThan(MinecraftVersion.TINY_TAKEOVER)); + assertTrue(MinecraftVersion.TRICKY_TRIALS.isOlderOrEqualThan(MinecraftVersion.TRICKY_TRIALS)); + assertTrue(new MinecraftVersion(1, 2, 2).isOlderOrEqualThan(new MinecraftVersion(1, 2, 3))); + assertFalse(MinecraftVersion.CAVES_AND_CLIFFS_2.isOlderOrEqualThan(MinecraftVersion.CAVES_AND_CLIFFS)); + } + + @Test + @DisplayName("isOlderThan(MinecraftVersion)") + void isOlderThan() { + assertTrue(MinecraftVersion.TRICKY_TRIALS.isOlderThan(MinecraftVersion.TINY_TAKEOVER)); + assertTrue(new MinecraftVersion(1, 2, 2).isOlderThan(new MinecraftVersion(1, 2, 3))); + assertFalse(MinecraftVersion.TINY_TAKEOVER.isOlderThan(MinecraftVersion.TRICKY_TRIALS)); + } + + @Test + @DisplayName("isNewerOrEqualThan(int)") + void isNewerOrEqualThanPrimitiveMajor() { + assertTrue(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(25)); + assertTrue(new MinecraftVersion(4, 5, 6).isNewerOrEqualThan(3)); + assertFalse(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(27)); + } + + @Test + @DisplayName("isNewerOrEqualThan(int, int)") + void isNewerOrEqualThanPrimitiveMajorMinor() { + assertTrue(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(25, 69)); + assertTrue(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(26, 1)); + assertTrue(new MinecraftVersion(4, 5, 6).isNewerOrEqualThan(4, 5)); + assertFalse(MinecraftVersion.TINY_TAKEOVER.isNewerOrEqualThan(26, 2)); + } + + @Test + @DisplayName("isOlderOrEqualThan(int)") + void isOlderOrEqualThanPrimitiveMajor() { + assertTrue(MinecraftVersion.TINY_TAKEOVER.isOlderOrEqualThan(27)); + assertTrue(new MinecraftVersion(4, 5, 6).isOlderOrEqualThan(5)); + assertFalse(MinecraftVersion.TINY_TAKEOVER.isOlderOrEqualThan(20)); + } + + @Test + @DisplayName("isOlderOrEqualThan(int, int)") + void isOlderOrEqualThanPrimitiveMajorMinor() { + assertTrue(MinecraftVersion.TINY_TAKEOVER.isOlderOrEqualThan(27, 69)); + assertTrue(MinecraftVersion.TINY_TAKEOVER.isOlderOrEqualThan(26, 2)); + assertTrue(new MinecraftVersion(4, 5, 6).isOlderOrEqualThan(4, 5)); + assertFalse(MinecraftVersion.TINY_TAKEOVER.isOlderOrEqualThan(26, 0)); + } + +} diff --git a/build.gradle.kts b/build.gradle.kts index 8da0037a6f..885e845156 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -216,7 +216,7 @@ tasks.getByName("jar") { enabled = false } -val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8") +val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.11", "26.1.2") tasks { register("cacheLatestFaweArtifact") { val lastSuccessfulBuildUrl = uri("https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/api/json").toURL() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d3802546fc..34cf060d80 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ # Platform expectations paper = "1.20.4-R0.1-SNAPSHOT" guice = "7.0.0" -spotbugs = "4.9.8" -checkerqual = "3.54.0" +spotbugs = "4.10.3" +checkerqual = "4.2.1" gson = "2.10" guava = "31.1-jre" snakeyaml = "2.0" @@ -13,8 +13,8 @@ log4j = "2.19.0" # Plugins worldedit = "7.2.20" -fawe = "2.15.0" -placeholderapi = "2.12.2" +fawe = "2.15.3" +placeholderapi = "2.12.3" luckperms = "5.5" essentialsx = "2.21.2" mvdwapi = "3.1.1" @@ -27,16 +27,15 @@ arkitektonika = "2.1.4" squirrelid = "0.3.2" paster = "1.1.7" bstats = "3.2.1" -paperlib = "1.0.8" informative-annotations = "1.6" vault = "1.7.1" serverlib = "2.3.7" # Gradle plugins -shadow = "9.4.0" +shadow = "9.6.1" grgit = "4.1.1" -spotless = "8.4.0" -publish = "0.36.0" +spotless = "8.9.0" +publish = "0.37.0" runPaper = "3.0.2" [libraries] @@ -73,7 +72,6 @@ arkitektonika = { group = "com.intellectualsites.arkitektonika", name = "Arkitek paster = { group = "com.intellectualsites.paster", name = "Paster", version.ref = "paster" } bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" } informativeAnnotations = { group = "com.intellectualsites.informative-annotations", name = "informative-annotations", version.ref = "informative-annotations" } -paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" } vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" } serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d997cfc60f..b1b8ef56b4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118f7d..a9db11550c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 739907dfd1..249efbb032 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index e509b2dd8f..8508ef684d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,29 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%