import 'dart:async'; import 'package:asciineverdie/src/core/engine/progress_loop.dart'; import 'package:asciineverdie/src/core/engine/progress_service.dart'; import 'package:asciineverdie/src/core/engine/resurrection_service.dart'; import 'package:asciineverdie/src/core/engine/shop_service.dart'; import 'package:asciineverdie/src/core/model/combat_stats.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_statistics.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart'; import 'package:asciineverdie/src/core/storage/save_manager.dart'; import 'package:asciineverdie/src/core/storage/statistics_storage.dart'; import 'package:flutter/foundation.dart'; enum GameSessionStatus { idle, loading, running, error, dead, complete } /// Presentation-friendly wrapper that owns ProgressLoop and SaveManager. class GameSessionController extends ChangeNotifier { GameSessionController({ required this.progressService, required this.saveManager, this.autoSaveConfig = const AutoSaveConfig(), Duration tickInterval = const Duration(milliseconds: 50), DateTime Function()? now, StatisticsStorage? statisticsStorage, HallOfFameStorage? hallOfFameStorage, }) : _tickInterval = tickInterval, _now = now ?? DateTime.now, _statisticsStorage = statisticsStorage ?? StatisticsStorage(), _hallOfFameStorage = hallOfFameStorage ?? HallOfFameStorage(); final ProgressService progressService; final SaveManager saveManager; final AutoSaveConfig autoSaveConfig; final StatisticsStorage _statisticsStorage; final HallOfFameStorage _hallOfFameStorage; final Duration _tickInterval; final DateTime Function() _now; ProgressLoop? _loop; StreamSubscription? _subscription; bool _cheatsEnabled = false; GameSessionStatus _status = GameSessionStatus.idle; GameState? _state; String? _error; // 통계 관련 필드 SessionStatistics _sessionStats = SessionStatistics.empty(); CumulativeStatistics _cumulativeStats = CumulativeStatistics.empty(); int _previousLevel = 0; int _previousGold = 0; int _previousMonstersKilled = 0; int _previousQuestsCompleted = 0; GameSessionStatus get status => _status; GameState? get state => _state; String? get error => _error; bool get isRunning => _status == GameSessionStatus.running; bool get cheatsEnabled => _cheatsEnabled; /// 현재 세션 통계 SessionStatistics get sessionStats => _sessionStats; /// 누적 통계 CumulativeStatistics get cumulativeStats => _cumulativeStats; /// 현재 ProgressLoop 인스턴스 (치트 기능용) ProgressLoop? get loop => _loop; Future startNew( GameState initialState, { bool cheatsEnabled = false, bool isNewGame = true, }) async { await _stopLoop(saveOnStop: false); // 새 게임인 경우 초기화 (프롤로그 태스크 설정) final state = isNewGame ? progressService.initializeNewGame(initialState) : initialState; _state = state; _error = null; _status = GameSessionStatus.running; _cheatsEnabled = cheatsEnabled; // 통계 초기화 if (isNewGame) { _sessionStats = SessionStatistics.empty(); await _statisticsStorage.recordGameStart(); } _initPreviousValues(state); // 명예의 전당 체크 → 가용 배속 결정 final availableSpeeds = await _getAvailableSpeeds(); _loop = ProgressLoop( initialState: state, progressService: progressService, saveManager: saveManager, autoSaveConfig: autoSaveConfig, tickInterval: _tickInterval, now: _now, cheatsEnabled: cheatsEnabled, onPlayerDied: _onPlayerDied, onGameComplete: _onGameComplete, availableSpeeds: availableSpeeds, ); _subscription = _loop!.stream.listen((next) { _updateStatistics(next); _state = next; notifyListeners(); }); _loop!.start(); notifyListeners(); } /// 명예의 전당 상태에 따른 가용 배속 목록 반환 /// - 디버그 모드(치트 활성화): [1, 5, 20] (터보 모드) /// - 명예의 전당에 캐릭터 없음: [1, 5] /// - 명예의 전당에 캐릭터 있음: [1, 2, 5] Future> _getAvailableSpeeds() async { // 디버그 모드면 터보(20x) 추가 if (_cheatsEnabled) { return [1, 5, 20]; } final hallOfFame = await _hallOfFameStorage.load(); return hallOfFame.isEmpty ? [1, 5] : [1, 2, 5]; } /// 이전 값 초기화 (통계 변화 추적용) void _initPreviousValues(GameState state) { _previousLevel = state.traits.level; _previousGold = state.inventory.gold; _previousMonstersKilled = state.progress.monstersKilled; _previousQuestsCompleted = state.progress.questCount; } /// 상태 변화에 따른 통계 업데이트 void _updateStatistics(GameState next) { // 플레이 시간 업데이트 _sessionStats = _sessionStats.updatePlayTime(next.skillSystem.elapsedMs); // 레벨업 감지 if (next.traits.level > _previousLevel) { final levelUps = next.traits.level - _previousLevel; for (var i = 0; i < levelUps; i++) { _sessionStats = _sessionStats.recordLevelUp(); } _previousLevel = next.traits.level; // 최고 레벨 업데이트 unawaited(_statisticsStorage.updateHighestLevel(next.traits.level)); } // 골드 변화 감지 if (next.inventory.gold > _previousGold) { final earned = next.inventory.gold - _previousGold; _sessionStats = _sessionStats.recordGoldEarned(earned); // 최대 골드 업데이트 unawaited(_statisticsStorage.updateHighestGold(next.inventory.gold)); } else if (next.inventory.gold < _previousGold) { final spent = _previousGold - next.inventory.gold; _sessionStats = _sessionStats.recordGoldSpent(spent); } _previousGold = next.inventory.gold; // 몬스터 처치 감지 if (next.progress.monstersKilled > _previousMonstersKilled) { final kills = next.progress.monstersKilled - _previousMonstersKilled; for (var i = 0; i < kills; i++) { _sessionStats = _sessionStats.recordKill(); } _previousMonstersKilled = next.progress.monstersKilled; } // 퀘스트 완료 감지 if (next.progress.questCount > _previousQuestsCompleted) { final quests = next.progress.questCount - _previousQuestsCompleted; for (var i = 0; i < quests; i++) { _sessionStats = _sessionStats.recordQuestComplete(); } _previousQuestsCompleted = next.progress.questCount; } } /// 누적 통계 로드 Future loadCumulativeStats() async { _cumulativeStats = await _statisticsStorage.loadCumulative(); notifyListeners(); } /// 세션 통계를 누적 통계에 병합 Future mergeSessionStats() async { await _statisticsStorage.mergeSession(_sessionStats); _cumulativeStats = await _statisticsStorage.loadCumulative(); notifyListeners(); } Future loadAndStart({ String? fileName, bool cheatsEnabled = false, }) async { _status = GameSessionStatus.loading; _error = null; notifyListeners(); final (outcome, loaded) = await saveManager.loadState(fileName: fileName); if (!outcome.success || loaded == null) { _status = GameSessionStatus.error; _error = outcome.error ?? 'Unknown error'; notifyListeners(); return; } await startNew(loaded, cheatsEnabled: cheatsEnabled, isNewGame: false); } Future pause({bool saveOnStop = false}) async { await _stopLoop(saveOnStop: saveOnStop); _status = GameSessionStatus.idle; notifyListeners(); } /// 일시 정지 상태에서 재개 Future resume() async { if (_state == null || _status != GameSessionStatus.idle) return; await startNew(_state!, cheatsEnabled: _cheatsEnabled, isNewGame: false); } /// 일시 정지/재개 토글 Future togglePause() async { if (isRunning) { await pause(saveOnStop: true); } else if (_state != null && _status == GameSessionStatus.idle) { await resume(); } } @override void dispose() { final stop = _stopLoop(saveOnStop: false); if (stop != null) { unawaited(stop); } super.dispose(); } Future? _stopLoop({required bool saveOnStop}) { final loop = _loop; final sub = _subscription; _loop = null; _subscription = null; sub?.cancel(); if (loop == null) return null; return loop.stop(saveOnStop: saveOnStop); } // ============================================================================ // Phase 4: 사망/부활 처리 // ============================================================================ /// 플레이어 사망 콜백 (ProgressLoop에서 호출) void _onPlayerDied() { _sessionStats = _sessionStats.recordDeath(); _status = GameSessionStatus.dead; notifyListeners(); } /// 게임 클리어 콜백 (ProgressLoop에서 호출, Act V 완료 시) void _onGameComplete() { _status = GameSessionStatus.complete; notifyListeners(); // Hall of Fame 등록 (비동기) unawaited(_registerToHallOfFame()); } /// 명예의 전당 등록 Future _registerToHallOfFame() async { if (_state == null) { debugPrint('[HallOfFame] _state is null, skipping registration'); return; } try { debugPrint('[HallOfFame] Starting registration...'); // 최종 전투 스탯 계산 (CombatStats) final combatStats = CombatStats.fromStats( stats: _state!.stats, equipment: _state!.equipment, level: _state!.traits.level, ); final entry = HallOfFameEntry.fromGameState( state: _state!, totalDeaths: _sessionStats.deathCount, monstersKilled: _state!.progress.monstersKilled, combatStats: combatStats, ); debugPrint('[HallOfFame] Entry created: ${entry.characterName} Lv.${entry.level}'); final success = await _hallOfFameStorage.addEntry(entry); debugPrint('[HallOfFame] Storage save result: $success'); // 통계 기록 await _statisticsStorage.recordGameComplete(); debugPrint('[HallOfFame] Registration complete'); // 클리어된 세이브 파일 삭제 (중복 등록 방지) if (success) { final deleteResult = await saveManager.deleteSave(); debugPrint('[HallOfFame] Save file deleted: ${deleteResult.success}'); } } catch (e, st) { debugPrint('[HallOfFame] ERROR: $e'); debugPrint('[HallOfFame] StackTrace: $st'); } } /// 플레이어 부활 처리 (상태만 업데이트, 게임 재개는 별도로) /// /// HP/MP 회복, 빈 슬롯에 장비 자동 구매 /// 게임 재개는 resumeAfterResurrection()으로 별도 호출 필요 Future resurrect() async { if (_state == null || !_state!.isDead) return; // ResurrectionService를 사용하여 부활 처리 final shopService = ShopService(rng: _state!.rng); final resurrectionService = ResurrectionService(shopService: shopService); final resurrectedState = resurrectionService.processResurrection(_state!); // 상태 업데이트 (게임 재개 없이) _state = resurrectedState; _status = GameSessionStatus.idle; // 사망 상태 해제 // 저장 await saveManager.saveState(resurrectedState); notifyListeners(); } /// 부활 후 게임 재개 /// /// resurrect() 호출 후 애니메이션이 끝난 뒤 호출 Future resumeAfterResurrection() async { if (_state == null) return; // 게임 재개 await startNew(_state!, cheatsEnabled: _cheatsEnabled, isNewGame: false); } /// 사망 상태 여부 bool get isDead => _status == GameSessionStatus.dead || (_state?.isDead ?? false); /// 게임 클리어 여부 bool get isComplete => _status == GameSessionStatus.complete; }