From c0ca488b9d36bec93b65661833110c95e5629a68 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Wed, 5 Aug 2026 22:48:26 +0200 Subject: [PATCH] ci: multi-version build matrix + multi-MC compat layer - Parametrize build: -PmcVersion / -PapiCoordinate / -PjavaRelease / -PapiVersion; jar name includes mc version, plugin.yml api-version parametrized - Compat (reflection): sendBlockChanges / getClientViewDistance / getStructureManager (Paper 1.19.3+); particle()/potionEffect() name lookup for 1.21 renames (BLOCK_CRACK->BLOCK, REDSTONE->DUST, FIREWORKS_SPARK-> FIREWORK, JUMP->JUMP_BOOST, SLOW->SLOWNESS); drop CHERRY materials (1.20+) - Fix Java 17 'unconditional instanceof' in JoinCommandExecutor - capabilitiesResolution for org.bukkit:bukkit capability conflict on newer paper-api artifacts - .github/workflows/build.yml: matrix for 1.19.2/1.20.1 (Java 17), 1.21/1.21.11 (Java 21), 26.1.2/26.2 (Java 25) + quality gate - Verified locally: default + 1.19.2/1.20.1/1.21/1.21.11 build; 26.x deps resolve --- .github/workflows/build.yml | 65 +++++++++ build.gradle.kts | 44 +++++-- .../command/player/JoinCommandExecutor.java | 3 +- .../elytra/generator/ElytraBoost.java | 10 +- .../elytra/section/ClientBlockChanger.java | 7 +- .../elytra/section/ElytraSection.java | 10 +- .../jump/placement/BlockSelector.java | 2 - .../jump/placement/BlockWorldPlacer.java | 12 +- .../mode/impl/barrier/BarrierRenderer.java | 6 +- .../mode/impl/solo/GravityShiftMode.java | 5 +- .../schematic/core/SchematicManager.java | 7 +- .../dev/loki/loparkour/util/misc/Compat.java | 123 ++++++++++++++++++ src/main/resources/plugin.yml | 2 +- 13 files changed, 260 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 src/main/java/dev/loki/loparkour/util/misc/Compat.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fdec47d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,65 @@ +name: Build + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +jobs: + # Build a shadowJar per Minecraft version against the matching Paper API. + build-matrix: + name: Build mc-${{ matrix.mc }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # 1.19.2 — Java 17 + - { mc: "1.19.2", coordinate: "io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT", java: 17, release: 17, api: "1.19" } + # 1.20.1 — Java 17 + - { mc: "1.20.1", coordinate: "io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT", java: 17, release: 17, api: "1.20" } + # 1.21 — Java 21 + - { mc: "1.21", coordinate: "io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT", java: 21, release: 21, api: "1.20" } + # 1.21.11 — Java 21 + - { mc: "1.21.11", coordinate: "io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT", java: 21, release: 21, api: "1.21" } + # 26.1.2 / 26.2 — Java 25 (new-style paper-api build artifacts; may need a + # Gradle wrapper >= 8.14 if the daemon rejects Java 25 — bump if so). + - { mc: "26.1.2", coordinate: "io.papermc.paper:paper-api:26.1.2.build.74-stable", java: 25, release: 25, api: "1.21" } + - { mc: "26.2", coordinate: "io.papermc.paper:paper-api:26.2.build.97-stable", java: 25, release: 25, api: "1.21" } + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ matrix.java }} + - name: Build jar (mc ${{ matrix.mc }}) + run: > + ./gradlew --no-daemon shadowJar + -PmcVersion=${{ matrix.mc }} + -PapiCoordinate='${{ matrix.coordinate }}' + -PjavaRelease=${{ matrix.release }} + -PapiVersion=${{ matrix.api }} + - name: Upload jar + uses: actions/upload-artifact@v4 + with: + name: LoParkour-${{ matrix.mc }} + path: build/libs/LoParkour-*.jar + + # Full quality gate (checkstyle + PMD + tests) on the default build. + quality: + name: Quality gate (default 1.20.4) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - name: Full build + run: ./gradlew --no-daemon build + - name: Upload default jar + uses: actions/upload-artifact@v4 + with: + name: LoParkour-default-1.20.4 + path: build/libs/LoParkour-*.jar diff --git a/build.gradle.kts b/build.gradle.kts index 0885b7f..f864b11 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,15 +10,16 @@ group = "dev.loki" version = "1.3.3" description = "LoParkour - Advanced parkour plugin for Minecraft" -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} +// Multi-version build: pass -PmcVersion / -PapiCoordinate / -PjavaRelease / -PapiVersion +// to build the same source against a different Paper API (e.g. GitHub Actions matrix). +val mcVersion: String = providers.gradleProperty("mcVersion").getOrElse("1.20.4") +val apiCoordinate: String = providers.gradleProperty("apiCoordinate") + .getOrElse("org.spigotmc:spigot-api:$mcVersion-R0.1-SNAPSHOT") +val javaRelease: Int = providers.gradleProperty("javaRelease").map { it.toInt() }.getOrElse(21) +val apiVersion: String = providers.gradleProperty("apiVersion").getOrElse("1.20") repositories { mavenCentral() - maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -76,9 +77,29 @@ repositories { } } +// Resolve the org.bukkit:bukkit capability conflict that newer paper-api artifacts +// introduce (they both bundle the bukkit classes and declare the capability). +configurations.configureEach { + resolutionStrategy.capabilitiesResolution.withCapability("org.bukkit:bukkit") { + selectHighestVersion() + } +} + dependencies { - // Spigot API - compileOnly("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT") + // Spigot/Paper API (overridable per MC version via -PapiCoordinate). + // Newer paper-api artifacts declare a conflicting org.bukkit:bukkit capability + // (they bundle the classes themselves) — drop the duplicate transitive. + if (apiCoordinate.startsWith("io.papermc.paper:paper-api")) { + val (apiGroup, apiName, apiVersion) = apiCoordinate.split(":") + compileOnly(mapOf( + "group" to apiGroup, + "name" to apiName, + "version" to apiVersion, + "exclude" to mapOf("group" to "org.bukkit", "module" to "bukkit") + )) + } else { + compileOnly(apiCoordinate) + } // Adventure API (for Component) compileOnly("net.kyori:adventure-api:4.14.0") @@ -116,7 +137,7 @@ dependencies { tasks.shadowJar { archiveClassifier.set("") - archiveFileName.set("LoParkour-${project.version}.jar") + archiveFileName.set("LoParkour-${project.version}-mc${mcVersion}.jar") // Relocate shaded dependencies relocate("dev.lolib", "dev.loki.loparkour.lib.lolib") @@ -141,7 +162,8 @@ tasks.withType { filesMatching(listOf("plugin.yml", "config.yml")) { expand( "version" to project.version, - "description" to project.description + "description" to project.description, + "apiVersion" to apiVersion ) } } @@ -152,7 +174,7 @@ tasks.withType { tasks.withType { options.encoding = "UTF-8" - options.release = 21 + options.release = javaRelease } tasks.withType { diff --git a/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java b/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java index 4a08852..b852e68 100644 --- a/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java +++ b/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java @@ -23,7 +23,8 @@ public class JoinCommandExecutor { if (!player.cooldown(sender, "join", COOLDOWN_MS) || !ParkourOption.JOIN.mayPerform(p)) { return; } - if (Registry.getMode(arg) instanceof Mode mode) { + final Mode mode = Registry.getMode(arg); + if (mode != null) { mode.create(p); return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/generator/ElytraBoost.java b/src/main/java/dev/loki/loparkour/elytra/generator/ElytraBoost.java index 0ad12de..7acbd30 100644 --- a/src/main/java/dev/loki/loparkour/elytra/generator/ElytraBoost.java +++ b/src/main/java/dev/loki/loparkour/elytra/generator/ElytraBoost.java @@ -1,6 +1,7 @@ package dev.loki.loparkour.elytra.generator; import dev.loki.loparkour.elytra.section.ElytraSection; +import dev.loki.loparkour.util.misc.Compat; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Particle; @@ -23,6 +24,7 @@ final class ElytraBoost { private static final float BLAST_PITCH = 1.4f; private static final double TRAIL_OFFSET = 0.2; private static final double TRAIL_SPEED = 0.02; + private static final Particle SPARK = Compat.particle("FIREWORKS_SPARK", "FIREWORK"); private int boostedSectionIdx = Integer.MIN_VALUE; void tick(Player player, ElytraSection section, int idx) { @@ -53,7 +55,9 @@ final class ElytraBoost { .add(direction.clone().multiply(INITIAL_PUSH)); player.setVelocity(velocity); final Location loc = player.getLocation(); - loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc, FIREWORK_COUNT, BURST_OFFSET, BURST_OFFSET, BURST_OFFSET, BURST_SPEED); + if (SPARK != null) { + loc.getWorld().spawnParticle(SPARK, loc, FIREWORK_COUNT, BURST_OFFSET, BURST_OFFSET, BURST_OFFSET, BURST_SPEED); + } loc.getWorld().playSound(loc, Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1.0f, 1.0f); loc.getWorld().playSound(loc, Sound.ENTITY_FIREWORK_ROCKET_BLAST, BLAST_VOLUME, BLAST_PITCH); } @@ -64,9 +68,9 @@ final class ElytraBoost { player.setGliding(true); } player.setVelocity(player.getVelocity().add(direction.clone().multiply(SUSTAINED_BOOST))); - if (player.getTicksLived() % PARTICLE_EVERY_TICKS == 0) { + if (SPARK != null && player.getTicksLived() % PARTICLE_EVERY_TICKS == 0) { final Location loc = player.getLocation(); - loc.getWorld().spawnParticle(Particle.FIREWORKS_SPARK, loc, FIREWORK_PARTICLE_COUNT, + loc.getWorld().spawnParticle(SPARK, loc, FIREWORK_PARTICLE_COUNT, TRAIL_OFFSET, TRAIL_OFFSET, TRAIL_OFFSET, TRAIL_SPEED); } } diff --git a/src/main/java/dev/loki/loparkour/elytra/section/ClientBlockChanger.java b/src/main/java/dev/loki/loparkour/elytra/section/ClientBlockChanger.java index 6093194..f7c2bdb 100644 --- a/src/main/java/dev/loki/loparkour/elytra/section/ClientBlockChanger.java +++ b/src/main/java/dev/loki/loparkour/elytra/section/ClientBlockChanger.java @@ -13,6 +13,7 @@ import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import dev.loki.loparkour.util.misc.Compat; import org.jetbrains.annotations.NotNull; /** @@ -49,7 +50,7 @@ public class ClientBlockChanger { public void check(Player player, Material material) { final int playerCx = player.getLocation().getChunk().getX(); - final int rawVd = player.getClientViewDistance(); + final int rawVd = Compat.getClientViewDistance(player); final int vd = rawVd > 0 ? rawVd : Math.max(DEFAULT_MIN_VIEW_DISTANCE, player.getWorld().getViewDistance()); final int forwardLimit = playerCx + Math.min(vd, MAX_FORWARD_CHUNKS); @@ -111,7 +112,7 @@ public class ClientBlockChanger { state.setType(material); states.add(state); } - player.sendBlockChanges(states); + Compat.sendBlockChanges(player, states); } /** @@ -134,7 +135,7 @@ public class ClientBlockChanger { state.setType(material); states.add(state); } - player.sendBlockChanges(states); + Compat.sendBlockChanges(player, states); } } } diff --git a/src/main/java/dev/loki/loparkour/elytra/section/ElytraSection.java b/src/main/java/dev/loki/loparkour/elytra/section/ElytraSection.java index 9234d5a..d86cdab 100644 --- a/src/main/java/dev/loki/loparkour/elytra/section/ElytraSection.java +++ b/src/main/java/dev/loki/loparkour/elytra/section/ElytraSection.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import dev.loki.loparkour.util.misc.Compat; import org.apache.commons.math3.analysis.interpolation.SplineInterpolator; import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; import org.bukkit.Material; @@ -16,11 +17,8 @@ import org.bukkit.entity.Player; import org.bukkit.util.Vector; /** - * Represents a section of the total parkour of size KNOTS. - * - *

Stores {@link Vector} coordinates instead of Bukkit Block instances to avoid - * pinning chunks in memory. Display blocks are sent client-side via - * {@link Player#sendBlockChanges} and undone in {@link #clear(Player)}.

+ * A parkour section of {@value #KNOTS} knots; stores Vectors (no chunk pinning). + * Display blocks are sent client-side and undone in {@link #clear(Player)}. */ public class ElytraSection { @@ -155,7 +153,7 @@ public class ElytraSection { state.setType(Material.AIR); states.add(state); } - player.sendBlockChanges(states); + Compat.sendBlockChanges(player, states); } localBlocks.clear(); } diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java index d3c06de..d44248b 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockSelector.java @@ -123,7 +123,6 @@ public class BlockSelector { material == Material.JUNGLE_FENCE || material == Material.ACACIA_FENCE || material == Material.MANGROVE_FENCE || - material == Material.CHERRY_FENCE || material == Material.CRIMSON_FENCE || material == Material.WARPED_FENCE || material == Material.NETHER_BRICK_FENCE; @@ -137,7 +136,6 @@ public class BlockSelector { material == Material.JUNGLE_TRAPDOOR || material == Material.ACACIA_TRAPDOOR || material == Material.MANGROVE_TRAPDOOR || - material == Material.CHERRY_TRAPDOOR || material == Material.CRIMSON_TRAPDOOR || material == Material.WARPED_TRAPDOOR || material == Material.IRON_TRAPDOOR; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java index af4557c..303777a 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java @@ -2,6 +2,7 @@ package dev.loki.loparkour.generator.jump.placement; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.util.misc.Compat; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Particle; @@ -31,11 +32,14 @@ class BlockWorldPlacer { block.setBlockData(data, false); final Location loc = block.getLocation().add(BLOCK_CENTER_OFFSET, BLOCK_CENTER_OFFSET, BLOCK_CENTER_OFFSET); + final Particle particle = Compat.particle("BLOCK_CRACK", "BLOCK"); for (final ParkourPlayer player : generator.getPlayers()) { - player.player.spawnParticle( - Particle.BLOCK_CRACK, loc, PARTICLE_COUNT, - PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_EXTRA, data - ); + if (particle != null) { + player.player.spawnParticle( + particle, loc, PARTICLE_COUNT, + PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_EXTRA, data + ); + } player.player.playSound(loc, Sound.BLOCK_STONE_PLACE, PLACE_SOUND_VOLUME, 1.0f); } } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierRenderer.java b/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierRenderer.java index b619680..0191795 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierRenderer.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierRenderer.java @@ -2,6 +2,7 @@ package dev.loki.loparkour.mode.impl.barrier; import dev.loki.loparkour.generator.core.model.GeneratorState; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.util.misc.Compat; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; @@ -144,12 +145,15 @@ class BarrierRenderer { final double length = Math.sqrt(dx * dx + dy * dy + dz * dz); final int steps = Math.max(1, (int) (length / PARTICLE_STEP)); + final Particle dust = Compat.particle("REDSTONE", "DUST"); for (int i = 0; i <= steps; i++) { final double t = (double) i / steps; final double px = start.x + dx * t; final double py = start.y + dy * t; final double pz = start.z + dz * t; - player.spawnParticle(Particle.REDSTONE, px, py, pz, 1, options); + if (dust != null) { + player.spawnParticle(dust, px, py, pz, 1, options); + } } } } \ No newline at end of file diff --git a/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java b/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java index ce9f5ed..f9eb54a 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.Modes; import dev.loki.loparkour.config.core.Config; import dev.loki.loparkour.config.locale.Locales; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; +import dev.loki.loparkour.util.misc.Compat; import dev.loki.loparkour.leaderboard.core.Leaderboard; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; @@ -87,9 +88,9 @@ public class GravityShiftMode implements Mode { ? Config.CONFIG.getInt("modes.gravity-shift.interval") : DEFAULT_INTERVAL; this.effects = new HashMap<>(); - loadEffect("jump-boost", PotionEffectType.JUMP); + loadEffect("jump-boost", Compat.potionEffect("JUMP_BOOST", "JUMP")); loadEffect("speed", PotionEffectType.SPEED); - loadEffect("slowness", PotionEffectType.SLOW); + loadEffect("slowness", Compat.potionEffect("SLOWNESS", "SLOW")); loadEffect("levitation", PotionEffectType.LEVITATION); // Initialize jump counts for all players diff --git a/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java b/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java index 419d81b..2e591b4 100644 --- a/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java +++ b/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java @@ -4,7 +4,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.schematic.schem.SchemLoader; import dev.loki.loparkour.config.core.Config; -import org.bukkit.Bukkit; +import dev.loki.loparkour.util.misc.Compat; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.structure.Structure; import org.bukkit.structure.StructureManager; @@ -93,7 +93,10 @@ public final class SchematicManager { return 0; } - final StructureManager structureManager = Bukkit.getStructureManager(); + final StructureManager structureManager = Compat.getStructureManager(); + if (structureManager == null) { + return 0; + } int ok = 0; for (final File file : files) { final String stem = stem(file, ".nbt"); diff --git a/src/main/java/dev/loki/loparkour/util/misc/Compat.java b/src/main/java/dev/loki/loparkour/util/misc/Compat.java new file mode 100644 index 0000000..eb55a3d --- /dev/null +++ b/src/main/java/dev/loki/loparkour/util/misc/Compat.java @@ -0,0 +1,123 @@ +package dev.loki.loparkour.util.misc; + +import org.bukkit.Bukkit; +import org.bukkit.Particle; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.structure.StructureManager; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.util.Collection; + +/** + * Reflection helpers for APIs that do not exist on every Minecraft version + * (sendBlockChanges / getClientViewDistance are Paper 1.19.3+; StructureManager + * is Bukkit 1.19.3+). Lets the single source tree compile against any Paper API + * and degrade gracefully at runtime on older servers. + */ +public final class Compat { + + private Compat() { + } + + private static final Method SEND_BLOCK_CHANGES = find(Player.class, "sendBlockChanges", Collection.class); + private static final Method CLIENT_VIEW_DISTANCE = find(Player.class, "getClientViewDistance"); + private static final Method STRUCTURE_MANAGER = findServerStructureManager(); + + private static Method findServerStructureManager() { + try { + return find(Bukkit.getServer().getClass(), "getStructureManager"); + } catch (IllegalStateException ignored) { + return null; + } + } + + /** + * Sends a batch of client-side block changes. No-op on servers where the + * method does not exist (Paper < 1.19.3). + */ + public static void sendBlockChanges(@Nullable Player player, @Nullable Collection states) { + if (player == null || states == null || states.isEmpty() || SEND_BLOCK_CHANGES == null) { + return; + } + try { + SEND_BLOCK_CHANGES.invoke(player, states); + } catch (ReflectiveOperationException ignored) { + // API removed at runtime — degrade gracefully + } + } + + /** + * Client view distance (Paper), or -1 when unavailable so the caller can + * fall back to the world view distance. + */ + public static int getClientViewDistance(@Nullable Player player) { + if (player == null || CLIENT_VIEW_DISTANCE == null) { + return -1; + } + try { + return (int) CLIENT_VIEW_DISTANCE.invoke(player); + } catch (ReflectiveOperationException ignored) { + return -1; + } + } + + /** + * {@link Bukkit#getStructureManager()}, or {@code null} on versions without it. + */ + @Nullable + public static StructureManager getStructureManager() { + if (STRUCTURE_MANAGER == null) { + return null; + } + try { + return (StructureManager) STRUCTURE_MANAGER.invoke(Bukkit.getServer()); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + @Nullable + private static Method find(Class type, String name, Class... params) { + try { + return type.getMethod(name, params); + } catch (NoSuchMethodException ignored) { + return null; + } + } + + /** + * Resolves a {@link Particle} by trying each candidate name in order. Particle + * enum constants were renamed between API versions (e.g. BLOCK_CRACK → BLOCK, + * REDSTONE → DUST, FIREWORKS_SPARK → FIREWORK), so pass the old and the new name. + * Returns {@code null} when none of the names exist. + */ + @Nullable + public static Particle particle(String... candidateNames) { + for (final String name : candidateNames) { + try { + return Particle.valueOf(name); + } catch (IllegalArgumentException ignored) { + // try next candidate + } + } + return null; + } + + /** + * Resolves a {@link PotionEffectType} by name (JUMP → JUMP_BOOST, SLOW → + * SLOWNESS were renamed in 1.21), or {@code null} when none exists. + */ + @Nullable + public static PotionEffectType potionEffect(String... candidateNames) { + for (final String name : candidateNames) { + final PotionEffectType type = PotionEffectType.getByName(name); + if (type != null) { + return type; + } + } + return null; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6884eb9..f1bb873 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: 'LoParkour' description: 'Advanced infinitely generating parkour plugin.' author: loki version: ${version} -api-version: 1.20 +api-version: ${apiVersion} main: dev.loki.loparkour.LoParkour softdepend: [floodgate, Vault, PlaceholderAPI, Multiverse-Core, VoidGen] commands: