Files
asciinevrdie/lib/src/features/game/game_session_controller.dart
JiWoong Sul 21bf057cfc feat(death): Phase 4 사망/부활 시스템 구현
- DeathInfo, DeathCause 클래스 정의 (game_state.dart)
  - 사망 원인, 상실 장비 수, 사망 시점 정보 기록
- ShopService 구현 (shop_service.dart)
  - 장비 가격 계산 (레벨 * 50 * 희귀도 배율)
  - 슬롯별 장비 생성 (프로그래밍 테마)
  - 자동 구매 (빈 슬롯에 Common 장비)
- ResurrectionService 구현 (resurrection_service.dart)
  - 사망 처리: 모든 장비 상실, 기본 무기만 유지
  - 부활 처리: HP/MP 회복, 자동 장비 구매
- progress_service.dart 사망 판정 로직 추가
  - 전투 중 HP <= 0 시 사망 처리
  - ProgressTickResult에 playerDied 플래그 추가
- progress_loop.dart 사망 시 루프 정지
  - onPlayerDied 콜백 추가
  - 사망 상태에서 틱 진행 방지
- DeathOverlay 위젯 구현 (death_overlay.dart)
  - ASCII 스컬 아트, 사망 원인, 상실 정보 표시
  - 부활 버튼
- GameSessionController 사망/부활 상태 관리
  - GameSessionStatus.dead 상태 추가
  - resurrect() 메서드로 부활 처리
2025-12-17 17:15:22 +09:00

177 lines
5.1 KiB
Dart

import 'dart:async';
import 'package:askiineverdie/src/core/engine/progress_loop.dart';
import 'package:askiineverdie/src/core/engine/progress_service.dart';
import 'package:askiineverdie/src/core/engine/resurrection_service.dart';
import 'package:askiineverdie/src/core/engine/shop_service.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/core/storage/save_manager.dart';
import 'package:flutter/foundation.dart';
enum GameSessionStatus { idle, loading, running, error, dead }
/// 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,
}) : _tickInterval = tickInterval,
_now = now ?? DateTime.now;
final ProgressService progressService;
final SaveManager saveManager;
final AutoSaveConfig autoSaveConfig;
final Duration _tickInterval;
final DateTime Function() _now;
ProgressLoop? _loop;
StreamSubscription<GameState>? _subscription;
bool _cheatsEnabled = false;
GameSessionStatus _status = GameSessionStatus.idle;
GameState? _state;
String? _error;
GameSessionStatus get status => _status;
GameState? get state => _state;
String? get error => _error;
bool get isRunning => _status == GameSessionStatus.running;
bool get cheatsEnabled => _cheatsEnabled;
/// 현재 ProgressLoop 인스턴스 (치트 기능용)
ProgressLoop? get loop => _loop;
Future<void> 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;
_loop = ProgressLoop(
initialState: state,
progressService: progressService,
saveManager: saveManager,
autoSaveConfig: autoSaveConfig,
tickInterval: _tickInterval,
now: _now,
cheatsEnabled: cheatsEnabled,
onPlayerDied: _onPlayerDied,
);
_subscription = _loop!.stream.listen((next) {
_state = next;
notifyListeners();
});
_loop!.start();
notifyListeners();
}
Future<void> 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<void> pause({bool saveOnStop = false}) async {
await _stopLoop(saveOnStop: saveOnStop);
_status = GameSessionStatus.idle;
notifyListeners();
}
/// 일시 정지 상태에서 재개
Future<void> resume() async {
if (_state == null || _status != GameSessionStatus.idle) return;
await startNew(_state!, cheatsEnabled: _cheatsEnabled, isNewGame: false);
}
/// 일시 정지/재개 토글
Future<void> 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<void>? _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() {
_status = GameSessionStatus.dead;
notifyListeners();
}
/// 플레이어 부활 처리
///
/// HP/MP 회복, 빈 슬롯에 장비 자동 구매, 게임 재개
Future<void> 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!);
// 저장
await saveManager.saveState(resurrectedState);
// 게임 재개
await startNew(resurrectedState, cheatsEnabled: _cheatsEnabled, isNewGame: false);
}
/// 사망 상태 여부
bool get isDead => _status == GameSessionStatus.dead || (_state?.isDead ?? false);
}