import 'dart:async'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/storage/save_manager.dart'; import 'package:asciineverdie/src/core/engine/progress_service.dart'; class AutoSaveConfig { const AutoSaveConfig({ this.onLevelUp = true, this.onQuestComplete = true, this.onActComplete = true, this.onStop = true, this.onDeath = true, }); final bool onLevelUp; final bool onQuestComplete; final bool onActComplete; final bool onStop; /// 사망 시 자동 저장 (Phase 4) final bool onDeath; bool shouldSave(ProgressTickResult result) { return (onLevelUp && result.leveledUp) || (onQuestComplete && result.completedQuest) || (onActComplete && result.completedAct) || (onDeath && result.playerDied); } } /// Runs the periodic timer loop that advances tasks/quests/plots. class ProgressLoop { ProgressLoop({ required GameState initialState, required this.progressService, this.saveManager, Duration tickInterval = const Duration(milliseconds: 50), AutoSaveConfig autoSaveConfig = const AutoSaveConfig(), DateTime Function()? now, this.cheatsEnabled = false, this.onPlayerDied, this.onGameComplete, List availableSpeeds = const [1, 5], int initialSpeedMultiplier = 1, }) : _state = initialState, _tickInterval = tickInterval, _autoSaveConfig = autoSaveConfig, _now = now ?? DateTime.now, _stateController = StreamController.broadcast(), _availableSpeeds = availableSpeeds.isNotEmpty ? availableSpeeds : [1], _speedMultiplier = initialSpeedMultiplier; final ProgressService progressService; final SaveManager? saveManager; final Duration _tickInterval; /// 플레이어 사망 시 콜백 (Phase 4) final void Function()? onPlayerDied; /// 게임 클리어 시 콜백 (Act V 완료) final void Function()? onGameComplete; final AutoSaveConfig _autoSaveConfig; final DateTime Function() _now; final StreamController _stateController; bool cheatsEnabled; Timer? _timer; int? _lastTickMs; int _speedMultiplier; List _availableSpeeds; GameState get current => _state; Stream get stream => _stateController.stream; GameState _state; /// 가용 배속 목록 List get availableSpeeds => List.unmodifiable(_availableSpeeds); /// 현재 배속 (1x, 2x, 5x) int get speedMultiplier => _speedMultiplier; /// 배속 순환: 가용 배속 목록 순환 /// 명예의 전당에 캐릭터 없으면: 1 -> 5 -> 1 /// 명예의 전당에 캐릭터 있으면: 1 -> 2 -> 5 -> 1 void cycleSpeed() { final currentIndex = _availableSpeeds.indexOf(_speedMultiplier); final nextIndex = (currentIndex + 1) % _availableSpeeds.length; _speedMultiplier = _availableSpeeds[nextIndex]; } /// 가용 배속 목록 업데이트 (명예의 전당 상태 변경 시) void updateAvailableSpeeds(List speeds) { if (speeds.isEmpty) return; _availableSpeeds = speeds; // 현재 배속이 새 목록에 없으면 첫 번째로 리셋 if (!_availableSpeeds.contains(_speedMultiplier)) { _speedMultiplier = _availableSpeeds.first; } } void start() { _lastTickMs = _now().millisecondsSinceEpoch; _timer ??= Timer.periodic(_tickInterval, (_) => tickOnce()); } Future stop({bool saveOnStop = false}) async { _timer?.cancel(); _timer = null; if (saveOnStop && _autoSaveConfig.onStop && saveManager != null) { await saveManager!.saveState(_state, cheatsEnabled: cheatsEnabled); } } void dispose() { _timer?.cancel(); _stateController.close(); } /// Run one iteration of the loop (used by Timer or manual stepping). GameState tickOnce({int? deltaMillis}) { // 사망 상태면 틱 진행 안 함 (Phase 4) if (_state.isDead) { return _state; } final baseDelta = deltaMillis ?? _computeDelta(); final delta = baseDelta * _speedMultiplier; final result = progressService.tick(_state, delta); _state = result.state; _stateController.add(_state); if (saveManager != null && _autoSaveConfig.shouldSave(result)) { saveManager!.saveState(_state, cheatsEnabled: cheatsEnabled); } // 사망 시 루프 정지 및 콜백 호출 (Phase 4) if (result.playerDied) { _timer?.cancel(); _timer = null; onPlayerDied?.call(); } // 게임 클리어 시 루프 정지 및 콜백 호출 (Act V 완료) if (result.gameComplete) { _timer?.cancel(); _timer = null; onGameComplete?.call(); } return _state; } /// Replace state (e.g., after loading) and reset timing. void replaceState(GameState newState) { _state = newState; _stateController.add(newState); _lastTickMs = _now().millisecondsSinceEpoch; } // Developer-only helpers mirroring original cheat panel actions. void cheatCompleteTask() { if (!cheatsEnabled) return; _state = progressService.forceTaskComplete(_state); _stateController.add(_state); } void cheatCompleteQuest() { if (!cheatsEnabled) return; _state = progressService.forceQuestComplete(_state); _stateController.add(_state); } void cheatCompletePlot() { if (!cheatsEnabled) return; _state = progressService.forcePlotComplete(_state); _stateController.add(_state); } int _computeDelta() { final nowMs = _now().millisecondsSinceEpoch; final last = _lastTickMs; _lastTickMs = nowMs; if (last == null) return 0; final delta = nowMs - last; if (delta < 0) return 0; return delta; } }