diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index 893c45b..a58bdf0 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -256,9 +256,6 @@ class _GamePlayScreenState extends State final taskType = state.progress.currentTask.type; final isInBattleTask = taskType == TaskType.kill; - debugPrint( - '[BGM] TaskType: $taskType, isInBattle: $isInBattleTask, currentBgm: ${audio.currentBgm}'); - // 전투 태스크 상태 결정 if (isInBattleTask) { // 전투 태스크: 보스 여부에 따라 BGM 선택 @@ -269,13 +266,11 @@ class _GamePlayScreenState extends State // 전환 시점이거나 현재 BGM이 일치하지 않으면 재생 if (!_wasInBattleTask || audio.currentBgm != expectedBgm) { - debugPrint('[BGM] Playing battle BGM: $expectedBgm'); audio.playBgm(expectedBgm); } } else { // 비전투 태스크: 항상 마을 BGM 유지 (이미 town이면 스킵) if (audio.currentBgm != 'town') { - debugPrint('[BGM] Playing town BGM (was: ${audio.currentBgm})'); audio.playBgm('town'); } } @@ -825,6 +820,12 @@ class _GamePlayScreenState extends State deathInfo: state.deathInfo!, traits: state.traits, onResurrect: _handleResurrect, + isAutoResurrectEnabled: widget.controller.autoResurrect, + onToggleAutoResurrect: () { + widget.controller.setAutoResurrect( + !widget.controller.autoResurrect, + ); + }, ), // 승리 오버레이 (게임 클리어) if (widget.controller.isComplete) @@ -968,6 +969,12 @@ class _GamePlayScreenState extends State deathInfo: state.deathInfo!, traits: state.traits, onResurrect: _handleResurrect, + isAutoResurrectEnabled: widget.controller.autoResurrect, + onToggleAutoResurrect: () { + widget.controller.setAutoResurrect( + !widget.controller.autoResurrect, + ); + }, ), // 승리 오버레이 (게임 클리어) if (widget.controller.isComplete) diff --git a/lib/src/features/game/game_session_controller.dart b/lib/src/features/game/game_session_controller.dart index 91a2f61..045ca06 100644 --- a/lib/src/features/game/game_session_controller.dart +++ b/lib/src/features/game/game_session_controller.dart @@ -47,6 +47,9 @@ class GameSessionController extends ChangeNotifier { GameState? _state; String? _error; + // 자동 부활 (Auto-Resurrection) 상태 + bool _autoResurrect = false; + // 통계 관련 필드 SessionStatistics _sessionStats = SessionStatistics.empty(); CumulativeStatistics _cumulativeStats = CumulativeStatistics.empty(); @@ -61,6 +64,15 @@ class GameSessionController extends ChangeNotifier { bool get isRunning => _status == GameSessionStatus.running; bool get cheatsEnabled => _cheatsEnabled; + /// 자동 부활 활성화 여부 + bool get autoResurrect => _autoResurrect; + + /// 자동 부활 설정 + void setAutoResurrect(bool value) { + _autoResurrect = value; + notifyListeners(); + } + /// 현재 세션 통계 SessionStatistics get sessionStats => _sessionStats; @@ -75,6 +87,9 @@ class GameSessionController extends ChangeNotifier { bool cheatsEnabled = false, bool isNewGame = true, }) async { + // 기존 배속 보존 (부활/재개 시 유지) + final previousSpeed = _loop?.speedMultiplier ?? 1; + await _stopLoop(saveOnStop: false); // 새 게임인 경우 초기화 (프롤로그 태스크 설정) @@ -97,6 +112,9 @@ class GameSessionController extends ChangeNotifier { // 명예의 전당 체크 → 가용 배속 결정 final availableSpeeds = await _getAvailableSpeeds(); + // 새 게임이면 1배속, 재개/부활이면 기존 배속 유지 + final initialSpeed = isNewGame ? 1 : previousSpeed; + _loop = ProgressLoop( initialState: state, progressService: progressService, @@ -108,6 +126,7 @@ class GameSessionController extends ChangeNotifier { onPlayerDied: _onPlayerDied, onGameComplete: _onGameComplete, availableSpeeds: availableSpeeds, + initialSpeedMultiplier: initialSpeed, ); _subscription = _loop!.stream.listen((next) { @@ -273,6 +292,24 @@ class GameSessionController extends ChangeNotifier { _sessionStats = _sessionStats.recordDeath(); _status = GameSessionStatus.dead; notifyListeners(); + + // 자동 부활이 활성화된 경우 잠시 후 자동으로 부활 + if (_autoResurrect) { + _scheduleAutoResurrect(); + } + } + + /// 자동 부활 예약 (Auto-Resurrection Scheduler) + /// + /// 사망 오버레이를 잠시 표시한 후 자동으로 부활 처리 + void _scheduleAutoResurrect() { + Future.delayed(const Duration(milliseconds: 800), () async { + // 상태가 여전히 dead이고, 자동 부활이 활성화된 경우에만 부활 + if (_status == GameSessionStatus.dead && _autoResurrect) { + await resurrect(); + await resumeAfterResurrection(); + } + }); } /// 게임 클리어 콜백 (ProgressLoop에서 호출, Act V 완료 시) diff --git a/lib/src/features/game/widgets/death_overlay.dart b/lib/src/features/game/widgets/death_overlay.dart index 7e4dc4f..cac50c2 100644 --- a/lib/src/features/game/widgets/death_overlay.dart +++ b/lib/src/features/game/widgets/death_overlay.dart @@ -15,6 +15,8 @@ class DeathOverlay extends StatelessWidget { required this.deathInfo, required this.traits, required this.onResurrect, + this.isAutoResurrectEnabled = false, + this.onToggleAutoResurrect, }); /// 사망 정보 @@ -26,6 +28,12 @@ class DeathOverlay extends StatelessWidget { /// 부활 버튼 콜백 final VoidCallback onResurrect; + /// 자동 부활 활성화 여부 + final bool isAutoResurrectEnabled; + + /// 자동 부활 토글 콜백 + final VoidCallback? onToggleAutoResurrect; + @override Widget build(BuildContext context) { // 테마 인식 색상 (Theme-aware colors) @@ -132,6 +140,12 @@ class DeathOverlay extends StatelessWidget { // 부활 버튼 _buildResurrectButton(context), + + // 자동 부활 버튼 + if (onToggleAutoResurrect != null) ...[ + const SizedBox(height: 12), + _buildAutoResurrectButton(context), + ], ], ), ), @@ -456,6 +470,75 @@ class DeathOverlay extends StatelessWidget { ); } + /// 자동 부활 토글 버튼 + Widget _buildAutoResurrectButton(BuildContext context) { + final mpColor = RetroColors.mpOf(context); + final mpDark = RetroColors.mpDarkOf(context); + final muted = RetroColors.textMutedOf(context); + + // 활성화 상태에 따른 색상 + final buttonColor = isAutoResurrectEnabled ? mpColor : muted; + final buttonDark = isAutoResurrectEnabled ? mpDark : muted.withValues(alpha: 0.5); + + return GestureDetector( + onTap: onToggleAutoResurrect, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: buttonColor.withValues(alpha: 0.15), + border: Border( + top: BorderSide(color: buttonColor, width: 2), + left: BorderSide(color: buttonColor, width: 2), + bottom: BorderSide( + color: buttonDark.withValues(alpha: 0.8), + width: 2, + ), + right: BorderSide( + color: buttonDark.withValues(alpha: 0.8), + width: 2, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + isAutoResurrectEnabled ? '◉' : '○', + style: TextStyle( + fontSize: 14, + color: buttonColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Text( + l10n.deathAutoResurrect.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: buttonColor, + letterSpacing: 1, + ), + ), + if (isAutoResurrectEnabled) ...[ + const SizedBox(width: 6), + Text( + 'ON', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: mpColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ], + ), + ), + ); + } + /// 사망 직전 전투 로그 표시 Widget _buildCombatLog(BuildContext context) { final events = deathInfo.lastCombatEvents;