diff --git a/lib/data/game_text_l10n.dart b/lib/data/game_text_l10n.dart index 60754ae..e558778 100644 --- a/lib/data/game_text_l10n.dart +++ b/lib/data/game_text_l10n.dart @@ -172,8 +172,7 @@ String returnRewardHoursAway(String time) => String returnRewardChests(int count) => _l('$count Treasure Chest(s)', '보물 상자 $count개', '宝箱 $count個'); String get returnRewardOpenChests => _l('Open Chests', '상자 열기', '宝箱を開ける'); -String get returnRewardBonusChests => - _l('Bonus Chests', '보너스 상자', 'ボーナス宝箱'); +String get returnRewardBonusChests => _l('Bonus Chests', '보너스 상자', 'ボーナス宝箱'); String get returnRewardClaimBonus => _l('Get Bonus (AD)', '보너스 받기 (광고)', 'ボーナス受取 (広告)'); String get returnRewardClaimBonusFree => @@ -193,8 +192,7 @@ String chestRewardExpAmount(int exp) => _l('+$exp EXP', '+$exp 경험치', '+$exp 経験値'); String chestRewardPotionAmount(String name, int count) => _l('$name x$count', '$name x$count', '$name x$count'); -String get chestRewardEquipped => - _l('Equipped!', '장착됨!', '装備しました!'); +String get chestRewardEquipped => _l('Equipped!', '장착됨!', '装備しました!'); String get chestRewardBetterItem => _l('Better than current!', '현재보다 좋습니다!', '現在より良い!'); @@ -1143,24 +1141,15 @@ String get uiSaveBattleLog => _l('Save Battle Log', '배틀로그 저장', 'バ String get iapRemoveAds => _l('Remove Ads', '광고 제거', '広告削除'); String get iapRemoveAdsDesc => _l('Enjoy ad-free experience', '광고 없이 플레이', '広告なしでプレイ'); -String get iapBenefitTitle => - _l('Premium Benefits', '프리미엄 혜택', 'プレミアム特典'); -String get iapBenefit1 => - _l('Ad-free gameplay', '광고 없는 쾌적한 플레이', '広告なしの快適プレイ'); +String get iapBenefitTitle => _l('Premium Benefits', '프리미엄 혜택', 'プレミアム特典'); +String get iapBenefit1 => _l('Ad-free gameplay', '광고 없는 쾌적한 플레이', '広告なしの快適プレイ'); String get iapBenefit2 => _l('Unlimited speed boost', '속도 부스트 무제한', 'スピードブースト無制限'); -String get iapBenefit3 => _l( - 'Stat reroll undo: 3 times', - '신규 캐릭터 스탯 가챠 되돌리기 3회', - '新キャラステ振り直し3回', -); -String get iapBenefit4 => - _l('Unlimited rerolls', '굴리기 무제한', 'リロール無制限'); -String get iapBenefit5 => _l( - '2x offline time credited', - '오프라인 시간 2배 인정', - 'オフライン時間2倍適用', -); +String get iapBenefit3 => + _l('Stat reroll undo: 3 times', '신규 캐릭터 스탯 가챠 되돌리기 3회', '新キャラステ振り直し3回'); +String get iapBenefit4 => _l('Unlimited rerolls', '굴리기 무제한', 'リロール無制限'); +String get iapBenefit5 => + _l('2x offline time credited', '오프라인 시간 2배 인정', 'オフライン時間2倍適用'); String get iapBenefit6 => _l('Return chests: 10 max', '복귀 상자 최대 10개', '帰還ボックス最大10個'); String get iapPurchaseButton => _l('Purchase', '구매하기', '購入する'); @@ -1232,17 +1221,12 @@ String get skillNoDetails => _l('No details', '상세 정보 없음', '詳細情 // ============================================================================ String get notifyLevelUp => _l('LEVEL UP!', '레벨 업!', 'レベルアップ!'); -String notifyLevel(int level) => - _l('Level $level', '레벨 $level', 'レベル $level'); -String get notifyQuestComplete => - _l('QUEST COMPLETE!', '퀘스트 완료!', 'クエスト完了!'); +String notifyLevel(int level) => _l('Level $level', '레벨 $level', 'レベル $level'); +String get notifyQuestComplete => _l('QUEST COMPLETE!', '퀘스트 완료!', 'クエスト完了!'); String get notifyPrologueComplete => _l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!'); -String notifyActComplete(int actNumber) => _l( - 'ACT $actNumber COMPLETE!', - '${actNumber}막 완료!', - '第${actNumber}幕完了!', -); +String notifyActComplete(int actNumber) => + _l('ACT $actNumber COMPLETE!', '${actNumber}막 완료!', '第${actNumber}幕完了!'); String get notifyNewSpell => _l('NEW SPELL!', '새 주문!', '新しい呪文!'); String get notifyNewEquipment => _l('NEW EQUIPMENT!', '새 장비!', '新しい装備!'); String get notifyBossDefeated => _l('BOSS DEFEATED!', '보스 처치!', 'ボス撃破!'); diff --git a/lib/src/core/engine/act_progression_service.dart b/lib/src/core/engine/act_progression_service.dart index 6f0f332..371bde3 100644 --- a/lib/src/core/engine/act_progression_service.dart +++ b/lib/src/core/engine/act_progression_service.dart @@ -16,9 +16,7 @@ import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic; /// /// ProgressService에서 추출된 Act 완료, 보스 생성 등의 로직 담당. class ActProgressionService { - const ActProgressionService({ - required this.config, - }); + const ActProgressionService({required this.config}); final PqConfig config; diff --git a/lib/src/core/engine/character_roll_service.dart b/lib/src/core/engine/character_roll_service.dart index c01479f..5037a63 100644 --- a/lib/src/core/engine/character_roll_service.dart +++ b/lib/src/core/engine/character_roll_service.dart @@ -64,8 +64,10 @@ class CharacterRollService { _resetUndoForNewSession(); _isInitialized = true; - debugPrint('[CharacterRollService] Initialized: ' - 'rolls=$_rollsRemaining, undo=$_undoRemaining'); + debugPrint( + '[CharacterRollService] Initialized: ' + 'rolls=$_rollsRemaining, undo=$_undoRemaining', + ); } /// 저장된 상태 로드 @@ -148,8 +150,10 @@ class CharacterRollService { // - 무료 유저: 1회 (광고 시청 필요) _undoRemaining = _isPaidUser ? maxUndoPaidUser : maxUndoFreeUser; - debugPrint('[CharacterRollService] Rolled: remaining=$_rollsRemaining, ' - 'history=${_rollHistory.length}, undo=$_undoRemaining'); + debugPrint( + '[CharacterRollService] Rolled: remaining=$_rollsRemaining, ' + 'history=${_rollHistory.length}, undo=$_undoRemaining', + ); return true; } @@ -214,8 +218,10 @@ class CharacterRollService { final snapshot = _rollHistory.removeAt(0); _undoRemaining--; - debugPrint('[CharacterRollService] Undo (paid): ' - 'remaining=$_undoRemaining, history=${_rollHistory.length}'); + debugPrint( + '[CharacterRollService] Undo (paid): ' + 'remaining=$_undoRemaining, history=${_rollHistory.length}', + ); return snapshot; } @@ -241,8 +247,10 @@ class CharacterRollService { ); if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) { - debugPrint('[CharacterRollService] Undo (free with ad): ' - 'remaining=$_undoRemaining, history=${_rollHistory.length}'); + debugPrint( + '[CharacterRollService] Undo (free with ad): ' + 'remaining=$_undoRemaining, history=${_rollHistory.length}', + ); return result; } diff --git a/lib/src/core/engine/chest_service.dart b/lib/src/core/engine/chest_service.dart index 2ad2897..c179180 100644 --- a/lib/src/core/engine/chest_service.dart +++ b/lib/src/core/engine/chest_service.dart @@ -12,7 +12,7 @@ import 'package:asciineverdie/src/core/util/deterministic_random.dart'; /// 상자 내용물 생성 및 오픈 로직 담당 class ChestService { ChestService({DeterministicRandom? rng}) - : _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch); + : _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch); final DeterministicRandom _rng; @@ -100,7 +100,9 @@ class ChestService { rarity: rarity, ); - debugPrint('[ChestService] Equipment reward: ${item.name} (${rarity.name})'); + debugPrint( + '[ChestService] Equipment reward: ${item.name} (${rarity.name})', + ); return ChestReward.equipment(item); } @@ -132,7 +134,10 @@ class ChestService { ChestReward _generateGoldReward(int playerLevel) { final baseGold = playerLevel * _goldPerLevel; final variance = _rng.nextInt(_goldVariance * 2 + 1) - _goldVariance; - final gold = (baseGold + (baseGold * variance / 100)).round().clamp(10, 99999); + final gold = (baseGold + (baseGold * variance / 100)).round().clamp( + 10, + 99999, + ); debugPrint('[ChestService] Gold reward: $gold'); return ChestReward.gold(gold); @@ -142,7 +147,10 @@ class ChestService { ChestReward _generateExperienceReward(int playerLevel) { final baseExp = playerLevel * _expPerLevel; final variance = _rng.nextInt(_expVariance * 2 + 1) - _expVariance; - final exp = (baseExp + (baseExp * variance / 100)).round().clamp(10, 999999); + final exp = (baseExp + (baseExp * variance / 100)).round().clamp( + 10, + 999999, + ); debugPrint('[ChestService] Experience reward: $exp'); return ChestReward.experience(exp); @@ -208,49 +216,49 @@ class ChestService { return switch (slot) { EquipmentSlot.weapon => ItemStats( - atk: baseValue * 2, - criRate: 0.01 * (level ~/ 5), - parryRate: 0.005 * level, - ), + atk: baseValue * 2, + criRate: 0.01 * (level ~/ 5), + parryRate: 0.005 * level, + ), EquipmentSlot.shield => ItemStats( - def: baseValue, - blockRate: 0.02 * (level ~/ 3).clamp(1, 10), - ), + def: baseValue, + blockRate: 0.02 * (level ~/ 3).clamp(1, 10), + ), EquipmentSlot.helm => ItemStats( - def: baseValue ~/ 2, - magDef: baseValue ~/ 2, - intBonus: level ~/ 10, - ), + def: baseValue ~/ 2, + magDef: baseValue ~/ 2, + intBonus: level ~/ 10, + ), EquipmentSlot.hauberk => ItemStats(def: baseValue, hpBonus: level * 2), EquipmentSlot.brassairts => ItemStats( - def: baseValue ~/ 2, - strBonus: level ~/ 15, - ), + def: baseValue ~/ 2, + strBonus: level ~/ 15, + ), EquipmentSlot.vambraces => ItemStats( - def: baseValue ~/ 2, - dexBonus: level ~/ 15, - ), + def: baseValue ~/ 2, + dexBonus: level ~/ 15, + ), EquipmentSlot.gauntlets => ItemStats( - atk: baseValue ~/ 2, - def: baseValue ~/ 4, - ), + atk: baseValue ~/ 2, + def: baseValue ~/ 4, + ), EquipmentSlot.gambeson => ItemStats( - def: baseValue ~/ 2, - conBonus: level ~/ 15, - ), + def: baseValue ~/ 2, + conBonus: level ~/ 15, + ), EquipmentSlot.cuisses => ItemStats( - def: baseValue ~/ 2, - evasion: 0.005 * level, - ), + def: baseValue ~/ 2, + evasion: 0.005 * level, + ), EquipmentSlot.greaves => ItemStats( - def: baseValue ~/ 2, - evasion: 0.003 * level, - ), + def: baseValue ~/ 2, + evasion: 0.003 * level, + ), EquipmentSlot.sollerets => ItemStats( - def: baseValue ~/ 3, - evasion: 0.002 * level, - dexBonus: level ~/ 20, - ), + def: baseValue ~/ 3, + evasion: 0.002 * level, + dexBonus: level ~/ 20, + ), }; } diff --git a/lib/src/core/engine/combat_tick_service.dart b/lib/src/core/engine/combat_tick_service.dart index cd7a7b0..83bdf9b 100644 --- a/lib/src/core/engine/combat_tick_service.dart +++ b/lib/src/core/engine/combat_tick_service.dart @@ -210,7 +210,8 @@ class CombatTickService { MonsterCombatStats monsterStats, int totalDamageDealt, List events, - }) _processDotTicks({ + }) + _processDotTicks({ required List activeDoTs, required MonsterCombatStats monsterStats, required int elapsedMs, @@ -272,7 +273,8 @@ class CombatTickService { int lastPotionUsedMs, PotionInventory potionInventory, List events, - })? _tryEmergencyPotion({ + })? + _tryEmergencyPotion({ required CombatStats playerStats, required PotionInventory potionInventory, required int lastPotionUsedMs, @@ -371,7 +373,8 @@ class CombatTickService { int totalDamageDealt, List events, bool isFirstPlayerAttack, - }) _processPlayerAttack({ + }) + _processPlayerAttack({ required GameState state, required CombatStats playerStats, required MonsterCombatStats monsterStats, @@ -508,10 +511,13 @@ class CombatTickService { newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown(); if (skillResult.debuffEffect != null) { - newActiveBuffs = newActiveBuffs - .where((d) => d.effect.id != skillResult.debuffEffect!.effect.id) - .toList() - ..add(skillResult.debuffEffect!); + newActiveBuffs = + newActiveBuffs + .where( + (d) => d.effect.id != skillResult.debuffEffect!.effect.id, + ) + .toList() + ..add(skillResult.debuffEffect!); } events.add( @@ -601,11 +607,8 @@ class CombatTickService { } /// 몬스터 공격 처리 - ({ - CombatStats playerStats, - int totalDamageTaken, - List events, - }) _processMonsterAttack({ + ({CombatStats playerStats, int totalDamageTaken, List events}) + _processMonsterAttack({ required CombatStats playerStats, required MonsterCombatStats monsterStats, required List activeDebuffs, diff --git a/lib/src/core/engine/iap_service.dart b/lib/src/core/engine/iap_service.dart index 205a833..4a9d519 100644 --- a/lib/src/core/engine/iap_service.dart +++ b/lib/src/core/engine/iap_service.dart @@ -131,9 +131,7 @@ class IAPService { final response = await _iap.queryProductDetails(IAPProductIds.all); if (response.notFoundIDs.isNotEmpty) { - debugPrint( - '[IAPService] Products not found: ${response.notFoundIDs}', - ); + debugPrint('[IAPService] Products not found: ${response.notFoundIDs}'); } for (final product in response.productDetails) { @@ -238,14 +236,10 @@ class IAPService { } // 구매 요청 - final purchaseParam = PurchaseParam( - productDetails: _removeAdsProduct!, - ); + final purchaseParam = PurchaseParam(productDetails: _removeAdsProduct!); try { - final success = await _iap.buyNonConsumable( - purchaseParam: purchaseParam, - ); + final success = await _iap.buyNonConsumable(purchaseParam: purchaseParam); debugPrint('[IAPService] Purchase initiated: $success'); return success ? IAPResult.success : IAPResult.failed; } catch (e) { diff --git a/lib/src/core/engine/item_service.dart b/lib/src/core/engine/item_service.dart index f0748f7..5eb2444 100644 --- a/lib/src/core/engine/item_service.dart +++ b/lib/src/core/engine/item_service.dart @@ -164,8 +164,9 @@ class ItemService { final magDef = hasMagDef ? (def * 0.7).round() : 0; // HP 보너스 (Uncommon 이상) - final hpBonus = - rarity.index >= ItemRarity.uncommon.index ? baseValue ~/ 3 : 0; + final hpBonus = rarity.index >= ItemRarity.uncommon.index + ? baseValue ~/ 3 + : 0; // CON 보너스 (Rare 이상) final conBonus = rarity.index >= ItemRarity.rare.index ? rarity.index : 0; @@ -273,8 +274,7 @@ class ItemService { EquipmentSlot.greaves => ItemStats( def: def, hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 3 : 0, - conBonus: - rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0, + conBonus: rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0, evasion: rarity.index >= ItemRarity.epic.index ? 0.01 : 0.0, ), diff --git a/lib/src/core/engine/market_service.dart b/lib/src/core/engine/market_service.dart index 092537c..323b4ed 100644 --- a/lib/src/core/engine/market_service.dart +++ b/lib/src/core/engine/market_service.dart @@ -9,10 +9,7 @@ import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic; /// 판매 처리 결과 class SellResult { - const SellResult({ - required this.state, - required this.continuesSelling, - }); + const SellResult({required this.state, required this.continuesSelling}); final GameState state; final bool continuesSelling; diff --git a/lib/src/core/engine/progress_service.dart b/lib/src/core/engine/progress_service.dart index e8809a2..c1e29e3 100644 --- a/lib/src/core/engine/progress_service.dart +++ b/lib/src/core/engine/progress_service.dart @@ -193,7 +193,11 @@ class ProgressService { } // 5. 시장/판매/구매 태스크 완료 처리 - final marketResult = _handleMarketTaskCompletion(nextState, progress, queue); + final marketResult = _handleMarketTaskCompletion( + nextState, + progress, + queue, + ); if (marketResult.earlyReturn != null) return marketResult.earlyReturn!; nextState = marketResult.state; progress = marketResult.progress; @@ -209,7 +213,11 @@ class ProgressService { // 7. 퀘스트 진행 처리 final questResult = _handleQuestProgress( - nextState, progress, queue, gain, incrementSeconds, + nextState, + progress, + queue, + gain, + incrementSeconds, ); nextState = questResult.state; progress = questResult.progress; @@ -217,9 +225,7 @@ class ProgressService { questDone = questResult.completed; // 8. 플롯 진행 및 Act Boss 소환 처리 - progress = _handlePlotProgress( - nextState, progress, gain, incrementSeconds, - ); + progress = _handlePlotProgress(nextState, progress, gain, incrementSeconds); // 9. 다음 태스크 디큐/생성 final dequeueResult = _handleTaskDequeue(nextState, progress, queue); @@ -341,7 +347,8 @@ class ProgressService { ProgressState progress, QueueState queue, ProgressTickResult? earlyReturn, - }) _handleKillTaskCompletion( + }) + _handleKillTaskCompletion( GameState state, ProgressState progress, QueueState queue, @@ -358,8 +365,9 @@ class ProgressService { final klass = ClassData.findById(nextState.traits.classId); if (klass != null) { - final postCombatHealRate = - klass.getPassiveValue(ClassPassiveType.postCombatHeal); + final postCombatHealRate = klass.getPassiveValue( + ClassPassiveType.postCombatHeal, + ); if (postCombatHealRate > 0) { healAmount += (maxHp * postCombatHealRate).round(); } @@ -446,7 +454,8 @@ class ProgressService { ProgressState progress, QueueState queue, ProgressTickResult? earlyReturn, - }) _handleMarketTaskCompletion( + }) + _handleMarketTaskCompletion( GameState state, ProgressState progress, QueueState queue, @@ -520,12 +529,8 @@ class ProgressService { } /// 퀘스트 진행 처리 - ({ - GameState state, - ProgressState progress, - QueueState queue, - bool completed, - }) _handleQuestProgress( + ({GameState state, ProgressState progress, QueueState queue, bool completed}) + _handleQuestProgress( GameState state, ProgressState progress, QueueState queue, @@ -603,7 +608,8 @@ class ProgressService { QueueState queue, bool actDone, bool gameComplete, - }) _handleTaskDequeue( + }) + _handleTaskDequeue( GameState state, ProgressState progress, QueueState queue, @@ -705,10 +711,7 @@ class ProgressService { 4 * 1000, ); final updatedProgress = taskResult.progress.copyWith( - currentTask: TaskInfo( - caption: taskResult.caption, - type: TaskType.market, - ), + currentTask: TaskInfo(caption: taskResult.caption, type: TaskType.market), currentCombat: null, ); return (progress: updatedProgress, queue: queue); @@ -1171,8 +1174,10 @@ class ProgressService { final shouldLoseEquipment = roll < lossChancePercent; // ignore: avoid_print - print('[Death] Lv$level lossChance=$lossChancePercent% roll=$roll ' - 'shouldLose=$shouldLoseEquipment'); + print( + '[Death] Lv$level lossChance=$lossChancePercent% roll=$roll ' + 'shouldLose=$shouldLoseEquipment', + ); if (shouldLoseEquipment) { // 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제 diff --git a/lib/src/core/engine/resurrection_service.dart b/lib/src/core/engine/resurrection_service.dart index 3107c12..3d32307 100644 --- a/lib/src/core/engine/resurrection_service.dart +++ b/lib/src/core/engine/resurrection_service.dart @@ -346,7 +346,10 @@ class ResurrectionService { // 해당 슬롯에 아이템 복원 final slotIndex = lostSlot.index; - final updatedEquipment = state.equipment.setItemByIndex(slotIndex, lostItem); + final updatedEquipment = state.equipment.setItemByIndex( + slotIndex, + lostItem, + ); // DeathInfo에서 상실 아이템 정보 제거 (복구 완료) final updatedDeathInfo = deathInfo.copyWith( diff --git a/lib/src/core/engine/return_rewards_service.dart b/lib/src/core/engine/return_rewards_service.dart index 92a50aa..7e97f87 100644 --- a/lib/src/core/engine/return_rewards_service.dart +++ b/lib/src/core/engine/return_rewards_service.dart @@ -94,8 +94,10 @@ class ReturnRewardsService { // 보너스 상자 (광고 시청 시 동일 개수 추가) final bonusChestCount = chestCount; - debugPrint('[ReturnRewards] $hoursAway hours away, ' - 'chests=$chestCount, bonus=$bonusChestCount, paid=$isPaidUser'); + debugPrint( + '[ReturnRewards] $hoursAway hours away, ' + 'chests=$chestCount, bonus=$bonusChestCount, paid=$isPaidUser', + ); return ReturnChestReward( hoursAway: hoursAway, @@ -125,9 +127,14 @@ class ReturnRewardsService { /// [reward] 복귀 보상 데이터 /// [playerLevel] 플레이어 레벨 /// Returns: 오픈된 상자 보상 목록 - List claimBasicReward(ReturnChestReward reward, int playerLevel) { + List claimBasicReward( + ReturnChestReward reward, + int playerLevel, + ) { if (!reward.hasReward) return []; - debugPrint('[ReturnRewards] Basic reward claimed: ${reward.chestCount} chests'); + debugPrint( + '[ReturnRewards] Basic reward claimed: ${reward.chestCount} chests', + ); return openChests(reward.chestCount, playerLevel); } @@ -146,8 +153,10 @@ class ReturnRewardsService { // 유료 유저는 무료 보너스 if (IAPService.instance.isAdRemovalPurchased) { - debugPrint('[ReturnRewards] Bonus claimed (paid user): ' - '${reward.bonusChestCount} chests'); + debugPrint( + '[ReturnRewards] Bonus claimed (paid user): ' + '${reward.bonusChestCount} chests', + ); return openChests(reward.bonusChestCount, playerLevel); } @@ -161,8 +170,10 @@ class ReturnRewardsService { ); if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) { - debugPrint('[ReturnRewards] Bonus claimed (free user with ad): ' - '${bonusRewards.length} chests'); + debugPrint( + '[ReturnRewards] Bonus claimed (free user with ad): ' + '${bonusRewards.length} chests', + ); return bonusRewards; } diff --git a/lib/src/core/model/combat_stats.dart b/lib/src/core/model/combat_stats.dart index 89f7dc7..9a2e64f 100644 --- a/lib/src/core/model/combat_stats.dart +++ b/lib/src/core/model/combat_stats.dart @@ -57,46 +57,64 @@ class CombatStats with _$CombatStats { // 기본 스탯 /// 힘: 물리 공격력 보정 required int str, + /// 체력: HP, 방어력 보정 required int con, + /// 민첩: 회피율, 크리티컬율, 명중률, 공격 속도 required int dex, + /// 지능: 마법 공격력, MP required int intelligence, + /// 지혜: 마법 방어력, MP 회복 required int wis, + /// 매력: 상점 가격, 드롭률 보정 required int cha, // 파생 스탯 (전투용) /// 물리 공격력 required int atk, + /// 물리 방어력 required int def, + /// 마법 공격력 required int magAtk, + /// 마법 방어력 required int magDef, + /// 크리티컬 확률 (0.0 ~ 1.0) required double criRate, + /// 크리티컬 데미지 배율 (1.5 ~ 3.0) required double criDamage, + /// 회피율 (0.0 ~ 0.5) required double evasion, + /// 명중률 (0.8 ~ 1.0) required double accuracy, + /// 방패 방어율 (0.0 ~ 0.4) required double blockRate, + /// 무기로 쳐내기 확률 (0.0 ~ 0.3) required double parryRate, + /// 공격 딜레이 (밀리초) required int attackDelayMs, // 자원 /// 최대 HP required int hpMax, + /// 현재 HP required int hpCurrent, + /// 최대 MP required int mpMax, + /// 현재 MP required int mpCurrent, }) = _CombatStats; diff --git a/lib/src/core/model/item_stats.dart b/lib/src/core/model/item_stats.dart index 18adcde..592e1e9 100644 --- a/lib/src/core/model/item_stats.dart +++ b/lib/src/core/model/item_stats.dart @@ -41,36 +41,52 @@ class ItemStats with _$ItemStats { const factory ItemStats({ /// 물리 공격력 보정 @Default(0) int atk, + /// 물리 방어력 보정 @Default(0) int def, + /// 마법 공격력 보정 @Default(0) int magAtk, + /// 마법 방어력 보정 @Default(0) int magDef, + /// 크리티컬 확률 보정 (0.0 ~ 1.0) @Default(0.0) double criRate, + /// 회피율 보정 (0.0 ~ 1.0) @Default(0.0) double evasion, + /// 방패 방어율 (방패 전용, 0.0 ~ 1.0) @Default(0.0) double blockRate, + /// 무기 쳐내기 확률 (무기 전용, 0.0 ~ 1.0) @Default(0.0) double parryRate, + /// HP 보너스 @Default(0) int hpBonus, + /// MP 보너스 @Default(0) int mpBonus, + /// STR 보너스 @Default(0) int strBonus, + /// CON 보너스 @Default(0) int conBonus, + /// DEX 보너스 @Default(0) int dexBonus, + /// INT 보너스 @Default(0) int intBonus, + /// WIS 보너스 @Default(0) int wisBonus, + /// CHA 보너스 @Default(0) int chaBonus, + /// 무기 공격속도 (밀리초, 무기 전용) /// /// 0이면 기본값(1000ms) 사용, 값이 클수록 느린 공격. diff --git a/lib/src/core/model/monetization_state.dart b/lib/src/core/model/monetization_state.dart index 6c0a0c1..a273f79 100644 --- a/lib/src/core/model/monetization_state.dart +++ b/lib/src/core/model/monetization_state.dart @@ -121,16 +121,18 @@ class MonetizationState with _$MonetizationState { List>? _statsListToJson(List? stats) { if (stats == null) return null; return stats - .map((s) => { - 'str': s.str, - 'con': s.con, - 'dex': s.dex, - 'int': s.intelligence, - 'wis': s.wis, - 'cha': s.cha, - 'hpMax': s.hpMax, - 'mpMax': s.mpMax, - }) + .map( + (s) => { + 'str': s.str, + 'con': s.con, + 'dex': s.dex, + 'int': s.intelligence, + 'wis': s.wis, + 'cha': s.cha, + 'hpMax': s.hpMax, + 'mpMax': s.mpMax, + }, + ) .toList(); } diff --git a/lib/src/core/model/potion.dart b/lib/src/core/model/potion.dart index 1e8d78f..3e4f280 100644 --- a/lib/src/core/model/potion.dart +++ b/lib/src/core/model/potion.dart @@ -62,9 +62,7 @@ class Potion { /// /// 보유 물약 수량 관리 (쿨타임은 CombatState에서 관리) class PotionInventory { - const PotionInventory({ - this.potions = const {}, - }); + const PotionInventory({this.potions = const {}}); /// 보유 물약 (물약 ID → 수량) final Map potions; @@ -99,11 +97,7 @@ class PotionInventory { /// 빈 인벤토리 static const empty = PotionInventory(); - PotionInventory copyWith({ - Map? potions, - }) { - return PotionInventory( - potions: potions ?? this.potions, - ); + PotionInventory copyWith({Map? potions}) { + return PotionInventory(potions: potions ?? this.potions); } } diff --git a/lib/src/core/model/treasure_chest.dart b/lib/src/core/model/treasure_chest.dart index 1f3990e..85c97a0 100644 --- a/lib/src/core/model/treasure_chest.dart +++ b/lib/src/core/model/treasure_chest.dart @@ -28,10 +28,7 @@ class ChestReward { /// 장비 보상 생성 factory ChestReward.equipment(EquipmentItem item) { - return ChestReward._( - type: ChestRewardType.equipment, - equipment: item, - ); + return ChestReward._(type: ChestRewardType.equipment, equipment: item); } /// 포션 보상 생성 @@ -45,18 +42,12 @@ class ChestReward { /// 골드 보상 생성 factory ChestReward.gold(int amount) { - return ChestReward._( - type: ChestRewardType.gold, - gold: amount, - ); + return ChestReward._(type: ChestRewardType.gold, gold: amount); } /// 경험치 보상 생성 factory ChestReward.experience(int amount) { - return ChestReward._( - type: ChestRewardType.experience, - experience: amount, - ); + return ChestReward._(type: ChestRewardType.experience, experience: amount); } /// 보상 타입 diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index b852271..58f8cf3 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -714,7 +714,10 @@ class _GamePlayScreenState extends State } /// 데스크톱 앱바 - PreferredSizeWidget _buildDesktopAppBar(BuildContext context, GameState state) { + PreferredSizeWidget _buildDesktopAppBar( + BuildContext context, + GameState state, + ) { return AppBar( backgroundColor: RetroColors.darkBrown, title: Text( @@ -969,9 +972,7 @@ class _GamePlayScreenState extends State // Potions (물약 인벤토리) _buildSectionHeader(game_l10n.uiPotions), Expanded( - child: PotionInventoryPanel( - inventory: state.potionInventory, - ), + child: PotionInventoryPanel(inventory: state.potionInventory), ), // Encumbrance 바 diff --git a/lib/src/features/game/game_session_controller.dart b/lib/src/features/game/game_session_controller.dart index f731775..7176d3e 100644 --- a/lib/src/features/game/game_session_controller.dart +++ b/lib/src/features/game/game_session_controller.dart @@ -35,8 +35,8 @@ class GameSessionController extends ChangeNotifier { DateTime Function()? now, StatisticsStorage? statisticsStorage, HallOfFameStorage? hallOfFameStorage, - }) : _tickInterval = tickInterval, - _now = now ?? DateTime.now { + }) : _tickInterval = tickInterval, + _now = now ?? DateTime.now { // 매니저 초기화 _statisticsManager = GameStatisticsManager( statisticsStorage: statisticsStorage, @@ -136,9 +136,9 @@ class GameSessionController extends ChangeNotifier { int get speedBoostDuration => _speedBoostManager.speedBoostDuration; int get speedBoostRemainingSeconds => _speedBoostManager.getRemainingSeconds( - _monetization, - _state?.skillSystem.elapsedMs ?? 0, - ); + _monetization, + _state?.skillSystem.elapsedMs ?? 0, + ); int get currentSpeedMultiplier => _speedBoostManager.getCurrentSpeedMultiplier(_loop); @@ -472,12 +472,12 @@ class GameSessionController extends ChangeNotifier { /// 속도 부스트 활성화 (광고 시청 후) Future activateSpeedBoost() async { - final (success, updatedMonetization) = - await _speedBoostManager.activateSpeedBoost( - loop: _loop, - monetization: _monetization, - currentElapsedMs: _state?.skillSystem.elapsedMs ?? 0, - ); + final (success, updatedMonetization) = await _speedBoostManager + .activateSpeedBoost( + loop: _loop, + monetization: _monetization, + currentElapsedMs: _state?.skillSystem.elapsedMs ?? 0, + ); if (success) { _monetization = updatedMonetization; diff --git a/lib/src/features/game/layouts/mobile_carousel_layout.dart b/lib/src/features/game/layouts/mobile_carousel_layout.dart index 4f9c664..8cccc7a 100644 --- a/lib/src/features/game/layouts/mobile_carousel_layout.dart +++ b/lib/src/features/game/layouts/mobile_carousel_layout.dart @@ -308,15 +308,14 @@ class _MobileCarouselLayoutState extends State { // 핸들 바 Padding( padding: const EdgeInsets.only(top: 8, bottom: 4), - child: Container( - width: 60, - height: 4, - color: border, - ), + child: Container(width: 60, height: 4, color: border), ), // 헤더 Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), width: double.infinity, decoration: BoxDecoration( color: RetroColors.panelBgOf(context), @@ -515,7 +514,8 @@ class _MobileCarouselLayoutState extends State { ], // === 디버그 도구 섹션 === - if (kDebugMode && widget.onCreateTestCharacter != null) ...[ + if (kDebugMode && + widget.onCreateTestCharacter != null) ...[ const SizedBox(height: 16), RetroMenuSection( title: L10n.of(context).debugToolsTitle, @@ -526,7 +526,9 @@ class _MobileCarouselLayoutState extends State { icon: Icons.science, iconColor: RetroColors.warningOf(context), label: L10n.of(context).debugCreateTestCharacter, - subtitle: L10n.of(context).debugCreateTestCharacterDesc, + subtitle: L10n.of( + context, + ).debugCreateTestCharacterDesc, onTap: () { Navigator.pop(context); _showTestCharacterDialog(context); diff --git a/lib/src/features/game/managers/game_statistics_manager.dart b/lib/src/features/game/managers/game_statistics_manager.dart index 84ce0a4..7307f18 100644 --- a/lib/src/features/game/managers/game_statistics_manager.dart +++ b/lib/src/features/game/managers/game_statistics_manager.dart @@ -9,9 +9,8 @@ import 'package:asciineverdie/src/core/storage/statistics_storage.dart'; /// 세션 통계와 누적 통계를 관리하고, 게임 상태 변화에 따라 /// 통계를 자동 업데이트합니다. class GameStatisticsManager { - GameStatisticsManager({ - StatisticsStorage? statisticsStorage, - }) : _statisticsStorage = statisticsStorage ?? StatisticsStorage(); + GameStatisticsManager({StatisticsStorage? statisticsStorage}) + : _statisticsStorage = statisticsStorage ?? StatisticsStorage(); final StatisticsStorage _statisticsStorage; diff --git a/lib/src/features/game/managers/hall_of_fame_manager.dart b/lib/src/features/game/managers/hall_of_fame_manager.dart index 35033e3..1d5bd81 100644 --- a/lib/src/features/game/managers/hall_of_fame_manager.dart +++ b/lib/src/features/game/managers/hall_of_fame_manager.dart @@ -11,9 +11,8 @@ import 'package:flutter/foundation.dart'; /// /// 게임 클리어 시 캐릭터 등록, 테스트 캐릭터 생성 등을 담당합니다. class HallOfFameManager { - HallOfFameManager({ - HallOfFameStorage? hallOfFameStorage, - }) : _hallOfFameStorage = hallOfFameStorage ?? HallOfFameStorage(); + HallOfFameManager({HallOfFameStorage? hallOfFameStorage}) + : _hallOfFameStorage = hallOfFameStorage ?? HallOfFameStorage(); final HallOfFameStorage _hallOfFameStorage; diff --git a/lib/src/features/game/managers/resurrection_manager.dart b/lib/src/features/game/managers/resurrection_manager.dart index 3e63c20..687e33d 100644 --- a/lib/src/features/game/managers/resurrection_manager.dart +++ b/lib/src/features/game/managers/resurrection_manager.dart @@ -107,7 +107,8 @@ class ResurrectionManager { updatedMonetization = monetization.copyWith(autoReviveEndMs: buffEndMs); debugPrint( - '[Resurrection] Ad revive complete, auto-revive buff until $buffEndMs ms'); + '[Resurrection] Ad revive complete, auto-revive buff until $buffEndMs ms', + ); } // 유료 유저는 광고 없이 부활 diff --git a/lib/src/features/game/managers/return_rewards_manager.dart b/lib/src/features/game/managers/return_rewards_manager.dart index eb4be2a..7db91cc 100644 --- a/lib/src/features/game/managers/return_rewards_manager.dart +++ b/lib/src/features/game/managers/return_rewards_manager.dart @@ -49,8 +49,10 @@ class ReturnRewardsManager { if (reward.hasReward) { _pendingReturnReward = reward; - debugPrint('[ReturnRewards] Reward available: ${reward.chestCount} chests, ' - '${reward.hoursAway} hours away'); + debugPrint( + '[ReturnRewards] Reward available: ${reward.chestCount} chests, ' + '${reward.hoursAway} hours away', + ); // UI에서 다이얼로그 표시를 위해 콜백 호출 // startNew 후에 호출하도록 딜레이 @@ -91,11 +93,13 @@ class ReturnRewardsManager { loop?.replaceState(updatedState); // ProgressLoop 상태도 업데이트 // 저장 - unawaited(saveManager.saveState( - updatedState, - cheatsEnabled: cheatsEnabled, - monetization: monetization, - )); + unawaited( + saveManager.saveState( + updatedState, + cheatsEnabled: cheatsEnabled, + monetization: monetization, + ), + ); _pendingReturnReward = null; @@ -129,18 +133,19 @@ class ReturnRewardsManager { reward.equipment!.itemWeight > currentItem.itemWeight) { debugPrint('[ReturnRewards] Equipped: ${reward.equipment!.name}'); return state.copyWith( - equipment: state.equipment.setItemByIndex( - slotIndex, - reward.equipment!, - ), + equipment: state.equipment.setItemByIndex(slotIndex, reward.equipment!), ); } // 더 좋지 않으면 판매 (골드로 변환) - final sellPrice = - (reward.equipment!.level * 50 * 0.3).round().clamp(1, 99999); - debugPrint('[ReturnRewards] Sold: ${reward.equipment!.name} ' - 'for $sellPrice gold'); + final sellPrice = (reward.equipment!.level * 50 * 0.3).round().clamp( + 1, + 99999, + ); + debugPrint( + '[ReturnRewards] Sold: ${reward.equipment!.name} ' + 'for $sellPrice gold', + ); return state.copyWith( inventory: state.inventory.copyWith( gold: state.inventory.gold + sellPrice, @@ -152,8 +157,10 @@ class ReturnRewardsManager { GameState _applyPotionReward(GameState state, ChestReward reward) { if (reward.potionId == null) return state; - debugPrint('[ReturnRewards] Added potion: ${reward.potionId} ' - 'x${reward.potionCount}'); + debugPrint( + '[ReturnRewards] Added potion: ${reward.potionId} ' + 'x${reward.potionCount}', + ); return state.copyWith( potionInventory: state.potionInventory.addPotion( reward.potionId!, diff --git a/lib/src/features/game/managers/speed_boost_manager.dart b/lib/src/features/game/managers/speed_boost_manager.dart index 8b97035..2c51861 100644 --- a/lib/src/features/game/managers/speed_boost_manager.dart +++ b/lib/src/features/game/managers/speed_boost_manager.dart @@ -12,8 +12,8 @@ class SpeedBoostManager { SpeedBoostManager({ required bool Function() cheatsEnabledGetter, required Future> Function() getAvailableSpeeds, - }) : _cheatsEnabledGetter = cheatsEnabledGetter, - _getAvailableSpeeds = getAvailableSpeeds; + }) : _cheatsEnabledGetter = cheatsEnabledGetter, + _getAvailableSpeeds = getAvailableSpeeds; final bool Function() _cheatsEnabledGetter; final Future> Function() _getAvailableSpeeds; @@ -52,7 +52,10 @@ class SpeedBoostManager { int get speedBoostDuration => _speedBoostDuration; /// 속도 부스트 남은 시간 (초) - 게임 시간(elapsedMs) 기준 계산 - int getRemainingSeconds(MonetizationState monetization, int currentElapsedMs) { + int getRemainingSeconds( + MonetizationState monetization, + int currentElapsedMs, + ) { if (!_isSpeedBoostActive) return 0; final endMs = monetization.speedBoostEndMs; if (endMs == null) return 0; @@ -203,7 +206,10 @@ class SpeedBoostManager { if (_isSpeedBoostActive) { // 부스트 상태: 부스트 배속만 사용, 기본 배속 저장 savedSpeedMultiplier = baseSpeed; - return (speeds: [speedBoostMultiplier], initialSpeed: speedBoostMultiplier); + return ( + speeds: [speedBoostMultiplier], + initialSpeed: speedBoostMultiplier, + ); } // 일반 상태: 기본 배속 사용 return (speeds: baseAvailableSpeeds, initialSpeed: baseSpeed); diff --git a/lib/src/features/game/pages/inventory_page.dart b/lib/src/features/game/pages/inventory_page.dart index 0fdab7b..6a6c4e0 100644 --- a/lib/src/features/game/pages/inventory_page.dart +++ b/lib/src/features/game/pages/inventory_page.dart @@ -37,9 +37,7 @@ class InventoryPage extends StatelessWidget { _buildSectionHeader(context, l10n.uiPotions), Expanded( flex: 2, - child: PotionInventoryPanel( - inventory: potionInventory, - ), + child: PotionInventoryPanel(inventory: potionInventory), ), // 무게 (Encumbrance) diff --git a/lib/src/features/game/pages/skills_page.dart b/lib/src/features/game/pages/skills_page.dart index ac1567f..da0c25e 100644 --- a/lib/src/features/game/pages/skills_page.dart +++ b/lib/src/features/game/pages/skills_page.dart @@ -147,7 +147,11 @@ class _SkillTile extends StatelessWidget { if (isOnCooldown) const Padding( padding: EdgeInsets.only(right: 8), - child: Icon(Icons.hourglass_empty, size: 14, color: Colors.orange), + child: Icon( + Icons.hourglass_empty, + size: 14, + color: Colors.orange, + ), ), _RankBadge(rank: rank), ], @@ -273,10 +277,12 @@ class _SkillStatsGrid extends StatelessWidget { // 공통: MP, 쿨타임 entries.add(_StatEntry(l10n.skillMpCost, '${skill.mpCost}')); - entries.add(_StatEntry( - l10n.skillCooldown, - '${(skill.cooldownMs / 1000).toStringAsFixed(1)}${l10n.skillSeconds}', - )); + entries.add( + _StatEntry( + l10n.skillCooldown, + '${(skill.cooldownMs / 1000).toStringAsFixed(1)}${l10n.skillSeconds}', + ), + ); // 타입별 스탯 추가 switch (skill.type) { @@ -309,33 +315,40 @@ class _SkillStatsGrid extends StatelessWidget { // DOT 정보 if (skill.isDot && skill.baseDotDamage != null) { - final dotDps = skill.baseDotDamage! * + final dotDps = + skill.baseDotDamage! * (skill.baseDotDurationMs! / skill.baseDotTickMs!); entries.add(_StatEntry(l10n.skillDot, '${dotDps.round()}')); } // HP 흡수 if (skill.lifestealPercent > 0) { - entries.add(_StatEntry( - l10n.skillLifesteal, - '${(skill.lifestealPercent * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillLifesteal, + '${(skill.lifestealPercent * 100).round()}%', + ), + ); } // 방어 무시 if (skill.targetDefReduction > 0) { - entries.add(_StatEntry( - l10n.skillDefPen, - '${(skill.targetDefReduction * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillDefPen, + '${(skill.targetDefReduction * 100).round()}%', + ), + ); } // 자해 데미지 if (skill.selfDamagePercent > 0) { - entries.add(_StatEntry( - l10n.skillSelfDmg, - '${(skill.selfDamagePercent * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillSelfDmg, + '${(skill.selfDamagePercent * 100).round()}%', + ), + ); } } @@ -347,10 +360,12 @@ class _SkillStatsGrid extends StatelessWidget { // % 회복 if (skill.healPercent > 0) { - entries.add(_StatEntry( - l10n.skillHealPercent, - '${(skill.healPercent * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillHealPercent, + '${(skill.healPercent * 100).round()}%', + ), + ); } // MP 회복 @@ -361,10 +376,12 @@ class _SkillStatsGrid extends StatelessWidget { // 부가 버프 if (skill.buff != null) { final buff = skill.buff!; - entries.add(_StatEntry( - l10n.skillBuffDuration, - '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', - )); + entries.add( + _StatEntry( + l10n.skillBuffDuration, + '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', + ), + ); } } @@ -373,39 +390,49 @@ class _SkillStatsGrid extends StatelessWidget { final buff = skill.buff!; // 지속시간 - entries.add(_StatEntry( - l10n.skillBuffDuration, - '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', - )); + entries.add( + _StatEntry( + l10n.skillBuffDuration, + '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', + ), + ); // 각 보정치 if (buff.atkModifier != 0) { final sign = buff.atkModifier > 0 ? '+' : ''; - entries.add(_StatEntry( - l10n.skillAtkMod, - '$sign${(buff.atkModifier * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillAtkMod, + '$sign${(buff.atkModifier * 100).round()}%', + ), + ); } if (buff.defModifier != 0) { final sign = buff.defModifier > 0 ? '+' : ''; - entries.add(_StatEntry( - l10n.skillDefMod, - '$sign${(buff.defModifier * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillDefMod, + '$sign${(buff.defModifier * 100).round()}%', + ), + ); } if (buff.criRateModifier != 0) { final sign = buff.criRateModifier > 0 ? '+' : ''; - entries.add(_StatEntry( - l10n.skillCriMod, - '$sign${(buff.criRateModifier * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillCriMod, + '$sign${(buff.criRateModifier * 100).round()}%', + ), + ); } if (buff.evasionModifier != 0) { final sign = buff.evasionModifier > 0 ? '+' : ''; - entries.add(_StatEntry( - l10n.skillEvaMod, - '$sign${(buff.evasionModifier * 100).round()}%', - )); + entries.add( + _StatEntry( + l10n.skillEvaMod, + '$sign${(buff.evasionModifier * 100).round()}%', + ), + ); } } @@ -414,23 +441,23 @@ class _SkillStatsGrid extends StatelessWidget { final buff = skill.buff!; // 지속시간 - entries.add(_StatEntry( - l10n.skillBuffDuration, - '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', - )); + entries.add( + _StatEntry( + l10n.skillBuffDuration, + '${(buff.durationMs / 1000).round()}${l10n.skillSeconds}', + ), + ); // 디버프 효과 (보통 음수) if (buff.atkModifier != 0) { - entries.add(_StatEntry( - l10n.skillAtkMod, - '${(buff.atkModifier * 100).round()}%', - )); + entries.add( + _StatEntry(l10n.skillAtkMod, '${(buff.atkModifier * 100).round()}%'), + ); } if (buff.defModifier != 0) { - entries.add(_StatEntry( - l10n.skillDefMod, - '${(buff.defModifier * 100).round()}%', - )); + entries.add( + _StatEntry(l10n.skillDefMod, '${(buff.defModifier * 100).round()}%'), + ); } } } diff --git a/lib/src/features/game/widgets/death_overlay.dart b/lib/src/features/game/widgets/death_overlay.dart index 0376f11..0f589e6 100644 --- a/lib/src/features/game/widgets/death_overlay.dart +++ b/lib/src/features/game/widgets/death_overlay.dart @@ -333,7 +333,8 @@ class DeathOverlay extends StatelessWidget { TextSpan( children: [ TextSpan( - text: '[${_getSlotName(deathInfo.lostItemSlot)}] ', + text: + '[${_getSlotName(deathInfo.lostItemSlot)}] ', style: TextStyle(color: muted), ), TextSpan( @@ -485,7 +486,10 @@ class DeathOverlay extends StatelessWidget { border: Border( top: BorderSide(color: gold, width: 3), left: BorderSide(color: gold, width: 3), - bottom: BorderSide(color: goldDark.withValues(alpha: 0.8), width: 3), + bottom: BorderSide( + color: goldDark.withValues(alpha: 0.8), + width: 3, + ), right: BorderSide(color: goldDark.withValues(alpha: 0.8), width: 3), ), ), @@ -495,10 +499,7 @@ class DeathOverlay extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '✨', - style: TextStyle(fontSize: 20, color: gold), - ), + Text('✨', style: TextStyle(fontSize: 20, color: gold)), const SizedBox(width: 8), Text( l10n.deathAdRevive.toUpperCase(), @@ -551,7 +552,8 @@ class DeathOverlay extends StatelessWidget { _buildBenefitRow( context, icon: '🔄', - text: '${l10n.deathAdReviveItem}: ${deathInfo.lostItemName}', + text: + '${l10n.deathAdReviveItem}: ${deathInfo.lostItemName}', color: itemRarityColor, ), const SizedBox(height: 4), diff --git a/lib/src/features/game/widgets/dialogs/retro_select_dialog.dart b/lib/src/features/game/widgets/dialogs/retro_select_dialog.dart index 68db200..45274d7 100644 --- a/lib/src/features/game/widgets/dialogs/retro_select_dialog.dart +++ b/lib/src/features/game/widgets/dialogs/retro_select_dialog.dart @@ -83,7 +83,9 @@ class RetroOptionItem extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( - color: isSelected ? gold.withValues(alpha: 0.15) : Colors.transparent, + color: isSelected + ? gold.withValues(alpha: 0.15) + : Colors.transparent, border: Border.all( color: isSelected ? gold : border, width: isSelected ? 2 : 1, @@ -101,7 +103,9 @@ class RetroOptionItem extends StatelessWidget { style: TextStyle( fontFamily: 'PressStart2P', fontSize: 18, - color: isSelected ? gold : RetroColors.textPrimaryOf(context), + color: isSelected + ? gold + : RetroColors.textPrimaryOf(context), ), ), ), diff --git a/lib/src/features/game/widgets/enhanced_animation_panel.dart b/lib/src/features/game/widgets/enhanced_animation_panel.dart index 0e907b7..37261e8 100644 --- a/lib/src/features/game/widgets/enhanced_animation_panel.dart +++ b/lib/src/features/game/widgets/enhanced_animation_panel.dart @@ -300,7 +300,9 @@ class _EnhancedAnimationPanelState extends State child: _buildBuffChip( icon: '⚡', label: '${widget.adSpeedMultiplier}x', - remainingMs: widget.isPaidUser ? -1 : _speedBoostRemainingMs, + remainingMs: widget.isPaidUser + ? -1 + : _speedBoostRemainingMs, color: Colors.orange, isPermanent: widget.isPaidUser, ), @@ -401,7 +403,9 @@ class _EnhancedAnimationPanelState extends State child: SizedBox.expand( child: LinearProgressIndicator( value: ratio.clamp(0.0, 1.0), - backgroundColor: Colors.red.withValues(alpha: 0.2), + backgroundColor: Colors.red.withValues( + alpha: 0.2, + ), valueColor: AlwaysStoppedAnimation( isLow ? Colors.red : Colors.red.shade600, ), @@ -502,7 +506,9 @@ class _EnhancedAnimationPanelState extends State child: SizedBox.expand( child: LinearProgressIndicator( value: ratio.clamp(0.0, 1.0), - backgroundColor: Colors.blue.withValues(alpha: 0.2), + backgroundColor: Colors.blue.withValues( + alpha: 0.2, + ), valueColor: AlwaysStoppedAnimation( Colors.blue.shade600, ), @@ -619,7 +625,10 @@ class _EnhancedAnimationPanelState extends State color: Colors.black.withValues(alpha: 0.8), blurRadius: 2, ), - const Shadow(color: Colors.black, blurRadius: 4), + const Shadow( + color: Colors.black, + blurRadius: 4, + ), ], ), ), @@ -783,8 +792,9 @@ class _EnhancedAnimationPanelState extends State ), TextSpan( text: _getStatusMessage(), - style: - gradeColor != null ? TextStyle(color: gradeColor) : null, + style: gradeColor != null + ? TextStyle(color: gradeColor) + : null, ), ], ), @@ -844,10 +854,7 @@ class _EnhancedAnimationPanelState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - icon, - style: TextStyle(fontSize: 12, color: color), - ), + Text(icon, style: TextStyle(fontSize: 12, color: color)), if (label != null) ...[ const SizedBox(width: 2), Text( diff --git a/lib/src/features/game/widgets/hp_mp_bar.dart b/lib/src/features/game/widgets/hp_mp_bar.dart index 451042f..0ea3adf 100644 --- a/lib/src/features/game/widgets/hp_mp_bar.dart +++ b/lib/src/features/game/widgets/hp_mp_bar.dart @@ -326,8 +326,10 @@ class _HpMpBarState extends State with TickerProviderStateMixin { height: 14, 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) { @@ -341,10 +343,8 @@ 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, diff --git a/lib/src/features/game/widgets/menu/retro_menu_widgets.dart b/lib/src/features/game/widgets/menu/retro_menu_widgets.dart index c886399..61f598d 100644 --- a/lib/src/features/game/widgets/menu/retro_menu_widgets.dart +++ b/lib/src/features/game/widgets/menu/retro_menu_widgets.dart @@ -4,11 +4,7 @@ import 'package:asciineverdie/src/shared/retro_colors.dart'; /// 메뉴 섹션 타이틀 class RetroMenuSection extends StatelessWidget { - const RetroMenuSection({ - super.key, - required this.title, - this.color, - }); + const RetroMenuSection({super.key, required this.title, this.color}); final String title; final Color? color; @@ -182,10 +178,7 @@ class RetroSpeedChip extends StatelessWidget { if (isAdBased && !isSelected && !isDisabled) Padding( padding: const EdgeInsets.only(right: 2), - child: Text( - '▶', - style: TextStyle(fontSize: 7, color: warning), - ), + child: Text('▶', style: TextStyle(fontSize: 7, color: warning)), ), Text( '${speed}x', diff --git a/lib/src/features/game/widgets/potion_inventory_panel.dart b/lib/src/features/game/widgets/potion_inventory_panel.dart index 1f42e32..075cb78 100644 --- a/lib/src/features/game/widgets/potion_inventory_panel.dart +++ b/lib/src/features/game/widgets/potion_inventory_panel.dart @@ -9,10 +9,7 @@ import 'package:asciineverdie/src/core/model/potion.dart'; /// 보유 중인 물약 목록과 수량을 표시. /// HP 물약은 빨간색, MP 물약은 파란색으로 구분. class PotionInventoryPanel extends StatelessWidget { - const PotionInventoryPanel({ - super.key, - required this.inventory, - }); + const PotionInventoryPanel({super.key, required this.inventory}); final PotionInventory inventory; @@ -38,10 +35,7 @@ class PotionInventoryPanel extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), itemBuilder: (context, index) { final entry = potionEntries[index]; - return _PotionRow( - potion: entry.potion, - quantity: entry.quantity, - ); + return _PotionRow(potion: entry.potion, quantity: entry.quantity); }, ); } @@ -82,10 +76,7 @@ class _PotionEntry { /// 물약 행 위젯 class _PotionRow extends StatelessWidget { - const _PotionRow({ - required this.potion, - required this.quantity, - }); + const _PotionRow({required this.potion, required this.quantity}); final Potion potion; final int quantity; diff --git a/lib/src/features/game/widgets/return_rewards_dialog.dart b/lib/src/features/game/widgets/return_rewards_dialog.dart index 127f6de..11fda5b 100644 --- a/lib/src/features/game/widgets/return_rewards_dialog.dart +++ b/lib/src/features/game/widgets/return_rewards_dialog.dart @@ -264,7 +264,8 @@ class _ReturnRewardsDialogState extends State return Transform.translate( offset: isOpening ? Offset( - _shakeAnimation.value * 2 * + _shakeAnimation.value * + 2 * ((_animController.value * 10).round() % 2 == 0 ? 1 : -1), @@ -314,8 +315,8 @@ class _ReturnRewardsDialogState extends State isGold ? l10n.returnRewardOpenChests : (isPaidUser - ? l10n.returnRewardClaimBonusFree - : l10n.returnRewardClaimBonus), + ? l10n.returnRewardClaimBonusFree + : l10n.returnRewardClaimBonus), style: TextStyle( fontFamily: 'PressStart2P', fontSize: 10, @@ -365,10 +366,7 @@ class _ReturnRewardsDialogState extends State count, (index) => Text( '📦', - style: TextStyle( - fontSize: 24, - color: enabled ? null : muted, - ), + style: TextStyle(fontSize: 24, color: enabled ? null : muted), ), ), ); @@ -387,7 +385,9 @@ class _ReturnRewardsDialogState extends State } return Column( - children: rewards.map((reward) => _buildRewardItem(context, reward)).toList(), + children: rewards + .map((reward) => _buildRewardItem(context, reward)) + .toList(), ); } diff --git a/lib/src/features/game/widgets/speed_boost_button.dart b/lib/src/features/game/widgets/speed_boost_button.dart index 2206edf..d200331 100644 --- a/lib/src/features/game/widgets/speed_boost_button.dart +++ b/lib/src/features/game/widgets/speed_boost_button.dart @@ -65,10 +65,7 @@ class SpeedBoostButton extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - '⚡', - style: TextStyle(fontSize: 18, color: expColor), - ), + Text('⚡', style: TextStyle(fontSize: 18, color: expColor)), const SizedBox(width: 4), Text( '${boostMultiplier}x', @@ -113,10 +110,7 @@ class SpeedBoostButton extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - '⚡', - style: TextStyle(fontSize: 18, color: gold), - ), + Text('⚡', style: TextStyle(fontSize: 18, color: gold)), const SizedBox(width: 4), Text( '${boostMultiplier}x', @@ -130,10 +124,7 @@ class SpeedBoostButton extends StatelessWidget { if (!isPaidUser) ...[ const SizedBox(width: 6), Container( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 2, - ), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), diff --git a/lib/src/features/new_character/new_character_screen.dart b/lib/src/features/new_character/new_character_screen.dart index 560fff1..0db56ab 100644 --- a/lib/src/features/new_character/new_character_screen.dart +++ b/lib/src/features/new_character/new_character_screen.dart @@ -257,7 +257,11 @@ class _NewCharacterScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ if (!isPaidUser) ...[ - const Icon(Icons.play_circle, size: 14, color: RetroColors.gold), + const Icon( + Icons.play_circle, + size: 14, + color: RetroColors.gold, + ), const SizedBox(width: 4), ], Text( diff --git a/lib/src/features/new_character/widgets/class_selection_section.dart b/lib/src/features/new_character/widgets/class_selection_section.dart index 5884fcb..a6beb5d 100644 --- a/lib/src/features/new_character/widgets/class_selection_section.dart +++ b/lib/src/features/new_character/widgets/class_selection_section.dart @@ -105,8 +105,9 @@ class _ClassInfo extends StatelessWidget { final percent = (passive.value * 100).round(); return switch (passive.type) { ClassPassiveType.hpBonus => game_l10n.passiveHpBonus(percent), - ClassPassiveType.physicalDamageBonus => - game_l10n.passivePhysicalBonus(percent), + ClassPassiveType.physicalDamageBonus => game_l10n.passivePhysicalBonus( + percent, + ), ClassPassiveType.defenseBonus => game_l10n.passiveDefenseBonus(percent), ClassPassiveType.magicDamageBonus => game_l10n.passiveMagicBonus(percent), ClassPassiveType.evasionBonus => game_l10n.passiveEvasionBonus(percent), diff --git a/lib/src/features/new_character/widgets/stats_section.dart b/lib/src/features/new_character/widgets/stats_section.dart index f779f5c..e9d5e7a 100644 --- a/lib/src/features/new_character/widgets/stats_section.dart +++ b/lib/src/features/new_character/widgets/stats_section.dart @@ -62,9 +62,15 @@ class StatsSection extends StatelessWidget { // 스탯 그리드 Row( children: [ - Expanded(child: _StatTile(label: l10n.statStr, value: str)), - Expanded(child: _StatTile(label: l10n.statCon, value: con)), - Expanded(child: _StatTile(label: l10n.statDex, value: dex)), + Expanded( + child: _StatTile(label: l10n.statStr, value: str), + ), + Expanded( + child: _StatTile(label: l10n.statCon, value: con), + ), + Expanded( + child: _StatTile(label: l10n.statDex, value: dex), + ), ], ), const SizedBox(height: 8), @@ -73,8 +79,12 @@ class StatsSection extends StatelessWidget { Expanded( child: _StatTile(label: l10n.statInt, value: intelligence), ), - Expanded(child: _StatTile(label: l10n.statWis, value: wis)), - Expanded(child: _StatTile(label: l10n.statCha, value: cha)), + Expanded( + child: _StatTile(label: l10n.statWis, value: wis), + ), + Expanded( + child: _StatTile(label: l10n.statCha, value: cha), + ), ], ), const SizedBox(height: 12), @@ -116,10 +126,7 @@ class StatsSection extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _UndoButton( - canUndo: canUndo, - onPressed: onUndo, - ), + _UndoButton(canUndo: canUndo, onPressed: onUndo), const SizedBox(width: 16), _RollButton( canRoll: canRoll, @@ -222,11 +229,7 @@ class _UndoButton extends StatelessWidget { children: [ // 무료 유저는 광고 아이콘 표시 if (!isPaidUser && canUndo) ...[ - const Icon( - Icons.play_circle, - size: 14, - color: RetroColors.gold, - ), + const Icon(Icons.play_circle, size: 14, color: RetroColors.gold), const SizedBox(width: 4), ], Icon( @@ -240,7 +243,9 @@ class _UndoButton extends StatelessWidget { style: TextStyle( fontFamily: 'PressStart2P', fontSize: 11, - color: canUndo ? RetroColors.textLight : RetroColors.textDisabled, + color: canUndo + ? RetroColors.textLight + : RetroColors.textDisabled, ), ), ], diff --git a/test/core/engine/combat_calculator_test.dart b/test/core/engine/combat_calculator_test.dart index 436e9f8..76bf597 100644 --- a/test/core/engine/combat_calculator_test.dart +++ b/test/core/engine/combat_calculator_test.dart @@ -9,7 +9,7 @@ void main() { group('playerAttackMonster', () { test('데미지 = (ATK * variation) - (DEF * 0.4)', () { // 고정 시드로 예측 가능한 결과 - final rng = DeterministicRandom( 42); + final rng = DeterministicRandom(42); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( @@ -49,7 +49,7 @@ void main() { test('크리티컬 발동 시 데미지 배율 적용', () { // 크리티컬이 항상 발동하도록 criRate = 1.0 - final rng = DeterministicRandom( 123); + final rng = DeterministicRandom(123); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( @@ -87,7 +87,7 @@ void main() { }); test('회피 발동 시 0 데미지', () { - final rng = DeterministicRandom( 42); + final rng = DeterministicRandom(42); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( @@ -130,7 +130,7 @@ void main() { test('블록 발동 시 70% 감소', () { // 블록이 발동하는 시드 찾기 // blockRate = 1.0으로 항상 블록 - final rng = DeterministicRandom( 99); + final rng = DeterministicRandom(99); final calculator = CombatCalculator(rng: rng); final monster = MonsterCombatStats( @@ -170,7 +170,7 @@ void main() { }); test('패리 발동 시 50% 감소', () { - final rng = DeterministicRandom( 77); + final rng = DeterministicRandom(77); final calculator = CombatCalculator(rng: rng); final monster = MonsterCombatStats( @@ -212,7 +212,7 @@ void main() { group('estimateCombatDurationMs', () { test('범위 2000~30000ms 내 반환', () { - final rng = DeterministicRandom( 42); + final rng = DeterministicRandom(42); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( @@ -247,7 +247,7 @@ void main() { }); test('고레벨 몬스터는 더 긴 전투 시간', () { - final rng = DeterministicRandom( 42); + final rng = DeterministicRandom(42); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( @@ -305,7 +305,7 @@ void main() { group('evaluateDifficulty', () { test('범위 0.0~1.0 내 반환', () { - final rng = DeterministicRandom( 42); + final rng = DeterministicRandom(42); final calculator = CombatCalculator(rng: rng); final player = CombatStats.empty().copyWith( diff --git a/test/core/engine/skill_service_test.dart b/test/core/engine/skill_service_test.dart index 18040dc..ea5829b 100644 --- a/test/core/engine/skill_service_test.dart +++ b/test/core/engine/skill_service_test.dart @@ -125,7 +125,10 @@ void main() { // ATK 100 * 2.0 - DEF 50 * 0.3 = 200 - 15 = 185 expect(result.result.success, isTrue); expect(result.result.damage, equals(185)); - expect(result.updatedPlayer.mpCurrent, equals(20)); // 50 - 30 (mpCost 30) + expect( + result.updatedPlayer.mpCurrent, + equals(20), + ); // 50 - 30 (mpCost 30) expect(result.updatedMonster.hpCurrent, equals(315)); // 500 - 185 }); @@ -349,10 +352,7 @@ void main() { const skill = SkillData.memoryDump; // baseDotDamage: 10, baseDotDurationMs: 6000, baseDotTickMs: 1000 - final player = CombatStats.empty().copyWith( - mpMax: 100, - mpCurrent: 50, - ); + final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50); final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000); final result = service.useDotSkill( @@ -379,10 +379,7 @@ void main() { final service = SkillService(rng: rng); const skill = SkillData.memoryDump; - final player = CombatStats.empty().copyWith( - mpMax: 100, - mpCurrent: 50, - ); + final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50); final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000); final result = service.useDotSkill( @@ -403,10 +400,7 @@ void main() { final service = SkillService(rng: rng); const skill = SkillData.memoryDump; - final player = CombatStats.empty().copyWith( - mpMax: 100, - mpCurrent: 50, - ); + final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50); final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000); final result = service.useDotSkill( @@ -555,10 +549,7 @@ void main() { final service = SkillService(rng: rng); const skill = SkillData.debugMode; // ATK +25% 버프, mpCost: 100 - final player = CombatStats.empty().copyWith( - mpMax: 200, - mpCurrent: 150, - ); + final player = CombatStats.empty().copyWith(mpMax: 200, mpCurrent: 150); final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000); final result = service.useBuffSkill( @@ -569,10 +560,7 @@ void main() { expect(result.result.success, isTrue); expect(result.result.appliedBuff, isNotNull); - expect( - result.result.appliedBuff!.effect.atkModifier, - equals(0.25), - ); + expect(result.result.appliedBuff!.effect.atkModifier, equals(0.25)); expect(result.updatedSkillSystem.activeBuffs.length, equals(1)); expect(result.updatedPlayer.mpCurrent, equals(50)); // 150 - 100 }); @@ -582,10 +570,7 @@ void main() { final service = SkillService(rng: rng); const skill = SkillData.debugMode; - final player = CombatStats.empty().copyWith( - mpMax: 100, - mpCurrent: 50, - ); + final player = CombatStats.empty().copyWith(mpMax: 100, mpCurrent: 50); final existingBuff = const ActiveBuff( effect: BuffEffect( id: 'debug_mode_buff', diff --git a/test/core/engine/stat_calculator_test.dart b/test/core/engine/stat_calculator_test.dart index 301b224..dd966b7 100644 --- a/test/core/engine/stat_calculator_test.dart +++ b/test/core/engine/stat_calculator_test.dart @@ -26,10 +26,7 @@ void main() { final race = RaceTraits( raceId: 'test_race', name: 'Test Race', - statModifiers: const { - StatType.str: 2, - StatType.intelligence: -1, - }, + statModifiers: const {StatType.str: 2, StatType.intelligence: -1}, ); // 보정 없는 클래스 @@ -73,10 +70,7 @@ void main() { const klass = ClassTraits( classId: 'test_class', name: 'Test Class', - statModifiers: { - StatType.con: 3, - StatType.dex: 1, - }, + statModifiers: {StatType.con: 3, StatType.dex: 1}, ); final result = calculator.applyModifiers( @@ -248,10 +242,7 @@ void main() { name: 'Test Class', statModifiers: {}, passives: [ - ClassPassive( - type: ClassPassiveType.evasionBonus, - value: 0.15, - ), + ClassPassive(type: ClassPassiveType.evasionBonus, value: 0.15), ], ); @@ -265,10 +256,7 @@ void main() { }); test('물리 공격력 보너스 적용', () { - final combatStats = CombatStats.empty().copyWith( - atk: 100, - def: 20, - ); + final combatStats = CombatStats.empty().copyWith(atk: 100, def: 20); const race = RaceTraits( raceId: 'test_race', @@ -322,10 +310,7 @@ void main() { name: 'Test Class', statModifiers: {}, passives: [ - ClassPassive( - type: ClassPassiveType.postCombatHeal, - value: 0.05, - ), + ClassPassive(type: ClassPassiveType.postCombatHeal, value: 0.05), ], ); diff --git a/test/features/game_session_controller_test.dart b/test/features/game_session_controller_test.dart index f76e84a..c03dddc 100644 --- a/test/features/game_session_controller_test.dart +++ b/test/features/game_session_controller_test.dart @@ -80,7 +80,8 @@ void main() { test('loadAndStart surfaces save load errors', () { fakeAsync((async) { final saveManager = FakeSaveManager() - ..onLoad = (_) => (const SaveOutcome.failure('boom'), null, false, null); + ..onLoad = (_) => + (const SaveOutcome.failure('boom'), null, false, null); final controller = buildController(async, saveManager); controller.loadAndStart(fileName: 'bad.pqf'); diff --git a/test/helpers/mock_factories.dart b/test/helpers/mock_factories.dart index ea27b6e..62abd32 100644 --- a/test/helpers/mock_factories.dart +++ b/test/helpers/mock_factories.dart @@ -95,10 +95,7 @@ class MockFactories { /// /// [seed]: 결정론적 랜덤 시드 /// [level]: 캐릭터 레벨 - static GameState createGameState({ - int seed = 42, - int level = 1, - }) { + static GameState createGameState({int seed = 42, int level = 1}) { return GameState.withSeed(seed: seed); }