diff --git a/lib/src/features/game/layouts/mobile_carousel_layout.dart b/lib/src/features/game/layouts/mobile_carousel_layout.dart index 5ad1ffb..0d360dd 100644 --- a/lib/src/features/game/layouts/mobile_carousel_layout.dart +++ b/lib/src/features/game/layouts/mobile_carousel_layout.dart @@ -1,21 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:askiineverdie/src/core/notification/notification_service.dart'; -import 'package:askiineverdie/data/game_text_l10n.dart' as l10n; -import 'package:askiineverdie/l10n/app_localizations.dart'; -import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart'; -import 'package:askiineverdie/src/core/model/game_state.dart'; -import 'package:askiineverdie/src/features/game/pages/character_sheet_page.dart'; -import 'package:askiineverdie/src/features/game/pages/combat_log_page.dart'; -import 'package:askiineverdie/src/features/game/pages/equipment_page.dart'; -import 'package:askiineverdie/src/features/game/pages/inventory_page.dart'; -import 'package:askiineverdie/src/features/game/pages/quest_page.dart'; -import 'package:askiineverdie/src/features/game/pages/skills_page.dart'; -import 'package:askiineverdie/src/features/game/pages/story_page.dart'; -import 'package:askiineverdie/src/features/game/widgets/carousel_nav_bar.dart'; -import 'package:askiineverdie/src/features/game/widgets/combat_log.dart'; -import 'package:askiineverdie/src/features/game/widgets/enhanced_animation_panel.dart'; -import 'package:askiineverdie/src/shared/retro_colors.dart'; +import 'package:asciineverdie/src/core/notification/notification_service.dart'; +import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; +import 'package:asciineverdie/l10n/app_localizations.dart'; +import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart'; +import 'package:asciineverdie/src/core/model/game_state.dart'; +import 'package:asciineverdie/src/features/game/pages/character_sheet_page.dart'; +import 'package:asciineverdie/src/features/game/pages/combat_log_page.dart'; +import 'package:asciineverdie/src/features/game/pages/equipment_page.dart'; +import 'package:asciineverdie/src/features/game/pages/inventory_page.dart'; +import 'package:asciineverdie/src/features/game/pages/quest_page.dart'; +import 'package:asciineverdie/src/features/game/pages/skills_page.dart'; +import 'package:asciineverdie/src/features/game/pages/story_page.dart'; +import 'package:asciineverdie/src/features/game/widgets/carousel_nav_bar.dart'; +import 'package:asciineverdie/src/features/game/widgets/combat_log.dart'; +import 'package:asciineverdie/src/features/game/widgets/enhanced_animation_panel.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; /// 모바일 캐로셀 레이아웃 /// @@ -159,11 +159,7 @@ class _MobileCarouselLayoutState extends State { ); } - Widget _buildThemeOption( - BuildContext context, - ThemeMode mode, - String label, - ) { + Widget _buildThemeOption(BuildContext context, ThemeMode mode, String label) { final isSelected = widget.currentThemeMode == mode; return ListTile( leading: Icon( @@ -371,188 +367,189 @@ class _MobileCarouselLayoutState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // 헤더 (레트로 스타일) - Container( - padding: const EdgeInsets.all(16), - width: double.infinity, - decoration: BoxDecoration( - color: surface, - border: Border( - bottom: BorderSide(color: gold, width: 2), - ), - ), - child: Text( - 'OPTIONS', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 12, - color: gold, - ), - ), - ), - - // 일시정지/재개 - ListTile( - leading: Icon( - widget.isPaused ? Icons.play_arrow : Icons.pause, - color: widget.isPaused ? Colors.green : Colors.orange, - ), - title: Text(widget.isPaused ? l10n.menuResume : l10n.menuPause), - onTap: () { - Navigator.pop(context); - widget.onPauseToggle(); - }, - ), - - // 속도 조절 - ListTile( - leading: const Icon(Icons.speed), - title: Text(l10n.menuSpeed), - trailing: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), + // 헤더 (레트로 스타일) + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), + color: surface, + border: Border(bottom: BorderSide(color: gold, width: 2)), ), child: Text( - '${widget.speedMultiplier}x', + 'OPTIONS', style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, + fontFamily: 'PressStart2P', + fontSize: 12, + color: gold, ), ), ), - onTap: () { - widget.onSpeedCycle(); - Navigator.pop(context); - }, - ), - const Divider(), - - // 통계 - if (widget.onShowStatistics != null) - ListTile( - leading: const Icon(Icons.bar_chart, color: Colors.blue), - title: Text(l10n.uiStatistics), - onTap: () { - Navigator.pop(context); - widget.onShowStatistics?.call(); - }, - ), - - // 도움말 - if (widget.onShowHelp != null) - ListTile( - leading: const Icon(Icons.help_outline, color: Colors.green), - title: Text(l10n.uiHelp), - onTap: () { - Navigator.pop(context); - widget.onShowHelp?.call(); - }, - ), - - const Divider(), - - // 언어 변경 - ListTile( - leading: const Icon(Icons.language, color: Colors.teal), - title: Text(l10n.menuLanguage), - trailing: Text( - _getCurrentLanguageName(), - style: TextStyle(color: Theme.of(context).colorScheme.primary), - ), - onTap: () { - Navigator.pop(context); - _showLanguageDialog(context); - }, - ), - - // 테마 변경 - if (widget.onThemeModeChange != null) + // 일시정지/재개 ListTile( leading: Icon( - _getThemeIcon(), - color: Colors.purple, - ), - title: Text(l10n.menuTheme), - trailing: Text( - _getCurrentThemeName(), - style: TextStyle(color: Theme.of(context).colorScheme.primary), + widget.isPaused ? Icons.play_arrow : Icons.pause, + color: widget.isPaused ? Colors.green : Colors.orange, ), + title: Text(widget.isPaused ? l10n.menuResume : l10n.menuPause), onTap: () { Navigator.pop(context); - _showThemeDialog(context); + widget.onPauseToggle(); }, ), - // 사운드 설정 - if (widget.onBgmVolumeChange != null || - widget.onSfxVolumeChange != null) + // 속도 조절 ListTile( - leading: Icon( - widget.bgmVolume == 0 && widget.sfxVolume == 0 - ? Icons.volume_off - : Icons.volume_up, - color: Colors.indigo, - ), - title: Text(l10n.uiSound), - trailing: Text( - _getSoundStatus(), - style: TextStyle(color: Theme.of(context).colorScheme.primary), + leading: const Icon(Icons.speed), + title: Text(l10n.menuSpeed), + trailing: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${widget.speedMultiplier}x', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ), ), onTap: () { + widget.onSpeedCycle(); Navigator.pop(context); - _showSoundDialog(context); }, ), - const Divider(), + const Divider(), - // 저장 - ListTile( - leading: const Icon(Icons.save, color: Colors.blue), - title: Text(l10n.menuSave), - onTap: () { - Navigator.pop(context); - widget.onSave(); - widget.notificationService.showGameSaved(l10n.menuSaved); - }, - ), - - // 새로하기 (세이브 삭제) - ListTile( - leading: const Icon(Icons.refresh, color: Colors.orange), - title: Text(l10n.menuNewGame), - subtitle: Text( - l10n.menuDeleteSave, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, + // 통계 + if (widget.onShowStatistics != null) + ListTile( + leading: const Icon(Icons.bar_chart, color: Colors.blue), + title: Text(l10n.uiStatistics), + onTap: () { + Navigator.pop(context); + widget.onShowStatistics?.call(); + }, ), + + // 도움말 + if (widget.onShowHelp != null) + ListTile( + leading: const Icon(Icons.help_outline, color: Colors.green), + title: Text(l10n.uiHelp), + onTap: () { + Navigator.pop(context); + widget.onShowHelp?.call(); + }, + ), + + const Divider(), + + // 언어 변경 + ListTile( + leading: const Icon(Icons.language, color: Colors.teal), + title: Text(l10n.menuLanguage), + trailing: Text( + _getCurrentLanguageName(), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + onTap: () { + Navigator.pop(context); + _showLanguageDialog(context); + }, ), - onTap: () { - Navigator.pop(context); - _showDeleteConfirmDialog(context); - }, - ), - // 종료 - ListTile( - leading: const Icon(Icons.exit_to_app, color: Colors.red), - title: Text(localizations.exitGame), - onTap: () { - Navigator.pop(context); - widget.onExit(); - }, - ), + // 테마 변경 + if (widget.onThemeModeChange != null) + ListTile( + leading: Icon(_getThemeIcon(), color: Colors.purple), + title: Text(l10n.menuTheme), + trailing: Text( + _getCurrentThemeName(), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + onTap: () { + Navigator.pop(context); + _showThemeDialog(context); + }, + ), - const SizedBox(height: 8), - ], - ), + // 사운드 설정 + if (widget.onBgmVolumeChange != null || + widget.onSfxVolumeChange != null) + ListTile( + leading: Icon( + widget.bgmVolume == 0 && widget.sfxVolume == 0 + ? Icons.volume_off + : Icons.volume_up, + color: Colors.indigo, + ), + title: Text(l10n.uiSound), + trailing: Text( + _getSoundStatus(), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + onTap: () { + Navigator.pop(context); + _showSoundDialog(context); + }, + ), + + const Divider(), + + // 저장 + ListTile( + leading: const Icon(Icons.save, color: Colors.blue), + title: Text(l10n.menuSave), + onTap: () { + Navigator.pop(context); + widget.onSave(); + widget.notificationService.showGameSaved(l10n.menuSaved); + }, + ), + + // 새로하기 (세이브 삭제) + ListTile( + leading: const Icon(Icons.refresh, color: Colors.orange), + title: Text(l10n.menuNewGame), + subtitle: Text( + l10n.menuDeleteSave, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + onTap: () { + Navigator.pop(context); + _showDeleteConfirmDialog(context); + }, + ), + + // 종료 + ListTile( + leading: const Icon(Icons.exit_to_app, color: Colors.red), + title: Text(localizations.exitGame), + onTap: () { + Navigator.pop(context); + widget.onExit(); + }, + ), + + const SizedBox(height: 8), + ], + ), ), ), ); @@ -586,82 +583,86 @@ class _MobileCarouselLayoutState extends State { ), ], ), - body: Column( - children: [ - // 상단: 확장 애니메이션 패널 - EnhancedAnimationPanel( - progress: state.progress, - stats: state.stats, - skillSystem: state.skillSystem, - speedMultiplier: widget.speedMultiplier, - onSpeedCycle: widget.onSpeedCycle, - isPaused: widget.isPaused, - onPauseToggle: widget.onPauseToggle, - specialAnimation: widget.specialAnimation, - weaponName: state.equipment.weapon, - shieldName: state.equipment.shield, - characterLevel: state.traits.level, - monsterLevel: state.progress.currentTask.monsterLevel, - latestCombatEvent: - state.progress.currentCombat?.recentEvents.lastOrNull, - raceId: state.traits.raceId, - ), - - // 중앙: 캐로셀 (PageView) - Expanded( - child: PageView( - controller: _pageController, - onPageChanged: _onPageChanged, - children: [ - // 0: 스킬 - SkillsPage( - spellBook: state.spellBook, - skillSystem: state.skillSystem, - ), - - // 1: 인벤토리 - InventoryPage( - inventory: state.inventory, - potionInventory: state.potionInventory, - encumbrance: state.progress.encumbrance, - usedPotionTypes: - state.progress.currentCombat?.usedPotionTypes ?? const {}, - ), - - // 2: 장비 - EquipmentPage(equipment: state.equipment), - - // 3: 캐릭터시트 (기본) - CharacterSheetPage( - traits: state.traits, - stats: state.stats, - exp: state.progress.exp, - ), - - // 4: 전투로그 - CombatLogPage(entries: widget.combatLogEntries), - - // 5: 퀘스트 - QuestPage( - questHistory: state.progress.questHistory, - quest: state.progress.quest, - ), - - // 6: 스토리 - StoryPage( - plotStageCount: state.progress.plotStageCount, - plot: state.progress.plot, - ), - ], + body: SafeArea( + top: false, // AppBar가 상단 처리 + child: Column( + children: [ + // 상단: 확장 애니메이션 패널 + EnhancedAnimationPanel( + progress: state.progress, + stats: state.stats, + skillSystem: state.skillSystem, + speedMultiplier: widget.speedMultiplier, + onSpeedCycle: widget.onSpeedCycle, + isPaused: widget.isPaused, + onPauseToggle: widget.onPauseToggle, + specialAnimation: widget.specialAnimation, + weaponName: state.equipment.weapon, + shieldName: state.equipment.shield, + characterLevel: state.traits.level, + monsterLevel: state.progress.currentTask.monsterLevel, + latestCombatEvent: + state.progress.currentCombat?.recentEvents.lastOrNull, + raceId: state.traits.raceId, ), - ), - // 하단: 네비게이션 바 - CarouselNavBar( - currentPage: _currentPage, - onPageSelected: _onNavPageSelected, - ), - ], + // 중앙: 캐로셀 (PageView) + Expanded( + child: PageView( + controller: _pageController, + onPageChanged: _onPageChanged, + children: [ + // 0: 스킬 + SkillsPage( + spellBook: state.spellBook, + skillSystem: state.skillSystem, + ), + + // 1: 인벤토리 + InventoryPage( + inventory: state.inventory, + potionInventory: state.potionInventory, + encumbrance: state.progress.encumbrance, + usedPotionTypes: + state.progress.currentCombat?.usedPotionTypes ?? + const {}, + ), + + // 2: 장비 + EquipmentPage(equipment: state.equipment), + + // 3: 캐릭터시트 (기본) + CharacterSheetPage( + traits: state.traits, + stats: state.stats, + exp: state.progress.exp, + ), + + // 4: 전투로그 + CombatLogPage(entries: widget.combatLogEntries), + + // 5: 퀘스트 + QuestPage( + questHistory: state.progress.questHistory, + quest: state.progress.quest, + ), + + // 6: 스토리 + StoryPage( + plotStageCount: state.progress.plotStageCount, + plot: state.progress.plot, + ), + ], + ), + ), + + // 하단: 네비게이션 바 + CarouselNavBar( + currentPage: _currentPage, + onPageSelected: _onNavPageSelected, + ), + ], + ), ), ); } diff --git a/lib/src/features/hall_of_fame/hall_of_fame_screen.dart b/lib/src/features/hall_of_fame/hall_of_fame_screen.dart index 2510765..7e84ba0 100644 --- a/lib/src/features/hall_of_fame/hall_of_fame_screen.dart +++ b/lib/src/features/hall_of_fame/hall_of_fame_screen.dart @@ -1,16 +1,13 @@ -import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; -import 'package:askiineverdie/data/game_text_l10n.dart' as l10n; -import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart'; -import 'package:askiineverdie/src/core/model/combat_stats.dart'; -import 'package:askiineverdie/src/core/model/equipment_item.dart'; -import 'package:askiineverdie/src/core/model/equipment_slot.dart'; -import 'package:askiineverdie/src/core/model/game_state.dart'; -import 'package:askiineverdie/src/core/model/hall_of_fame.dart'; -import 'package:askiineverdie/src/core/model/item_stats.dart'; -import 'package:askiineverdie/src/core/storage/hall_of_fame_storage.dart'; -import 'package:askiineverdie/src/features/game/widgets/ascii_animation_card.dart'; +import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/model/equipment_slot.dart'; +import 'package:asciineverdie/src/core/model/game_state.dart'; +import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; +import 'package:asciineverdie/src/core/model/item_stats.dart'; +import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart'; +import 'package:asciineverdie/src/features/game/widgets/ascii_animation_card.dart'; /// 명예의 전당 화면 (Phase 10: Hall of Fame Screen) class HallOfFameScreen extends StatefulWidget { @@ -32,13 +29,7 @@ class _HallOfFameScreenState extends State { } Future _loadHallOfFame() async { - var hallOfFame = await _storage.load(); - - // 디버그 모드일 때 샘플 엔트리 추가 (빈 경우에만) - if (kDebugMode && hallOfFame.isEmpty) { - hallOfFame = hallOfFame.addEntry(_createDebugSampleEntry()); - hallOfFame = hallOfFame.addEntry(_createDebugSampleEntry2()); - } + final hallOfFame = await _storage.load(); if (mounted) { setState(() { @@ -52,9 +43,12 @@ class _HallOfFameScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(l10n.uiHallOfFame), centerTitle: true), - body: _isLoading - ? const Center(child: CircularProgressIndicator()) - : _buildContent(), + body: SafeArea( + top: false, // AppBar가 상단 처리 + child: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _buildContent(), + ), ); } @@ -148,333 +142,6 @@ class _HallOfFameScreenState extends State { } } -/// 디버그 모드 샘플 엔트리 생성 (kDebugMode에서만 사용) -HallOfFameEntry _createDebugSampleEntry() { - return HallOfFameEntry( - id: 'debug_sample_001', - characterName: 'Debug Hero', - race: 'byte_human', - klass: 'recursion_master', - level: 100, - totalPlayTimeMs: 10 * 60 * 60 * 1000, // 10시간 - totalDeaths: 3, - monstersKilled: 1234, - questsCompleted: 42, - clearedAt: DateTime.now(), - finalEquipment: [ - // 무기: Universe Simulator|15, 레벨 100 → plus=85 - const EquipmentItem( - name: '+85 AI-Augmented Universe Simulator', - slot: EquipmentSlot.weapon, - level: 100, - weight: 15, - rarity: ItemRarity.legendary, - stats: ItemStats(atk: 180, magAtk: 120, criRate: 0.15, attackSpeed: 600), - ), - // 방패: Entropy Shield|65, 레벨 100 → plus=35 - const EquipmentItem( - name: '+35 Air-gapped Entropy Shield', - slot: EquipmentSlot.shield, - level: 100, - weight: 12, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 85, magDef: 60, blockRate: 0.25), - ), - // 방어구: Multiverse Armor|60, 레벨 100 → plus=40 - const EquipmentItem( - name: '+40 Containerized Multiverse Armor', - slot: EquipmentSlot.helm, - level: 100, - weight: 8, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 45, magDef: 55, intBonus: 5), - ), - // Singularity Barrier|55, 레벨 100 → plus=45 - const EquipmentItem( - name: '+45 Quantum-safe Singularity Barrier', - slot: EquipmentSlot.hauberk, - level: 100, - weight: 20, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 95, magDef: 40, hpBonus: 200), - ), - // AI Firewall|45, 레벨 100 → plus=55 - const EquipmentItem( - name: '+55 Hardened AI Firewall', - slot: EquipmentSlot.brassairts, - level: 100, - weight: 6, - rarity: ItemRarity.epic, - stats: ItemStats(def: 35, magDef: 25), - ), - // Neural Network Mesh|40, 레벨 100 → plus=60 - const EquipmentItem( - name: '+60 Encrypted Neural Network Mesh', - slot: EquipmentSlot.vambraces, - level: 100, - weight: 5, - rarity: ItemRarity.epic, - stats: ItemStats(def: 30, magDef: 20, dexBonus: 3), - ), - // Zero-Day Aegis|30, 레벨 100 → plus=70 - const EquipmentItem( - name: '+70 Patched Zero-Day Aegis', - slot: EquipmentSlot.gauntlets, - level: 100, - weight: 4, - rarity: ItemRarity.epic, - stats: ItemStats(def: 25, atk: 15, criRate: 0.05), - ), - // Blockchain Platemail|35, 레벨 100 → plus=65 - const EquipmentItem( - name: '+65 Certified Blockchain Platemail', - slot: EquipmentSlot.gambeson, - level: 100, - weight: 10, - rarity: ItemRarity.epic, - stats: ItemStats(def: 40, magDef: 30), - ), - // Container Suit|19, 레벨 100 → plus=81 - const EquipmentItem( - name: '+81 Sandboxed Container Suit', - slot: EquipmentSlot.cuisses, - level: 100, - weight: 8, - rarity: ItemRarity.epic, - stats: ItemStats(def: 35, evasion: 0.05), - ), - // Virtualization Mail|20, 레벨 100 → plus=80 - const EquipmentItem( - name: '+80 Patched Virtualization Mail', - slot: EquipmentSlot.greaves, - level: 100, - weight: 7, - rarity: ItemRarity.epic, - stats: ItemStats(def: 30, dexBonus: 2), - ), - // Sandbox Shell|18, 레벨 100 → plus=82 - const EquipmentItem( - name: '+82 Hardened Sandbox Shell', - slot: EquipmentSlot.sollerets, - level: 100, - weight: 5, - rarity: ItemRarity.epic, - stats: ItemStats(def: 20, evasion: 0.03), - ), - ], - // 레벨 100 캐릭터의 스펠: randomLow 분포로 낮은 인덱스 스펠이 높은 랭크 - // 100번의 레벨업 = 100번 스펠 학습 기회, 초반 스펠은 여러 번 학습되어 랭크 상승 - finalSpells: [ - {'name': 'Garbage Collection', 'rank': 'XIV'}, // 인덱스 0 - 가장 많이 학습 - {'name': 'Memory Optimization', 'rank': 'XII'}, // 인덱스 1 - {'name': 'Debug Mode', 'rank': 'XI'}, // 인덱스 2 - {'name': 'Breakpoint', 'rank': 'X'}, // 인덱스 3 - {'name': 'Step Over', 'rank': 'IX'}, // 인덱스 4 - {'name': 'Step Into', 'rank': 'VIII'}, // 인덱스 5 - {'name': 'Watch Variable', 'rank': 'VII'}, // 인덱스 6 - {'name': 'Hot Reload', 'rank': 'VI'}, // 인덱스 7 - {'name': 'Cold Boot', 'rank': 'V'}, // 인덱스 8 - {'name': 'Safe Mode', 'rank': 'IV'}, // 인덱스 9 - {'name': 'Kernel Panic', 'rank': 'III'}, // 인덱스 10 - {'name': 'Blue Screen', 'rank': 'II'}, // 인덱스 11 - {'name': 'Stack Trace', 'rank': 'I'}, // 인덱스 12 - ], - // 레벨 100 기본 스탯: 시작 ~60 + 99레벨 × 2스탯 = ~258 총합 - // recursion_master (마법사 계열): INT/WIS 집중 빌드 - // CombatStats는 게임 공식 CombatStats.fromStats()에 따라 계산 - // baseAtk = STR*2 + level + equipStats.atk = 32*2 + 100 + 195 = 359 - // baseDef = CON + level/2 + equipStats.def = 38 + 50 + 440 = 528 - // baseMagAtk = INT*2 + level + equipStats.magAtk = 65*2 + 100 + 120 = 350 - // baseMagDef = WIS + level/2 + equipStats.magDef = 55 + 50 + 210 = 315 - finalStats: const CombatStats( - str: 32, - con: 38, - dex: 45, - intelligence: 65, - wis: 55, - cha: 23, - atk: 359, - def: 528, - magAtk: 350, - magDef: 315, - criRate: 0.32, - criDamage: 1.95, - evasion: 0.30, - accuracy: 0.89, - blockRate: 0.37, - parryRate: 0.15, - attackDelayMs: 650, - hpMax: 1850, - hpCurrent: 1850, - mpMax: 2100, - mpCurrent: 2100, - ), - ); -} - -/// 디버그 모드 샘플 엔트리 2 (전사 계열 캐릭터) -HallOfFameEntry _createDebugSampleEntry2() { - return HallOfFameEntry( - id: 'debug_sample_002', - characterName: 'Binary Knight', - race: 'null_elf', - klass: 'overflow_warrior', - level: 95, - totalPlayTimeMs: 8 * 60 * 60 * 1000 + 30 * 60 * 1000, // 8시간 30분 - totalDeaths: 7, - monstersKilled: 2156, - questsCompleted: 38, - clearedAt: DateTime.now().subtract(const Duration(days: 3)), - finalEquipment: [ - // 무기: Quantum Entangler|10, 레벨 95 → plus=85 - const EquipmentItem( - name: '+85 GPU-Powered Quantum Entangler', - slot: EquipmentSlot.weapon, - level: 95, - weight: 18, - rarity: ItemRarity.legendary, - stats: ItemStats(atk: 220, criRate: 0.12, parryRate: 0.08, attackSpeed: 850), - ), - // 방패: Multiverse Barrier|50, 레벨 95 → plus=45 - const EquipmentItem( - name: '+45 Air-gapped Multiverse Barrier', - slot: EquipmentSlot.shield, - level: 95, - weight: 16, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 120, magDef: 45, blockRate: 0.35, hpBonus: 150), - ), - // Multiverse Armor|60, 레벨 95 → plus=35 - const EquipmentItem( - name: '+35 Certified Multiverse Armor', - slot: EquipmentSlot.helm, - level: 95, - weight: 10, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 55, conBonus: 4), - ), - // Quantum Shield Matrix|50, 레벨 95 → plus=45 - const EquipmentItem( - name: '+45 Containerized Quantum Shield Matrix', - slot: EquipmentSlot.hauberk, - level: 95, - weight: 25, - rarity: ItemRarity.legendary, - stats: ItemStats(def: 110, hpBonus: 300, conBonus: 3), - ), - // ASLR Armor|17, 레벨 95 → plus=78 - const EquipmentItem( - name: '+78 Patched ASLR Armor', - slot: EquipmentSlot.brassairts, - level: 95, - weight: 8, - rarity: ItemRarity.epic, - stats: ItemStats(def: 40, strBonus: 2), - ), - // Stack Protector|15, 레벨 95 → plus=80 - const EquipmentItem( - name: '+80 Encrypted Stack Protector', - slot: EquipmentSlot.vambraces, - level: 95, - weight: 6, - rarity: ItemRarity.epic, - stats: ItemStats(def: 35, atk: 10), - ), - // Heap Guard|16, 레벨 95 → plus=79 - const EquipmentItem( - name: '+79 Hardened Heap Guard', - slot: EquipmentSlot.gauntlets, - level: 95, - weight: 5, - rarity: ItemRarity.epic, - stats: ItemStats(def: 30, atk: 25, criRate: 0.03), - ), - // Memory Barrier|14, 레벨 95 → plus=81 - const EquipmentItem( - name: '+81 Quantum-safe Memory Barrier', - slot: EquipmentSlot.gambeson, - level: 95, - weight: 12, - rarity: ItemRarity.epic, - stats: ItemStats(def: 45, magDef: 20), - ), - // Kernel Guard|12, 레벨 95 → plus=83 - const EquipmentItem( - name: '+83 Certified Kernel Guard', - slot: EquipmentSlot.cuisses, - level: 95, - weight: 10, - rarity: ItemRarity.epic, - stats: ItemStats(def: 42, strBonus: 2), - ), - // Protocol Suit|10, 레벨 95 → plus=85 - const EquipmentItem( - name: '+85 Sandboxed Protocol Suit', - slot: EquipmentSlot.greaves, - level: 95, - weight: 8, - rarity: ItemRarity.epic, - stats: ItemStats(def: 38, dexBonus: 2), - ), - // Encryption Layer|6, 레벨 95 → plus=89 - const EquipmentItem( - name: '+89 Patched Encryption Layer', - slot: EquipmentSlot.sollerets, - level: 95, - weight: 6, - rarity: ItemRarity.epic, - stats: ItemStats(def: 25, evasion: 0.02), - ), - ], - // 레벨 95 캐릭터의 스펠: randomLow 분포 적용 - // 95번의 레벨업 기회, 전사 계열이라 WIS가 낮아 학습 가능 스펠 범위 제한 - // WIS 30 + level 95 = 125 → 대부분의 스펠 접근 가능 - finalSpells: [ - {'name': 'Garbage Collection', 'rank': 'XIII'}, // 인덱스 0 - {'name': 'Memory Optimization', 'rank': 'XI'}, // 인덱스 1 - {'name': 'Debug Mode', 'rank': 'X'}, // 인덱스 2 - {'name': 'Breakpoint', 'rank': 'IX'}, // 인덱스 3 - {'name': 'Step Over', 'rank': 'VIII'}, // 인덱스 4 - {'name': 'Step Into', 'rank': 'VII'}, // 인덱스 5 - {'name': 'Watch Variable', 'rank': 'VI'}, // 인덱스 6 - {'name': 'Hot Reload', 'rank': 'V'}, // 인덱스 7 - {'name': 'Cold Boot', 'rank': 'IV'}, // 인덱스 8 - {'name': 'Safe Mode', 'rank': 'III'}, // 인덱스 9 - {'name': 'Kernel Panic', 'rank': 'II'}, // 인덱스 10 - ], - // 레벨 95 기본 스탯: 시작 ~60 + 94레벨 × 2스탯 = ~248 총합 - // overflow_warrior (전사 계열): STR/CON 집중 빌드 - // CombatStats는 게임 공식에 따라 계산 - // baseAtk = STR*2 + level + equipStats.atk = 58*2 + 95 + 255 = 466 - // baseDef = CON + level/2 + equipStats.def = 55 + 47 + 577 = 679 - // baseMagAtk = INT*2 + level + equipStats.magAtk = 28*2 + 95 + 0 = 151 - // baseMagDef = WIS + level/2 + equipStats.magDef = 30 + 47 + 65 = 142 - finalStats: const CombatStats( - str: 58, - con: 55, - dex: 42, - intelligence: 28, - wis: 30, - cha: 35, - atk: 466, - def: 679, - magAtk: 151, - magDef: 142, - criRate: 0.26, - criDamage: 1.92, - evasion: 0.23, - accuracy: 0.88, - blockRate: 0.47, - parryRate: 0.28, - attackDelayMs: 800, - hpMax: 2200, - hpCurrent: 2200, - mpMax: 1100, - mpCurrent: 1100, - ), - ); -} - /// 명예의 전당 엔트리 카드 class _HallOfFameEntryCard extends StatelessWidget { const _HallOfFameEntryCard({required this.entry, required this.rank}); diff --git a/lib/src/features/new_character/new_character_screen.dart b/lib/src/features/new_character/new_character_screen.dart index f8b638e..613ac63 100644 --- a/lib/src/features/new_character/new_character_screen.dart +++ b/lib/src/features/new_character/new_character_screen.dart @@ -1,20 +1,21 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:askiineverdie/data/class_data.dart'; -import 'package:askiineverdie/data/game_text_l10n.dart' as game_l10n; -import 'package:askiineverdie/data/race_data.dart'; -import 'package:askiineverdie/l10n/app_localizations.dart'; -import 'package:askiineverdie/src/core/model/class_traits.dart'; -import 'package:askiineverdie/src/core/model/game_state.dart'; -import 'package:askiineverdie/src/core/model/race_traits.dart'; -import 'package:askiineverdie/src/core/util/deterministic_random.dart'; -import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart'; -import 'package:askiineverdie/src/core/util/pq_logic.dart'; -import 'package:askiineverdie/src/features/new_character/widgets/race_preview.dart'; -import 'package:askiineverdie/src/shared/retro_colors.dart'; -import 'package:askiineverdie/src/shared/widgets/retro_widgets.dart'; +import 'package:asciineverdie/data/class_data.dart'; +import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n; +import 'package:asciineverdie/data/race_data.dart'; +import 'package:asciineverdie/l10n/app_localizations.dart'; +import 'package:asciineverdie/src/core/model/class_traits.dart'; +import 'package:asciineverdie/src/core/model/game_state.dart'; +import 'package:asciineverdie/src/core/model/race_traits.dart'; +import 'package:asciineverdie/src/core/util/deterministic_random.dart'; +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/util/pq_logic.dart'; +import 'package:asciineverdie/src/features/new_character/widgets/race_preview.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; +import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart'; /// 캐릭터 생성 화면 (NewGuy.pas 포팅) class NewCharacterScreen extends StatefulWidget { @@ -60,8 +61,8 @@ class _NewCharacterScreenState extends State { // 이름 생성용 RNG late DeterministicRandom _nameRng; - // 테스트 모드 (웹에서 모바일 캐로셀 레이아웃 활성화) - bool _testModeEnabled = false; + // 치트 모드 (디버그 전용: 100x 터보 배속 활성화) + bool _cheatsEnabled = false; // 굴리기 버튼 연속 클릭 방지 bool _isRolling = false; @@ -264,7 +265,7 @@ class _NewCharacterScreenState extends State { queue: QueueState.empty(), ); - widget.onCharacterCreated?.call(initialState, testMode: _testModeEnabled); + widget.onCharacterCreated?.call(initialState, testMode: _cheatsEnabled); } @override @@ -284,53 +285,100 @@ class _NewCharacterScreenState extends State { centerTitle: true, iconTheme: const IconThemeData(color: RetroColors.gold), ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // 이름 입력 섹션 - _buildNameSection(), - const SizedBox(height: 16), + body: SafeArea( + top: false, // AppBar가 상단 처리 + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 이름 입력 섹션 + _buildNameSection(), + const SizedBox(height: 16), - // 능력치 섹션 - _buildStatsSection(), - const SizedBox(height: 16), + // 능력치 섹션 + _buildStatsSection(), + const SizedBox(height: 16), - // 종족 미리보기 (Phase 5: 종족별 캐릭터 애니메이션) - RetroPanel( - title: 'PREVIEW', - padding: const EdgeInsets.all(8), - child: Center( - child: RacePreview( - raceId: _races[_selectedRaceIndex].raceId, + // 종족 미리보기 (Phase 5: 종족별 캐릭터 애니메이션) + RetroPanel( + title: 'PREVIEW', + padding: const EdgeInsets.all(8), + child: Center( + child: RacePreview(raceId: _races[_selectedRaceIndex].raceId), ), ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // 종족/직업 선택 섹션 - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: _buildRaceSection()), - const SizedBox(width: 16), - Expanded(child: _buildKlassSection()), + // 종족/직업 선택 섹션 + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildRaceSection()), + const SizedBox(width: 16), + Expanded(child: _buildKlassSection()), + ], + ), + const SizedBox(height: 16), + + // Sold! 버튼 + RetroTextButton( + text: L10n.of(context).soldButton, + icon: Icons.check, + onPressed: _onSold, + ), + + // 디버그 전용: 치트 모드 토글 (100x 터보 배속) + if (kDebugMode) ...[ + const SizedBox(height: 16), + GestureDetector( + onTap: () => setState(() => _cheatsEnabled = !_cheatsEnabled), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: _cheatsEnabled + ? RetroColors.hpRed.withValues(alpha: 0.3) + : RetroColors.panelBg, + border: Border.all( + color: _cheatsEnabled + ? RetroColors.hpRed + : RetroColors.panelBorderInner, + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _cheatsEnabled + ? Icons.bug_report + : Icons.bug_report_outlined, + size: 16, + color: _cheatsEnabled + ? RetroColors.hpRed + : RetroColors.textDisabled, + ), + const SizedBox(width: 8), + Text( + 'DEBUG: TURBO MODE (100x)', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: _cheatsEnabled + ? RetroColors.hpRed + : RetroColors.textDisabled, + ), + ), + ], + ), + ), + ), ], - ), - const SizedBox(height: 16), - - // 테스트 모드 토글 (웹에서 모바일 레이아웃 테스트) - _buildTestModeToggle(), - const SizedBox(height: 24), - - // Sold! 버튼 - RetroTextButton( - text: L10n.of(context).soldButton, - icon: Icons.check, - onPressed: _onSold, - ), - ], + ], + ), ), ), ); @@ -372,10 +420,7 @@ class _NewCharacterScreenState extends State { ), ), const SizedBox(width: 8), - RetroIconButton( - icon: Icons.casino, - onPressed: _onGenerateName, - ), + RetroIconButton(icon: Icons.casino, onPressed: _onGenerateName), ], ), ); @@ -735,84 +780,4 @@ class _NewCharacterScreenState extends State { }; } - /// 테스트 모드 토글 위젯 - Widget _buildTestModeToggle() { - return RetroPanel( - title: 'OPTIONS', - child: GestureDetector( - onTap: () => setState(() => _testModeEnabled = !_testModeEnabled), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - child: Row( - children: [ - Icon( - Icons.phone_android, - size: 18, - color: _testModeEnabled - ? RetroColors.gold - : RetroColors.textDisabled, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - game_l10n.uiTestMode, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 8, - color: _testModeEnabled - ? RetroColors.gold - : RetroColors.textLight, - ), - ), - const SizedBox(height: 4), - Text( - game_l10n.uiTestModeDesc, - style: const TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textDisabled, - ), - ), - ], - ), - ), - Container( - width: 40, - height: 20, - decoration: BoxDecoration( - color: _testModeEnabled - ? RetroColors.expGreen - : RetroColors.panelBgLight, - border: Border.all( - color: _testModeEnabled - ? RetroColors.expGreen - : RetroColors.panelBorderInner, - width: 2, - ), - ), - child: Row( - mainAxisAlignment: _testModeEnabled - ? MainAxisAlignment.end - : MainAxisAlignment.start, - children: [ - Container( - width: 16, - height: 16, - margin: const EdgeInsets.all(1), - color: _testModeEnabled - ? RetroColors.textLight - : RetroColors.textDisabled, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } }