v1.2.4: Remove roguelike perks, update bStats to 3.2.1, add new game modes and lpschem system

- Removed roguelike perk system (double jump, magnet, shield, second chance)
- Updated bStats dependency to 3.2.1 with proper relocation
- Changed bStats plugin ID to 29754
- Added new game modes: Speedrun, Gravity Shift, Hardcore
- Implemented .lpschem format with GZIP + JSON compression
- Added JumpValidator for physics-based jump validation
- Added JumpType system (neo-jump, head-hitter, fence, trapdoor, ladder)
- Implemented Ghost system for recording/playback top players
- Added loparkour-vilib as local dependency
- Updated config with new mode settings and removed roguelike section
- Cleaned up TODO.md roadmap
This commit is contained in:
loki 2026-02-25 00:55:24 +01:00
parent aecb03ccf8
commit 8aeb547897
68 changed files with 6186 additions and 163 deletions

View file

@ -0,0 +1,172 @@
package dev.loki.loparkour.mode;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.generator.ParkourGenerator;
import dev.loki.loparkour.leaderboard.Leaderboard;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.item.Item;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class GravityShiftMode implements Mode {
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
private final Random random = new Random();
@Override
@NotNull
public String getName() {
return "gravity-shift";
}
@Override
@Nullable
public Item getItem(String locale) {
return Locales.getItem(locale, "play.single.gravity-shift");
}
@Override
@NotNull
public Leaderboard getLeaderboard() {
return leaderboard;
}
@Override
public void create(Player player) {
if (!Config.CONFIG.getBoolean("joining")) {
player.sendMessage("Joining is currently disabled.");
return;
}
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof GravityShiftMode) {
return;
}
player.closeInventory();
Session.create(session -> new GravityShiftGenerator(session), null, null, player);
}
private static class GravityShiftGenerator extends ParkourGenerator {
private int jumpsUntilShift;
private final int shiftInterval;
private PotionEffect currentEffect;
public GravityShiftGenerator(@NotNull Session session) {
super(session);
this.shiftInterval = Config.CONFIG.getInt("modes.gravity-shift.interval");
this.jumpsUntilShift = shiftInterval;
}
@Override
protected void score() {
super.score();
jumpsUntilShift--;
if (jumpsUntilShift <= 0) {
applyRandomEffect();
jumpsUntilShift = shiftInterval;
}
}
private void applyRandomEffect() {
if (currentEffect != null) {
player.player.removePotionEffect(currentEffect.getType());
}
List<EffectData> effects = new ArrayList<>();
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.jump-boost.enabled")) {
effects.add(new EffectData(
PotionEffectType.JUMP,
Config.CONFIG.getInt("modes.gravity-shift.effects.jump-boost.amplifier"),
Config.CONFIG.getInt("modes.gravity-shift.effects.jump-boost.duration"),
"Jump Boost"
));
}
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.speed.enabled")) {
effects.add(new EffectData(
PotionEffectType.SPEED,
Config.CONFIG.getInt("modes.gravity-shift.effects.speed.amplifier"),
Config.CONFIG.getInt("modes.gravity-shift.effects.speed.duration"),
"Speed"
));
}
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.slowness.enabled")) {
effects.add(new EffectData(
PotionEffectType.SLOW,
Config.CONFIG.getInt("modes.gravity-shift.effects.slowness.amplifier"),
Config.CONFIG.getInt("modes.gravity-shift.effects.slowness.duration"),
"Slowness"
));
}
if (Config.CONFIG.getBoolean("modes.gravity-shift.effects.levitation.enabled")) {
effects.add(new EffectData(
PotionEffectType.LEVITATION,
Config.CONFIG.getInt("modes.gravity-shift.effects.levitation.amplifier"),
Config.CONFIG.getInt("modes.gravity-shift.effects.levitation.duration"),
"Levitation"
));
}
if (effects.isEmpty()) {
return;
}
EffectData selected = effects.get(new Random().nextInt(effects.size()));
currentEffect = new PotionEffect(
selected.type,
selected.duration * 20,
selected.amplifier - 1,
false,
true,
true
);
player.player.addPotionEffect(currentEffect);
player.sendTranslated("modes.gravity-shift.effect-applied", selected.name);
}
@Override
public void reset(boolean regenerate) {
if (currentEffect != null && player != null && player.player != null) {
player.player.removePotionEffect(currentEffect.getType());
}
jumpsUntilShift = shiftInterval;
super.reset(regenerate);
}
@Override
public Mode getMode() {
return Modes.GRAVITY_SHIFT;
}
}
private static class EffectData {
final PotionEffectType type;
final int amplifier;
final int duration;
final String name;
EffectData(PotionEffectType type, int amplifier, int duration, String name) {
this.type = type;
this.amplifier = amplifier;
this.duration = duration;
this.name = name;
}
}
}

View file

@ -0,0 +1,77 @@
package dev.loki.loparkour.mode;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.generator.ParkourGenerator;
import dev.loki.loparkour.leaderboard.Leaderboard;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.item.Item;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class HardcoreMode implements Mode {
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
@Override
@NotNull
public String getName() {
return "hardcore";
}
@Override
@Nullable
public Item getItem(String locale) {
return Locales.getItem(locale, "play.single.hardcore");
}
@Override
@NotNull
public Leaderboard getLeaderboard() {
return leaderboard;
}
@Override
public void create(Player player) {
if (!Config.CONFIG.getBoolean("joining")) {
player.sendMessage("Joining is currently disabled.");
return;
}
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof HardcoreMode) {
return;
}
player.closeInventory();
Session.create(session -> new HardcoreGenerator(session), null, null, player);
}
private static class HardcoreGenerator extends ParkourGenerator {
public HardcoreGenerator(@NotNull Session session) {
super(session);
}
@Override
protected void fall() {
boolean resetRewards = Config.CONFIG.getBoolean("modes.hardcore.reset-rewards");
if (resetRewards) {
player.sendTranslated("modes.hardcore.rewards-lost");
}
player.sendTranslated("modes.hardcore.full-reset");
super.fall();
}
@Override
public Mode getMode() {
return Modes.HARDCORE;
}
}
}

View file

@ -6,9 +6,15 @@ public class Modes {
public static DefaultMode DEFAULT;
public static SpectatorMode SPECTATOR;
public static SpeedrunMode SPEEDRUN;
public static GravityShiftMode GRAVITY_SHIFT;
public static HardcoreMode HARDCORE;
public static void init() {
DEFAULT = (DefaultMode) Registry.getMode("default");
SPECTATOR = (SpectatorMode) Registry.getMode("spectator");
SPEEDRUN = (SpeedrunMode) Registry.getMode("speedrun");
GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift");
HARDCORE = (HardcoreMode) Registry.getMode("hardcore");
}
}

View file

@ -0,0 +1,112 @@
package dev.loki.loparkour.mode;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.Config;
import dev.loki.loparkour.config.Locales;
import dev.loki.loparkour.generator.ParkourGenerator;
import dev.loki.loparkour.leaderboard.Leaderboard;
import dev.loki.loparkour.player.ParkourPlayer;
import dev.loki.loparkour.session.Session;
import dev.efnilite.vilib.inventory.item.Item;
import dev.efnilite.vilib.util.Task;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class SpeedrunMode implements Mode {
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
private final Map<Block, Long> blockTimestamps = new HashMap<>();
@Override
@NotNull
public String getName() {
return "speedrun";
}
@Override
@Nullable
public Item getItem(String locale) {
return Locales.getItem(locale, "play.single.speedrun");
}
@Override
@NotNull
public Leaderboard getLeaderboard() {
return leaderboard;
}
@Override
public void create(Player player) {
if (!Config.CONFIG.getBoolean("joining")) {
player.sendMessage("Joining is currently disabled.");
return;
}
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof SpeedrunMode) {
return;
}
player.closeInventory();
Session.create(session -> new SpeedrunGenerator(session), null, null, player);
}
public void onBlockTouch(@NotNull Block block, @NotNull ParkourPlayer player) {
if (!blockTimestamps.containsKey(block)) {
blockTimestamps.put(block, System.currentTimeMillis());
scheduleBlockRemoval(player, block);
}
}
private void scheduleBlockRemoval(@NotNull ParkourPlayer player, @NotNull Block block) {
double blockLifetime = Config.CONFIG.getDouble("modes.speedrun.block-lifetime");
double warningTime = Config.CONFIG.getDouble("modes.speedrun.warning-time");
long removalTime = (long) (blockLifetime * 1000);
long warningTimeMs = (long) (warningTime * 1000);
Task.create(LoParkour.getPlugin())
.delay((int) ((removalTime - warningTimeMs) / 50))
.execute(() -> {
if (block.getType() != Material.AIR) {
player.player.playSound(block.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f);
player.player.spawnParticle(Particle.FLAME, block.getLocation().add(0.5, 1, 0.5), 10, 0.3, 0.3, 0.3, 0.01);
}
})
.run();
Task.create(LoParkour.getPlugin())
.delay((int) (removalTime / 50))
.execute(() -> {
if (block.getType() != Material.AIR) {
block.setType(Material.AIR);
blockTimestamps.remove(block);
}
})
.run();
}
public void reset() {
blockTimestamps.clear();
}
private static class SpeedrunGenerator extends ParkourGenerator {
public SpeedrunGenerator(@NotNull Session session) {
super(session);
}
@Override
public Mode getMode() {
return Modes.SPEEDRUN;
}
}
}