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,131 @@
package dev.loki.loparkour.ghost;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.Config;
import org.bukkit.Location;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class GhostManager {
private static final File GHOST_FOLDER = LoParkour.getInFolder("ghosts");
private static final int MAX_GHOSTS_PER_MODE = 3;
private final Map<String, List<GhostData>> ghostsByMode = new HashMap<>();
private final List<GhostPlayer> activeGhosts = new ArrayList<>();
public GhostManager() {
if (!GHOST_FOLDER.exists()) {
GHOST_FOLDER.mkdirs();
}
}
public void loadGhosts(@NotNull String mode) {
File modeFolder = new File(GHOST_FOLDER, mode);
if (!modeFolder.exists()) {
return;
}
List<GhostData> ghosts = new ArrayList<>();
File[] files = modeFolder.listFiles((dir, name) -> name.endsWith(".ghost"));
if (files != null) {
for (File file : files) {
try {
GhostData data = GhostData.loadFromFile(file);
ghosts.add(data);
} catch (IOException e) {
LoParkour.logging().error("Failed to load ghost: " + file.getName());
}
}
}
ghosts.sort((a, b) -> Integer.compare(b.getScore(), a.getScore()));
ghostsByMode.put(mode, ghosts);
}
public void saveGhost(@NotNull String mode, @NotNull GhostData data) {
File modeFolder = new File(GHOST_FOLDER, mode);
if (!modeFolder.exists()) {
modeFolder.mkdirs();
}
List<GhostData> ghosts = ghostsByMode.getOrDefault(mode, new ArrayList<>());
ghosts.add(data);
ghosts.sort((a, b) -> Integer.compare(b.getScore(), a.getScore()));
while (ghosts.size() > MAX_GHOSTS_PER_MODE) {
GhostData removed = ghosts.remove(ghosts.size() - 1);
File file = new File(modeFolder, removed.getPlayerName() + ".ghost");
file.delete();
}
ghostsByMode.put(mode, ghosts);
File file = new File(modeFolder, data.getPlayerName() + ".ghost");
try {
data.saveToFile(file);
} catch (IOException e) {
LoParkour.logging().error("Failed to save ghost: " + file.getName());
}
}
public void spawnGhosts(@NotNull String mode, @NotNull Location startLocation, @NotNull World world) {
if (!Config.CONFIG.getBoolean("ghost-mode.enabled")) {
return;
}
stopAllGhosts();
List<GhostData> ghosts = ghostsByMode.get(mode);
if (ghosts == null || ghosts.isEmpty()) {
return;
}
int showTop = Math.min(Config.CONFIG.getInt("ghost-mode.show-top"), ghosts.size());
for (int i = 0; i < showTop; i++) {
GhostPlayer ghost = new GhostPlayer(ghosts.get(i), world);
ghost.spawn(startLocation);
activeGhosts.add(ghost);
}
}
public void stopAllGhosts() {
for (GhostPlayer ghost : activeGhosts) {
ghost.stop();
}
activeGhosts.clear();
}
@Nullable
public GhostData getTopGhost(@NotNull String mode) {
List<GhostData> ghosts = ghostsByMode.get(mode);
if (ghosts == null || ghosts.isEmpty()) {
return null;
}
return ghosts.get(0);
}
public List<GhostData> getTopGhosts(@NotNull String mode, int count) {
List<GhostData> ghosts = ghostsByMode.get(mode);
if (ghosts == null || ghosts.isEmpty()) {
return Collections.emptyList();
}
return ghosts.subList(0, Math.min(count, ghosts.size()));
}
public boolean shouldRecordGhost(@NotNull String mode, int score) {
List<GhostData> ghosts = ghostsByMode.get(mode);
if (ghosts == null || ghosts.size() < MAX_GHOSTS_PER_MODE) {
return true;
}
GhostData lowestGhost = ghosts.get(ghosts.size() - 1);
return score > lowestGhost.getScore();
}
}