SaveManager.java
package com.gvi.project.helper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.gvi.project.GamePanel;
import com.gvi.project.models.game_maps.GameMaps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
public class SaveManager {
private static final Logger log = LoggerFactory.getLogger(SaveManager.class);
private static final Path DEFAULT_SAVE_DIR = Path.of(System.getProperty("user.home"), ".sql-dungeon", "saves");
private static final DateTimeFormatter TIMESTAMP_FMT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private final ObjectMapper mapper;
private final Path saveDir;
public SaveManager() {
this(DEFAULT_SAVE_DIR);
}
SaveManager(Path saveDir) {
this.saveDir = saveDir;
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
private static String resolveCurrentMapName(GamePanel gp) {
if (gp.currentMap == null) return GameMaps.MAP_01.name();
try {
GameMaps.valueOf(gp.currentMap.name.toUpperCase());
return gp.currentMap.name.toUpperCase();
} catch (IllegalArgumentException e) {
return GameMaps.MAP_01.name();
}
}
private static GameMaps parseMap(String name) {
if (name == null) return GameMaps.MAP_01;
try {
return GameMaps.valueOf(name);
} catch (IllegalArgumentException e) {
return GameMaps.MAP_01;
}
}
private Path slotFile(int slot) {
return saveDir.resolve("save_" + slot + ".json");
}
public boolean hasSave(int slot) {
return Files.exists(slotFile(slot));
}
public boolean deleteSave(int slot) {
try {
return Files.deleteIfExists(slotFile(slot));
} catch (IOException e) {
return false;
}
}
public SlotInfo getSlotInfo(int slot) {
if (!hasSave(slot)) return new SlotInfo(false, null, 0, null, null);
try {
SaveData d = mapper.readValue(slotFile(slot).toFile(), SaveData.class);
return new SlotInfo(true, d.playerName, d.score, d.currentMap, d.savedAt);
} catch (IOException e) {
return new SlotInfo(false, null, 0, null, null);
}
}
public boolean save(GamePanel gp, int slot) {
SaveData data = new SaveData();
data.playerName = gp.player.playerName;
data.worldX = gp.player.worldX;
data.worldY = gp.player.worldY;
data.gridX = gp.player.gridX;
data.gridY = gp.player.gridY;
data.direction = gp.player.direction;
data.score = gp.player.score;
data.healthHalf = gp.player.healthHalf;
data.maxHealthHalf = gp.player.maxHealthHalf;
data.playerItems = new HashMap<>(gp.player.playerItems);
data.currentMap = resolveCurrentMapName(gp);
data.savedAt = LocalDateTime.now().format(TIMESTAMP_FMT);
data.playtime = gp.ui != null ? gp.ui.playtime : 0;
gp.progressManager.snapshotCurrentMap(gp);
data.allMapObjects = new HashMap<>(gp.progressManager.getSnapshots());
try {
Files.createDirectories(saveDir);
mapper.writeValue(slotFile(slot).toFile(), data);
return true;
} catch (IOException e) {
log.warn("Save slot {} failed", slot, e);
return false;
}
}
public boolean load(GamePanel gp, int slot) {
if (!hasSave(slot)) return false;
SaveData data;
try {
data = mapper.readValue(slotFile(slot).toFile(), SaveData.class);
} catch (IOException e) {
log.warn("Load slot {} failed", slot, e);
return false;
}
gp.player.playerName = data.playerName != null ? data.playerName : "Player";
gp.player.worldX = data.worldX;
gp.player.worldY = data.worldY;
gp.player.gridX = data.gridX;
gp.player.gridY = data.gridY;
gp.player.targetGridX = data.gridX;
gp.player.targetGridY = data.gridY;
gp.player.direction = data.direction != null ? data.direction : "down";
gp.player.score = data.score;
gp.player.healthHalf = data.healthHalf;
gp.player.maxHealthHalf = data.maxHealthHalf;
gp.player.playerItems = data.playerItems != null ? new HashMap<>(data.playerItems) : new HashMap<>();
gp.player.isMoving = false;
gp.player.isDead = false;
if (gp.ui != null) gp.ui.playtime = data.playtime;
gp.progressManager.reset();
gp.currentMap = null;
gp.progressManager.restoreFrom(data.allMapObjects);
gp.loadMap(parseMap(data.currentMap));
return true;
}
public record SlotInfo(boolean exists, String playerName, int score, String mapName, String savedAt) {}
}