diff --git a/analysis_options.yaml b/analysis_options.yaml index dbbfc90..8e6254b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,6 +14,9 @@ analyzer: strict-casts: true strict-inference: true strict-raw-types: true + exclude: + - "**/*.freezed.dart" + - "**/*.g.dart" linter: # Keep the rule set lean; we will tighten as the engine port stabilizes. diff --git a/lib/data/game_text_l10n.dart b/lib/data/game_text_l10n.dart index fa17c2f..a740007 100644 --- a/lib/data/game_text_l10n.dart +++ b/lib/data/game_text_l10n.dart @@ -159,7 +159,7 @@ String get speedBoostTitle => _l('Speed Boost', '속도 부스트', 'スピー String get speedBoostActivate => _l('Activate 10x Speed', '10배속 활성화', '10倍速を有効化'); String speedBoostRemaining(int seconds) => - _l('${seconds}s remaining', '${seconds}초 남음', '残り${seconds}秒'); + _l('${seconds}s remaining', '$seconds초 남음', '残り$seconds秒'); String get speedBoostActive => _l('BOOST ACTIVE', '부스트 활성화', 'ブースト中'); // ============================================================================ @@ -650,8 +650,9 @@ String translateImpressiveTitle(String englishName) { /// 특수 아이템 이름 번역 String translateSpecial(String englishName) { if (isKoreanLocale) return specialTranslationsKo[englishName] ?? englishName; - if (isJapaneseLocale) + if (isJapaneseLocale) { return specialTranslationsJa[englishName] ?? englishName; + } return englishName; } @@ -828,54 +829,65 @@ String translateItemNameL10n(String itemString) { /// Act 제목 번역 String translateActTitle(String englishTitle) { - if (isKoreanLocale) + if (isKoreanLocale) { return actTitleTranslationsKo[englishTitle] ?? englishTitle; - if (isJapaneseLocale) + } + if (isJapaneseLocale) { return actTitleTranslationsJa[englishTitle] ?? englishTitle; + } return englishTitle; } /// Act 보스 이름 번역 String translateActBoss(String englishBoss) { if (isKoreanLocale) return actBossTranslationsKo[englishBoss] ?? englishBoss; - if (isJapaneseLocale) + if (isJapaneseLocale) { return actBossTranslationsJa[englishBoss] ?? englishBoss; + } return englishBoss; } /// Act 퀘스트 번역 String translateActQuest(String englishQuest) { - if (isKoreanLocale) + if (isKoreanLocale) { return actQuestTranslationsKo[englishQuest] ?? englishQuest; - if (isJapaneseLocale) + } + if (isJapaneseLocale) { return actQuestTranslationsJa[englishQuest] ?? englishQuest; + } return englishQuest; } /// 시네마틱 텍스트 번역 String translateCinematic(String englishText) { - if (isKoreanLocale) + if (isKoreanLocale) { return cinematicTranslationsKo[englishText] ?? englishText; - if (isJapaneseLocale) + } + if (isJapaneseLocale) { return cinematicTranslationsJa[englishText] ?? englishText; + } return englishText; } /// 지역 이름 번역 String translateLocation(String englishLocation) { - if (isKoreanLocale) + if (isKoreanLocale) { return locationTranslationsKo[englishLocation] ?? englishLocation; - if (isJapaneseLocale) + } + if (isJapaneseLocale) { return locationTranslationsJa[englishLocation] ?? englishLocation; + } return englishLocation; } /// 세력/조직 이름 번역 String translateFaction(String englishFaction) { - if (isKoreanLocale) + if (isKoreanLocale) { return factionTranslationsKo[englishFaction] ?? englishFaction; - if (isJapaneseLocale) + } + if (isJapaneseLocale) { return factionTranslationsJa[englishFaction] ?? englishFaction; + } return englishFaction; } @@ -1233,7 +1245,7 @@ String get notifyQuestComplete => _l('QUEST COMPLETE!', '퀘스트 완료!', ' String get notifyPrologueComplete => _l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!'); String notifyActComplete(int actNumber) => - _l('ACT $actNumber COMPLETE!', '${actNumber}막 완료!', '第${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/infrastructure/iap_service.dart b/lib/src/core/infrastructure/iap_service.dart index 80f94aa..4190597 100644 --- a/lib/src/core/infrastructure/iap_service.dart +++ b/lib/src/core/infrastructure/iap_service.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; - import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; @@ -190,10 +188,7 @@ class IAPService implements IIAPService { /// 구매 상태 저장 (보안 저장소 사용) Future _savePurchaseState(bool purchased) async { - await _secureStorage.write( - key: _purchaseKey, - value: purchased.toString(), - ); + await _secureStorage.write(key: _purchaseKey, value: purchased.toString()); _adRemovalPurchased = purchased; debugPrint('[IAPService] Saved purchase state: $purchased'); } @@ -451,10 +446,7 @@ class IAPService implements IIAPService { pc.PublicKeyParameter(publicKey), ); - return signer.verifySignature( - dataBytes, - pc.RSASignature(signatureBytes), - ); + return signer.verifySignature(dataBytes, pc.RSASignature(signatureBytes)); } catch (e) { debugPrint('[IAPService] RSA verification error: $e'); return false; @@ -477,10 +469,8 @@ class IAPService implements IIAPService { final rsaParser = ASN1Parser(publicKeyBytes); final rsaSequence = rsaParser.nextObject() as ASN1Sequence; - final modulus = - (rsaSequence.elements![0] as ASN1Integer).integer!; - final exponent = - (rsaSequence.elements![1] as ASN1Integer).integer!; + final modulus = (rsaSequence.elements![0] as ASN1Integer).integer!; + final exponent = (rsaSequence.elements![1] as ASN1Integer).integer!; return pc.RSAPublicKey(modulus, exponent); } diff --git a/lib/src/core/util/pq_logic.dart b/lib/src/core/util/pq_logic.dart index 0c05b12..a9e25fc 100644 --- a/lib/src/core/util/pq_logic.dart +++ b/lib/src/core/util/pq_logic.dart @@ -2,6 +2,7 @@ /// /// 유틸리티 함수 모음. /// 이 파일은 분할된 모듈들을 re-export하여 기존 코드 호환성 유지. +library; // 랜덤/확률 함수 export 'package:asciineverdie/src/core/util/pq_random.dart'; diff --git a/lib/src/features/front/save_picker_dialog.dart b/lib/src/features/front/save_picker_dialog.dart index bbaebb9..89843a6 100644 --- a/lib/src/features/front/save_picker_dialog.dart +++ b/lib/src/features/front/save_picker_dialog.dart @@ -52,7 +52,7 @@ class SavePickerDialog extends StatelessWidget { child: ListView.separated( shrinkWrap: true, itemCount: saves.length, - separatorBuilder: (_, __) => const Divider(height: 1), + separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, index) { final save = saves[index]; return _SaveListTile( diff --git a/lib/src/features/game/widgets/desktop_equipment_panel.dart b/lib/src/features/game/widgets/desktop_equipment_panel.dart index 620cc8f..fee8aa6 100644 --- a/lib/src/features/game/widgets/desktop_equipment_panel.dart +++ b/lib/src/features/game/widgets/desktop_equipment_panel.dart @@ -4,7 +4,6 @@ import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n; import 'package:asciineverdie/l10n/app_localizations.dart'; import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; -import 'package:asciineverdie/src/core/model/inventory.dart'; import 'package:asciineverdie/src/features/game/widgets/combat_log.dart'; import 'package:asciineverdie/src/features/game/widgets/desktop_panel_widgets.dart'; import 'package:asciineverdie/src/features/game/widgets/equipment_stats_panel.dart'; diff --git a/lib/src/shared/animation/canvas/combat_text_frames.dart b/lib/src/shared/animation/canvas/combat_text_frames.dart index 449634f..9cbd242 100644 --- a/lib/src/shared/animation/canvas/combat_text_frames.dart +++ b/lib/src/shared/animation/canvas/combat_text_frames.dart @@ -2,6 +2,7 @@ /// /// CanvasBattleComposer에서 분리된 전투 텍스트 프레임 상수. /// 크리티컬, 회피, 미스, 디버프, DOT, 블록, 패리 텍스트 프레임. +library; // ============================================================================ // 몬스터 공격 이펙트 (← 방향, Phase 8) - 5줄 diff --git a/test/core/engine/death_handler_test.dart b/test/core/engine/death_handler_test.dart index d1088b6..7fc9e0d 100644 --- a/test/core/engine/death_handler_test.dart +++ b/test/core/engine/death_handler_test.dart @@ -1,5 +1,4 @@ import 'package:asciineverdie/src/core/engine/death_handler.dart'; -import 'package:asciineverdie/src/core/model/equipment_container.dart'; import 'package:asciineverdie/src/core/model/equipment_item.dart'; import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; @@ -13,7 +12,7 @@ void main() { const handler = DeathHandler(); /// 테스트용 장비 생성 헬퍼 - EquipmentItem _makeItem(String name, EquipmentSlot slot) { + EquipmentItem makeItem(String name, EquipmentSlot slot) { return EquipmentItem( name: name, slot: slot, @@ -25,20 +24,20 @@ void main() { } /// 모든 슬롯에 장비가 장착된 Equipment 생성 - Equipment _fullyEquipped() { + Equipment fullyEquipped() { return Equipment( items: [ EquipmentItem.defaultWeapon(), // 0: 무기(weapon) - _makeItem('Iron Shield', EquipmentSlot.shield), - _makeItem('Iron Helm', EquipmentSlot.helm), - _makeItem('Iron Hauberk', EquipmentSlot.hauberk), - _makeItem('Iron Brassairts', EquipmentSlot.brassairts), - _makeItem('Iron Vambraces', EquipmentSlot.vambraces), - _makeItem('Iron Gauntlets', EquipmentSlot.gauntlets), - _makeItem('Iron Gambeson', EquipmentSlot.gambeson), - _makeItem('Iron Cuisses', EquipmentSlot.cuisses), - _makeItem('Iron Greaves', EquipmentSlot.greaves), - _makeItem('Iron Sollerets', EquipmentSlot.sollerets), + makeItem('Iron Shield', EquipmentSlot.shield), + makeItem('Iron Helm', EquipmentSlot.helm), + makeItem('Iron Hauberk', EquipmentSlot.hauberk), + makeItem('Iron Brassairts', EquipmentSlot.brassairts), + makeItem('Iron Vambraces', EquipmentSlot.vambraces), + makeItem('Iron Gauntlets', EquipmentSlot.gauntlets), + makeItem('Iron Gambeson', EquipmentSlot.gambeson), + makeItem('Iron Cuisses', EquipmentSlot.cuisses), + makeItem('Iron Greaves', EquipmentSlot.greaves), + makeItem('Iron Sollerets', EquipmentSlot.sollerets), ], bestIndex: 0, ); @@ -50,7 +49,7 @@ void main() { /// [level]: 캐릭터 레벨 /// [isBossFight]: 보스전(boss fight) 여부 /// [equipment]: 커스텀 장비 - GameState _createState({ + GameState createState({ int seed = 42, int level = 1, bool isBossFight = false, @@ -59,7 +58,7 @@ void main() { final state = GameState( rng: DeterministicRandom(seed), traits: Traits.empty().copyWith(level: level), - equipment: equipment ?? _fullyEquipped(), + equipment: equipment ?? fullyEquipped(), progress: ProgressState.empty().copyWith( currentCombat: MockFactories.createCombat(), finalBossState: isBossFight @@ -75,7 +74,7 @@ void main() { group('deathInfo 생성', () { test('사망 시 deathInfo가 올바르게 생성된다', () { // 준비(arrange): Lv10 캐릭터 - 100% 장비 손실 확률 - final state = _createState(level: 10); + final state = createState(level: 10); // 실행(act) final result = handler.processPlayerDeath( @@ -94,7 +93,7 @@ void main() { }); test('selfDamage 원인으로 사망 시 cause가 올바르다', () { - final state = _createState(level: 1); + final state = createState(level: 1); final result = handler.processPlayerDeath( state, @@ -108,8 +107,8 @@ void main() { group('보스전(boss fight) 사망', () { test('보스전 사망 시 장비가 보호된다 (lostCount == 0)', () { - final equipment = _fullyEquipped(); - final state = _createState( + final equipment = fullyEquipped(); + final state = createState( level: 10, isBossFight: true, equipment: equipment, @@ -136,7 +135,7 @@ void main() { }); test('보스전 사망 시 bossLevelingEndTime이 설정된다 (5분)', () { - final state = _createState(isBossFight: true); + final state = createState(isBossFight: true); final before = DateTime.now().millisecondsSinceEpoch; final result = handler.processPlayerDeath( @@ -157,7 +156,7 @@ void main() { }); test('일반 사망 시 bossLevelingEndTime은 null이다', () { - final state = _createState(level: 1); + final state = createState(level: 1); final result = handler.processPlayerDeath( state, @@ -185,7 +184,7 @@ void main() { } expect(lossySeed, isNotNull, reason: '장비 손실 시드를 찾을 수 없음'); - final state = _createState(seed: lossySeed!, level: 1); + final state = createState(seed: lossySeed!, level: 1); final result = handler.processPlayerDeath( state, killerName: 'Goblin', @@ -208,7 +207,7 @@ void main() { } expect(safeSeed, isNotNull, reason: '장비 보호 시드를 찾을 수 없음'); - final state = _createState(seed: safeSeed!, level: 1); + final state = createState(seed: safeSeed!, level: 1); final result = handler.processPlayerDeath( state, killerName: 'Goblin', @@ -221,7 +220,7 @@ void main() { test('Lv10+: 100% 확률로 장비 손실', () { // Lv10 이상은 항상 100% 손실 - final state = _createState(seed: 42, level: 10); + final state = createState(seed: 42, level: 10); final result = handler.processPlayerDeath( state, @@ -249,7 +248,7 @@ void main() { // Lv10(100% 손실)으로 여러 시드를 반복 테스트 // 무기는 절대 사라지지 않아야 함 for (var s = 0; s < 50; s++) { - final state = _createState(seed: s, level: 10); + final state = createState(seed: s, level: 10); final result = handler.processPlayerDeath( state, killerName: 'Monster', @@ -280,7 +279,7 @@ void main() { // 무기만 있고 나머지는 빈 장비 final emptyEquipment = Equipment.empty(); // Equipment.empty()는 기본 무기(Keyboard)만 있음 - final state = _createState( + final state = createState( seed: 42, level: 10, // 100% 손실 확률 equipment: emptyEquipment, @@ -300,7 +299,7 @@ void main() { group('deathCount 증가', () { test('사망 시 deathCount가 1 증가한다', () { - final state = _createState(); + final state = createState(); expect(state.progress.deathCount, 0); final result = handler.processPlayerDeath( @@ -314,7 +313,7 @@ void main() { test('연속 사망 시 deathCount가 누적된다', () { // 첫 번째 사망 (deathCount: 0 -> 1) - var state = _createState(seed: 100); + var state = createState(seed: 100); state = handler.processPlayerDeath( state, killerName: 'Goblin', @@ -338,7 +337,7 @@ void main() { group('currentCombat 초기화', () { test('사망 후 currentCombat이 null로 초기화되어야 한다', () { // 전투 중(combat active) 상태에서 사망 - final state = _createState(); + final state = createState(); expect(state.progress.currentCombat, isNotNull); final result = handler.processPlayerDeath( diff --git a/test/core/engine/gcd_simulation_test.dart b/test/core/engine/gcd_simulation_test.dart index 0d72043..fff873b 100644 --- a/test/core/engine/gcd_simulation_test.dart +++ b/test/core/engine/gcd_simulation_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_print + import 'dart:math'; import 'package:flutter_test/flutter_test.dart'; @@ -7,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; /// 다양한 GCD 값에 대해 전투 효율성을 측정 void main() { test('GCD 시뮬레이션 - 다양한 값 비교', () { - print('\n' + '=' * 80); + print('\n${'=' * 80}'); print('글로벌 쿨타임(GCD) 시뮬레이션 결과'); print('=' * 80); @@ -93,14 +95,14 @@ void main() { '- DPS 손실이 크지 않음 (${((1 - gcd1500.avgDps / baselineDps) * 100).toStringAsFixed(0)}% ~ ${((1 - gcd2000.avgDps / baselineDps) * 100).toStringAsFixed(0)}%)', ); - print('\n' + '=' * 80); + print('\n${'=' * 80}'); // 테스트는 항상 통과 (정보 출력용) expect(true, isTrue); }); test('GCD 상세 시뮬레이션 - 레벨별 영향', () { - print('\n' + '=' * 80); + print('\n${'=' * 80}'); print('레벨별 GCD 영향 분석'); print('=' * 80); @@ -153,7 +155,7 @@ void main() { } } - print('\n' + '=' * 80); + print('\n${'=' * 80}'); expect(true, isTrue); }); } diff --git a/test/core/model/save_data_roundtrip_test.dart b/test/core/model/save_data_roundtrip_test.dart index e7ed4d7..eb41666 100644 --- a/test/core/model/save_data_roundtrip_test.dart +++ b/test/core/model/save_data_roundtrip_test.dart @@ -1,7 +1,8 @@ +// ignore_for_file: inference_failure_on_collection_literal + import 'dart:collection'; import 'dart:convert'; -import 'package:asciineverdie/src/core/model/equipment_container.dart'; import 'package:asciineverdie/src/core/model/equipment_item.dart'; import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/item_stats.dart'; @@ -17,7 +18,7 @@ void main() { // 헬퍼: 모든 필드가 채워진 GameSave 생성 // ========================================================================= - GameSave _createFullSave() { + GameSave createFullSave() { return GameSave( version: kSaveVersion, rngState: 12345, @@ -144,10 +145,10 @@ void main() { // ========================================================================= test('toJson() → fromJson() 라운드트립 — 모든 필드 보존', () { - final original = _createFullSave(); + final original = createFullSave(); // jsonEncode → jsonDecode로 순수 Map 변환 (freezed 객체 제거) - final json = jsonDecode(jsonEncode(original.toJson())) - as Map; + final json = + jsonDecode(jsonEncode(original.toJson())) as Map; final restored = GameSave.fromJson(json); // traits 검증 @@ -184,10 +185,7 @@ void main() { expect(restored.equipment.weapon, equals('Flaming Sword')); expect(restored.equipment.shield, equals('Tower Shield')); expect(restored.equipment.bestIndex, equals(0)); - expect( - restored.equipment.weaponItem.rarity, - equals(ItemRarity.epic), - ); + expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.epic)); expect(restored.equipment.weaponItem.level, equals(10)); expect(restored.equipment.weaponItem.stats.atk, equals(50)); @@ -201,7 +199,10 @@ void main() { expect(restored.progress.task.max, equals(100)); expect(restored.progress.quest.position, equals(3)); expect(restored.progress.exp.position, equals(500)); - expect(restored.progress.currentTask.caption, equals('Executing a Goblin')); + expect( + restored.progress.currentTask.caption, + equals('Executing a Goblin'), + ); expect(restored.progress.currentTask.type, equals(TaskType.kill)); expect( restored.progress.currentTask.monsterGrade, @@ -252,7 +253,14 @@ void main() { 'version': 2, 'rng': 100, 'traits': {'name': 'OldHero', 'race': 'Elf', 'klass': 'Mage'}, - 'stats': {'str': 10, 'con': 10, 'dex': 10, 'int': 10, 'wis': 10, 'cha': 10}, + 'stats': { + 'str': 10, + 'con': 10, + 'dex': 10, + 'int': 10, + 'wis': 10, + 'cha': 10, + }, 'inventory': {'gold': 500, 'items': []}, 'equipment': { 'weapon': 'Ancient Sword', @@ -291,7 +299,10 @@ void main() { // 레거시 아이템은 level 1, common으로 변환 expect(restored.equipment.weaponItem.level, equals(1)); expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.common)); - expect(restored.equipment.weaponItem.slot, equals(EquipmentSlot.weapon)); + expect( + restored.equipment.weaponItem.slot, + equals(EquipmentSlot.weapon), + ); // 버전 정보 보존 expect(restored.version, equals(2)); @@ -299,30 +310,34 @@ void main() { test('v2 세이브에 monetization 없으면 null', () { // jsonDecode로 순수 Map 생성 (타입 캐스팅 호환) - final legacyJson = jsonDecode(jsonEncode({ - 'version': 2, - 'rng': 0, - 'traits': {}, - 'stats': {}, - 'inventory': {'gold': 0, 'items': []}, - 'equipment': { - 'weapon': 'Keyboard', - 'shield': '', - 'helm': '', - 'hauberk': '', - 'brassairts': '', - 'vambraces': '', - 'gauntlets': '', - 'gambeson': '', - 'cuisses': '', - 'greaves': '', - 'sollerets': '', - 'bestIndex': 0, - }, - 'skills': [], - 'progress': {}, - 'queue': [], - })) as Map; + final legacyJson = + jsonDecode( + jsonEncode({ + 'version': 2, + 'rng': 0, + 'traits': {}, + 'stats': {}, + 'inventory': {'gold': 0, 'items': []}, + 'equipment': { + 'weapon': 'Keyboard', + 'shield': '', + 'helm': '', + 'hauberk': '', + 'brassairts': '', + 'vambraces': '', + 'gauntlets': '', + 'gambeson': '', + 'cuisses': '', + 'greaves': '', + 'sollerets': '', + 'bestIndex': 0, + }, + 'skills': [], + 'progress': {}, + 'queue': [], + }), + ) + as Map; final restored = GameSave.fromJson(legacyJson); @@ -404,10 +419,7 @@ void main() { 'plot': {'pos': 0, 'max': 1}, 'exp': {'pos': 0, 'max': 1}, 'encumbrance': {'pos': 0, 'max': 1}, - 'taskInfo': { - 'caption': 'Unknown task', - 'type': 'nonexistent_type', - }, + 'taskInfo': {'caption': 'Unknown task', 'type': 'nonexistent_type'}, }, 'queue': [ { @@ -424,10 +436,7 @@ void main() { // 알 수 없는 enum 값은 기본값으로 대체 expect(restored.progress.currentTask.type, equals(TaskType.neutral)); expect(restored.queue.entries.first.kind, equals(QueueKind.task)); - expect( - restored.queue.entries.first.taskType, - equals(TaskType.neutral), - ); + expect(restored.queue.entries.first.taskType, equals(TaskType.neutral)); }); }); @@ -448,9 +457,7 @@ void main() { }); test('skills가 빈 배열이면 빈 스킬북', () { - final json = { - 'skills': [], - }; + final json = {'skills': []}; final restored = GameSave.fromJson(json); @@ -494,9 +501,7 @@ void main() { }); test('queue가 빈 배열이면 빈 큐', () { - final json = { - 'queue': [], - }; + final json = {'queue': []}; final restored = GameSave.fromJson(json); @@ -504,9 +509,7 @@ void main() { }); test('monetization null이면 null 유지', () { - final json = { - 'monetization': null, - }; + final json = {'monetization': null}; final restored = GameSave.fromJson(json);