From cbbbbba1a51ae0fe80af65ae408e35c8a5a48a41 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Mon, 12 Jan 2026 16:17:20 +0900 Subject: [PATCH] =?UTF-8?q?refactor(game):=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B0=8F=20=EC=9C=84=EC=A0=AF=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/features/game/game_play_screen.dart | 289 ++++++++------- .../game/game_session_controller.dart | 13 +- .../game/widgets/ascii_animation_card.dart | 122 ++++++- .../game/widgets/carousel_nav_bar.dart | 4 +- lib/src/features/game/widgets/combat_log.dart | 9 + .../features/game/widgets/death_overlay.dart | 138 +++---- .../widgets/enhanced_animation_panel.dart | 10 +- .../features/game/widgets/help_dialog.dart | 338 ++++++++---------- lib/src/features/game/widgets/hp_mp_bar.dart | 27 +- .../game/widgets/notification_overlay.dart | 17 +- .../game/widgets/statistics_dialog.dart | 206 ++++++----- .../game/widgets/task_progress_panel.dart | 19 +- .../game/widgets/victory_overlay.dart | 40 ++- 13 files changed, 662 insertions(+), 570 deletions(-) diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index ef4dab5..36999e6 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -491,9 +491,7 @@ class _GamePlayScreenState extends State /// VictoryOverlay 완료 후 명예의 전당 화면으로 이동 void _handleVictoryComplete() { Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HallOfFameScreen(), - ), + MaterialPageRoute(builder: (context) => const HallOfFameScreen()), ); } @@ -570,7 +568,8 @@ class _GamePlayScreenState extends State super.didChangeAppLifecycleState(appState); // 모바일 환경 확인 (iOS/Android) - final isMobile = !kIsWeb && + final isMobile = + !kIsWeb && (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.android); @@ -815,7 +814,8 @@ class _GamePlayScreenState extends State setState(() {}); }, // 특수 애니메이션 중에는 일시정지 상태로 표시하지 않음 - isPaused: !widget.controller.isRunning && _specialAnimation == null, + isPaused: + !widget.controller.isRunning && _specialAnimation == null, onPauseToggle: () async { await widget.controller.togglePause(); setState(() {}); @@ -883,7 +883,8 @@ class _GamePlayScreenState extends State // 치트 (디버그 모드) cheatsEnabled: widget.controller.cheatsEnabled, onCheatTask: () => widget.controller.loop?.cheatCompleteTask(), - onCheatQuest: () => widget.controller.loop?.cheatCompleteQuest(), + onCheatQuest: () => + widget.controller.loop?.cheatCompleteQuest(), onCheatPlot: () => widget.controller.loop?.cheatCompletePlot(), ), // 사망 오버레이 @@ -935,132 +936,137 @@ class _GamePlayScreenState extends State autofocus: true, onKeyEvent: (node, event) => _handleKeyboardShortcut(event, context), child: Scaffold( - backgroundColor: RetroColors.deepBrown, - appBar: AppBar( - backgroundColor: RetroColors.darkBrown, - title: Text( - L10n.of(context).progressQuestTitle(state.traits.name), - style: const TextStyle( - fontFamily: 'PressStart2P', - fontSize: 12, - color: RetroColors.gold, - ), - ), - actions: [ - // 치트 버튼 (디버그용) - if (widget.controller.cheatsEnabled) ...[ - IconButton( - icon: const Text('L+1'), - tooltip: L10n.of(context).levelUp, - onPressed: () => widget.controller.loop?.cheatCompleteTask(), + backgroundColor: RetroColors.deepBrown, + appBar: AppBar( + backgroundColor: RetroColors.darkBrown, + title: Text( + L10n.of(context).progressQuestTitle(state.traits.name), + style: const TextStyle( + fontFamily: 'PressStart2P', + fontSize: 12, + color: RetroColors.gold, ), - IconButton( - icon: const Text('Q!'), - tooltip: L10n.of(context).completeQuest, - onPressed: () => widget.controller.loop?.cheatCompleteQuest(), - ), - IconButton( - icon: const Text('P!'), - tooltip: L10n.of(context).completePlot, - onPressed: () => widget.controller.loop?.cheatCompletePlot(), - ), - ], - // 통계 버튼 - IconButton( - icon: const Icon(Icons.bar_chart), - tooltip: game_l10n.uiStatistics, - onPressed: () => _showStatisticsDialog(context), ), - // 도움말 버튼 - IconButton( - icon: const Icon(Icons.help_outline), - tooltip: game_l10n.uiHelp, - onPressed: () => HelpDialog.show(context), - ), - // 설정 버튼 - IconButton( - icon: const Icon(Icons.settings), - tooltip: game_l10n.uiSettings, - onPressed: () => _showSettingsScreen(context), - ), - ], - ), - body: Stack( - children: [ - // 메인 게임 UI - Column( - children: [ - // 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트) - TaskProgressPanel( - progress: state.progress, - speedMultiplier: - widget.controller.loop?.speedMultiplier ?? 1, - onSpeedCycle: () { - widget.controller.loop?.cycleSpeed(); - setState(() {}); - }, - // 특수 애니메이션 중에는 일시정지 상태로 표시하지 않음 - isPaused: !widget.controller.isRunning && _specialAnimation == null, - onPauseToggle: () async { - await widget.controller.togglePause(); - setState(() {}); - }, - specialAnimation: _specialAnimation, - weaponName: state.equipment.weapon, - shieldName: state.equipment.shield, - characterLevel: state.traits.level, - monsterLevel: state.progress.currentTask.monsterLevel, - monsterGrade: state.progress.currentTask.monsterGrade, - latestCombatEvent: - state.progress.currentCombat?.recentEvents.lastOrNull, - raceId: state.traits.raceId, + actions: [ + // 치트 버튼 (디버그용) + if (widget.controller.cheatsEnabled) ...[ + IconButton( + icon: const Text('L+1'), + tooltip: L10n.of(context).levelUp, + onPressed: () => + widget.controller.loop?.cheatCompleteTask(), ), - - // 메인 3패널 영역 - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // 좌측 패널: Character Sheet - Expanded(flex: 2, child: _buildCharacterPanel(state)), - - // 중앙 패널: Equipment/Inventory - Expanded(flex: 3, child: _buildEquipmentPanel(state)), - - // 우측 패널: Plot/Quest - Expanded(flex: 2, child: _buildQuestPanel(state)), - ], - ), + IconButton( + icon: const Text('Q!'), + tooltip: L10n.of(context).completeQuest, + onPressed: () => + widget.controller.loop?.cheatCompleteQuest(), + ), + IconButton( + icon: const Text('P!'), + tooltip: L10n.of(context).completePlot, + onPressed: () => + widget.controller.loop?.cheatCompletePlot(), ), ], - ), + // 통계 버튼 + IconButton( + icon: const Icon(Icons.bar_chart), + tooltip: game_l10n.uiStatistics, + onPressed: () => _showStatisticsDialog(context), + ), + // 도움말 버튼 + IconButton( + icon: const Icon(Icons.help_outline), + tooltip: game_l10n.uiHelp, + onPressed: () => HelpDialog.show(context), + ), + // 설정 버튼 + IconButton( + icon: const Icon(Icons.settings), + tooltip: game_l10n.uiSettings, + onPressed: () => _showSettingsScreen(context), + ), + ], + ), + body: Stack( + children: [ + // 메인 게임 UI + Column( + children: [ + // 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트) + TaskProgressPanel( + progress: state.progress, + speedMultiplier: + widget.controller.loop?.speedMultiplier ?? 1, + onSpeedCycle: () { + widget.controller.loop?.cycleSpeed(); + setState(() {}); + }, + // 특수 애니메이션 중에는 일시정지 상태로 표시하지 않음 + isPaused: + !widget.controller.isRunning && + _specialAnimation == null, + onPauseToggle: () async { + await widget.controller.togglePause(); + setState(() {}); + }, + specialAnimation: _specialAnimation, + weaponName: state.equipment.weapon, + shieldName: state.equipment.shield, + characterLevel: state.traits.level, + monsterLevel: state.progress.currentTask.monsterLevel, + monsterGrade: state.progress.currentTask.monsterGrade, + latestCombatEvent: + state.progress.currentCombat?.recentEvents.lastOrNull, + raceId: state.traits.raceId, + ), - // Phase 4: 사망 오버레이 (Death Overlay) - if (state.isDead && state.deathInfo != null) - DeathOverlay( - deathInfo: state.deathInfo!, - traits: state.traits, - onResurrect: _handleResurrect, - isAutoResurrectEnabled: widget.controller.autoResurrect, - onToggleAutoResurrect: () { - widget.controller.setAutoResurrect( - !widget.controller.autoResurrect, - ); - }, + // 메인 3패널 영역 + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 좌측 패널: Character Sheet + Expanded(flex: 2, child: _buildCharacterPanel(state)), + + // 중앙 패널: Equipment/Inventory + Expanded(flex: 3, child: _buildEquipmentPanel(state)), + + // 우측 패널: Plot/Quest + Expanded(flex: 2, child: _buildQuestPanel(state)), + ], + ), + ), + ], ), - // 승리 오버레이 (게임 클리어) - if (widget.controller.isComplete) - VictoryOverlay( - traits: state.traits, - stats: state.stats, - progress: state.progress, - elapsedMs: state.skillSystem.elapsedMs, - onComplete: _handleVictoryComplete, - ), - ], + + // Phase 4: 사망 오버레이 (Death Overlay) + if (state.isDead && state.deathInfo != null) + DeathOverlay( + deathInfo: state.deathInfo!, + traits: state.traits, + onResurrect: _handleResurrect, + isAutoResurrectEnabled: widget.controller.autoResurrect, + onToggleAutoResurrect: () { + widget.controller.setAutoResurrect( + !widget.controller.autoResurrect, + ); + }, + ), + // 승리 오버레이 (게임 클리어) + if (widget.controller.isComplete) + VictoryOverlay( + traits: state.traits, + stats: state.stats, + progress: state.progress, + elapsedMs: state.skillSystem.elapsedMs, + onComplete: _handleVictoryComplete, + ), + ], + ), ), ), - ), ), ); } @@ -1159,7 +1165,8 @@ class _GamePlayScreenState extends State state.progress.exp.position, state.progress.exp.max, Colors.blue, - tooltip: '${state.progress.exp.position} / ${state.progress.exp.max}', + tooltip: + '${state.progress.exp.position} / ${state.progress.exp.max}', ), // 스킬 (Skills - SpellBook 기반) @@ -1286,9 +1293,7 @@ class _GamePlayScreenState extends State padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: const BoxDecoration( color: RetroColors.darkBrown, - border: Border( - bottom: BorderSide(color: RetroColors.gold, width: 2), - ), + border: Border(bottom: BorderSide(color: RetroColors.gold, width: 2)), ), child: Text( title.toUpperCase(), @@ -1345,7 +1350,9 @@ class _GamePlayScreenState extends State border: Border( right: index < segmentCount - 1 ? BorderSide( - color: RetroColors.panelBorderOuter.withValues(alpha: 0.3), + color: RetroColors.panelBorderOuter.withValues( + alpha: 0.3, + ), width: 1, ) : BorderSide.none, @@ -1478,7 +1485,11 @@ class _GamePlayScreenState extends State padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ - const Icon(Icons.monetization_on, size: 10, color: RetroColors.gold), + const Icon( + Icons.monetization_on, + size: 10, + color: RetroColors.gold, + ), const SizedBox(width: 4), Expanded( child: Text( @@ -1568,7 +1579,9 @@ class _GamePlayScreenState extends State Icon( isCompleted ? Icons.check_box - : (isCurrent ? Icons.arrow_right : Icons.check_box_outline_blank), + : (isCurrent + ? Icons.arrow_right + : Icons.check_box_outline_blank), size: 12, color: isCompleted ? RetroColors.expGreen @@ -1583,7 +1596,9 @@ class _GamePlayScreenState extends State fontSize: 8, color: isCompleted ? RetroColors.textDisabled - : (isCurrent ? RetroColors.gold : RetroColors.textLight), + : (isCurrent + ? RetroColors.gold + : RetroColors.textLight), decoration: isCompleted ? TextDecoration.lineThrough : TextDecoration.none, @@ -1632,14 +1647,14 @@ class _GamePlayScreenState extends State isCurrentQuest ? Icons.arrow_right : (quest.isComplete - ? Icons.check_box - : Icons.check_box_outline_blank), + ? Icons.check_box + : Icons.check_box_outline_blank), size: 12, color: isCurrentQuest ? RetroColors.gold : (quest.isComplete - ? RetroColors.expGreen - : RetroColors.textDisabled), + ? RetroColors.expGreen + : RetroColors.textDisabled), ), const SizedBox(width: 4), Expanded( @@ -1651,8 +1666,8 @@ class _GamePlayScreenState extends State color: isCurrentQuest ? RetroColors.gold : (quest.isComplete - ? RetroColors.textDisabled - : RetroColors.textLight), + ? RetroColors.textDisabled + : RetroColors.textLight), decoration: quest.isComplete ? TextDecoration.lineThrough : TextDecoration.none, diff --git a/lib/src/features/game/game_session_controller.dart b/lib/src/features/game/game_session_controller.dart index f599cd5..dad5e96 100644 --- a/lib/src/features/game/game_session_controller.dart +++ b/lib/src/features/game/game_session_controller.dart @@ -223,15 +223,14 @@ class GameSessionController extends ChangeNotifier { notifyListeners(); } - Future loadAndStart({ - String? fileName, - }) async { + Future loadAndStart({String? fileName}) async { _status = GameSessionStatus.loading; _error = null; notifyListeners(); - final (outcome, loaded, savedCheatsEnabled) = - await saveManager.loadState(fileName: fileName); + final (outcome, loaded, savedCheatsEnabled) = await saveManager.loadState( + fileName: fileName, + ); if (!outcome.success || loaded == null) { _status = GameSessionStatus.error; _error = outcome.error ?? 'Unknown error'; @@ -346,7 +345,9 @@ class GameSessionController extends ChangeNotifier { combatStats: combatStats, ); - debugPrint('[HallOfFame] Entry created: ${entry.characterName} Lv.${entry.level}'); + debugPrint( + '[HallOfFame] Entry created: ${entry.characterName} Lv.${entry.level}', + ); final success = await _hallOfFameStorage.addEntry(entry); debugPrint('[HallOfFame] Storage save result: $success'); diff --git a/lib/src/features/game/widgets/ascii_animation_card.dart b/lib/src/features/game/widgets/ascii_animation_card.dart index 646ecfa..6b006e2 100644 --- a/lib/src/features/game/widgets/ascii_animation_card.dart +++ b/lib/src/features/game/widgets/ascii_animation_card.dart @@ -247,74 +247,163 @@ class _AsciiAnimationCardState extends State { CombatEventType.playerAttack => ( BattlePhase.prepare, event.isCritical, - false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, ), // 스킬 사용 → prepare 페이즈부터 시작 + 스킬 이펙트 CombatEventType.playerSkill => ( BattlePhase.prepare, event.isCritical, - false, false, true, false, false, false, false, + false, + false, + true, + false, + false, + false, + false, ), // 몬스터 공격 → prepare 페이즈부터 시작 CombatEventType.monsterAttack => ( BattlePhase.prepare, - false, false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, ), // 블록 → hit 페이즈 + 블록 이펙트 + 텍스트 CombatEventType.playerBlock => ( BattlePhase.hit, - false, true, false, false, false, false, false, false, + false, + true, + false, + false, + false, + false, + false, + false, ), // 패리 → hit 페이즈 + 패리 이펙트 + 텍스트 CombatEventType.playerParry => ( BattlePhase.hit, - false, false, true, false, false, false, false, false, + false, + false, + true, + false, + false, + false, + false, + false, ), // 플레이어 회피 → recover 페이즈 + 회피 텍스트 CombatEventType.playerEvade => ( BattlePhase.recover, - false, false, false, false, true, false, false, false, + false, + false, + false, + false, + true, + false, + false, + false, ), // 몬스터 회피 → idle 페이즈 + 미스 텍스트 CombatEventType.monsterEvade => ( BattlePhase.idle, - false, false, false, false, false, true, false, false, + false, + false, + false, + false, + false, + true, + false, + false, ), // 회복/버프 → idle 페이즈 유지 CombatEventType.playerHeal => ( BattlePhase.idle, - false, false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, ), CombatEventType.playerBuff => ( BattlePhase.idle, - false, false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, ), // 디버프 적용 → idle 페이즈 + 디버프 텍스트 CombatEventType.playerDebuff => ( BattlePhase.idle, - false, false, false, false, false, false, true, false, + false, + false, + false, + false, + false, + false, + true, + false, ), // DOT 틱 → attack 페이즈 + DOT 텍스트 CombatEventType.dotTick => ( BattlePhase.attack, - false, false, false, false, false, false, false, true, + false, + false, + false, + false, + false, + false, + false, + true, ), // 물약 사용 → idle 페이즈 유지 CombatEventType.playerPotion => ( BattlePhase.idle, - false, false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, ), // 물약 드랍 → idle 페이즈 유지 CombatEventType.potionDrop => ( BattlePhase.idle, - false, false, false, false, false, false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, ), }; @@ -375,7 +464,8 @@ class _AsciiAnimationCardState extends State { // 특수 애니메이션 프레임 간격 계산 (200ms tick 기준) // 예: resurrection 600ms → 600/200 = 3 tick마다 1 프레임 final frameInterval = - (specialAnimationFrameIntervals[_currentSpecialAnimation] ?? 200) ~/ + (specialAnimationFrameIntervals[_currentSpecialAnimation] ?? + 200) ~/ 200; if (_specialTick >= frameInterval) { _specialTick = 0; @@ -576,9 +666,7 @@ class _AsciiAnimationCardState extends State { ); } else if (isSpecial) { // 특수 애니메이션: 포지티브 색상 테두리 - borderEffect = Border.all( - color: positiveColor.withValues(alpha: 0.5), - ); + borderEffect = Border.all(color: positiveColor.withValues(alpha: 0.5)); } return Container( diff --git a/lib/src/features/game/widgets/carousel_nav_bar.dart b/lib/src/features/game/widgets/carousel_nav_bar.dart index ccb2557..2c544e2 100644 --- a/lib/src/features/game/widgets/carousel_nav_bar.dart +++ b/lib/src/features/game/widgets/carousel_nav_bar.dart @@ -44,9 +44,7 @@ class CarouselNavBar extends StatelessWidget { ), decoration: BoxDecoration( color: panelBg, - border: Border( - top: BorderSide(color: accent, width: 2), - ), + border: Border(top: BorderSide(color: accent, width: 2)), ), child: SizedBox( height: 56, diff --git a/lib/src/features/game/widgets/combat_log.dart b/lib/src/features/game/widgets/combat_log.dart index 56913c7..d62b4df 100644 --- a/lib/src/features/game/widgets/combat_log.dart +++ b/lib/src/features/game/widgets/combat_log.dart @@ -11,6 +11,15 @@ class CombatLogEntry { final String message; final DateTime timestamp; final CombatLogType type; + + /// JSON 직렬화 (배틀 로그 저장용) + Map toJson() { + return { + 'message': message, + 'timestamp': timestamp.toIso8601String(), + 'type': type.name, + }; + } } /// 로그 타입에 따른 스타일 구분 diff --git a/lib/src/features/game/widgets/death_overlay.dart b/lib/src/features/game/widgets/death_overlay.dart index cac50c2..ffc36f6 100644 --- a/lib/src/features/game/widgets/death_overlay.dart +++ b/lib/src/features/game/widgets/death_overlay.dart @@ -70,20 +70,18 @@ class DeathOverlay extends StatelessWidget { // 헤더 바 Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), decoration: BoxDecoration( color: hpColor.withValues(alpha: 0.3), - border: Border( - bottom: BorderSide(color: hpColor, width: 2), - ), + border: Border(bottom: BorderSide(color: hpColor, width: 2)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '☠', - style: TextStyle(fontSize: 16, color: hpColor), - ), + Text('☠', style: TextStyle(fontSize: 16, color: hpColor)), const SizedBox(width: 8), Text( 'GAME OVER', @@ -95,10 +93,7 @@ class DeathOverlay extends StatelessWidget { ), ), const SizedBox(width: 8), - Text( - '☠', - style: TextStyle(fontSize: 16, color: hpColor), - ), + Text('☠', style: TextStyle(fontSize: 16, color: hpColor)), ], ), ), @@ -107,49 +102,49 @@ class DeathOverlay extends StatelessWidget { child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 사망 타이틀 - _buildDeathTitle(context), - const SizedBox(height: 16), - - // 캐릭터 정보 - _buildCharacterInfo(context), - const SizedBox(height: 16), - - // 사망 원인 - _buildDeathCause(context), - const SizedBox(height: 20), - - // 구분선 - _buildRetroDivider(hpColor, hpDark), - const SizedBox(height: 16), - - // 상실 정보 - _buildLossInfo(context), - - // 전투 로그 (있는 경우만 표시) - if (deathInfo.lastCombatEvents.isNotEmpty) ...[ + mainAxisSize: MainAxisSize.min, + children: [ + // 사망 타이틀 + _buildDeathTitle(context), const SizedBox(height: 16), + + // 캐릭터 정보 + _buildCharacterInfo(context), + const SizedBox(height: 16), + + // 사망 원인 + _buildDeathCause(context), + const SizedBox(height: 20), + + // 구분선 _buildRetroDivider(hpColor, hpDark), - const SizedBox(height: 8), - _buildCombatLog(context), + const SizedBox(height: 16), + + // 상실 정보 + _buildLossInfo(context), + + // 전투 로그 (있는 경우만 표시) + if (deathInfo.lastCombatEvents.isNotEmpty) ...[ + const SizedBox(height: 16), + _buildRetroDivider(hpColor, hpDark), + const SizedBox(height: 8), + _buildCombatLog(context), + ], + + const SizedBox(height: 24), + + // 부활 버튼 + _buildResurrectButton(context), + + // 자동 부활 버튼 + if (onToggleAutoResurrect != null) ...[ + const SizedBox(height: 12), + _buildAutoResurrectButton(context), + ], ], - - const SizedBox(height: 24), - - // 부활 버튼 - _buildResurrectButton(context), - - // 자동 부활 버튼 - if (onToggleAutoResurrect != null) ...[ - const SizedBox(height: 12), - _buildAutoResurrectButton(context), - ], - ], + ), ), ), - ), ], ), ), @@ -264,10 +259,7 @@ class DeathOverlay extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - '⚔', - style: TextStyle(fontSize: 14, color: hpColor), - ), + Text('⚔', style: TextStyle(fontSize: 14, color: hpColor)), const SizedBox(width: 8), Flexible( child: Text( @@ -309,16 +301,11 @@ class DeathOverlay extends StatelessWidget { padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: hpDark.withValues(alpha: 0.2), - border: Border.all( - color: hpColor.withValues(alpha: 0.4), - ), + border: Border.all(color: hpColor.withValues(alpha: 0.4)), ), child: Row( children: [ - const Text( - '🔥', - style: TextStyle(fontSize: 16), - ), + const Text('🔥', style: TextStyle(fontSize: 16)), const SizedBox(width: 8), Expanded( child: Column( @@ -383,10 +370,7 @@ class DeathOverlay extends StatelessWidget { children: [ Row( children: [ - Text( - asciiIcon, - style: TextStyle(fontSize: 14, color: valueColor), - ), + Text(asciiIcon, style: TextStyle(fontSize: 14, color: valueColor)), const SizedBox(width: 8), Text( label, @@ -433,14 +417,8 @@ class DeathOverlay extends StatelessWidget { border: Border( top: BorderSide(color: expColor, width: 3), left: BorderSide(color: expColor, width: 3), - bottom: BorderSide( - color: expDark.withValues(alpha: 0.8), - width: 3, - ), - right: BorderSide( - color: expDark.withValues(alpha: 0.8), - width: 3, - ), + bottom: BorderSide(color: expDark.withValues(alpha: 0.8), width: 3), + right: BorderSide(color: expDark.withValues(alpha: 0.8), width: 3), ), ), child: Row( @@ -478,7 +456,9 @@ class DeathOverlay extends StatelessWidget { // 활성화 상태에 따른 색상 final buttonColor = isAutoResurrectEnabled ? mpColor : muted; - final buttonDark = isAutoResurrectEnabled ? mpDark : muted.withValues(alpha: 0.5); + final buttonDark = isAutoResurrectEnabled + ? mpDark + : muted.withValues(alpha: 0.5); return GestureDetector( onTap: onToggleAutoResurrect, @@ -551,10 +531,7 @@ class DeathOverlay extends StatelessWidget { children: [ Row( children: [ - const Text( - '📜', - style: TextStyle(fontSize: 12), - ), + const Text('📜', style: TextStyle(fontSize: 12)), const SizedBox(width: 6), Text( l10n.deathCombatLog.toUpperCase(), @@ -595,10 +572,7 @@ class DeathOverlay extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 1), child: Row( children: [ - Text( - asciiIcon, - style: TextStyle(fontSize: 10, color: color), - ), + Text(asciiIcon, style: TextStyle(fontSize: 10, color: color)), const SizedBox(width: 4), Expanded( child: Text( diff --git a/lib/src/features/game/widgets/enhanced_animation_panel.dart b/lib/src/features/game/widgets/enhanced_animation_panel.dart index 91dfcda..046a6a2 100644 --- a/lib/src/features/game/widgets/enhanced_animation_panel.dart +++ b/lib/src/features/game/widgets/enhanced_animation_panel.dart @@ -645,10 +645,12 @@ class _EnhancedAnimationPanelState extends State // 몬스터 등급에 따른 접두사와 색상 final grade = widget.monsterGrade; final isKillTask = widget.progress.currentTask.type == TaskType.kill; - final gradePrefix = - (isKillTask && grade != null) ? grade.displayPrefix : ''; - final gradeColor = - (isKillTask && grade != null) ? grade.displayColor : null; + final gradePrefix = (isKillTask && grade != null) + ? grade.displayPrefix + : ''; + final gradeColor = (isKillTask && grade != null) + ? grade.displayColor + : null; return Column( children: [ diff --git a/lib/src/features/game/widgets/help_dialog.dart b/lib/src/features/game/widgets/help_dialog.dart index cc7809d..bf06acc 100644 --- a/lib/src/features/game/widgets/help_dialog.dart +++ b/lib/src/features/game/widgets/help_dialog.dart @@ -46,14 +46,14 @@ class _HelpDialogState extends State final title = isKorean ? '도움말' : isJapanese - ? 'ヘルプ' - : 'Help'; + ? 'ヘルプ' + : 'Help'; final tabs = isKorean ? ['기본', '전투', '스킬', 'UI'] : isJapanese - ? ['基本', '戦闘', 'スキル', 'UI'] - : ['Basics', 'Combat', 'Skills', 'UI']; + ? ['基本', '戦闘', 'スキル', 'UI'] + : ['Basics', 'Combat', 'Skills', 'UI']; // 도움말은 MP 블루 색상 사용 (테마 인식) final mpColor = RetroColors.mpOf(context); @@ -75,22 +75,10 @@ class _HelpDialogState extends State child: TabBarView( controller: _tabController, children: [ - _BasicsHelpView( - isKorean: isKorean, - isJapanese: isJapanese, - ), - _CombatHelpView( - isKorean: isKorean, - isJapanese: isJapanese, - ), - _SkillsHelpView( - isKorean: isKorean, - isJapanese: isJapanese, - ), - _UIHelpView( - isKorean: isKorean, - isJapanese: isJapanese, - ), + _BasicsHelpView(isKorean: isKorean, isJapanese: isJapanese), + _CombatHelpView(isKorean: isKorean, isJapanese: isJapanese), + _SkillsHelpView(isKorean: isKorean, isJapanese: isJapanese), + _UIHelpView(isKorean: isKorean, isJapanese: isJapanese), ], ), ), @@ -102,10 +90,7 @@ class _HelpDialogState extends State /// 기본 도움말 뷰 class _BasicsHelpView extends StatelessWidget { - const _BasicsHelpView({ - required this.isKorean, - required this.isJapanese, - }); + const _BasicsHelpView({required this.isKorean, required this.isJapanese}); final bool isKorean; final bool isJapanese; @@ -120,16 +105,16 @@ class _BasicsHelpView extends StatelessWidget { title: isKorean ? '게임 소개' : isJapanese - ? 'ゲーム紹介' - : 'About the Game', + ? 'ゲーム紹介' + : 'About the Game', content: isKorean ? 'Askii Never Die는 자동 진행 RPG입니다. 캐릭터가 자동으로 몬스터와 싸우고, ' - '퀘스트를 완료하며, 레벨업합니다. 여러분은 장비와 스킬을 관리하면 됩니다.' + '퀘스트를 완료하며, 레벨업합니다. 여러분은 장비와 스킬을 관리하면 됩니다.' : isJapanese - ? 'Askii Never Dieは自動進行RPGです。キャラクターが自動でモンスターと戦い、' - 'クエストを完了し、レベルアップします。装備とスキルの管理だけで大丈夫です。' - : 'Askii Never Die is an idle RPG. Your character automatically fights monsters, ' - 'completes quests, and levels up. You manage equipment and skills.', + ? 'Askii Never Dieは自動進行RPGです。キャラクターが自動でモンスターと戦い、' + 'クエストを完了し、レベルアップします。装備とスキルの管理だけで大丈夫です。' + : 'Askii Never Die is an idle RPG. Your character automatically fights monsters, ' + 'completes quests, and levels up. You manage equipment and skills.', ), const SizedBox(height: 12), _HelpSection( @@ -137,22 +122,22 @@ class _BasicsHelpView extends StatelessWidget { title: isKorean ? '진행 방식' : isJapanese - ? '進行方式' - : 'Progression', + ? '進行方式' + : 'Progression', content: isKorean ? '• 몬스터 처치 → 전리품 획득 → 장비 업그레이드\n' - '• 경험치 획득 → 레벨업 → 스탯 상승\n' - '• 퀘스트 완료 → 보상 획득\n' - '• 플롯 진행 → 새로운 Act 해금' + '• 경험치 획득 → 레벨업 → 스탯 상승\n' + '• 퀘스트 완료 → 보상 획득\n' + '• 플롯 진행 → 새로운 Act 해금' : isJapanese - ? '• モンスター討伐 → 戦利品獲得 → 装備アップグレード\n' - '• 経験値獲得 → レベルアップ → ステータス上昇\n' - '• クエスト完了 → 報酬獲得\n' - '• プロット進行 → 新しいAct解放' - : '• Kill monsters → Get loot → Upgrade equipment\n' - '• Gain XP → Level up → Stats increase\n' - '• Complete quests → Get rewards\n' - '• Progress plot → Unlock new Acts', + ? '• モンスター討伐 → 戦利品獲得 → 装備アップグレード\n' + '• 経験値獲得 → レベルアップ → ステータス上昇\n' + '• クエスト完了 → 報酬獲得\n' + '• プロット進行 → 新しいAct解放' + : '• Kill monsters → Get loot → Upgrade equipment\n' + '• Gain XP → Level up → Stats increase\n' + '• Complete quests → Get rewards\n' + '• Progress plot → Unlock new Acts', ), const SizedBox(height: 12), _HelpSection( @@ -160,16 +145,16 @@ class _BasicsHelpView extends StatelessWidget { title: isKorean ? '저장' : isJapanese - ? 'セーブ' - : 'Saving', + ? 'セーブ' + : 'Saving', content: isKorean ? '게임은 자동으로 저장됩니다. 레벨업, 퀘스트 완료, Act 진행 시 자동 저장됩니다. ' - '뒤로 가기 시 저장 여부를 선택할 수 있습니다.' + '뒤로 가기 시 저장 여부를 선택할 수 있습니다.' : isJapanese - ? 'ゲームは自動保存されます。レベルアップ、クエスト完了、Act進行時に自動保存されます。' - '戻る時に保存するかどうか選択できます。' - : 'The game auto-saves. It saves on level up, quest completion, and Act progression. ' - 'When exiting, you can choose whether to save.', + ? 'ゲームは自動保存されます。レベルアップ、クエスト完了、Act進行時に自動保存されます。' + '戻る時に保存するかどうか選択できます。' + : 'The game auto-saves. It saves on level up, quest completion, and Act progression. ' + 'When exiting, you can choose whether to save.', ), ], ); @@ -178,10 +163,7 @@ class _BasicsHelpView extends StatelessWidget { /// 전투 도움말 뷰 class _CombatHelpView extends StatelessWidget { - const _CombatHelpView({ - required this.isKorean, - required this.isJapanese, - }); + const _CombatHelpView({required this.isKorean, required this.isJapanese}); final bool isKorean; final bool isJapanese; @@ -196,16 +178,16 @@ class _CombatHelpView extends StatelessWidget { title: isKorean ? '전투 시스템' : isJapanese - ? '戦闘システム' - : 'Combat System', + ? '戦闘システム' + : 'Combat System', content: isKorean ? '전투는 자동으로 진행됩니다. 플레이어와 몬스터가 번갈아 공격하며, ' - '공격 속도(Attack Speed)에 따라 공격 빈도가 결정됩니다.' + '공격 속도(Attack Speed)에 따라 공격 빈도가 결정됩니다.' : isJapanese - ? '戦闘は自動で進行します。プレイヤーとモンスターが交互に攻撃し、' - '攻撃速度(Attack Speed)によって攻撃頻度が決まります。' - : 'Combat is automatic. Player and monster take turns attacking, ' - 'with attack frequency based on Attack Speed.', + ? '戦闘は自動で進行します。プレイヤーとモンスターが交互に攻撃し、' + '攻撃速度(Attack Speed)によって攻撃頻度が決まります。' + : 'Combat is automatic. Player and monster take turns attacking, ' + 'with attack frequency based on Attack Speed.', ), const SizedBox(height: 12), _HelpSection( @@ -213,22 +195,22 @@ class _CombatHelpView extends StatelessWidget { title: isKorean ? '방어 메카닉' : isJapanese - ? '防御メカニック' - : 'Defense Mechanics', + ? '防御メカニック' + : 'Defense Mechanics', content: isKorean ? '• 회피(Evasion): DEX 기반, 공격을 완전히 피함\n' - '• 방패 방어(Block): 방패 장착 시, 피해 감소\n' - '• 무기 쳐내기(Parry): 무기로 공격 일부 막음\n' - '• 방어력(DEF): 모든 피해에서 차감' + '• 방패 방어(Block): 방패 장착 시, 피해 감소\n' + '• 무기 쳐내기(Parry): 무기로 공격 일부 막음\n' + '• 방어력(DEF): 모든 피해에서 차감' : isJapanese - ? '• 回避(Evasion): DEX基準、攻撃を完全に回避\n' - '• 盾防御(Block): 盾装備時、ダメージ軽減\n' - '• 武器受け流し(Parry): 武器で攻撃を一部防ぐ\n' - '• 防御力(DEF): 全ダメージから差し引き' - : '• Evasion: DEX-based, completely avoid attacks\n' - '• Block: With shield, reduce damage\n' - '• Parry: Deflect some damage with weapon\n' - '• DEF: Subtracted from all damage', + ? '• 回避(Evasion): DEX基準、攻撃を完全に回避\n' + '• 盾防御(Block): 盾装備時、ダメージ軽減\n' + '• 武器受け流し(Parry): 武器で攻撃を一部防ぐ\n' + '• 防御力(DEF): 全ダメージから差し引き' + : '• Evasion: DEX-based, completely avoid attacks\n' + '• Block: With shield, reduce damage\n' + '• Parry: Deflect some damage with weapon\n' + '• DEF: Subtracted from all damage', ), const SizedBox(height: 12), _HelpSection( @@ -236,16 +218,16 @@ class _CombatHelpView extends StatelessWidget { title: isKorean ? '사망과 부활' : isJapanese - ? '死亡と復活' - : 'Death & Revival', + ? '死亡と復活' + : 'Death & Revival', content: isKorean ? 'HP가 0이 되면 사망합니다. 사망 시 장비 하나를 제물로 바쳐 부활할 수 있습니다. ' - '부활 후 HP/MP가 완전 회복되고 빈 장비 슬롯에 기본 장비가 지급됩니다.' + '부활 후 HP/MP가 완전 회복되고 빈 장비 슬롯에 기본 장비가 지급됩니다.' : isJapanese - ? 'HPが0になると死亡します。死亡時に装備1つを捧げて復活できます。' - '復活後HP/MPが完全回復し、空の装備スロットに基本装備が支給されます。' - : 'You die when HP reaches 0. Sacrifice one equipment piece to revive. ' - 'After revival, HP/MP fully restore and empty slots get basic equipment.', + ? 'HPが0になると死亡します。死亡時に装備1つを捧げて復活できます。' + '復活後HP/MPが完全回復し、空の装備スロットに基本装備が支給されます。' + : 'You die when HP reaches 0. Sacrifice one equipment piece to revive. ' + 'After revival, HP/MP fully restore and empty slots get basic equipment.', ), ], ); @@ -254,10 +236,7 @@ class _CombatHelpView extends StatelessWidget { /// 스킬 도움말 뷰 class _SkillsHelpView extends StatelessWidget { - const _SkillsHelpView({ - required this.isKorean, - required this.isJapanese, - }); + const _SkillsHelpView({required this.isKorean, required this.isJapanese}); final bool isKorean; final bool isJapanese; @@ -272,25 +251,25 @@ class _SkillsHelpView extends StatelessWidget { title: isKorean ? '스킬 종류' : isJapanese - ? 'スキル種類' - : 'Skill Types', + ? 'スキル種類' + : 'Skill Types', content: isKorean ? '• 공격(Attack): 적에게 직접 피해\n' - '• 회복(Heal): HP/MP 회복\n' - '• 버프(Buff): 자신에게 유리한 효과\n' - '• 디버프(Debuff): 적에게 불리한 효과\n' - '• DOT: 시간에 걸쳐 지속 피해' + '• 회복(Heal): HP/MP 회복\n' + '• 버프(Buff): 자신에게 유리한 효과\n' + '• 디버프(Debuff): 적에게 불리한 효과\n' + '• DOT: 시간에 걸쳐 지속 피해' : isJapanese - ? '• 攻撃(Attack): 敵に直接ダメージ\n' - '• 回復(Heal): HP/MP回復\n' - '• バフ(Buff): 自分に有利な効果\n' - '• デバフ(Debuff): 敵に不利な効果\n' - '• DOT: 時間経過でダメージ' - : '• Attack: Deal direct damage\n' - '• Heal: Restore HP/MP\n' - '• Buff: Beneficial effects on self\n' - '• Debuff: Harmful effects on enemies\n' - '• DOT: Damage over time', + ? '• 攻撃(Attack): 敵に直接ダメージ\n' + '• 回復(Heal): HP/MP回復\n' + '• バフ(Buff): 自分に有利な効果\n' + '• デバフ(Debuff): 敵に不利な効果\n' + '• DOT: 時間経過でダメージ' + : '• Attack: Deal direct damage\n' + '• Heal: Restore HP/MP\n' + '• Buff: Beneficial effects on self\n' + '• Debuff: Harmful effects on enemies\n' + '• DOT: Damage over time', ), const SizedBox(height: 12), _HelpSection( @@ -298,25 +277,25 @@ class _SkillsHelpView extends StatelessWidget { title: isKorean ? '자동 스킬 선택' : isJapanese - ? '自動スキル選択' - : 'Auto Skill Selection', + ? '自動スキル選択' + : 'Auto Skill Selection', content: isKorean ? '스킬은 AI가 자동으로 선택합니다:\n' - '1. HP 낮음 → 회복 스킬 우선\n' - '2. HP/MP 충분 → 버프 스킬 사용\n' - '3. 몬스터 HP 높음 → 디버프 적용\n' - '4. 공격 스킬로 마무리' + '1. HP 낮음 → 회복 스킬 우선\n' + '2. HP/MP 충분 → 버프 스킬 사용\n' + '3. 몬스터 HP 높음 → 디버프 적용\n' + '4. 공격 스킬로 마무리' : isJapanese - ? 'スキルはAIが自動選択します:\n' - '1. HP低い → 回復スキル優先\n' - '2. HP/MP十分 → バフスキル使用\n' - '3. モンスターHP高い → デバフ適用\n' - '4. 攻撃スキルで仕上げ' - : 'Skills are auto-selected by AI:\n' - '1. Low HP → Heal skills priority\n' - '2. HP/MP sufficient → Use buff skills\n' - '3. Monster HP high → Apply debuffs\n' - '4. Finish with attack skills', + ? 'スキルはAIが自動選択します:\n' + '1. HP低い → 回復スキル優先\n' + '2. HP/MP十分 → バフスキル使用\n' + '3. モンスターHP高い → デバフ適用\n' + '4. 攻撃スキルで仕上げ' + : 'Skills are auto-selected by AI:\n' + '1. Low HP → Heal skills priority\n' + '2. HP/MP sufficient → Use buff skills\n' + '3. Monster HP high → Apply debuffs\n' + '4. Finish with attack skills', ), const SizedBox(height: 12), _HelpSection( @@ -324,25 +303,25 @@ class _SkillsHelpView extends StatelessWidget { title: isKorean ? '스킬 랭크' : isJapanese - ? 'スキルランク' - : 'Skill Ranks', + ? 'スキルランク' + : 'Skill Ranks', content: isKorean ? '스킬은 I ~ IX 랭크가 있습니다. 랭크가 높을수록:\n' - '• 데미지/회복량 증가\n' - '• MP 소모량 증가\n' - '• 쿨타임 증가\n' - '레벨업 시 랜덤하게 스킬을 배웁니다.' + '• 데미지/회복량 증가\n' + '• MP 소모량 증가\n' + '• 쿨타임 증가\n' + '레벨업 시 랜덤하게 스킬을 배웁니다.' : isJapanese - ? 'スキルにはI~IXランクがあります。ランクが高いほど:\n' - '• ダメージ/回復量増加\n' - '• MP消費量増加\n' - '• クールタイム増加\n' - 'レベルアップ時にランダムでスキルを習得します。' - : 'Skills have ranks I~IX. Higher rank means:\n' - '• More damage/healing\n' - '• More MP cost\n' - '• Longer cooldown\n' - 'Learn random skills on level up.', + ? 'スキルにはI~IXランクがあります。ランクが高いほど:\n' + '• ダメージ/回復量増加\n' + '• MP消費量増加\n' + '• クールタイム増加\n' + 'レベルアップ時にランダムでスキルを習得します。' + : 'Skills have ranks I~IX. Higher rank means:\n' + '• More damage/healing\n' + '• More MP cost\n' + '• Longer cooldown\n' + 'Learn random skills on level up.', ), ], ); @@ -351,10 +330,7 @@ class _SkillsHelpView extends StatelessWidget { /// UI 도움말 뷰 class _UIHelpView extends StatelessWidget { - const _UIHelpView({ - required this.isKorean, - required this.isJapanese, - }); + const _UIHelpView({required this.isKorean, required this.isJapanese}); final bool isKorean; final bool isJapanese; @@ -369,22 +345,22 @@ class _UIHelpView extends StatelessWidget { title: isKorean ? '화면 구성' : isJapanese - ? '画面構成' - : 'Screen Layout', + ? '画面構成' + : 'Screen Layout', content: isKorean ? '• 상단: 전투 애니메이션, 태스크 진행바\n' - '• 좌측: 캐릭터 정보, HP/MP, 스탯\n' - '• 중앙: 장비, 인벤토리\n' - '• 우측: 플롯/퀘스트 진행, 스펠북' + '• 좌측: 캐릭터 정보, HP/MP, 스탯\n' + '• 중앙: 장비, 인벤토리\n' + '• 우측: 플롯/퀘스트 진행, 스펠북' : isJapanese - ? '• 上部: 戦闘アニメーション、タスク進行バー\n' - '• 左側: キャラクター情報、HP/MP、ステータス\n' - '• 中央: 装備、インベントリ\n' - '• 右側: プロット/クエスト進行、スペルブック' - : '• Top: Combat animation, task progress bar\n' - '• Left: Character info, HP/MP, stats\n' - '• Center: Equipment, inventory\n' - '• Right: Plot/quest progress, spellbook', + ? '• 上部: 戦闘アニメーション、タスク進行バー\n' + '• 左側: キャラクター情報、HP/MP、ステータス\n' + '• 中央: 装備、インベントリ\n' + '• 右側: プロット/クエスト進行、スペルブック' + : '• Top: Combat animation, task progress bar\n' + '• Left: Character info, HP/MP, stats\n' + '• Center: Equipment, inventory\n' + '• Right: Plot/quest progress, spellbook', ), const SizedBox(height: 12), _HelpSection( @@ -392,25 +368,25 @@ class _UIHelpView extends StatelessWidget { title: isKorean ? '속도 조절' : isJapanese - ? '速度調整' - : 'Speed Control', + ? '速度調整' + : 'Speed Control', content: isKorean ? '태스크 진행바 옆 속도 버튼으로 게임 속도를 조절할 수 있습니다:\n' - '• 1x: 기본 속도\n' - '• 2x: 2배 속도\n' - '• 5x: 5배 속도\n' - '• 10x: 10배 속도' + '• 1x: 기본 속도\n' + '• 2x: 2배 속도\n' + '• 5x: 5배 속도\n' + '• 10x: 10배 속도' : isJapanese - ? 'タスク進行バー横の速度ボタンでゲーム速度を調整できます:\n' - '• 1x: 基本速度\n' - '• 2x: 2倍速\n' - '• 5x: 5倍速\n' - '• 10x: 10倍速' - : 'Use the speed button next to task bar to adjust game speed:\n' - '• 1x: Normal speed\n' - '• 2x: 2x speed\n' - '• 5x: 5x speed\n' - '• 10x: 10x speed', + ? 'タスク進行バー横の速度ボタンでゲーム速度を調整できます:\n' + '• 1x: 基本速度\n' + '• 2x: 2倍速\n' + '• 5x: 5倍速\n' + '• 10x: 10倍速' + : 'Use the speed button next to task bar to adjust game speed:\n' + '• 1x: Normal speed\n' + '• 2x: 2x speed\n' + '• 5x: 5x speed\n' + '• 10x: 10x speed', ), const SizedBox(height: 12), _HelpSection( @@ -418,16 +394,16 @@ class _UIHelpView extends StatelessWidget { title: isKorean ? '일시정지' : isJapanese - ? '一時停止' - : 'Pause', + ? '一時停止' + : 'Pause', content: isKorean ? '일시정지 버튼으로 게임을 멈출 수 있습니다. ' - '일시정지 중에도 UI를 확인하고 설정을 변경할 수 있습니다.' + '일시정지 중에도 UI를 확인하고 설정을 변경할 수 있습니다.' : isJapanese - ? '一時停止ボタンでゲームを止められます。' - '一時停止中もUIを確認し設定を変更できます。' - : 'Use the pause button to stop the game. ' - 'You can still view UI and change settings while paused.', + ? '一時停止ボタンでゲームを止められます。' + '一時停止中もUIを確認し設定を変更できます。' + : 'Use the pause button to stop the game. ' + 'You can still view UI and change settings while paused.', ), const SizedBox(height: 12), _HelpSection( @@ -435,16 +411,16 @@ class _UIHelpView extends StatelessWidget { title: isKorean ? '통계' : isJapanese - ? '統計' - : 'Statistics', + ? '統計' + : 'Statistics', content: isKorean ? '통계 버튼에서 현재 세션과 누적 게임 통계를 확인할 수 있습니다. ' - '처치한 몬스터, 획득 골드, 플레이 시간 등을 추적합니다.' + '처치한 몬스터, 획득 골드, 플레이 시간 등을 추적합니다.' : isJapanese - ? '統計ボタンで現在のセッションと累積ゲーム統計を確認できます。' - '倒したモンスター、獲得ゴールド、プレイ時間などを追跡します。' - : 'View current session and cumulative stats in the statistics button. ' - 'Track monsters killed, gold earned, play time, etc.', + ? '統計ボタンで現在のセッションと累積ゲーム統計を確認できます。' + '倒したモンスター、獲得ゴールド、プレイ時間などを追跡します。' + : 'View current session and cumulative stats in the statistics button. ' + 'Track monsters killed, gold earned, play time, etc.', ), ], ); diff --git a/lib/src/features/game/widgets/hp_mp_bar.dart b/lib/src/features/game/widgets/hp_mp_bar.dart index 43978e1..5892e75 100644 --- a/lib/src/features/game/widgets/hp_mp_bar.dart +++ b/lib/src/features/game/widgets/hp_mp_bar.dart @@ -223,11 +223,15 @@ class _HpMpBarState extends State with TickerProviderStateMixin { // 플래시 색상 (데미지=빨강, 회복=녹색) final flashColor = isDamage ? RetroColors.hpRed.withValues(alpha: flashController.value * 0.4) - : RetroColors.expGreen.withValues(alpha: flashController.value * 0.4); + : RetroColors.expGreen.withValues( + alpha: flashController.value * 0.4, + ); // 위험 깜빡임 배경 final lowBgColor = isLow - ? RetroColors.hpRed.withValues(alpha: (1 - _blinkAnimation.value) * 0.3) + ? RetroColors.hpRed.withValues( + alpha: (1 - _blinkAnimation.value) * 0.3, + ) : Colors.transparent; return Container( @@ -263,7 +267,9 @@ class _HpMpBarState extends State with TickerProviderStateMixin { fontFamily: 'PressStart2P', fontSize: 8, fontWeight: FontWeight.bold, - color: isDamage ? RetroColors.hpRed : RetroColors.expGreen, + color: isDamage + ? RetroColors.hpRed + : RetroColors.expGreen, shadows: const [ Shadow(color: Colors.black, blurRadius: 3), Shadow(color: Colors.black, blurRadius: 6), @@ -314,10 +320,7 @@ class _HpMpBarState extends State with TickerProviderStateMixin { height: 12, decoration: BoxDecoration( color: emptyColor.withValues(alpha: 0.3), - border: Border.all( - color: RetroColors.panelBorderOuter, - width: 1, - ), + border: Border.all(color: RetroColors.panelBorderOuter, width: 1), ), child: Row( children: List.generate(segmentCount, (index) { @@ -331,8 +334,9 @@ class _HpMpBarState extends State with TickerProviderStateMixin { border: Border( right: index < segmentCount - 1 ? BorderSide( - color: RetroColors.panelBorderOuter - .withValues(alpha: 0.3), + color: RetroColors.panelBorderOuter.withValues( + alpha: 0.3, + ), width: 1, ) : BorderSide.none, @@ -420,8 +424,9 @@ class _HpMpBarState extends State with TickerProviderStateMixin { decoration: BoxDecoration( color: isFilled ? RetroColors.gold - : RetroColors.panelBorderOuter - .withValues(alpha: 0.3), + : RetroColors.panelBorderOuter.withValues( + alpha: 0.3, + ), border: Border( right: index < segmentCount - 1 ? BorderSide( diff --git a/lib/src/features/game/widgets/notification_overlay.dart b/lib/src/features/game/widgets/notification_overlay.dart index ef4696a..2cd3680 100644 --- a/lib/src/features/game/widgets/notification_overlay.dart +++ b/lib/src/features/game/widgets/notification_overlay.dart @@ -119,7 +119,10 @@ class _NotificationCard extends StatelessWidget { @override Widget build(BuildContext context) { - final (accentColor, icon, asciiIcon) = _getStyleForType(context, notification.type); + final (accentColor, icon, asciiIcon) = _getStyleForType( + context, + notification.type, + ); final panelBg = RetroColors.panelBgOf(context); final borderColor = RetroColors.borderOf(context); final surface = RetroColors.surfaceOf(context); @@ -260,8 +263,16 @@ class _NotificationCard extends StatelessWidget { NotificationType.levelUp => (gold, Icons.arrow_upward, '★'), NotificationType.questComplete => (exp, Icons.check, '☑'), NotificationType.actComplete => (mp, Icons.flag, '⚑'), - NotificationType.newSpell => (const Color(0xFF9966FF), Icons.auto_fix_high, '✧'), - NotificationType.newEquipment => (const Color(0xFFFF9933), Icons.shield, '⚔'), + NotificationType.newSpell => ( + const Color(0xFF9966FF), + Icons.auto_fix_high, + '✧', + ), + NotificationType.newEquipment => ( + const Color(0xFFFF9933), + Icons.shield, + '⚔', + ), NotificationType.bossDefeat => (hp, Icons.whatshot, '☠'), NotificationType.gameSaved => (exp, Icons.save, '💾'), NotificationType.info => (mp, Icons.info_outline, 'ℹ'), diff --git a/lib/src/features/game/widgets/statistics_dialog.dart b/lib/src/features/game/widgets/statistics_dialog.dart index 0fe7d39..dabd1f0 100644 --- a/lib/src/features/game/widgets/statistics_dialog.dart +++ b/lib/src/features/game/widgets/statistics_dialog.dart @@ -25,10 +25,8 @@ class StatisticsDialog extends StatefulWidget { return showDialog( context: context, barrierColor: Colors.black87, - builder: (_) => StatisticsDialog( - session: session, - cumulative: cumulative, - ), + builder: (_) => + StatisticsDialog(session: session, cumulative: cumulative), ); } @@ -60,14 +58,14 @@ class _StatisticsDialogState extends State final title = isKorean ? '통계' : isJapanese - ? '統計' - : 'Statistics'; + ? '統計' + : 'Statistics'; final tabs = isKorean ? ['세션', '누적'] : isJapanese - ? ['セッション', '累積'] - : ['Session', 'Total']; + ? ['セッション', '累積'] + : ['Session', 'Total']; return RetroDialog( title: title, @@ -117,40 +115,40 @@ class _SessionStatisticsView extends StatelessWidget { title: isKorean ? '전투' : isJapanese - ? '戦闘' - : 'Combat', + ? '戦闘' + : 'Combat', icon: '⚔', items: [ _StatItem( label: isKorean ? '플레이 시간' : isJapanese - ? 'プレイ時間' - : 'Play Time', + ? 'プレイ時間' + : 'Play Time', value: stats.formattedPlayTime, ), _StatItem( label: isKorean ? '처치한 몬스터' : isJapanese - ? '倒したモンスター' - : 'Monsters Killed', + ? '倒したモンスター' + : 'Monsters Killed', value: _formatNumber(stats.monstersKilled), ), _StatItem( label: isKorean ? '보스 처치' : isJapanese - ? 'ボス討伐' - : 'Bosses Defeated', + ? 'ボス討伐' + : 'Bosses Defeated', value: _formatNumber(stats.bossesDefeated), ), _StatItem( label: isKorean ? '사망 횟수' : isJapanese - ? '死亡回数' - : 'Deaths', + ? '死亡回数' + : 'Deaths', value: _formatNumber(stats.deathCount), ), ], @@ -160,32 +158,32 @@ class _SessionStatisticsView extends StatelessWidget { title: isKorean ? '데미지' : isJapanese - ? 'ダメージ' - : 'Damage', + ? 'ダメージ' + : 'Damage', icon: '⚡', items: [ _StatItem( label: isKorean ? '입힌 데미지' : isJapanese - ? '与えたダメージ' - : 'Damage Dealt', + ? '与えたダメージ' + : 'Damage Dealt', value: _formatNumber(stats.totalDamageDealt), ), _StatItem( label: isKorean ? '받은 데미지' : isJapanese - ? '受けたダメージ' - : 'Damage Taken', + ? '受けたダメージ' + : 'Damage Taken', value: _formatNumber(stats.totalDamageTaken), ), _StatItem( label: isKorean ? '평균 DPS' : isJapanese - ? '平均DPS' - : 'Average DPS', + ? '平均DPS' + : 'Average DPS', value: stats.averageDps.toStringAsFixed(1), ), ], @@ -195,40 +193,40 @@ class _SessionStatisticsView extends StatelessWidget { title: isKorean ? '스킬' : isJapanese - ? 'スキル' - : 'Skills', + ? 'スキル' + : 'Skills', icon: '✧', items: [ _StatItem( label: isKorean ? '스킬 사용' : isJapanese - ? 'スキル使用' - : 'Skills Used', + ? 'スキル使用' + : 'Skills Used', value: _formatNumber(stats.skillsUsed), ), _StatItem( label: isKorean ? '크리티컬 히트' : isJapanese - ? 'クリティカルヒット' - : 'Critical Hits', + ? 'クリティカルヒット' + : 'Critical Hits', value: _formatNumber(stats.criticalHits), ), _StatItem( label: isKorean ? '최대 연속 크리티컬' : isJapanese - ? '最大連続クリティカル' - : 'Max Critical Streak', + ? '最大連続クリティカル' + : 'Max Critical Streak', value: _formatNumber(stats.maxCriticalStreak), ), _StatItem( label: isKorean ? '크리티컬 비율' : isJapanese - ? 'クリティカル率' - : 'Critical Rate', + ? 'クリティカル率' + : 'Critical Rate', value: '${(stats.criticalRate * 100).toStringAsFixed(1)}%', ), ], @@ -238,40 +236,40 @@ class _SessionStatisticsView extends StatelessWidget { title: isKorean ? '경제' : isJapanese - ? '経済' - : 'Economy', + ? '経済' + : 'Economy', icon: '💰', items: [ _StatItem( label: isKorean ? '획득 골드' : isJapanese - ? '獲得ゴールド' - : 'Gold Earned', + ? '獲得ゴールド' + : 'Gold Earned', value: _formatNumber(stats.goldEarned), ), _StatItem( label: isKorean ? '소비 골드' : isJapanese - ? '消費ゴールド' - : 'Gold Spent', + ? '消費ゴールド' + : 'Gold Spent', value: _formatNumber(stats.goldSpent), ), _StatItem( label: isKorean ? '판매 아이템' : isJapanese - ? '売却アイテム' - : 'Items Sold', + ? '売却アイテム' + : 'Items Sold', value: _formatNumber(stats.itemsSold), ), _StatItem( label: isKorean ? '물약 사용' : isJapanese - ? 'ポーション使用' - : 'Potions Used', + ? 'ポーション使用' + : 'Potions Used', value: _formatNumber(stats.potionsUsed), ), ], @@ -281,24 +279,24 @@ class _SessionStatisticsView extends StatelessWidget { title: isKorean ? '진행' : isJapanese - ? '進行' - : 'Progress', + ? '進行' + : 'Progress', icon: '↑', items: [ _StatItem( label: isKorean ? '레벨업' : isJapanese - ? 'レベルアップ' - : 'Level Ups', + ? 'レベルアップ' + : 'Level Ups', value: _formatNumber(stats.levelUps), ), _StatItem( label: isKorean ? '완료한 퀘스트' : isJapanese - ? '完了したクエスト' - : 'Quests Completed', + ? '完了したクエスト' + : 'Quests Completed', value: _formatNumber(stats.questsCompleted), ), ], @@ -326,16 +324,16 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '기록' : isJapanese - ? '記録' - : 'Records', + ? '記録' + : 'Records', icon: '🏆', items: [ _StatItem( label: isKorean ? '최고 레벨' : isJapanese - ? '最高レベル' - : 'Highest Level', + ? '最高レベル' + : 'Highest Level', value: _formatNumber(stats.highestLevel), highlight: true, ), @@ -343,8 +341,8 @@ class _CumulativeStatisticsView extends StatelessWidget { label: isKorean ? '최대 보유 골드' : isJapanese - ? '最大所持ゴールド' - : 'Highest Gold Held', + ? '最大所持ゴールド' + : 'Highest Gold Held', value: _formatNumber(stats.highestGoldHeld), highlight: true, ), @@ -352,8 +350,8 @@ class _CumulativeStatisticsView extends StatelessWidget { label: isKorean ? '최고 연속 크리티컬' : isJapanese - ? '最高連続クリティカル' - : 'Best Critical Streak', + ? '最高連続クリティカル' + : 'Best Critical Streak', value: _formatNumber(stats.bestCriticalStreak), highlight: true, ), @@ -364,40 +362,40 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '총 플레이' : isJapanese - ? '総プレイ' - : 'Total Play', + ? '総プレイ' + : 'Total Play', icon: '⏱', items: [ _StatItem( label: isKorean ? '총 플레이 시간' : isJapanese - ? '総プレイ時間' - : 'Total Play Time', + ? '総プレイ時間' + : 'Total Play Time', value: stats.formattedTotalPlayTime, ), _StatItem( label: isKorean ? '시작한 게임' : isJapanese - ? '開始したゲーム' - : 'Games Started', + ? '開始したゲーム' + : 'Games Started', value: _formatNumber(stats.gamesStarted), ), _StatItem( label: isKorean ? '클리어한 게임' : isJapanese - ? 'クリアしたゲーム' - : 'Games Completed', + ? 'クリアしたゲーム' + : 'Games Completed', value: _formatNumber(stats.gamesCompleted), ), _StatItem( label: isKorean ? '클리어율' : isJapanese - ? 'クリア率' - : 'Completion Rate', + ? 'クリア率' + : 'Completion Rate', value: '${(stats.completionRate * 100).toStringAsFixed(1)}%', ), ], @@ -407,40 +405,40 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '총 전투' : isJapanese - ? '総戦闘' - : 'Total Combat', + ? '総戦闘' + : 'Total Combat', icon: '⚔', items: [ _StatItem( label: isKorean ? '처치한 몬스터' : isJapanese - ? '倒したモンスター' - : 'Monsters Killed', + ? '倒したモンスター' + : 'Monsters Killed', value: _formatNumber(stats.totalMonstersKilled), ), _StatItem( label: isKorean ? '보스 처치' : isJapanese - ? 'ボス討伐' - : 'Bosses Defeated', + ? 'ボス討伐' + : 'Bosses Defeated', value: _formatNumber(stats.totalBossesDefeated), ), _StatItem( label: isKorean ? '총 사망' : isJapanese - ? '総死亡' - : 'Total Deaths', + ? '総死亡' + : 'Total Deaths', value: _formatNumber(stats.totalDeaths), ), _StatItem( label: isKorean ? '총 레벨업' : isJapanese - ? '総レベルアップ' - : 'Total Level Ups', + ? '総レベルアップ' + : 'Total Level Ups', value: _formatNumber(stats.totalLevelUps), ), ], @@ -450,24 +448,24 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '총 데미지' : isJapanese - ? '総ダメージ' - : 'Total Damage', + ? '総ダメージ' + : 'Total Damage', icon: '⚡', items: [ _StatItem( label: isKorean ? '입힌 데미지' : isJapanese - ? '与えたダメージ' - : 'Damage Dealt', + ? '与えたダメージ' + : 'Damage Dealt', value: _formatNumber(stats.totalDamageDealt), ), _StatItem( label: isKorean ? '받은 데미지' : isJapanese - ? '受けたダメージ' - : 'Damage Taken', + ? '受けたダメージ' + : 'Damage Taken', value: _formatNumber(stats.totalDamageTaken), ), ], @@ -477,24 +475,24 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '총 스킬' : isJapanese - ? '総スキル' - : 'Total Skills', + ? '総スキル' + : 'Total Skills', icon: '✧', items: [ _StatItem( label: isKorean ? '스킬 사용' : isJapanese - ? 'スキル使用' - : 'Skills Used', + ? 'スキル使用' + : 'Skills Used', value: _formatNumber(stats.totalSkillsUsed), ), _StatItem( label: isKorean ? '크리티컬 히트' : isJapanese - ? 'クリティカルヒット' - : 'Critical Hits', + ? 'クリティカルヒット' + : 'Critical Hits', value: _formatNumber(stats.totalCriticalHits), ), ], @@ -504,48 +502,48 @@ class _CumulativeStatisticsView extends StatelessWidget { title: isKorean ? '총 경제' : isJapanese - ? '総経済' - : 'Total Economy', + ? '総経済' + : 'Total Economy', icon: '💰', items: [ _StatItem( label: isKorean ? '획득 골드' : isJapanese - ? '獲得ゴールド' - : 'Gold Earned', + ? '獲得ゴールド' + : 'Gold Earned', value: _formatNumber(stats.totalGoldEarned), ), _StatItem( label: isKorean ? '소비 골드' : isJapanese - ? '消費ゴールド' - : 'Gold Spent', + ? '消費ゴールド' + : 'Gold Spent', value: _formatNumber(stats.totalGoldSpent), ), _StatItem( label: isKorean ? '판매 아이템' : isJapanese - ? '売却アイテム' - : 'Items Sold', + ? '売却アイテム' + : 'Items Sold', value: _formatNumber(stats.totalItemsSold), ), _StatItem( label: isKorean ? '물약 사용' : isJapanese - ? 'ポーション使用' - : 'Potions Used', + ? 'ポーション使用' + : 'Potions Used', value: _formatNumber(stats.totalPotionsUsed), ), _StatItem( label: isKorean ? '완료 퀘스트' : isJapanese - ? '完了クエスト' - : 'Quests Completed', + ? '完了クエスト' + : 'Quests Completed', value: _formatNumber(stats.totalQuestsCompleted), ), ], diff --git a/lib/src/features/game/widgets/task_progress_panel.dart b/lib/src/features/game/widgets/task_progress_panel.dart index 6b5f239..a746580 100644 --- a/lib/src/features/game/widgets/task_progress_panel.dart +++ b/lib/src/features/game/widgets/task_progress_panel.dart @@ -92,9 +92,7 @@ class TaskProgressPanel extends StatelessWidget { children: [ _buildPauseButton(context), const SizedBox(width: 8), - Expanded( - child: _buildStatusMessage(context), - ), + Expanded(child: _buildStatusMessage(context)), const SizedBox(width: 8), _buildSpeedButton(context), ], @@ -162,10 +160,12 @@ class TaskProgressPanel extends StatelessWidget { // 몬스터 등급에 따른 접두사와 색상 final grade = monsterGrade; final isKillTask = progress.currentTask.type == TaskType.kill; - final gradePrefix = - (isKillTask && grade != null) ? grade.displayPrefix : ''; - final gradeColor = - (isKillTask && grade != null) ? grade.displayColor : null; + final gradePrefix = (isKillTask && grade != null) + ? grade.displayPrefix + : ''; + final gradeColor = (isKillTask && grade != null) + ? grade.displayColor + : null; return Text.rich( TextSpan( @@ -173,10 +173,7 @@ class TaskProgressPanel extends StatelessWidget { if (gradePrefix.isNotEmpty) TextSpan( text: gradePrefix, - style: TextStyle( - color: gradeColor, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: gradeColor, fontWeight: FontWeight.bold), ), TextSpan( text: message, diff --git a/lib/src/features/game/widgets/victory_overlay.dart b/lib/src/features/game/widgets/victory_overlay.dart index 75686b3..3b6a3c8 100644 --- a/lib/src/features/game/widgets/victory_overlay.dart +++ b/lib/src/features/game/widgets/victory_overlay.dart @@ -52,9 +52,10 @@ class _VictoryOverlayState extends State vsync: this, ); - _scrollAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _scrollController, curve: Curves.linear), - ); + _scrollAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _scrollController, curve: Curves.linear)); // 스크롤 완료 시 버튼 표시 (자동 종료하지 않음) _scrollController.addStatusListener((status) { @@ -380,26 +381,43 @@ class _VictoryOverlayState extends State final hours = playTime.inHours; final minutes = playTime.inMinutes % 60; final seconds = playTime.inSeconds % 60; - final playTimeStr = '${hours.toString().padLeft(2, '0')}:' + final playTimeStr = + '${hours.toString().padLeft(2, '0')}:' '${minutes.toString().padLeft(2, '0')}:' '${seconds.toString().padLeft(2, '0')}'; return Column( children: [ - _buildStatLine(l10n.endingMonstersSlain, - '${widget.progress.monstersKilled}', textPrimary, exp), - const SizedBox(height: 8), - _buildStatLine(l10n.endingQuestsCompleted, - '${widget.progress.questCount}', textPrimary, exp), + _buildStatLine( + l10n.endingMonstersSlain, + '${widget.progress.monstersKilled}', + textPrimary, + exp, + ), const SizedBox(height: 8), _buildStatLine( - l10n.endingPlayTime, playTimeStr, textPrimary, textPrimary), + l10n.endingQuestsCompleted, + '${widget.progress.questCount}', + textPrimary, + exp, + ), + const SizedBox(height: 8), + _buildStatLine( + l10n.endingPlayTime, + playTimeStr, + textPrimary, + textPrimary, + ), ], ); } Widget _buildStatLine( - String label, String value, Color labelColor, Color valueColor) { + String label, + String value, + Color labelColor, + Color valueColor, + ) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [