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
This commit is contained in:
loki5512344 2026-08-05 22:48:26 +02:00
parent 3b5d3d97d5
commit c0ca488b9d
Signed by: boba
GPG key ID: 253067914055423B
13 changed files with 260 additions and 36 deletions

65
.github/workflows/build.yml vendored Normal file
View file

@ -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

View file

@ -10,15 +10,16 @@ group = "dev.loki"
version = "1.3.3" version = "1.3.3"
description = "LoParkour - Advanced parkour plugin for Minecraft" description = "LoParkour - Advanced parkour plugin for Minecraft"
java { // Multi-version build: pass -PmcVersion / -PapiCoordinate / -PjavaRelease / -PapiVersion
toolchain { // to build the same source against a different Paper API (e.g. GitHub Actions matrix).
languageVersion = JavaLanguageVersion.of(21) 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 { repositories {
mavenCentral() mavenCentral()
maven { maven {
name = "papermc" name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/") 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 { dependencies {
// Spigot API // Spigot/Paper API (overridable per MC version via -PapiCoordinate).
compileOnly("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT") // 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) // Adventure API (for Component)
compileOnly("net.kyori:adventure-api:4.14.0") compileOnly("net.kyori:adventure-api:4.14.0")
@ -116,7 +137,7 @@ dependencies {
tasks.shadowJar { tasks.shadowJar {
archiveClassifier.set("") archiveClassifier.set("")
archiveFileName.set("LoParkour-${project.version}.jar") archiveFileName.set("LoParkour-${project.version}-mc${mcVersion}.jar")
// Relocate shaded dependencies // Relocate shaded dependencies
relocate("dev.lolib", "dev.loki.loparkour.lib.lolib") relocate("dev.lolib", "dev.loki.loparkour.lib.lolib")
@ -141,7 +162,8 @@ tasks.withType<ProcessResources> {
filesMatching(listOf("plugin.yml", "config.yml")) { filesMatching(listOf("plugin.yml", "config.yml")) {
expand( expand(
"version" to project.version, "version" to project.version,
"description" to project.description "description" to project.description,
"apiVersion" to apiVersion
) )
} }
} }
@ -152,7 +174,7 @@ tasks.withType<Test> {
tasks.withType<JavaCompile> { tasks.withType<JavaCompile> {
options.encoding = "UTF-8" options.encoding = "UTF-8"
options.release = 21 options.release = javaRelease
} }
tasks.withType<Javadoc> { tasks.withType<Javadoc> {

View file

@ -23,7 +23,8 @@ public class JoinCommandExecutor {
if (!player.cooldown(sender, "join", COOLDOWN_MS) || !ParkourOption.JOIN.mayPerform(p)) { if (!player.cooldown(sender, "join", COOLDOWN_MS) || !ParkourOption.JOIN.mayPerform(p)) {
return; return;
} }
if (Registry.getMode(arg) instanceof Mode mode) { final Mode mode = Registry.getMode(arg);
if (mode != null) {
mode.create(p); mode.create(p);
return; return;
} }

View file

@ -1,6 +1,7 @@
package dev.loki.loparkour.elytra.generator; package dev.loki.loparkour.elytra.generator;
import dev.loki.loparkour.elytra.section.ElytraSection; import dev.loki.loparkour.elytra.section.ElytraSection;
import dev.loki.loparkour.util.misc.Compat;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Particle; import org.bukkit.Particle;
@ -23,6 +24,7 @@ final class ElytraBoost {
private static final float BLAST_PITCH = 1.4f; private static final float BLAST_PITCH = 1.4f;
private static final double TRAIL_OFFSET = 0.2; private static final double TRAIL_OFFSET = 0.2;
private static final double TRAIL_SPEED = 0.02; private static final double TRAIL_SPEED = 0.02;
private static final Particle SPARK = Compat.particle("FIREWORKS_SPARK", "FIREWORK");
private int boostedSectionIdx = Integer.MIN_VALUE; private int boostedSectionIdx = Integer.MIN_VALUE;
void tick(Player player, ElytraSection section, int idx) { void tick(Player player, ElytraSection section, int idx) {
@ -53,7 +55,9 @@ final class ElytraBoost {
.add(direction.clone().multiply(INITIAL_PUSH)); .add(direction.clone().multiply(INITIAL_PUSH));
player.setVelocity(velocity); player.setVelocity(velocity);
final Location loc = player.getLocation(); 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_LAUNCH, 1.0f, 1.0f);
loc.getWorld().playSound(loc, Sound.ENTITY_FIREWORK_ROCKET_BLAST, BLAST_VOLUME, BLAST_PITCH); loc.getWorld().playSound(loc, Sound.ENTITY_FIREWORK_ROCKET_BLAST, BLAST_VOLUME, BLAST_PITCH);
} }
@ -64,9 +68,9 @@ final class ElytraBoost {
player.setGliding(true); player.setGliding(true);
} }
player.setVelocity(player.getVelocity().add(direction.clone().multiply(SUSTAINED_BOOST))); 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(); 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); TRAIL_OFFSET, TRAIL_OFFSET, TRAIL_OFFSET, TRAIL_SPEED);
} }
} }

View file

@ -13,6 +13,7 @@ import org.bukkit.World;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import dev.loki.loparkour.util.misc.Compat;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@ -49,7 +50,7 @@ public class ClientBlockChanger {
public void check(Player player, Material material) { public void check(Player player, Material material) {
final int playerCx = player.getLocation().getChunk().getX(); 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 vd = rawVd > 0 ? rawVd : Math.max(DEFAULT_MIN_VIEW_DISTANCE, player.getWorld().getViewDistance());
final int forwardLimit = playerCx + Math.min(vd, MAX_FORWARD_CHUNKS); final int forwardLimit = playerCx + Math.min(vd, MAX_FORWARD_CHUNKS);
@ -111,7 +112,7 @@ public class ClientBlockChanger {
state.setType(material); state.setType(material);
states.add(state); states.add(state);
} }
player.sendBlockChanges(states); Compat.sendBlockChanges(player, states);
} }
/** /**
@ -134,7 +135,7 @@ public class ClientBlockChanger {
state.setType(material); state.setType(material);
states.add(state); states.add(state);
} }
player.sendBlockChanges(states); Compat.sendBlockChanges(player, states);
} }
} }
} }

View file

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; 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.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction; import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import org.bukkit.Material; import org.bukkit.Material;
@ -16,11 +17,8 @@ import org.bukkit.entity.Player;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
/** /**
* Represents a section of the total parkour of size KNOTS. * A parkour section of {@value #KNOTS} knots; stores Vectors (no chunk pinning).
* * Display blocks are sent client-side and undone in {@link #clear(Player)}.
* <p>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)}.</p>
*/ */
public class ElytraSection { public class ElytraSection {
@ -155,7 +153,7 @@ public class ElytraSection {
state.setType(Material.AIR); state.setType(Material.AIR);
states.add(state); states.add(state);
} }
player.sendBlockChanges(states); Compat.sendBlockChanges(player, states);
} }
localBlocks.clear(); localBlocks.clear();
} }

View file

@ -123,7 +123,6 @@ public class BlockSelector {
material == Material.JUNGLE_FENCE || material == Material.JUNGLE_FENCE ||
material == Material.ACACIA_FENCE || material == Material.ACACIA_FENCE ||
material == Material.MANGROVE_FENCE || material == Material.MANGROVE_FENCE ||
material == Material.CHERRY_FENCE ||
material == Material.CRIMSON_FENCE || material == Material.CRIMSON_FENCE ||
material == Material.WARPED_FENCE || material == Material.WARPED_FENCE ||
material == Material.NETHER_BRICK_FENCE; material == Material.NETHER_BRICK_FENCE;
@ -137,7 +136,6 @@ public class BlockSelector {
material == Material.JUNGLE_TRAPDOOR || material == Material.JUNGLE_TRAPDOOR ||
material == Material.ACACIA_TRAPDOOR || material == Material.ACACIA_TRAPDOOR ||
material == Material.MANGROVE_TRAPDOOR || material == Material.MANGROVE_TRAPDOOR ||
material == Material.CHERRY_TRAPDOOR ||
material == Material.CRIMSON_TRAPDOOR || material == Material.CRIMSON_TRAPDOOR ||
material == Material.WARPED_TRAPDOOR || material == Material.WARPED_TRAPDOOR ||
material == Material.IRON_TRAPDOOR; material == Material.IRON_TRAPDOOR;

View file

@ -2,6 +2,7 @@ package dev.loki.loparkour.generator.jump.placement;
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.util.misc.Compat;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Particle; import org.bukkit.Particle;
@ -31,11 +32,14 @@ class BlockWorldPlacer {
block.setBlockData(data, false); block.setBlockData(data, false);
final Location loc = block.getLocation().add(BLOCK_CENTER_OFFSET, BLOCK_CENTER_OFFSET, BLOCK_CENTER_OFFSET); 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()) { for (final ParkourPlayer player : generator.getPlayers()) {
if (particle != null) {
player.player.spawnParticle( player.player.spawnParticle(
Particle.BLOCK_CRACK, loc, PARTICLE_COUNT, particle, loc, PARTICLE_COUNT,
PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_EXTRA, data PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_OFFSET, PARTICLE_EXTRA, data
); );
}
player.player.playSound(loc, Sound.BLOCK_STONE_PLACE, PLACE_SOUND_VOLUME, 1.0f); player.player.playSound(loc, Sound.BLOCK_STONE_PLACE, PLACE_SOUND_VOLUME, 1.0f);
} }
} }

View file

@ -2,6 +2,7 @@ package dev.loki.loparkour.mode.impl.barrier;
import dev.loki.loparkour.generator.core.model.GeneratorState; import dev.loki.loparkour.generator.core.model.GeneratorState;
import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.util.misc.Compat;
import org.bukkit.Color; import org.bukkit.Color;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@ -144,12 +145,15 @@ class BarrierRenderer {
final double length = Math.sqrt(dx * dx + dy * dy + dz * dz); final double length = Math.sqrt(dx * dx + dy * dy + dz * dz);
final int steps = Math.max(1, (int) (length / PARTICLE_STEP)); final int steps = Math.max(1, (int) (length / PARTICLE_STEP));
final Particle dust = Compat.particle("REDSTONE", "DUST");
for (int i = 0; i <= steps; i++) { for (int i = 0; i <= steps; i++) {
final double t = (double) i / steps; final double t = (double) i / steps;
final double px = start.x + dx * t; final double px = start.x + dx * t;
final double py = start.y + dy * t; final double py = start.y + dy * t;
final double pz = start.z + dz * 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);
}
} }
} }
} }

View file

@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.Modes;
import dev.loki.loparkour.config.core.Config; import dev.loki.loparkour.config.core.Config;
import dev.loki.loparkour.config.locale.Locales; import dev.loki.loparkour.config.locale.Locales;
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; 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.leaderboard.core.Leaderboard;
import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourPlayer;
import dev.loki.loparkour.session.core.Session; 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; ? Config.CONFIG.getInt("modes.gravity-shift.interval") : DEFAULT_INTERVAL;
this.effects = new HashMap<>(); this.effects = new HashMap<>();
loadEffect("jump-boost", PotionEffectType.JUMP); loadEffect("jump-boost", Compat.potionEffect("JUMP_BOOST", "JUMP"));
loadEffect("speed", PotionEffectType.SPEED); loadEffect("speed", PotionEffectType.SPEED);
loadEffect("slowness", PotionEffectType.SLOW); loadEffect("slowness", Compat.potionEffect("SLOWNESS", "SLOW"));
loadEffect("levitation", PotionEffectType.LEVITATION); loadEffect("levitation", PotionEffectType.LEVITATION);
// Initialize jump counts for all players // Initialize jump counts for all players

View file

@ -4,7 +4,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.schematic.schem.SchemLoader; import dev.loki.loparkour.schematic.schem.SchemLoader;
import dev.loki.loparkour.config.core.Config; 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.configuration.ConfigurationSection;
import org.bukkit.structure.Structure; import org.bukkit.structure.Structure;
import org.bukkit.structure.StructureManager; import org.bukkit.structure.StructureManager;
@ -93,7 +93,10 @@ public final class SchematicManager {
return 0; return 0;
} }
final StructureManager structureManager = Bukkit.getStructureManager(); final StructureManager structureManager = Compat.getStructureManager();
if (structureManager == null) {
return 0;
}
int ok = 0; int ok = 0;
for (final File file : files) { for (final File file : files) {
final String stem = stem(file, ".nbt"); final String stem = stem(file, ".nbt");

View file

@ -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 &lt; 1.19.3).
*/
public static void sendBlockChanges(@Nullable Player player, @Nullable Collection<BlockState> 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;
}
}

View file

@ -2,7 +2,7 @@ name: 'LoParkour'
description: 'Advanced infinitely generating parkour plugin.' description: 'Advanced infinitely generating parkour plugin.'
author: loki author: loki
version: ${version} version: ${version}
api-version: 1.20 api-version: ${apiVersion}
main: dev.loki.loparkour.LoParkour main: dev.loki.loparkour.LoParkour
softdepend: [floodgate, Vault, PlaceholderAPI, Multiverse-Core, VoidGen] softdepend: [floodgate, Vault, PlaceholderAPI, Multiverse-Core, VoidGen]
commands: commands: