package dev.loki.loparkour.ghost; import org.jetbrains.annotations.NotNull; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Immutable snapshot of a player's run: who ran it, their score, and every recorded frame. * *

Bugs fixed vs original: *

*/ public class GhostData { /** Binary format version — bump when changing the serialised layout. */ private static final int FORMAT_VERSION = 2; private final UUID playerUuid; private final String playerName; private final int score; private final List frames; public GhostData(@NotNull UUID playerUuid, @NotNull String playerName, int score, @NotNull List frames) { this.playerUuid = playerUuid; this.playerName = playerName; this.score = score; this.frames = new ArrayList<>(frames); } // ── Getters ─────────────────────────────────────────────────────────────── public UUID getPlayerUuid() { return playerUuid; } public String getPlayerName() { return playerName; } public int getScore() { return score; } public List getFrames() { return frames; } // ── Serialisation ───────────────────────────────────────────────────────── public void saveToFile(@NotNull File file) throws IOException { try (DataOutputStream out = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(file)))) { out.writeInt(FORMAT_VERSION); // version header out.writeUTF(playerUuid.toString()); // UUID (filename-safe, collision-free) out.writeUTF(playerName); // display name (for the ghost label) out.writeInt(score); out.writeInt(frames.size()); for (GhostFrame frame : frames) { out.writeLong(frame.getTimestamp()); out.writeDouble(frame.getX()); out.writeDouble(frame.getY()); out.writeDouble(frame.getZ()); out.writeFloat(frame.getYaw()); out.writeFloat(frame.getPitch()); } } } /** * Loads a ghost from file. * * @return The loaded data, or {@code null} if the file is an old v1 format * (no version header / UUID) — caller should delete and ignore it. */ public static GhostData loadFromFile(@NotNull File file) throws IOException { try (DataInputStream in = new DataInputStream( new BufferedInputStream(new FileInputStream(file)))) { int version = in.readInt(); if (version != FORMAT_VERSION) { // Old format (v1) started with readUTF for playerName, not an int version. // Those files are stale — return null so the manager can discard them. return null; } UUID uuid = UUID.fromString(in.readUTF()); String name = in.readUTF(); int score = in.readInt(); int frameCount = in.readInt(); List frames = new ArrayList<>(frameCount); for (int i = 0; i < frameCount; i++) { frames.add(new GhostFrame( in.readLong(), in.readDouble(), in.readDouble(), in.readDouble(), in.readFloat(), in.readFloat())); } return new GhostData(uuid, name, score, frames); } } }