Compare commits
3 Commits
56b568a832
...
4af3830bb5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4af3830bb5 | ||
|
|
cfc1537af2 | ||
|
|
606d052e2c |
@@ -116,7 +116,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
// 세이브 파일에서 미리보기 정보 추출
|
// 세이브 파일에서 미리보기 정보 추출
|
||||||
final (outcome, state) = await _controller.saveManager.loadState();
|
final (outcome, state, _) = await _controller.saveManager.loadState();
|
||||||
if (outcome.success && state != null) {
|
if (outcome.success && state != null) {
|
||||||
final actName = _getActName(state.progress.plotStageCount);
|
final actName = _getActName(state.progress.plotStageCount);
|
||||||
preview = SavedGamePreview(
|
preview = SavedGamePreview(
|
||||||
@@ -477,7 +477,10 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
).then((_) {
|
||||||
|
// 새 게임 후 돌아오면 세이브 정보 갱신
|
||||||
|
_checkForExistingSave();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadSave(BuildContext context) async {
|
Future<void> _loadSave(BuildContext context) async {
|
||||||
@@ -502,11 +505,8 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
|
|
||||||
if (selectedFileName == null || !context.mounted) return;
|
if (selectedFileName == null || !context.mounted) return;
|
||||||
|
|
||||||
// 선택된 파일 로드
|
// 선택된 파일 로드 (치트 모드는 저장된 상태에서 복원)
|
||||||
await _controller.loadAndStart(
|
await _controller.loadAndStart(fileName: selectedFileName);
|
||||||
fileName: selectedFileName,
|
|
||||||
cheatsEnabled: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_controller.status == GameSessionStatus.running) {
|
if (_controller.status == GameSessionStatus.running) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@@ -553,7 +553,10 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
onThemeModeChange: _changeThemeMode,
|
onThemeModeChange: _changeThemeMode,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
).then((_) {
|
||||||
|
// 게임에서 돌아오면 세이브 정보 갱신
|
||||||
|
_checkForExistingSave();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 10: 명예의 전당 화면으로 이동
|
/// Phase 10: 명예의 전당 화면으로 이동
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class ProgressLoop {
|
|||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = null;
|
_timer = null;
|
||||||
if (saveOnStop && _autoSaveConfig.onStop && saveManager != null) {
|
if (saveOnStop && _autoSaveConfig.onStop && saveManager != null) {
|
||||||
await saveManager!.saveState(_state);
|
await saveManager!.saveState(_state, cheatsEnabled: cheatsEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ class ProgressLoop {
|
|||||||
_stateController.add(_state);
|
_stateController.add(_state);
|
||||||
|
|
||||||
if (saveManager != null && _autoSaveConfig.shouldSave(result)) {
|
if (saveManager != null && _autoSaveConfig.shouldSave(result)) {
|
||||||
saveManager!.saveState(_state);
|
saveManager!.saveState(_state, cheatsEnabled: cheatsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사망 시 루프 정지 및 콜백 호출 (Phase 4)
|
// 사망 시 루프 정지 및 콜백 호출 (Phase 4)
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ class GameSave {
|
|||||||
required this.skillBook,
|
required this.skillBook,
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.queue,
|
required this.queue,
|
||||||
|
this.cheatsEnabled = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory GameSave.fromState(GameState state) {
|
factory GameSave.fromState(GameState state, {bool cheatsEnabled = false}) {
|
||||||
return GameSave(
|
return GameSave(
|
||||||
version: kSaveVersion,
|
version: kSaveVersion,
|
||||||
rngState: state.rng.state,
|
rngState: state.rng.state,
|
||||||
@@ -29,6 +30,7 @@ class GameSave {
|
|||||||
skillBook: state.skillBook,
|
skillBook: state.skillBook,
|
||||||
progress: state.progress,
|
progress: state.progress,
|
||||||
queue: state.queue,
|
queue: state.queue,
|
||||||
|
cheatsEnabled: cheatsEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +43,12 @@ class GameSave {
|
|||||||
final SkillBook skillBook;
|
final SkillBook skillBook;
|
||||||
final ProgressState progress;
|
final ProgressState progress;
|
||||||
final QueueState queue;
|
final QueueState queue;
|
||||||
|
final bool cheatsEnabled;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'version': version,
|
'version': version,
|
||||||
|
'cheatsEnabled': cheatsEnabled,
|
||||||
'rng': rngState,
|
'rng': rngState,
|
||||||
'traits': {
|
'traits': {
|
||||||
'name': traits.name,
|
'name': traits.name,
|
||||||
@@ -144,6 +148,7 @@ class GameSave {
|
|||||||
|
|
||||||
return GameSave(
|
return GameSave(
|
||||||
version: json['version'] as int? ?? kSaveVersion,
|
version: json['version'] as int? ?? kSaveVersion,
|
||||||
|
cheatsEnabled: json['cheatsEnabled'] as bool? ?? false,
|
||||||
rngState: json['rng'] as int? ?? 0,
|
rngState: json['rng'] as int? ?? 0,
|
||||||
traits: Traits(
|
traits: Traits(
|
||||||
name: traitsJson['name'] as String? ?? '',
|
name: traitsJson['name'] as String? ?? '',
|
||||||
|
|||||||
@@ -13,19 +13,23 @@ class SaveManager {
|
|||||||
|
|
||||||
/// Save current game state to disk. [fileName] may be absolute or relative.
|
/// Save current game state to disk. [fileName] may be absolute or relative.
|
||||||
/// Returns outcome with error on failure.
|
/// Returns outcome with error on failure.
|
||||||
Future<SaveOutcome> saveState(GameState state, {String? fileName}) {
|
Future<SaveOutcome> saveState(
|
||||||
final save = GameSave.fromState(state);
|
GameState state, {
|
||||||
|
String? fileName,
|
||||||
|
bool cheatsEnabled = false,
|
||||||
|
}) {
|
||||||
|
final save = GameSave.fromState(state, cheatsEnabled: cheatsEnabled);
|
||||||
return _repo.save(save, fileName ?? defaultFileName);
|
return _repo.save(save, fileName ?? defaultFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load game state from disk. [fileName] may be absolute (e.g., file picker).
|
/// Load game state from disk. [fileName] may be absolute (e.g., file picker).
|
||||||
/// Returns outcome + optional state.
|
/// Returns outcome + optional state + cheatsEnabled flag.
|
||||||
Future<(SaveOutcome, GameState?)> loadState({String? fileName}) async {
|
Future<(SaveOutcome, GameState?, bool)> loadState({String? fileName}) async {
|
||||||
final (outcome, save) = await _repo.load(fileName ?? defaultFileName);
|
final (outcome, save) = await _repo.load(fileName ?? defaultFileName);
|
||||||
if (!outcome.success || save == null) {
|
if (!outcome.success || save == null) {
|
||||||
return (outcome, null);
|
return (outcome, null, false);
|
||||||
}
|
}
|
||||||
return (outcome, save.toState());
|
return (outcome, save.toState(), save.cheatsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 저장 파일 목록 조회
|
/// 저장 파일 목록 조회
|
||||||
|
|||||||
@@ -528,8 +528,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
|
|
||||||
/// 모바일 재진입 시 전체 화면 재로드
|
/// 모바일 재진입 시 전체 화면 재로드
|
||||||
Future<void> _reloadGameScreen() async {
|
Future<void> _reloadGameScreen() async {
|
||||||
// 세이브 파일에서 다시 로드
|
// 세이브 파일에서 다시 로드 (치트 모드는 저장된 상태에서 복원)
|
||||||
await widget.controller.loadAndStart(cheatsEnabled: widget.controller.cheatsEnabled);
|
await widget.controller.loadAndStart();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
@@ -550,7 +550,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
final currentState = widget.controller.state;
|
final currentState = widget.controller.state;
|
||||||
if (currentState == null || !widget.controller.isRunning) return;
|
if (currentState == null || !widget.controller.isRunning) return;
|
||||||
|
|
||||||
await widget.controller.saveManager.saveState(currentState);
|
await widget.controller.saveManager.saveState(
|
||||||
|
currentState,
|
||||||
|
cheatsEnabled: widget.controller.cheatsEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 뒤로가기 시 저장 확인 다이얼로그
|
/// 뒤로가기 시 저장 확인 다이얼로그
|
||||||
|
|||||||
@@ -225,13 +225,13 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> loadAndStart({
|
Future<void> loadAndStart({
|
||||||
String? fileName,
|
String? fileName,
|
||||||
bool cheatsEnabled = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
_status = GameSessionStatus.loading;
|
_status = GameSessionStatus.loading;
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final (outcome, loaded) = await saveManager.loadState(fileName: fileName);
|
final (outcome, loaded, savedCheatsEnabled) =
|
||||||
|
await saveManager.loadState(fileName: fileName);
|
||||||
if (!outcome.success || loaded == null) {
|
if (!outcome.success || loaded == null) {
|
||||||
_status = GameSessionStatus.error;
|
_status = GameSessionStatus.error;
|
||||||
_error = outcome.error ?? 'Unknown error';
|
_error = outcome.error ?? 'Unknown error';
|
||||||
@@ -239,7 +239,8 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await startNew(loaded, cheatsEnabled: cheatsEnabled, isNewGame: false);
|
// 저장된 치트 모드 상태 복원
|
||||||
|
await startNew(loaded, cheatsEnabled: savedCheatsEnabled, isNewGame: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pause({bool saveOnStop = false}) async {
|
Future<void> pause({bool saveOnStop = false}) async {
|
||||||
@@ -382,8 +383,11 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
_state = resurrectedState;
|
_state = resurrectedState;
|
||||||
_status = GameSessionStatus.idle; // 사망 상태 해제
|
_status = GameSessionStatus.idle; // 사망 상태 해제
|
||||||
|
|
||||||
// 저장
|
// 저장 (치트 모드 상태 유지)
|
||||||
await saveManager.saveState(resurrectedState);
|
await saveManager.saveState(
|
||||||
|
resurrectedState,
|
||||||
|
cheatsEnabled: _cheatsEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,18 @@ class _FakeSaveManager implements SaveManager {
|
|||||||
final List<GameState> savedStates = [];
|
final List<GameState> savedStates = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SaveOutcome> saveState(GameState state, {String? fileName}) async {
|
Future<SaveOutcome> saveState(
|
||||||
|
GameState state, {
|
||||||
|
String? fileName,
|
||||||
|
bool cheatsEnabled = false,
|
||||||
|
}) async {
|
||||||
savedStates.add(state);
|
savedStates.add(state);
|
||||||
return const SaveOutcome.success();
|
return const SaveOutcome.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<(SaveOutcome, GameState?)> loadState({String? fileName}) async {
|
Future<(SaveOutcome, GameState?, bool)> loadState({String? fileName}) async {
|
||||||
return (const SaveOutcome.success(), null);
|
return (const SaveOutcome.success(), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -23,13 +23,17 @@ Widget _buildTestApp(Widget child) {
|
|||||||
|
|
||||||
class _FakeSaveManager implements SaveManager {
|
class _FakeSaveManager implements SaveManager {
|
||||||
@override
|
@override
|
||||||
Future<SaveOutcome> saveState(GameState state, {String? fileName}) async {
|
Future<SaveOutcome> saveState(
|
||||||
|
GameState state, {
|
||||||
|
String? fileName,
|
||||||
|
bool cheatsEnabled = false,
|
||||||
|
}) async {
|
||||||
return const SaveOutcome.success();
|
return const SaveOutcome.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<(SaveOutcome, GameState?)> loadState({String? fileName}) async {
|
Future<(SaveOutcome, GameState?, bool)> loadState({String? fileName}) async {
|
||||||
return (const SaveOutcome.success(), null);
|
return (const SaveOutcome.success(), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -12,21 +12,25 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
|
|
||||||
class FakeSaveManager implements SaveManager {
|
class FakeSaveManager implements SaveManager {
|
||||||
final List<GameState> savedStates = [];
|
final List<GameState> savedStates = [];
|
||||||
(SaveOutcome, GameState?) Function(String?)? onLoad;
|
(SaveOutcome, GameState?, bool) Function(String?)? onLoad;
|
||||||
SaveOutcome saveOutcome = const SaveOutcome.success();
|
SaveOutcome saveOutcome = const SaveOutcome.success();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SaveOutcome> saveState(GameState state, {String? fileName}) async {
|
Future<SaveOutcome> saveState(
|
||||||
|
GameState state, {
|
||||||
|
String? fileName,
|
||||||
|
bool cheatsEnabled = false,
|
||||||
|
}) async {
|
||||||
savedStates.add(state);
|
savedStates.add(state);
|
||||||
return saveOutcome;
|
return saveOutcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<(SaveOutcome, GameState?)> loadState({String? fileName}) async {
|
Future<(SaveOutcome, GameState?, bool)> loadState({String? fileName}) async {
|
||||||
if (onLoad != null) {
|
if (onLoad != null) {
|
||||||
return onLoad!(fileName);
|
return onLoad!(fileName);
|
||||||
}
|
}
|
||||||
return (const SaveOutcome.success(), null);
|
return (const SaveOutcome.success(), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -122,7 +126,7 @@ void main() {
|
|||||||
test('loadAndStart surfaces save load errors', () {
|
test('loadAndStart surfaces save load errors', () {
|
||||||
fakeAsync((async) {
|
fakeAsync((async) {
|
||||||
final saveManager = FakeSaveManager()
|
final saveManager = FakeSaveManager()
|
||||||
..onLoad = (_) => (const SaveOutcome.failure('boom'), null);
|
..onLoad = (_) => (const SaveOutcome.failure('boom'), null, false);
|
||||||
final controller = buildController(async, saveManager);
|
final controller = buildController(async, saveManager);
|
||||||
|
|
||||||
controller.loadAndStart(fileName: 'bad.pqf');
|
controller.loadAndStart(fileName: 'bad.pqf');
|
||||||
|
|||||||
Reference in New Issue
Block a user