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/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_statistics.dart'; import 'package:asciineverdie/src/core/model/monetization_state.dart'; import 'package:asciineverdie/src/core/model/treasure_chest.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:asciineverdie/src/features/game/managers/game_statistics_manager.dart'; import 'package:asciineverdie/src/features/game/managers/hall_of_fame_manager.dart'; import 'package:asciineverdie/src/features/game/managers/resurrection_manager.dart'; import 'package:asciineverdie/src/features/game/managers/return_rewards_manager.dart'; import 'package:asciineverdie/src/features/game/managers/speed_boost_manager.dart'; import 'package:flutter/foundation.dart'; enum GameSessionStatus { idle, loading, running, error, dead, complete } /// Presentation-friendly wrapper that owns ProgressLoop and SaveManager. /// /// 게임 루프 관리를 담당하며, 대부분의 기능은 매니저에 위임합니다. /// - 통계: GameStatisticsManager /// - 속도 부스트: SpeedBoostManager /// - 복귀 보상: ReturnRewardsManager /// - 부활: ResurrectionManager /// - 명예의 전당: HallOfFameManager 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 { // 매니저 초기화 _statisticsManager = GameStatisticsManager( statisticsStorage: statisticsStorage, ); _hallOfFameManager = HallOfFameManager( hallOfFameStorage: hallOfFameStorage, ); _speedBoostManager = SpeedBoostManager( cheatsEnabledGetter: () => _cheatsEnabled, getAvailableSpeeds: _hallOfFameManager.getAvailableSpeeds, ); _returnRewardsManager = ReturnRewardsManager(); _resurrectionManager = ResurrectionManager(); // 매니저 콜백 설정 _speedBoostManager.onStateChanged = notifyListeners; _returnRewardsManager.onReturnRewardAvailable = (reward) { onReturnRewardAvailable?.call(reward); }; } final ProgressService progressService; final SaveManager saveManager; final AutoSaveConfig autoSaveConfig; final Duration _tickInterval; final DateTime Function() _now; // 매니저들 late final GameStatisticsManager _statisticsManager; late final HallOfFameManager _hallOfFameManager; late final SpeedBoostManager _speedBoostManager; late final ReturnRewardsManager _returnRewardsManager; late final ResurrectionManager _resurrectionManager; ProgressLoop? _loop; StreamSubscription? _subscription; bool _cheatsEnabled = false; GameSessionStatus _status = GameSessionStatus.idle; GameState? _state; String? _error; // 복귀 보상 상태 (Phase 7) MonetizationState _monetization = MonetizationState.initial(); /// 복귀 보상 콜백 (UI에서 다이얼로그 표시용) void Function(ReturnChestReward reward)? onReturnRewardAvailable; // ========================================================================== // Getters // ========================================================================== GameSessionStatus get status => _status; GameState? get state => _state; String? get error => _error; bool get isRunning => _status == GameSessionStatus.running; bool get cheatsEnabled => _cheatsEnabled; /// 자동 부활 활성화 여부 bool get autoResurrect => _resurrectionManager.autoResurrect; /// 자동 부활 설정 void setAutoResurrect(bool value) { _resurrectionManager.autoResurrect = value; notifyListeners(); } /// 현재 세션 통계 SessionStatistics get sessionStats => _statisticsManager.sessionStats; /// 누적 통계 CumulativeStatistics get cumulativeStats => _statisticsManager.cumulativeStats; /// 현재 ProgressLoop 인스턴스 (치트 기능용) ProgressLoop? get loop => _loop; /// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x) int get adSpeedMultiplier => _speedBoostManager.speedBoostMultiplier; /// 2x 배속 해금 여부 (명예의 전당에 캐릭터가 있으면 true) bool get has2xUnlocked => _loop?.availableSpeeds.contains(2) ?? false; /// 사망 상태 여부 bool get isDead => _status == GameSessionStatus.dead || (_state?.isDead ?? false); /// 게임 클리어 여부 bool get isComplete => _status == GameSessionStatus.complete; // 속도 부스트 관련 getters (매니저 위임) bool get isSpeedBoostActive => _speedBoostManager.isSpeedBoostActive; bool get isShowingAd => _speedBoostManager.isShowingAd; bool get isRecentlyShowedAd => _speedBoostManager.isRecentlyShowedAd; int get speedBoostMultiplier => _speedBoostManager.speedBoostMultiplier; int get speedBoostDuration => _speedBoostManager.speedBoostDuration; int get speedBoostRemainingSeconds => _speedBoostManager.getRemainingSeconds( _monetization, _state?.skillSystem.elapsedMs ?? 0, ); int get currentSpeedMultiplier => _speedBoostManager.getCurrentSpeedMultiplier(_loop); // 복귀 보상 관련 getters (매니저 위임) MonetizationState get monetization => _monetization; ReturnChestReward? get pendingReturnReward => _returnRewardsManager.pendingReturnReward; // ========================================================================== // 게임 루프 관리 // ========================================================================== Future startNew( GameState initialState, { bool cheatsEnabled = false, bool isNewGame = true, }) async { // 기존 배속 보존 (부활/재개 시 유지) final previousSpeed = _loop?.speedMultiplier ?? _speedBoostManager.savedSpeedMultiplier; await _stopLoop(saveOnStop: false); // 새 게임인 경우 초기화 (프롤로그 태스크 설정) final state = isNewGame ? progressService.initializeNewGame(initialState) : initialState; _state = state; _error = null; _status = GameSessionStatus.running; _cheatsEnabled = cheatsEnabled; // 통계 초기화 if (isNewGame) { await _statisticsManager.initializeForNewGame(); } else { _statisticsManager.restoreFromLoadedGame(state); } _statisticsManager.initPreviousValues(state); // 명예의 전당 체크 → 기본 가용 배속 결정 final baseAvailableSpeeds = await _hallOfFameManager.getAvailableSpeeds(); final hasHallOfFame = baseAvailableSpeeds.contains(2); // 기본 배속 결정 (부스트 미적용 시) final int baseSpeed; if (isNewGame) { baseSpeed = hasHallOfFame ? 2 : 1; } else { baseSpeed = (hasHallOfFame && previousSpeed < 2) ? 2 : previousSpeed; } // 배속 부스트 활성화 상태면 부스트 배속 적용, 아니면 기본 배속 final speedConfig = _speedBoostManager.calculateInitialSpeeds( baseAvailableSpeeds: baseAvailableSpeeds, baseSpeed: baseSpeed, ); _loop = ProgressLoop( initialState: state, progressService: progressService, saveManager: saveManager, autoSaveConfig: autoSaveConfig, tickInterval: _tickInterval, now: _now, cheatsEnabled: cheatsEnabled, onPlayerDied: _onPlayerDied, onGameComplete: _onGameComplete, availableSpeeds: speedConfig.speeds, initialSpeedMultiplier: speedConfig.initialSpeed, ); _subscription = _loop!.stream.listen((next) { final elapsedMs = next.skillSystem.elapsedMs; // 버프 만료 체크 (게임 시간 기준) _checkBuffExpiries(elapsedMs); _statisticsManager.updateStatistics(next); _state = next; notifyListeners(); }); _loop!.start(); notifyListeners(); } /// 매 틱마다 버프 만료 체크 void _checkBuffExpiries(int elapsedMs) { // 속도 부스트 만료 체크 final boostEnded = _speedBoostManager.checkExpiry( elapsedMs: elapsedMs, monetization: _monetization, loop: _loop, ); if (boostEnded) { _monetization = _monetization.copyWith(speedBoostEndMs: null); } // 자동부활 버프 만료 체크 final endMs = _monetization.autoReviveEndMs; if (endMs != null && elapsedMs >= endMs) { _monetization = _monetization.copyWith(autoReviveEndMs: null); debugPrint('[GameSession] Auto-revive buff expired'); notifyListeners(); } } /// 누적 통계 로드 Future loadCumulativeStats() async { await _statisticsManager.loadCumulativeStats(); notifyListeners(); } /// 세션 통계를 누적 통계에 병합 Future mergeSessionStats() async { await _statisticsManager.mergeSessionStats(); notifyListeners(); } Future loadAndStart({String? fileName}) async { _status = GameSessionStatus.loading; _error = null; notifyListeners(); final (outcome, loaded, savedCheatsEnabled, savedMonetization) = await saveManager.loadState(fileName: fileName); if (!outcome.success || loaded == null) { _status = GameSessionStatus.error; _error = outcome.error ?? 'Unknown error'; notifyListeners(); return; } // 저장된 수익화 상태 복원 _monetization = savedMonetization ?? MonetizationState.initial(); // 복귀 보상 체크 (Phase 7) _returnRewardsManager.checkReturnRewards( monetization: _monetization, loaded: loaded, ); // 저장된 치트 모드 상태 복원 await startNew(loaded, cheatsEnabled: savedCheatsEnabled, 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; // 배속 저장 (resume 시 복원용) if (loop != null) { _speedBoostManager.savedSpeedMultiplier = loop.speedMultiplier; } _loop = null; _subscription = null; sub?.cancel(); if (loop == null) return null; return loop.stop(saveOnStop: saveOnStop); } // ========================================================================== // 사망/부활 처리 // ========================================================================== /// 플레이어 사망 콜백 (ProgressLoop에서 호출) void _onPlayerDied() { _statisticsManager.recordDeath(); _status = GameSessionStatus.dead; notifyListeners(); // 자동 부활 조건 확인 final elapsedMs = _state?.skillSystem.elapsedMs ?? 0; final shouldAutoResurrect = _resurrectionManager.shouldAutoResurrect( monetization: _monetization, elapsedMs: elapsedMs, ); if (shouldAutoResurrect) { _scheduleAutoResurrect(); } } /// 자동 부활 예약 (Auto-Resurrection Scheduler) void _scheduleAutoResurrect() { Future.delayed(const Duration(milliseconds: 800), () async { if (_status != GameSessionStatus.dead) return; // 자동 부활 조건 재확인 final elapsedMs = _state?.skillSystem.elapsedMs ?? 0; final shouldAutoResurrect = _resurrectionManager.shouldAutoResurrect( monetization: _monetization, elapsedMs: elapsedMs, ); if (shouldAutoResurrect) { await resurrect(); await resumeAfterResurrection(); } }); } /// 플레이어 부활 처리 (상태만 업데이트, 게임 재개는 별도로) Future resurrect() async { if (_state == null || !_state!.isDead) return; final resurrectedState = await _resurrectionManager.processResurrection( state: _state!, saveManager: saveManager, cheatsEnabled: _cheatsEnabled, monetization: _monetization, ); if (resurrectedState != null) { _state = resurrectedState; _status = GameSessionStatus.idle; notifyListeners(); } } /// 부활 후 게임 재개 Future resumeAfterResurrection() async { if (_state == null) return; await startNew(_state!, cheatsEnabled: _cheatsEnabled, isNewGame: false); } /// 광고 부활 (HP 100% + 아이템 복구 + 10분 자동부활 버프) Future adRevive() async { if (_state == null || !_state!.isDead) return; final result = await _resurrectionManager.processAdRevive( state: _state!, saveManager: saveManager, cheatsEnabled: _cheatsEnabled, monetization: _monetization, ); if (result.success) { _state = result.state; _monetization = result.monetization; _status = GameSessionStatus.idle; notifyListeners(); } } // ========================================================================== // 명예의 전당 // ========================================================================== /// 게임 클리어 콜백 (ProgressLoop에서 호출, Act V 완료 시) void _onGameComplete() { _status = GameSessionStatus.complete; notifyListeners(); unawaited(_registerToHallOfFame()); } /// 명예의 전당 등록 Future _registerToHallOfFame() async { if (_state == null) { debugPrint('[HallOfFame] _state is null, skipping registration'); return; } await _hallOfFameManager.registerCharacter( state: _state!, saveManager: saveManager, statisticsManager: _statisticsManager, ); } /// 테스트 캐릭터 생성 (디버그 모드 전용) Future createTestCharacter() async { if (_state == null) { debugPrint('[TestCharacter] _state is null'); return false; } await _stopLoop(saveOnStop: false); final success = await _hallOfFameManager.createTestCharacter( state: _state!, saveManager: saveManager, ); if (success) { _state = null; _status = GameSessionStatus.idle; notifyListeners(); } return success; } // ========================================================================== // 속도 부스트 (Phase 6) // ========================================================================== /// 속도 부스트 활성화 (광고 시청 후) Future activateSpeedBoost() async { final (success, updatedMonetization) = await _speedBoostManager.activateSpeedBoost( loop: _loop, monetization: _monetization, currentElapsedMs: _state?.skillSystem.elapsedMs ?? 0, ); if (success) { _monetization = updatedMonetization; notifyListeners(); } return success; } /// 속도 부스트 수동 취소 void cancelSpeedBoost() { _monetization = _speedBoostManager.cancelSpeedBoost( loop: _loop, monetization: _monetization, ); notifyListeners(); } // ========================================================================== // 복귀 보상 (Phase 7) // ========================================================================== /// 복귀 보상 수령 완료 (상자 보상 적용) Future applyReturnReward(List rewards) async { if (_state == null) return; final updatedState = await _returnRewardsManager.applyReturnReward( rewards: rewards, state: _state!, loop: _loop, saveManager: saveManager, cheatsEnabled: _cheatsEnabled, monetization: _monetization, ); if (updatedState != null) { _state = updatedState; notifyListeners(); } } /// 복귀 보상 건너뛰기 void skipReturnReward() { _returnRewardsManager.skipReturnReward(); } }