refactor(core): 모델 및 유틸리티 개선
- GameState 확장 - BalanceConstants 조정 - PqLogic, Roman 정리
This commit is contained in:
@@ -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({
|
factory Equipment.fromStrings({
|
||||||
required String weapon,
|
required String weapon,
|
||||||
|
|||||||
@@ -150,16 +150,24 @@ class MonsterBaseStats {
|
|||||||
/// 레벨 기반 기본 스탯 생성
|
/// 레벨 기반 기본 스탯 생성
|
||||||
///
|
///
|
||||||
/// HP: 50 + level * 20 + (level^2 / 5)
|
/// HP: 50 + level * 20 + (level^2 / 5)
|
||||||
/// ATK: 10 + level * 12 (장비 DEF 스케일링 대응)
|
/// ATK: 레벨별 차등 적용
|
||||||
/// - 장비 DEF ≈ level * 16 (9개 방어구 합산)
|
/// - 레벨 1~5: 2 + level * 3 (초반 난이도 완화)
|
||||||
/// - 데미지 공식: ATK - DEF * 0.5 → 의미있는 피해를 위해 상향
|
/// - 레벨 6+: 3 + level * 4 (기존 공식)
|
||||||
|
/// - DEF 감산율 0.5와 연동하여 밸런스 조정
|
||||||
|
/// - 플레이어가 5~10회 공격 생존 가능하도록 설계
|
||||||
/// DEF: 2 + level * 2
|
/// DEF: 2 + level * 2
|
||||||
/// EXP: 10 + level * 5
|
/// EXP: 10 + level * 5
|
||||||
/// GOLD: 5 + level * 3
|
/// GOLD: 5 + level * 3
|
||||||
factory MonsterBaseStats.forLevel(int level) {
|
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(
|
return MonsterBaseStats(
|
||||||
hp: 50 + level * 20 + (level * level ~/ 5),
|
hp: 50 + level * 20 + (level * level ~/ 5),
|
||||||
atk: 10 + level * 12,
|
atk: atk,
|
||||||
def: 2 + level * 2,
|
def: 2 + level * 2,
|
||||||
exp: 10 + level * 5,
|
exp: 10 + level * 5,
|
||||||
gold: 5 + level * 3,
|
gold: 5 + level * 3,
|
||||||
@@ -447,14 +455,14 @@ class ActMonsterLevel {
|
|||||||
class PlayerScaling {
|
class PlayerScaling {
|
||||||
PlayerScaling._();
|
PlayerScaling._();
|
||||||
|
|
||||||
/// 레벨당 HP 증가량 (10 → 12 상향)
|
/// 레벨당 HP 증가량 (12 → 18 상향, 생존율 개선)
|
||||||
static const int hpPerLevel = 12;
|
static const int hpPerLevel = 18;
|
||||||
|
|
||||||
/// 레벨당 MP 증가량 (5 → 6 상향)
|
/// 레벨당 MP 증가량 (5 → 6 상향)
|
||||||
static const int mpPerLevel = 6;
|
static const int mpPerLevel = 6;
|
||||||
|
|
||||||
/// CON당 HP 보너스 (5 → 6 상향)
|
/// CON당 HP 보너스 (6 → 10 상향, 체력 투자 효율 개선)
|
||||||
static const int hpPerCon = 6;
|
static const int hpPerCon = 10;
|
||||||
|
|
||||||
/// INT당 MP 보너스 (3 → 4 상향)
|
/// INT당 MP 보너스 (3 → 4 상향)
|
||||||
static const int mpPerInt = 4;
|
static const int mpPerInt = 4;
|
||||||
|
|||||||
@@ -798,12 +798,15 @@ ActResult completeAct(int existingActCount) {
|
|||||||
: 3600;
|
: 3600;
|
||||||
|
|
||||||
final rewards = <RewardKind>[];
|
final rewards = <RewardKind>[];
|
||||||
|
// 프롤로그 완료 시(existingActCount=1)부터 장비 보상 지급
|
||||||
|
// 원본: existingActCount > 2 (Act II 이후)
|
||||||
|
// 수정: existingActCount >= 1 (프롤로그 완료 후)
|
||||||
|
if (existingActCount >= 1) {
|
||||||
|
rewards.add(RewardKind.equip);
|
||||||
|
}
|
||||||
if (existingActCount > 1) {
|
if (existingActCount > 1) {
|
||||||
rewards.add(RewardKind.item);
|
rewards.add(RewardKind.item);
|
||||||
}
|
}
|
||||||
if (existingActCount > 2) {
|
|
||||||
rewards.add(RewardKind.equip);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActResult(
|
return ActResult(
|
||||||
actTitle: title,
|
actTitle: title,
|
||||||
|
|||||||
@@ -13,58 +13,41 @@ const _romanMap = <String, int>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
String intToRoman(int n) {
|
String intToRoman(int n) {
|
||||||
|
if (n <= 0) return '';
|
||||||
|
|
||||||
final buffer = StringBuffer();
|
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) {
|
while (n >= value) {
|
||||||
buffer.write(numeral);
|
buffer.write(numeral);
|
||||||
n -= value;
|
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();
|
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) {
|
int romanToInt(String n) {
|
||||||
var result = 0;
|
var result = 0;
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user