LoParkour/src/main/java/dev/loki/loparkour/LoParkour.java
loki5512344 8c8046a4f5
feat: personal run replays (ghost system)
- GhostData: worldName field, format v4; GhostManager stores last N runs per player (ghosts/<uuid>/<ts>.ghost)
- GhostService: per-player recording (start on run start, every tick, finalize on fall), gated by ghost-mode.enabled + per-player toggle
- PlayerGhost: packet-based fake-player NPC with viewer skin (NMS via remapNms gradle task, reflection send, graceful degradation)
- GhostReplay: teleport viewer to run start, replay frames via PlayerGhost, restore on finish
- /lp replay [index] command + tab completion; settings menu ghost_recording toggle (persisted disk/SQL)
- config: ghost-mode.{enabled,max-runs}, remove dead show-top/transparency + GHOST_* statics
- tests: GhostData v4 round-trip, GhostManager eviction/ordering, yawToFixed (79 total)
2026-08-06 13:13:59 +02:00

132 lines
3.8 KiB
Java

package dev.loki.loparkour;
import dev.loki.loparkour.bootstrap.PluginBootstrap;
import dev.loki.loparkour.config.core.Config;
import dev.loki.loparkour.ghost.service.GhostService;
import dev.loki.loparkour.hook.papi.PAPIHook;
import dev.loki.loparkour.mode.base.Modes;
import dev.loki.loparkour.player.core.ParkourUser;
import dev.loki.loparkour.reward.core.Rewards;
import dev.loki.loparkour.schematic.core.SchematicManager;
import dev.loki.loparkour.storage.Storage;
import dev.loki.loparkour.world.core.Divider;
import dev.loki.loparkour.world.core.World;
import dev.lolib.core.LoPlugin;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.logging.Level;
/**
* Main class of LoParkour
*
* @author loki
*/
public final class LoParkour extends LoPlugin {
public static final String NAME = "&#FF6464&lLoParkour&r";
public static final String PREFIX = NAME + " &#404040» &#A0A0A0";
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.create();
private static LoParkour instance;
private static SchematicManager schematicManager;
private static GhostService ghostService;
@Nullable
private static PAPIHook placeholderHook;
public static Gson getGson() {
return GSON;
}
public static void log(String message) {
if (Config.CONFIG.getBoolean("debug")) {
LoParkour.getPlugin().getLogger().info("[Debug] " + message);
}
}
public static File getInFolder(String child) {
return new File(instance.getDataFolder(), child);
}
public static LoParkour getPlugin() {
return instance;
}
@Nullable
public static PAPIHook getPlaceholderHook() {
return placeholderHook;
}
public static SchematicManager getSchematicManager() {
return schematicManager;
}
public static GhostService getGhostService() {
return ghostService;
}
public void setSchematicManager(@NotNull SchematicManager manager) {
schematicManager = manager;
}
public void setGhostService(@NotNull GhostService service) {
ghostService = service;
}
public void setPlaceholderHook(@Nullable PAPIHook hook) {
placeholderHook = hook;
}
@Override
public void enable() {
instance = this;
PluginBootstrap.enable(this);
}
@Override
public void disable() {
PluginBootstrap.disable(this);
}
/** Called from {@link PluginBootstrap} on shutdown. */
public void runShutdownSequence() {
try {
new ArrayList<>(Divider.SECTIONS.keySet()).forEach(session -> {
try {
session.onAllPlayersLeft();
} catch (Exception e) {
getLogger().warning("Error cleaning up session: " + e.getMessage());
}
});
new ArrayList<>(Divider.SECTIONS.keySet()).forEach(Divider::remove);
for (final ParkourUser user : ParkourUser.getUsers()) {
ParkourUser.leave(user);
}
if (Modes.DEFAULT != null && Modes.DEFAULT.getLeaderboard() != null) {
Modes.DEFAULT.getLeaderboard().write(false);
}
Rewards.clear();
dev.loki.loparkour.adaptive.bootstrap.AdaptiveServices.shutdown();
if (ghostService != null) {
ghostService.shutdown();
}
Storage.close();
World.delete();
} catch (Throwable t) {
getLogger().log(Level.SEVERE, "Error during plugin shutdown: " + t.getMessage(), t);
}
}
}