refactor(core): 밸런스 상수 및 진행 서비스 개선

- BalanceConstants 정리
- ProgressService 로직 개선
This commit is contained in:
JiWoong Sul
2026-01-08 20:46:55 +09:00
parent 61edd87252
commit 5487c79474
2 changed files with 53 additions and 45 deletions

View File

@@ -104,7 +104,8 @@ class ProgressService {
); );
// ExpBar 초기화 (원본 743-746줄) // ExpBar 초기화 (원본 743-746줄)
final expBar = ProgressBarState(position: 0, max: pq_logic.levelUpTime(1)); final expBar =
ProgressBarState(position: 0, max: ExpConstants.requiredExp(1));
// PlotBar 초기화 - Prologue 5분 (300초) // PlotBar 초기화 - Prologue 5분 (300초)
final plotBar = const ProgressBarState(position: 0, max: 300); final plotBar = const ProgressBarState(position: 0, max: 300);
@@ -253,6 +254,10 @@ class ProgressService {
final gain = progress.currentTask.type == TaskType.kill; final gain = progress.currentTask.type == TaskType.kill;
final incrementSeconds = progress.task.max ~/ 1000; final incrementSeconds = progress.task.max ~/ 1000;
// 몬스터 경험치 미리 저장 (currentCombat이 null되기 전)
final int monsterExpReward =
progress.currentCombat?.monsterStats.expReward ?? 0;
// 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득 // 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
if (gain) { if (gain) {
// 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복 // 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복
@@ -354,19 +359,26 @@ class ProgressService {
} }
} }
// Gain XP / level up. // Gain XP / level up (몬스터 경험치 기반)
// 최대 레벨(100) 제한: 100레벨에서는 더 이상 레벨업하지 않음 // 최대 레벨(100) 제한: 100레벨에서는 더 이상 레벨업하지 않음
if (gain) { if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
if (progress.exp.position >= progress.exp.max && final newExpPos = progress.exp.position + monsterExpReward;
nextState.traits.level < 100) {
// 레벨업 체크 (경험치가 필요량 이상일 때)
if (newExpPos >= progress.exp.max) {
// 초과 경험치 계산
final overflowExp = newExpPos - progress.exp.max;
nextState = _levelUp(nextState); nextState = _levelUp(nextState);
leveledUp = true; leveledUp = true;
progress = nextState.progress; progress = nextState.progress;
} else if (nextState.traits.level < 100) {
final uncappedExp = progress.exp.position + incrementSeconds; // 초과 경험치를 다음 레벨에 적용
final int newExpPos = uncappedExp > progress.exp.max if (overflowExp > 0 && nextState.traits.level < 100) {
? progress.exp.max progress = progress.copyWith(
: uncappedExp; exp: progress.exp.copyWith(position: overflowExp),
);
}
} else {
progress = progress.copyWith( progress = progress.copyWith(
exp: progress.exp.copyWith(position: newExpPos), exp: progress.exp.copyWith(position: newExpPos),
); );
@@ -870,15 +882,18 @@ class ProgressService {
nextState = _levelUp(nextState); nextState = _levelUp(nextState);
} }
final progress = nextState.progress.copyWith( // 태스크 바 완료 처리
var progress = nextState.progress.copyWith(
task: nextState.progress.task.copyWith( task: nextState.progress.task.copyWith(
position: nextState.progress.task.max, position: nextState.progress.task.max,
), ),
plot: nextState.progress.plot.copyWith(
position: nextState.progress.plot.max,
),
); );
return nextState.copyWith(progress: progress); nextState = nextState.copyWith(progress: progress);
// 디버그 모드에서는 completeAct 직접 호출하여 plotStageCount 즉시 업데이트
// 시네마틱은 생략하고 바로 다음 Act로 진입
final actResult = completeAct(nextState);
return actResult.state;
} }
GameState _applyReward(GameState state, pq_logic.RewardKind reward) { GameState _applyReward(GameState state, pq_logic.RewardKind reward) {
@@ -912,7 +927,7 @@ class ProgressService {
final expBar = ProgressBarState( final expBar = ProgressBarState(
position: 0, position: 0,
max: pq_logic.levelUpTime(nextLevel), max: ExpConstants.requiredExp(nextLevel),
); );
final progress = nextState.progress.copyWith(exp: expBar); final progress = nextState.progress.copyWith(exp: expBar);
nextState = nextState.copyWith(progress: progress); nextState = nextState.copyWith(progress: progress);

View File

@@ -7,36 +7,27 @@ library;
class ExpConstants { class ExpConstants {
ExpConstants._(); ExpConstants._();
/// 기본 경험치 값 /// 레벨업에 필요한 경험치 계산 (몬스터 기반)
static const int baseExp = 100;
/// 레벨 구간별 경험치 증가율 (tiered growth rate)
/// - 1-30: 1.10 (초반 빠른 진행)
/// - 31-60: 1.12 (중반 적정 속도)
/// - 61-100: 1.14 (후반 도전)
static double _getGrowthRate(int level) {
if (level <= 30) return 1.10;
if (level <= 60) return 1.12;
return 1.14;
}
/// 레벨업에 필요한 경험치 계산 (구간별 차등 적용)
/// ///
/// 조정 후 예상: /// 공식: (10 + level * 5) * (25 + level / 3)
/// 레벨 10: ~259 exp /// - 몬스터 경험치와 동기화 (MonsterBaseStats.exp = 10 + level * 5)
/// 레벨 30: ~1,744 exp /// - 레벨당 약 25~58마리 처치 필요
/// 레벨 50: ~9,705 exp ///
/// 레벨 80: ~133,860 exp /// 예상:
/// 레벨 100: ~636,840 exp /// 레벨 1: 15 * 25 = 375 exp (~25마리)
/// 레벨 10: 60 * 28 = 1,680 exp (~28마리)
/// 레벨 30: 160 * 35 = 5,600 exp (~35마리)
/// 레벨 50: 260 * 42 = 10,920 exp (~42마리)
/// 레벨 80: 410 * 52 = 21,320 exp (~52마리)
/// 레벨 100: 510 * 58 = 29,580 exp (~58마리)
static int requiredExp(int level) { static int requiredExp(int level) {
if (level <= 0) return baseExp; if (level <= 0) return 375;
// 구간별 복합 성장 계산 // 해당 레벨 몬스터 경험치 = 10 + level * 5
double result = baseExp.toDouble(); final monsterExp = 10 + level * 5;
for (int i = 1; i <= level; i++) { // 필요 킬 수 = 25 + level / 3 (레벨이 올라갈수록 약간 더 많이)
result *= _getGrowthRate(i); final killsRequired = 25 + level ~/ 3;
} return monsterExp * killsRequired;
return result.round();
} }
/// 총 누적 경험치 계산 (특정 레벨까지) /// 총 누적 경험치 계산 (특정 레벨까지)
@@ -159,14 +150,16 @@ class MonsterBaseStats {
/// 레벨 기반 기본 스탯 생성 /// 레벨 기반 기본 스탯 생성
/// ///
/// HP: 50 + level * 20 + (level^2 / 5) /// HP: 50 + level * 20 + (level^2 / 5)
/// ATK: 5 + level * 4 (플레이어 DEF 스케일링에 맞춰 상향) /// ATK: 10 + level * 12 (장비 DEF 스케일링 대응)
/// - 장비 DEF ≈ level * 16 (9개 방어구 합산)
/// - 데미지 공식: ATK - DEF * 0.5 → 의미있는 피해를 위해 상향
/// 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) {
return MonsterBaseStats( return MonsterBaseStats(
hp: 50 + level * 20 + (level * level ~/ 5), hp: 50 + level * 20 + (level * level ~/ 5),
atk: 5 + level * 4, atk: 10 + level * 12,
def: 2 + level * 2, def: 2 + level * 2,
exp: 10 + level * 5, exp: 10 + level * 5,
gold: 5 + level * 3, gold: 5 + level * 3,