refactor(core): 모델 및 유틸리티 개선

- GameState 확장
- BalanceConstants 조정
- PqLogic, Roman 정리
This commit is contained in:
JiWoong Sul
2026-01-12 20:02:50 +09:00
parent a1d22369cb
commit 12f195bed7
4 changed files with 83 additions and 53 deletions

View File

@@ -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,

View File

@@ -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 보너스 (56 상향)
static const int hpPerCon = 6;
/// CON당 HP 보너스 (610 상향, 체력 투자 효율 개선)
static const int hpPerCon = 10;
/// INT당 MP 보너스 (3 → 4 상향)
static const int mpPerInt = 4;

View File

@@ -798,12 +798,15 @@ ActResult completeAct(int existingActCount) {
: 3600;
final rewards = <RewardKind>[];
// 프롤로그 완료 시(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,

View File

@@ -13,58 +13,41 @@ const _romanMap = <String, int>{
};
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;