import 'dart:async'; import 'package:askiineverdie/src/core/model/game_state.dart'; import 'package:askiineverdie/src/core/storage/save_manager.dart'; import 'package:askiineverdie/src/core/engine/progress_service.dart'; class AutoSaveConfig { const AutoSaveConfig({ this.onLevelUp = true, this.onQuestComplete = true, this.onActComplete = true, this.onStop = true, }); final bool onLevelUp; final bool onQuestComplete; final bool onActComplete; final bool onStop; bool shouldSave(ProgressTickResult result) { return (onLevelUp && result.leveledUp) || (onQuestComplete && result.completedQuest) || (onActComplete && result.completedAct); } } /// 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, }) : _state = initialState, _tickInterval = tickInterval, _autoSaveConfig = autoSaveConfig, _now = now ?? DateTime.now, _stateController = StreamController.broadcast(); final ProgressService progressService; final SaveManager? saveManager; final Duration _tickInterval; final AutoSaveConfig _autoSaveConfig; final DateTime Function() _now; final StreamController _stateController; bool cheatsEnabled; Timer? _timer; int? _lastTickMs; int _speedMultiplier = 1; GameState get current => _state; Stream get stream => _stateController.stream; GameState _state; /// 현재 배속 (1x, 2x, 5x) int get speedMultiplier => _speedMultiplier; /// 배속 순환: 1 -> 2 -> 5 -> 1 void cycleSpeed() { _speedMultiplier = switch (_speedMultiplier) { 1 => 2, 2 => 5, _ => 1, }; } 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); } } void dispose() { _timer?.cancel(); _stateController.close(); } /// Run one iteration of the loop (used by Timer or manual stepping). GameState tickOnce({int? deltaMillis}) { 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); } 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; } }