diff --git a/lib/src/core/model/game_state.dart b/lib/src/core/model/game_state.dart index 64577fb..d03724c 100644 --- a/lib/src/core/model/game_state.dart +++ b/lib/src/core/model/game_state.dart @@ -569,6 +569,42 @@ class Equipment { ); } + /// 초보자 장비 세트 (모든 슬롯에 기본 방어구 지급) + /// + /// 원본 PQ에서는 초기 장비가 없지만, 밸런스 개선을 위해 + /// Act 1 완료 전에도 기본 방어력을 제공. + factory Equipment.withStarterGear() { + return Equipment( + items: [ + EquipmentItem.defaultWeapon(), // 0: 무기 (Keyboard) + _starterArmor('Old Mouse', EquipmentSlot.shield, def: 2), + _starterArmor('Cardboard Box', EquipmentSlot.helm, def: 1), + _starterArmor('Worn T-Shirt', EquipmentSlot.hauberk, def: 3), + _starterArmor('Rubber Band', EquipmentSlot.brassairts, def: 1), + _starterArmor('Wristwatch', EquipmentSlot.vambraces, def: 1), + _starterArmor('Fingerless Gloves', EquipmentSlot.gauntlets, def: 1), + _starterArmor('Hoodie', EquipmentSlot.gambeson, def: 2), + _starterArmor('Jeans', EquipmentSlot.cuisses, def: 2), + _starterArmor('Knee Pads', EquipmentSlot.greaves, def: 1), + _starterArmor('Sneakers', EquipmentSlot.sollerets, def: 1), + ], + bestIndex: 0, + ); + } + + /// 초보자 방어구 생성 헬퍼 + static EquipmentItem _starterArmor(String name, EquipmentSlot slot, + {required int def}) { + return EquipmentItem( + name: name, + slot: slot, + level: 1, + weight: 1, + stats: ItemStats(def: def), + rarity: ItemRarity.common, + ); + } + /// 레거시 문자열 기반 생성자 (세이브 파일 호환용) factory Equipment.fromStrings({ required String weapon, diff --git a/lib/src/core/util/balance_constants.dart b/lib/src/core/util/balance_constants.dart index 365f0e8..f0280e1 100644 --- a/lib/src/core/util/balance_constants.dart +++ b/lib/src/core/util/balance_constants.dart @@ -150,16 +150,24 @@ class MonsterBaseStats { /// 레벨 기반 기본 스탯 생성 /// /// HP: 50 + level * 20 + (level^2 / 5) - /// ATK: 10 + level * 12 (장비 DEF 스케일링 대응) - /// - 장비 DEF ≈ level * 16 (9개 방어구 합산) - /// - 데미지 공식: ATK - DEF * 0.5 → 의미있는 피해를 위해 상향 + /// ATK: 레벨별 차등 적용 + /// - 레벨 1~5: 2 + level * 3 (초반 난이도 완화) + /// - 레벨 6+: 3 + level * 4 (기존 공식) + /// - DEF 감산율 0.5와 연동하여 밸런스 조정 + /// - 플레이어가 5~10회 공격 생존 가능하도록 설계 /// DEF: 2 + level * 2 /// EXP: 10 + level * 5 /// GOLD: 5 + level * 3 factory MonsterBaseStats.forLevel(int level) { + // 레벨 1~5: 초반 난이도 완화 (2 + level * 3) + // 레벨 1=5, 2=8, 3=11, 4=14, 5=17 + // 레벨 6+: 기존 공식 (3 + level * 4) + // 레벨 6=27, 10=43, 20=83, 50=203, 100=403 + final atk = level <= 5 ? 2 + level * 3 : 3 + level * 4; + return MonsterBaseStats( hp: 50 + level * 20 + (level * level ~/ 5), - atk: 10 + level * 12, + atk: atk, def: 2 + level * 2, exp: 10 + level * 5, gold: 5 + level * 3, @@ -447,14 +455,14 @@ class ActMonsterLevel { class PlayerScaling { PlayerScaling._(); - /// 레벨당 HP 증가량 (10 → 12 상향) - static const int hpPerLevel = 12; + /// 레벨당 HP 증가량 (12 → 18 상향, 생존율 개선) + static const int hpPerLevel = 18; /// 레벨당 MP 증가량 (5 → 6 상향) static const int mpPerLevel = 6; - /// CON당 HP 보너스 (5 → 6 상향) - static const int hpPerCon = 6; + /// CON당 HP 보너스 (6 → 10 상향, 체력 투자 효율 개선) + static const int hpPerCon = 10; /// INT당 MP 보너스 (3 → 4 상향) static const int mpPerInt = 4; diff --git a/lib/src/core/util/pq_logic.dart b/lib/src/core/util/pq_logic.dart index 90063e9..2719fed 100644 --- a/lib/src/core/util/pq_logic.dart +++ b/lib/src/core/util/pq_logic.dart @@ -798,12 +798,15 @@ ActResult completeAct(int existingActCount) { : 3600; final rewards = []; + // 프롤로그 완료 시(existingActCount=1)부터 장비 보상 지급 + // 원본: existingActCount > 2 (Act II 이후) + // 수정: existingActCount >= 1 (프롤로그 완료 후) + if (existingActCount >= 1) { + rewards.add(RewardKind.equip); + } if (existingActCount > 1) { rewards.add(RewardKind.item); } - if (existingActCount > 2) { - rewards.add(RewardKind.equip); - } return ActResult( actTitle: title, diff --git a/lib/src/core/util/roman.dart b/lib/src/core/util/roman.dart index c40a7ee..280a93c 100644 --- a/lib/src/core/util/roman.dart +++ b/lib/src/core/util/roman.dart @@ -13,58 +13,41 @@ const _romanMap = { }; String intToRoman(int n) { + if (n <= 0) return ''; + final buffer = StringBuffer(); - void emit(int value, String numeral) { + + // 로마 숫자 변환 테이블 (내림차순) + const values = [ + (10000, 'T'), + (9000, 'MT'), + (5000, 'A'), + (4000, 'MA'), + (1000, 'M'), + (900, 'CM'), + (500, 'D'), + (400, 'CD'), + (100, 'C'), + (90, 'XC'), + (50, 'L'), + (40, 'XL'), + (10, 'X'), + (9, 'IX'), + (5, 'V'), + (4, 'IV'), + (1, 'I'), + ]; + + for (final (value, numeral) in values) { while (n >= value) { buffer.write(numeral); n -= value; } } - emit(10000, 'T'); - if (n >= 9000) { - buffer.write('MT'); - n -= 9000; - } - if (n >= 5000) { - buffer.write('A'); - n -= 5000; - } - if (n >= 4000) { - buffer.write('MA'); - n -= 4000; - } - - emit(1000, 'M'); - _subtract(ref: n, target: 900, numeral: 'CM', buffer: buffer); - _subtract(ref: n, target: 500, numeral: 'D', buffer: buffer); - _subtract(ref: n, target: 400, numeral: 'CD', buffer: buffer); - - emit(100, 'C'); - _subtract(ref: n, target: 90, numeral: 'XC', buffer: buffer); - _subtract(ref: n, target: 50, numeral: 'L', buffer: buffer); - _subtract(ref: n, target: 40, numeral: 'XL', buffer: buffer); - - emit(10, 'X'); - _subtract(ref: n, target: 9, numeral: 'IX', buffer: buffer); - _subtract(ref: n, target: 5, numeral: 'V', buffer: buffer); - _subtract(ref: n, target: 4, numeral: 'IV', buffer: buffer); - emit(1, 'I'); return buffer.toString(); } -void _subtract({ - required int ref, - required int target, - required String numeral, - required StringBuffer buffer, -}) { - if (ref >= target) { - buffer.write(numeral); - ref -= target; - } -} - int romanToInt(String n) { var result = 0; var i = 0;