diff --git a/lib/src/core/engine/progress_service.dart b/lib/src/core/engine/progress_service.dart index daec89b..00047e6 100644 --- a/lib/src/core/engine/progress_service.dart +++ b/lib/src/core/engine/progress_service.dart @@ -17,6 +17,7 @@ import 'package:asciineverdie/src/core/model/monster_combat_stats.dart'; import 'package:asciineverdie/src/core/model/potion.dart'; import 'package:asciineverdie/src/core/model/pq_config.dart'; import 'package:asciineverdie/src/core/model/skill.dart'; +import 'package:asciineverdie/src/core/util/balance_constants.dart'; import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic; class ProgressTickResult { @@ -549,10 +550,13 @@ class ProgressService { ); // 전투용 몬스터 레벨 조정 (밸런스) - // config의 raw 레벨이 플레이어보다 너무 높으면 전투가 불가능 - // 플레이어 레벨 ±3 범위로 제한 (최소 1) + // Act별 최소 레벨과 플레이어 레벨 중 큰 값을 기준으로 ±3 범위 제한 + final actMinLevel = ActMonsterLevel.forPlotStage( + state.progress.plotStageCount, + ); + final baseLevel = math.max(level, actMinLevel); final effectiveMonsterLevel = monsterResult.level - .clamp(math.max(1, level - 3), level + 3) + .clamp(math.max(1, baseLevel - 3), baseLevel + 3) .toInt(); final monsterCombatStats = MonsterCombatStats.fromLevel( @@ -849,11 +853,25 @@ class ProgressService { } GameState forcePlotComplete(GameState state) { - final progress = state.progress.copyWith( - task: state.progress.task.copyWith(position: state.progress.task.max), - plot: state.progress.plot.copyWith(position: state.progress.plot.max), + // 다음 Act의 최소 몬스터 레벨까지 레벨업 + final nextPlotStage = state.progress.plotStageCount + 1; + final targetLevel = ActMonsterLevel.forPlotStage(nextPlotStage); + var nextState = state; + + // 현재 레벨이 목표 레벨보다 낮으면 레벨업 + while (nextState.traits.level < targetLevel) { + nextState = _levelUp(nextState); + } + + final progress = nextState.progress.copyWith( + task: nextState.progress.task.copyWith( + position: nextState.progress.task.max, + ), + plot: nextState.progress.plot.copyWith( + position: nextState.progress.plot.max, + ), ); - return state.copyWith(progress: progress); + return nextState.copyWith(progress: progress); } GameState _applyReward(GameState state, pq_logic.RewardKind reward) { diff --git a/lib/src/core/util/balance_constants.dart b/lib/src/core/util/balance_constants.dart index 32030ae..5a948dc 100644 --- a/lib/src/core/util/balance_constants.dart +++ b/lib/src/core/util/balance_constants.dart @@ -416,6 +416,40 @@ class LevelTierSettings { } } +/// Act별 최소 몬스터 레벨 (act minimum monster level) +/// +/// 플레이어 레벨과 무관하게 각 Act에서 등장하는 몬스터의 최소 레벨. +/// 저레벨 플레이어가 고Act에 도달해도 적절한 난이도 유지. +class ActMonsterLevel { + ActMonsterLevel._(); + + /// plotStageCount 기준 최소 몬스터 레벨 + /// - 1: Prologue → 1 + /// - 2: Act I → 3 + /// - 3: Act II → 22 + /// - 4: Act III → 45 + /// - 5: Act IV → 72 + /// - 6: Act V → 88 + static const List _minimumLevels = [ + 1, // index 0: unused (plotStageCount starts at 1) + 1, // Prologue (plotStageCount = 1) + 3, // Act I (plotStageCount = 2) + 22, // Act II (plotStageCount = 3) + 45, // Act III (plotStageCount = 4) + 72, // Act IV (plotStageCount = 5) + 88, // Act V (plotStageCount = 6) + ]; + + /// plotStageCount에 해당하는 최소 몬스터 레벨 반환 + static int forPlotStage(int plotStageCount) { + if (plotStageCount < 1) return 1; + if (plotStageCount >= _minimumLevels.length) { + return _minimumLevels.last; + } + return _minimumLevels[plotStageCount]; + } +} + /// 플레이어 스탯 스케일링 (player stat scaling) class PlayerScaling { PlayerScaling._();