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:
parent
3b5d3d97d5
commit
c0ca488b9d
13 changed files with 260 additions and 36 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <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>
|
||||
* 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
123
src/main/java/dev/loki/loparkour/util/misc/Compat.java
Normal file
123
src/main/java/dev/loki/loparkour/util/misc/Compat.java
Normal 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 < 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue