package com.projectsacrifice.game; import android.content.Context; import android.content.SharedPreferences; public final class ProgressStore { private static final String FILE = "project_sacrifice_progress_v1"; public static final int CURRENT_SAVE_VERSION = 3; private final SharedPreferences preferences; public ProgressStore(Context context) { preferences = context.getSharedPreferences(FILE, Context.MODE_PRIVATE); migrateIfSupported(); } public int unlockedCount() { return Math.max(1, Math.min(LevelCatalog.all().size(), preferences.getInt("unlocked", 1))); } public boolean isCompleted(int levelNumber) { return isCompleted(levelId(levelNumber)); } public boolean isCompleted(String levelId) { return preferences.getBoolean(idKey(levelId, "completed"), false); } public int bestDeaths(int levelNumber) { return bestDeaths(levelId(levelNumber)); } public int bestDeaths(String levelId) { return preferences.getInt(idKey(levelId, "deaths"), Integer.MAX_VALUE); } public long bestTimeMs(int levelNumber) { return bestTimeMs(levelId(levelNumber)); } public long bestTimeMs(String levelId) { return preferences.getLong(idKey(levelId, "time"), Long.MAX_VALUE); } public boolean collectibleFound(String levelId) { return preferences.getBoolean(idKey(levelId, "collectible"), false); } public void recordCompletion(int levelNumber, int deaths, long elapsedMs) { recordCompletion(LevelCatalog.get(levelNumber - 1), deaths, elapsedMs, false); } public void recordCompletion(LevelDefinition level, int deaths, long elapsedMs, boolean collectibleFound) { if (level == null) throw new IllegalArgumentException("level is required"); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean(idKey(level.levelId, "completed"), true); editor.putInt(idKey(level.levelId, "deaths"), Math.min(Math.max(0, deaths), bestDeaths(level.levelId))); editor.putLong(idKey(level.levelId, "time"), Math.min(Math.max(0L, elapsedMs), bestTimeMs(level.levelId))); if (collectibleFound || collectibleFound(level.levelId)) { editor.putBoolean(idKey(level.levelId, "collectible"), true); } editor.putInt("unlocked", Math.min(LevelCatalog.all().size(), Math.max(unlockedCount(), level.number + 1))); editor.apply(); } public int earnedStars(LevelDefinition level) { if (level == null || !isCompleted(level.levelId)) return 0; return ChallengeResult.evaluate(level, bestDeaths(level.levelId), bestTimeMs(level.levelId), collectibleFound(level.levelId)).stars(); } public int saveVersion() { return preferences.getInt("save_version", 1); } private void migrateIfSupported() { int version = preferences.getInt("save_version", 1); if (version >= 1 && version < CURRENT_SAVE_VERSION) { SharedPreferences.Editor editor = preferences.edit(); if (version < 3) migrateNumericKeys(editor); editor.putInt("save_version", CURRENT_SAVE_VERSION).apply(); } } private void migrateNumericKeys(SharedPreferences.Editor editor) { for (LevelDefinition level : LevelCatalog.all()) { copyBooleanIfPresent(editor, key(level.number, "completed"), idKey(level.levelId, "completed")); copyIntIfPresent(editor, key(level.number, "deaths"), idKey(level.levelId, "deaths")); copyLongIfPresent(editor, key(level.number, "time"), idKey(level.levelId, "time")); } } private void copyBooleanIfPresent(SharedPreferences.Editor editor, String oldKey, String newKey) { if (preferences.contains(oldKey) && !preferences.contains(newKey)) { editor.putBoolean(newKey, preferences.getBoolean(oldKey, false)); } } private void copyIntIfPresent(SharedPreferences.Editor editor, String oldKey, String newKey) { if (preferences.contains(oldKey) && !preferences.contains(newKey)) { editor.putInt(newKey, preferences.getInt(oldKey, Integer.MAX_VALUE)); } } private void copyLongIfPresent(SharedPreferences.Editor editor, String oldKey, String newKey) { if (preferences.contains(oldKey) && !preferences.contains(newKey)) { editor.putLong(newKey, preferences.getLong(oldKey, Long.MAX_VALUE)); } } private static String levelId(int levelNumber) { if (levelNumber >= 1 && levelNumber <= LevelCatalog.all().size()) { return LevelCatalog.get(levelNumber - 1).levelId; } return LevelDefinition.defaultLevelId(levelNumber); } private static String key(int levelNumber, String suffix) { return "level_" + levelNumber + "_" + suffix; } private static String idKey(String levelId, String suffix) { return "level_id_" + levelId + "_" + suffix; } }