refactor(engine): tick() 메서드 분할 (350→80 LOC)
- 8개 헬퍼 메서드로 책임 분리 - _generateNextTask() 35 LOC로 감소
This commit is contained in:
@@ -161,36 +161,107 @@ class ProgressService {
|
|||||||
|
|
||||||
/// Tick the timer loop (equivalent to Timer1Timer in the original code).
|
/// Tick the timer loop (equivalent to Timer1Timer in the original code).
|
||||||
ProgressTickResult tick(GameState state, int elapsedMillis) {
|
ProgressTickResult tick(GameState state, int elapsedMillis) {
|
||||||
// 10000ms 제한: 100x 배속 (50ms * 100 = 5000ms) + 여유 공간
|
|
||||||
// 디버그 터보 모드(100x) 지원을 위해 확장
|
|
||||||
final int clamped = elapsedMillis.clamp(0, 10000).toInt();
|
final int clamped = elapsedMillis.clamp(0, 10000).toInt();
|
||||||
var progress = state.progress;
|
|
||||||
var queue = state.queue;
|
// 1. 스킬 시스템 업데이트 (시간, 버프, MP 회복)
|
||||||
var nextState = state;
|
var nextState = _updateSkillSystem(state, clamped);
|
||||||
|
var progress = nextState.progress;
|
||||||
|
var queue = nextState.queue;
|
||||||
|
|
||||||
|
// 2. 태스크 바 진행 중이면 전투 틱 처리
|
||||||
|
if (progress.task.position < progress.task.max) {
|
||||||
|
return _processTaskInProgress(nextState, clamped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 태스크 완료 처리
|
||||||
|
final gain = progress.currentTask.type == TaskType.kill;
|
||||||
|
final incrementSeconds = progress.task.max ~/ 1000;
|
||||||
|
final int monsterExpReward =
|
||||||
|
progress.currentCombat?.monsterStats.expReward ?? 0;
|
||||||
var leveledUp = false;
|
var leveledUp = false;
|
||||||
var questDone = false;
|
var questDone = false;
|
||||||
var actDone = false;
|
var actDone = false;
|
||||||
var gameComplete = false;
|
var gameComplete = false;
|
||||||
|
|
||||||
// 스킬 시스템 시간 업데이트 (Phase 3)
|
// 4. 킬 태스크 완료 처리
|
||||||
|
if (gain) {
|
||||||
|
final killResult = _handleKillTaskCompletion(nextState, progress, queue);
|
||||||
|
if (killResult.earlyReturn != null) return killResult.earlyReturn!;
|
||||||
|
nextState = killResult.state;
|
||||||
|
progress = killResult.progress;
|
||||||
|
queue = killResult.queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 시장/판매/구매 태스크 완료 처리
|
||||||
|
final marketResult = _handleMarketTaskCompletion(nextState, progress, queue);
|
||||||
|
if (marketResult.earlyReturn != null) return marketResult.earlyReturn!;
|
||||||
|
nextState = marketResult.state;
|
||||||
|
progress = marketResult.progress;
|
||||||
|
queue = marketResult.queue;
|
||||||
|
|
||||||
|
// 6. 경험치/레벨업 처리
|
||||||
|
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
||||||
|
final expResult = _handleExpGain(nextState, progress, monsterExpReward);
|
||||||
|
nextState = expResult.state;
|
||||||
|
progress = expResult.progress;
|
||||||
|
leveledUp = expResult.leveledUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 퀘스트 진행 처리
|
||||||
|
final questResult = _handleQuestProgress(
|
||||||
|
nextState, progress, queue, gain, incrementSeconds,
|
||||||
|
);
|
||||||
|
nextState = questResult.state;
|
||||||
|
progress = questResult.progress;
|
||||||
|
queue = questResult.queue;
|
||||||
|
questDone = questResult.completed;
|
||||||
|
|
||||||
|
// 8. 플롯 진행 및 Act Boss 소환 처리
|
||||||
|
progress = _handlePlotProgress(
|
||||||
|
nextState, progress, gain, incrementSeconds,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 9. 다음 태스크 디큐/생성
|
||||||
|
final dequeueResult = _handleTaskDequeue(nextState, progress, queue);
|
||||||
|
nextState = dequeueResult.state;
|
||||||
|
progress = dequeueResult.progress;
|
||||||
|
queue = dequeueResult.queue;
|
||||||
|
actDone = dequeueResult.actDone;
|
||||||
|
gameComplete = dequeueResult.gameComplete;
|
||||||
|
|
||||||
|
nextState = _recalculateEncumbrance(
|
||||||
|
nextState.copyWith(progress: progress, queue: queue),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ProgressTickResult(
|
||||||
|
state: nextState,
|
||||||
|
leveledUp: leveledUp,
|
||||||
|
completedQuest: questDone,
|
||||||
|
completedAct: actDone,
|
||||||
|
gameComplete: gameComplete,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 스킬 시스템 업데이트 (시간, 버프 정리, MP 회복)
|
||||||
|
GameState _updateSkillSystem(GameState state, int elapsedMs) {
|
||||||
final skillService = SkillService(rng: state.rng);
|
final skillService = SkillService(rng: state.rng);
|
||||||
var skillSystem = skillService.updateElapsedTime(
|
var skillSystem = skillService.updateElapsedTime(
|
||||||
state.skillSystem,
|
state.skillSystem,
|
||||||
clamped,
|
elapsedMs,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 만료된 버프 정리
|
|
||||||
skillSystem = skillService.cleanupExpiredBuffs(skillSystem);
|
skillSystem = skillService.cleanupExpiredBuffs(skillSystem);
|
||||||
|
|
||||||
|
var nextState = state.copyWith(skillSystem: skillSystem);
|
||||||
|
|
||||||
// 비전투 시 MP 회복
|
// 비전투 시 MP 회복
|
||||||
final isInCombat =
|
final isInCombat =
|
||||||
progress.currentTask.type == TaskType.kill &&
|
state.progress.currentTask.type == TaskType.kill &&
|
||||||
progress.currentCombat != null &&
|
state.progress.currentCombat != null &&
|
||||||
progress.currentCombat!.isActive;
|
state.progress.currentCombat!.isActive;
|
||||||
|
|
||||||
if (!isInCombat && nextState.stats.mp < nextState.stats.mpMax) {
|
if (!isInCombat && nextState.stats.mp < nextState.stats.mpMax) {
|
||||||
final mpRegen = skillService.calculateMpRegen(
|
final mpRegen = skillService.calculateMpRegen(
|
||||||
elapsedMs: clamped,
|
elapsedMs: elapsedMs,
|
||||||
isInCombat: false,
|
isInCombat: false,
|
||||||
wis: nextState.stats.wis,
|
wis: nextState.stats.wis,
|
||||||
);
|
);
|
||||||
@@ -205,232 +276,271 @@ class ProgressService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextState = nextState.copyWith(skillSystem: skillSystem);
|
return nextState;
|
||||||
|
}
|
||||||
|
|
||||||
// Advance task bar if still running.
|
/// 태스크 진행 중 처리 (전투 틱 포함)
|
||||||
if (progress.task.position < progress.task.max) {
|
ProgressTickResult _processTaskInProgress(GameState state, int elapsedMs) {
|
||||||
final uncapped = progress.task.position + clamped;
|
var progress = state.progress;
|
||||||
final int newTaskPos = uncapped > progress.task.max
|
final uncapped = progress.task.position + elapsedMs;
|
||||||
? progress.task.max
|
final int newTaskPos = uncapped > progress.task.max
|
||||||
: uncapped;
|
? progress.task.max
|
||||||
|
: uncapped;
|
||||||
|
|
||||||
// 킬 태스크 중 전투 진행 (CombatTickService 사용)
|
var updatedCombat = progress.currentCombat;
|
||||||
var updatedCombat = progress.currentCombat;
|
var updatedSkillSystem = state.skillSystem;
|
||||||
var updatedSkillSystem = nextState.skillSystem;
|
var updatedPotionInventory = state.potionInventory;
|
||||||
var updatedPotionInventory = nextState.potionInventory;
|
var nextState = state;
|
||||||
if (progress.currentTask.type == TaskType.kill &&
|
|
||||||
updatedCombat != null &&
|
|
||||||
updatedCombat.isActive) {
|
|
||||||
final combatTickService = CombatTickService(rng: nextState.rng);
|
|
||||||
final combatResult = combatTickService.processTick(
|
|
||||||
state: nextState,
|
|
||||||
combat: updatedCombat,
|
|
||||||
skillSystem: updatedSkillSystem,
|
|
||||||
elapsedMs: clamped,
|
|
||||||
);
|
|
||||||
updatedCombat = combatResult.combat;
|
|
||||||
updatedSkillSystem = combatResult.skillSystem;
|
|
||||||
if (combatResult.potionInventory != null) {
|
|
||||||
updatedPotionInventory = combatResult.potionInventory!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 4: 플레이어 사망 체크
|
// 킬 태스크 중 전투 진행
|
||||||
if (!updatedCombat.playerStats.isAlive) {
|
if (progress.currentTask.type == TaskType.kill &&
|
||||||
final monsterName = updatedCombat.monsterStats.name;
|
updatedCombat != null &&
|
||||||
nextState = _processPlayerDeath(
|
updatedCombat.isActive) {
|
||||||
nextState,
|
final combatTickService = CombatTickService(rng: state.rng);
|
||||||
killerName: monsterName,
|
final combatResult = combatTickService.processTick(
|
||||||
cause: DeathCause.monster,
|
state: state,
|
||||||
);
|
combat: updatedCombat,
|
||||||
return ProgressTickResult(state: nextState, playerDied: true);
|
skillSystem: updatedSkillSystem,
|
||||||
}
|
elapsedMs: elapsedMs,
|
||||||
|
);
|
||||||
|
updatedCombat = combatResult.combat;
|
||||||
|
updatedSkillSystem = combatResult.skillSystem;
|
||||||
|
if (combatResult.potionInventory != null) {
|
||||||
|
updatedPotionInventory = combatResult.potionInventory!;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress = progress.copyWith(
|
// 플레이어 사망 체크
|
||||||
task: progress.task.copyWith(position: newTaskPos),
|
if (!updatedCombat.playerStats.isAlive) {
|
||||||
currentCombat: updatedCombat,
|
final monsterName = updatedCombat.monsterStats.name;
|
||||||
);
|
nextState = _processPlayerDeath(
|
||||||
nextState = _recalculateEncumbrance(
|
state,
|
||||||
nextState.copyWith(
|
killerName: monsterName,
|
||||||
progress: progress,
|
cause: DeathCause.monster,
|
||||||
skillSystem: updatedSkillSystem,
|
);
|
||||||
potionInventory: updatedPotionInventory,
|
return ProgressTickResult(state: nextState, playerDied: true);
|
||||||
),
|
}
|
||||||
);
|
|
||||||
return ProgressTickResult(state: nextState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final gain = progress.currentTask.type == TaskType.kill;
|
progress = progress.copyWith(
|
||||||
final incrementSeconds = progress.task.max ~/ 1000;
|
task: progress.task.copyWith(position: newTaskPos),
|
||||||
|
currentCombat: updatedCombat,
|
||||||
// 몬스터 경험치 미리 저장 (currentCombat이 null되기 전)
|
);
|
||||||
final int monsterExpReward =
|
nextState = _recalculateEncumbrance(
|
||||||
progress.currentCombat?.monsterStats.expReward ?? 0;
|
state.copyWith(
|
||||||
|
|
||||||
// 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
|
|
||||||
if (gain) {
|
|
||||||
// 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복
|
|
||||||
final combat = progress.currentCombat;
|
|
||||||
if (combat != null && combat.isActive) {
|
|
||||||
// 전투 중 남은 HP
|
|
||||||
final remainingHp = combat.playerStats.hpCurrent;
|
|
||||||
final maxHp = combat.playerStats.hpMax;
|
|
||||||
|
|
||||||
// 전투 승리 시 HP 회복 (50% + CON/2 + 클래스 패시브)
|
|
||||||
// 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
|
|
||||||
final conBonus = nextState.stats.con ~/ 2;
|
|
||||||
var healAmount = (maxHp * 0.5).round() + conBonus;
|
|
||||||
|
|
||||||
// 클래스 패시브: 전투 후 HP 회복 (예: Garbage Collector +5%)
|
|
||||||
final klass = ClassData.findById(nextState.traits.classId);
|
|
||||||
if (klass != null) {
|
|
||||||
final postCombatHealRate =
|
|
||||||
klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
|
||||||
if (postCombatHealRate > 0) {
|
|
||||||
healAmount += (maxHp * postCombatHealRate).round();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final newHp = (remainingHp + healAmount).clamp(0, maxHp);
|
|
||||||
|
|
||||||
nextState = nextState.copyWith(
|
|
||||||
stats: nextState.stats.copyWith(hpCurrent: newHp),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 전리품 획득 (원본 Main.pas:625-630)
|
|
||||||
final lootResult = _winLoot(nextState);
|
|
||||||
nextState = lootResult.state;
|
|
||||||
|
|
||||||
// 물약 드랍 시 전투 로그에 이벤트 추가
|
|
||||||
var combatForReset = progress.currentCombat;
|
|
||||||
if (lootResult.droppedPotion != null && combatForReset != null) {
|
|
||||||
final potionDropEvent = CombatEvent.potionDrop(
|
|
||||||
timestamp: nextState.skillSystem.elapsedMs,
|
|
||||||
potionName: lootResult.droppedPotion!.name,
|
|
||||||
isHp: lootResult.droppedPotion!.isHpPotion,
|
|
||||||
);
|
|
||||||
final updatedEvents = [...combatForReset.recentEvents, potionDropEvent];
|
|
||||||
combatForReset = combatForReset.copyWith(
|
|
||||||
recentEvents: updatedEvents.length > 10
|
|
||||||
? updatedEvents.sublist(updatedEvents.length - 10)
|
|
||||||
: updatedEvents,
|
|
||||||
);
|
|
||||||
progress = progress.copyWith(currentCombat: combatForReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boss 승리 처리: 시네마틱 트리거
|
|
||||||
if (progress.pendingActCompletion) {
|
|
||||||
// Act Boss를 처치했으므로 시네마틱 재생
|
|
||||||
final cinematicEntries = pq_logic.interplotCinematic(
|
|
||||||
config,
|
|
||||||
nextState.rng,
|
|
||||||
nextState.traits.level,
|
|
||||||
progress.plotStageCount,
|
|
||||||
);
|
|
||||||
queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
|
|
||||||
progress = progress.copyWith(
|
|
||||||
currentCombat: null,
|
|
||||||
monstersKilled: progress.monstersKilled + 1,
|
|
||||||
pendingActCompletion: false, // Boss 처치 완료
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 일반 전투 종료
|
|
||||||
progress = progress.copyWith(
|
|
||||||
currentCombat: null,
|
|
||||||
monstersKilled: progress.monstersKilled + 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
nextState = nextState.copyWith(
|
|
||||||
progress: progress,
|
progress: progress,
|
||||||
queue: queue,
|
skillSystem: updatedSkillSystem,
|
||||||
|
potionInventory: updatedPotionInventory,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return ProgressTickResult(state: nextState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 킬 태스크 완료 처리 (HP 회복, 전리품, 보스 처리)
|
||||||
|
({
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
ProgressTickResult? earlyReturn,
|
||||||
|
}) _handleKillTaskCompletion(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
var nextState = state;
|
||||||
|
|
||||||
|
// 전투 후 HP 회복
|
||||||
|
final combat = progress.currentCombat;
|
||||||
|
if (combat != null && combat.isActive) {
|
||||||
|
final remainingHp = combat.playerStats.hpCurrent;
|
||||||
|
final maxHp = combat.playerStats.hpMax;
|
||||||
|
final conBonus = nextState.stats.con ~/ 2;
|
||||||
|
var healAmount = (maxHp * 0.5).round() + conBonus;
|
||||||
|
|
||||||
|
final klass = ClassData.findById(nextState.traits.classId);
|
||||||
|
if (klass != null) {
|
||||||
|
final postCombatHealRate =
|
||||||
|
klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
||||||
|
if (postCombatHealRate > 0) {
|
||||||
|
healAmount += (maxHp * postCombatHealRate).round();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final newHp = (remainingHp + healAmount).clamp(0, maxHp);
|
||||||
|
nextState = nextState.copyWith(
|
||||||
|
stats: nextState.stats.copyWith(hpCurrent: newHp),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 최종 보스 처치 체크
|
// 전리품 획득
|
||||||
if (progress.finalBossState == FinalBossState.fighting) {
|
final lootResult = _winLoot(nextState);
|
||||||
// 글리치 갓 처치 완료 - 게임 클리어
|
nextState = lootResult.state;
|
||||||
progress = progress.copyWith(finalBossState: FinalBossState.defeated);
|
|
||||||
nextState = nextState.copyWith(progress: progress);
|
|
||||||
|
|
||||||
// completeAct를 호출하여 게임 완료 처리
|
// 물약 드랍 로그 추가
|
||||||
final actResult = completeAct(nextState);
|
var combatForReset = progress.currentCombat;
|
||||||
nextState = actResult.state;
|
if (lootResult.droppedPotion != null && combatForReset != null) {
|
||||||
|
final potionDropEvent = CombatEvent.potionDrop(
|
||||||
|
timestamp: nextState.skillSystem.elapsedMs,
|
||||||
|
potionName: lootResult.droppedPotion!.name,
|
||||||
|
isHp: lootResult.droppedPotion!.isHpPotion,
|
||||||
|
);
|
||||||
|
final updatedEvents = [...combatForReset.recentEvents, potionDropEvent];
|
||||||
|
combatForReset = combatForReset.copyWith(
|
||||||
|
recentEvents: updatedEvents.length > 10
|
||||||
|
? updatedEvents.sublist(updatedEvents.length - 10)
|
||||||
|
: updatedEvents,
|
||||||
|
);
|
||||||
|
progress = progress.copyWith(currentCombat: combatForReset);
|
||||||
|
}
|
||||||
|
|
||||||
return ProgressTickResult(
|
// Boss 승리 처리
|
||||||
state: nextState,
|
if (progress.pendingActCompletion) {
|
||||||
leveledUp: false,
|
final cinematicEntries = pq_logic.interplotCinematic(
|
||||||
completedQuest: false,
|
config,
|
||||||
|
nextState.rng,
|
||||||
|
nextState.traits.level,
|
||||||
|
progress.plotStageCount,
|
||||||
|
);
|
||||||
|
queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
|
||||||
|
progress = progress.copyWith(
|
||||||
|
currentCombat: null,
|
||||||
|
monstersKilled: progress.monstersKilled + 1,
|
||||||
|
pendingActCompletion: false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
progress = progress.copyWith(
|
||||||
|
currentCombat: null,
|
||||||
|
monstersKilled: progress.monstersKilled + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState = nextState.copyWith(progress: progress, queue: queue);
|
||||||
|
|
||||||
|
// 최종 보스 처치 체크
|
||||||
|
if (progress.finalBossState == FinalBossState.fighting) {
|
||||||
|
progress = progress.copyWith(finalBossState: FinalBossState.defeated);
|
||||||
|
nextState = nextState.copyWith(progress: progress);
|
||||||
|
final actResult = completeAct(nextState);
|
||||||
|
return (
|
||||||
|
state: actResult.state,
|
||||||
|
progress: actResult.state.progress,
|
||||||
|
queue: actResult.state.queue,
|
||||||
|
earlyReturn: ProgressTickResult(
|
||||||
|
state: actResult.state,
|
||||||
completedAct: true,
|
completedAct: true,
|
||||||
gameComplete: true,
|
gameComplete: true,
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 시장/판매/구매 태스크 완료 시 처리 (MarketService 사용)
|
return (
|
||||||
final marketService = MarketService(rng: nextState.rng);
|
state: nextState,
|
||||||
|
progress: progress,
|
||||||
|
queue: queue,
|
||||||
|
earlyReturn: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 시장/판매/구매 태스크 완료 처리
|
||||||
|
({
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
ProgressTickResult? earlyReturn,
|
||||||
|
}) _handleMarketTaskCompletion(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
var nextState = state;
|
||||||
|
final marketService = MarketService(rng: state.rng);
|
||||||
final taskType = progress.currentTask.type;
|
final taskType = progress.currentTask.type;
|
||||||
|
|
||||||
if (taskType == TaskType.buying) {
|
if (taskType == TaskType.buying) {
|
||||||
// 장비 구매 완료 (원본 631-634)
|
|
||||||
nextState = marketService.completeBuying(nextState);
|
nextState = marketService.completeBuying(nextState);
|
||||||
progress = nextState.progress;
|
progress = nextState.progress;
|
||||||
} else if (taskType == TaskType.market || taskType == TaskType.sell) {
|
} else if (taskType == TaskType.market || taskType == TaskType.sell) {
|
||||||
// 시장 도착 또는 판매 완료 (원본 635-649)
|
|
||||||
final sellResult = marketService.processSell(nextState);
|
final sellResult = marketService.processSell(nextState);
|
||||||
nextState = sellResult.state;
|
nextState = sellResult.state;
|
||||||
progress = nextState.progress;
|
progress = nextState.progress;
|
||||||
queue = nextState.queue;
|
queue = nextState.queue;
|
||||||
|
|
||||||
// 판매 중이면 다른 로직 건너뛰기
|
|
||||||
if (sellResult.continuesSelling) {
|
if (sellResult.continuesSelling) {
|
||||||
nextState = _recalculateEncumbrance(
|
nextState = _recalculateEncumbrance(
|
||||||
nextState.copyWith(progress: progress, queue: queue),
|
nextState.copyWith(progress: progress, queue: queue),
|
||||||
);
|
);
|
||||||
return ProgressTickResult(
|
return (
|
||||||
state: nextState,
|
state: nextState,
|
||||||
leveledUp: false,
|
progress: progress,
|
||||||
completedQuest: false,
|
queue: queue,
|
||||||
completedAct: false,
|
earlyReturn: ProgressTickResult(state: nextState),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gain XP / level up (몬스터 경험치 기반)
|
return (
|
||||||
// 최대 레벨(100) 제한: 100레벨에서는 더 이상 레벨업하지 않음
|
state: nextState,
|
||||||
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
progress: progress,
|
||||||
// 종족 경험치 배율 적용 (예: Byte Human +5%, Callback Seraph +3%)
|
queue: queue,
|
||||||
final race = RaceData.findById(nextState.traits.raceId);
|
earlyReturn: null,
|
||||||
final expMultiplier = race?.expMultiplier ?? 1.0;
|
);
|
||||||
final adjustedExp = (monsterExpReward * expMultiplier).round();
|
}
|
||||||
final newExpPos = progress.exp.position + adjustedExp;
|
|
||||||
|
|
||||||
// 레벨업 체크 (경험치가 필요량 이상일 때)
|
/// 경험치 획득 및 레벨업 처리
|
||||||
if (newExpPos >= progress.exp.max) {
|
({GameState state, ProgressState progress, bool leveledUp}) _handleExpGain(
|
||||||
// 초과 경험치 계산
|
GameState state,
|
||||||
final overflowExp = newExpPos - progress.exp.max;
|
ProgressState progress,
|
||||||
nextState = _levelUp(nextState);
|
int monsterExpReward,
|
||||||
leveledUp = true;
|
) {
|
||||||
progress = nextState.progress;
|
var nextState = state;
|
||||||
|
var leveledUp = false;
|
||||||
|
|
||||||
// 초과 경험치를 다음 레벨에 적용
|
final race = RaceData.findById(nextState.traits.raceId);
|
||||||
if (overflowExp > 0 && nextState.traits.level < 100) {
|
final expMultiplier = race?.expMultiplier ?? 1.0;
|
||||||
progress = progress.copyWith(
|
final adjustedExp = (monsterExpReward * expMultiplier).round();
|
||||||
exp: progress.exp.copyWith(position: overflowExp),
|
final newExpPos = progress.exp.position + adjustedExp;
|
||||||
);
|
|
||||||
}
|
if (newExpPos >= progress.exp.max) {
|
||||||
} else {
|
final overflowExp = newExpPos - progress.exp.max;
|
||||||
|
nextState = _levelUp(nextState);
|
||||||
|
leveledUp = true;
|
||||||
|
progress = nextState.progress;
|
||||||
|
|
||||||
|
if (overflowExp > 0 && nextState.traits.level < 100) {
|
||||||
progress = progress.copyWith(
|
progress = progress.copyWith(
|
||||||
exp: progress.exp.copyWith(position: newExpPos),
|
exp: progress.exp.copyWith(position: overflowExp),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
progress = progress.copyWith(
|
||||||
|
exp: progress.exp.copyWith(position: newExpPos),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance quest bar after Act I.
|
return (state: nextState, progress: progress, leveledUp: leveledUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 퀘스트 진행 처리
|
||||||
|
({
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
bool completed,
|
||||||
|
}) _handleQuestProgress(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
bool gain,
|
||||||
|
int incrementSeconds,
|
||||||
|
) {
|
||||||
|
var nextState = state;
|
||||||
|
var questDone = false;
|
||||||
|
|
||||||
final canQuestProgress =
|
final canQuestProgress =
|
||||||
gain &&
|
gain &&
|
||||||
progress.plotStageCount > 1 &&
|
progress.plotStageCount > 1 &&
|
||||||
progress.questCount > 0 &&
|
progress.questCount > 0 &&
|
||||||
progress.quest.max > 0;
|
progress.quest.max > 0;
|
||||||
|
|
||||||
if (canQuestProgress) {
|
if (canQuestProgress) {
|
||||||
if (progress.quest.position + incrementSeconds >= progress.quest.max) {
|
if (progress.quest.position + incrementSeconds >= progress.quest.max) {
|
||||||
nextState = completeQuest(nextState);
|
nextState = completeQuest(nextState);
|
||||||
@@ -446,19 +556,31 @@ class ProgressService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플롯(plot) 바가 완료되면 Act Boss 소환
|
return (
|
||||||
// (개선: Boss 처치 → 시네마틱 → Act 전환 순서)
|
state: nextState,
|
||||||
|
progress: progress,
|
||||||
|
queue: queue,
|
||||||
|
completed: questDone,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 플롯 진행 및 Act Boss 소환 처리
|
||||||
|
ProgressState _handlePlotProgress(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
bool gain,
|
||||||
|
int incrementSeconds,
|
||||||
|
) {
|
||||||
if (gain &&
|
if (gain &&
|
||||||
progress.plot.max > 0 &&
|
progress.plot.max > 0 &&
|
||||||
progress.plot.position >= progress.plot.max &&
|
progress.plot.position >= progress.plot.max &&
|
||||||
!progress.pendingActCompletion) {
|
!progress.pendingActCompletion) {
|
||||||
// Act Boss 소환 및 플래그 설정
|
|
||||||
final actProgressionService = ActProgressionService(config: config);
|
final actProgressionService = ActProgressionService(config: config);
|
||||||
final actBoss = actProgressionService.createActBoss(nextState);
|
final actBoss = actProgressionService.createActBoss(state);
|
||||||
progress = progress.copyWith(
|
return progress.copyWith(
|
||||||
plot: progress.plot.copyWith(position: 0), // Plot bar 리셋
|
plot: progress.plot.copyWith(position: 0),
|
||||||
currentCombat: actBoss,
|
currentCombat: actBoss,
|
||||||
pendingActCompletion: true, // Boss 처치 대기 플래그
|
pendingActCompletion: true,
|
||||||
);
|
);
|
||||||
} else if (progress.currentTask.type != TaskType.load &&
|
} else if (progress.currentTask.type != TaskType.load &&
|
||||||
progress.plot.max > 0 &&
|
progress.plot.max > 0 &&
|
||||||
@@ -467,12 +589,29 @@ class ProgressService {
|
|||||||
final int newPlotPos = uncappedPlot > progress.plot.max
|
final int newPlotPos = uncappedPlot > progress.plot.max
|
||||||
? progress.plot.max
|
? progress.plot.max
|
||||||
: uncappedPlot;
|
: uncappedPlot;
|
||||||
progress = progress.copyWith(
|
return progress.copyWith(
|
||||||
plot: progress.plot.copyWith(position: newPlotPos),
|
plot: progress.plot.copyWith(position: newPlotPos),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 태스크 디큐 및 생성 처리
|
||||||
|
({
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
bool actDone,
|
||||||
|
bool gameComplete,
|
||||||
|
}) _handleTaskDequeue(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
var nextState = state;
|
||||||
|
var actDone = false;
|
||||||
|
var gameComplete = false;
|
||||||
|
|
||||||
// Dequeue next scripted task if available.
|
|
||||||
final dq = pq_logic.dequeue(progress, queue);
|
final dq = pq_logic.dequeue(progress, queue);
|
||||||
if (dq != null) {
|
if (dq != null) {
|
||||||
progress = dq.progress.copyWith(
|
progress = dq.progress.copyWith(
|
||||||
@@ -480,7 +619,6 @@ class ProgressService {
|
|||||||
);
|
);
|
||||||
queue = dq.queue;
|
queue = dq.queue;
|
||||||
|
|
||||||
// plot 타입이 dequeue 되면 completeAct 실행 (원본 Main.pas 로직)
|
|
||||||
if (dq.kind == QueueKind.plot) {
|
if (dq.kind == QueueKind.plot) {
|
||||||
nextState = nextState.copyWith(progress: progress, queue: queue);
|
nextState = nextState.copyWith(progress: progress, queue: queue);
|
||||||
final actResult = completeAct(nextState);
|
final actResult = completeAct(nextState);
|
||||||
@@ -491,22 +629,17 @@ class ProgressService {
|
|||||||
queue = nextState.queue;
|
queue = nextState.queue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 큐가 비어있으면 새 태스크 생성 (원본 Dequeue 667-684줄)
|
|
||||||
nextState = nextState.copyWith(progress: progress, queue: queue);
|
nextState = nextState.copyWith(progress: progress, queue: queue);
|
||||||
final newTaskResult = _generateNextTask(nextState);
|
final newTaskResult = _generateNextTask(nextState);
|
||||||
progress = newTaskResult.progress;
|
progress = newTaskResult.progress;
|
||||||
queue = newTaskResult.queue;
|
queue = newTaskResult.queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextState = _recalculateEncumbrance(
|
return (
|
||||||
nextState.copyWith(progress: progress, queue: queue),
|
|
||||||
);
|
|
||||||
|
|
||||||
return ProgressTickResult(
|
|
||||||
state: nextState,
|
state: nextState,
|
||||||
leveledUp: leveledUp,
|
progress: progress,
|
||||||
completedQuest: questDone,
|
queue: queue,
|
||||||
completedAct: actDone,
|
actDone: actDone,
|
||||||
gameComplete: gameComplete,
|
gameComplete: gameComplete,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -519,120 +652,155 @@ class ProgressService {
|
|||||||
final queue = state.queue;
|
final queue = state.queue;
|
||||||
final oldTaskType = progress.currentTask.type;
|
final oldTaskType = progress.currentTask.type;
|
||||||
|
|
||||||
// 1. Encumbrance가 가득 찼으면 시장으로 이동 (원본 667-669줄)
|
// 1. Encumbrance 초과 시 시장 이동
|
||||||
if (progress.encumbrance.position >= progress.encumbrance.max &&
|
if (_shouldGoToMarket(progress)) {
|
||||||
progress.encumbrance.max > 0) {
|
return _createMarketTask(progress, queue);
|
||||||
final taskResult = pq_logic.startTask(
|
|
||||||
progress,
|
|
||||||
l10n.taskHeadingToMarket(),
|
|
||||||
4 * 1000,
|
|
||||||
);
|
|
||||||
progress = taskResult.progress.copyWith(
|
|
||||||
currentTask: TaskInfo(
|
|
||||||
caption: taskResult.caption,
|
|
||||||
type: TaskType.market,
|
|
||||||
),
|
|
||||||
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화
|
|
||||||
);
|
|
||||||
return (progress: progress, queue: queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. kill/heading/buying 태스크가 아니었으면 heading 또는 buying 태스크 실행
|
// 2. 전환 태스크 (buying/heading)
|
||||||
// (원본 670-677줄) - buying 완료 후 무한 루프 방지
|
if (_needsTransitionTask(oldTaskType)) {
|
||||||
if (oldTaskType != TaskType.kill &&
|
return _createTransitionTask(state, progress, queue);
|
||||||
oldTaskType != TaskType.neutral &&
|
|
||||||
oldTaskType != TaskType.buying) {
|
|
||||||
// Gold가 충분하면 장비 구매 (Common 장비 가격 기준)
|
|
||||||
// 실제 구매 가격과 동일한 공식 사용: level * 50
|
|
||||||
final gold = state.inventory.gold;
|
|
||||||
final equipPrice = state.traits.level * 50; // Common 장비 1개 가격
|
|
||||||
if (gold > equipPrice) {
|
|
||||||
final taskResult = pq_logic.startTask(
|
|
||||||
progress,
|
|
||||||
l10n.taskUpgradingHardware(),
|
|
||||||
5 * 1000,
|
|
||||||
);
|
|
||||||
progress = taskResult.progress.copyWith(
|
|
||||||
currentTask: TaskInfo(
|
|
||||||
caption: taskResult.caption,
|
|
||||||
type: TaskType.buying,
|
|
||||||
),
|
|
||||||
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화
|
|
||||||
);
|
|
||||||
return (progress: progress, queue: queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gold가 부족하면 전장으로 이동 (원본 674-676줄)
|
|
||||||
final taskResult = pq_logic.startTask(
|
|
||||||
progress,
|
|
||||||
l10n.taskEnteringDebugZone(),
|
|
||||||
4 * 1000,
|
|
||||||
);
|
|
||||||
progress = taskResult.progress.copyWith(
|
|
||||||
currentTask: TaskInfo(
|
|
||||||
caption: taskResult.caption,
|
|
||||||
type: TaskType.neutral,
|
|
||||||
),
|
|
||||||
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화
|
|
||||||
);
|
|
||||||
return (progress: progress, queue: queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Act Boss 리트라이 체크
|
// 3. Act Boss 리트라이
|
||||||
// pendingActCompletion이 true면 Act Boss 재소환
|
|
||||||
if (state.progress.pendingActCompletion) {
|
if (state.progress.pendingActCompletion) {
|
||||||
final actProgressionService = ActProgressionService(config: config);
|
return _createActBossRetryTask(state, progress, queue);
|
||||||
final actBoss = actProgressionService.createActBoss(state);
|
}
|
||||||
final combatCalculator = CombatCalculator(rng: state.rng);
|
|
||||||
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
|
||||||
player: actBoss.playerStats,
|
|
||||||
monster: actBoss.monsterStats,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// 4. 최종 보스 전투
|
||||||
|
if (state.progress.finalBossState == FinalBossState.fighting &&
|
||||||
|
!state.progress.isInBossLevelingMode) {
|
||||||
|
if (state.progress.bossLevelingEndTime != null) {
|
||||||
|
progress = progress.copyWith(clearBossLevelingEndTime: true);
|
||||||
|
}
|
||||||
|
final actProgressionService = ActProgressionService(config: config);
|
||||||
|
return actProgressionService.startFinalBossFight(state, progress, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 일반 몬스터 전투
|
||||||
|
return _createMonsterTask(state, progress, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 시장 이동 조건 확인
|
||||||
|
bool _shouldGoToMarket(ProgressState progress) {
|
||||||
|
return progress.encumbrance.position >= progress.encumbrance.max &&
|
||||||
|
progress.encumbrance.max > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 전환 태스크 필요 여부 확인
|
||||||
|
bool _needsTransitionTask(TaskType oldTaskType) {
|
||||||
|
return oldTaskType != TaskType.kill &&
|
||||||
|
oldTaskType != TaskType.neutral &&
|
||||||
|
oldTaskType != TaskType.buying;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 시장 이동 태스크 생성
|
||||||
|
({ProgressState progress, QueueState queue}) _createMarketTask(
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
final taskResult = pq_logic.startTask(
|
||||||
|
progress,
|
||||||
|
l10n.taskHeadingToMarket(),
|
||||||
|
4 * 1000,
|
||||||
|
);
|
||||||
|
final updatedProgress = taskResult.progress.copyWith(
|
||||||
|
currentTask: TaskInfo(
|
||||||
|
caption: taskResult.caption,
|
||||||
|
type: TaskType.market,
|
||||||
|
),
|
||||||
|
currentCombat: null,
|
||||||
|
);
|
||||||
|
return (progress: updatedProgress, queue: queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 전환 태스크 생성 (buying 또는 heading)
|
||||||
|
({ProgressState progress, QueueState queue}) _createTransitionTask(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
final gold = state.inventory.gold;
|
||||||
|
final equipPrice = state.traits.level * 50;
|
||||||
|
|
||||||
|
// Gold 충분 시 장비 구매
|
||||||
|
if (gold > equipPrice) {
|
||||||
final taskResult = pq_logic.startTask(
|
final taskResult = pq_logic.startTask(
|
||||||
progress,
|
progress,
|
||||||
l10n.taskDebugging(actBoss.monsterStats.name),
|
l10n.taskUpgradingHardware(),
|
||||||
durationMillis,
|
5 * 1000,
|
||||||
);
|
);
|
||||||
|
final updatedProgress = taskResult.progress.copyWith(
|
||||||
progress = taskResult.progress.copyWith(
|
|
||||||
currentTask: TaskInfo(
|
currentTask: TaskInfo(
|
||||||
caption: taskResult.caption,
|
caption: taskResult.caption,
|
||||||
type: TaskType.kill,
|
type: TaskType.buying,
|
||||||
monsterBaseName: actBoss.monsterStats.name,
|
|
||||||
monsterPart: '*', // Boss는 WinItem 드랍
|
|
||||||
monsterLevel: actBoss.monsterStats.level,
|
|
||||||
monsterGrade: MonsterGrade.boss,
|
|
||||||
monsterSize: getBossSizeForAct(state.progress.plotStageCount),
|
|
||||||
),
|
),
|
||||||
currentCombat: actBoss,
|
currentCombat: null,
|
||||||
);
|
);
|
||||||
|
return (progress: updatedProgress, queue: queue);
|
||||||
return (progress: progress, queue: queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 최종 보스 전투 체크
|
// Gold 부족 시 전장 이동
|
||||||
// finalBossState == fighting이면 Glitch God 스폰
|
final taskResult = pq_logic.startTask(
|
||||||
// 단, 레벨링 모드 중이면 일반 몬스터로 레벨업 후 재도전
|
progress,
|
||||||
if (state.progress.finalBossState == FinalBossState.fighting) {
|
l10n.taskEnteringDebugZone(),
|
||||||
if (state.progress.isInBossLevelingMode) {
|
4 * 1000,
|
||||||
// 레벨링 모드: 일반 몬스터 전투로 대체 (아래 MonsterTask로 진행)
|
);
|
||||||
} else {
|
final updatedProgress = taskResult.progress.copyWith(
|
||||||
// 레벨링 모드 종료 또는 첫 도전: 보스전 시작
|
currentTask: TaskInfo(
|
||||||
// 레벨링 모드가 끝났으면 타이머 초기화
|
caption: taskResult.caption,
|
||||||
if (state.progress.bossLevelingEndTime != null) {
|
type: TaskType.neutral,
|
||||||
progress = progress.copyWith(clearBossLevelingEndTime: true);
|
),
|
||||||
}
|
currentCombat: null,
|
||||||
final actProgressionService = ActProgressionService(config: config);
|
);
|
||||||
return actProgressionService.startFinalBossFight(state, progress, queue);
|
return (progress: updatedProgress, queue: queue);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 5. MonsterTask 실행 (원본 678-684줄)
|
/// Act Boss 재도전 태스크 생성
|
||||||
|
({ProgressState progress, QueueState queue}) _createActBossRetryTask(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
|
final actProgressionService = ActProgressionService(config: config);
|
||||||
|
final actBoss = actProgressionService.createActBoss(state);
|
||||||
|
final combatCalculator = CombatCalculator(rng: state.rng);
|
||||||
|
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
||||||
|
player: actBoss.playerStats,
|
||||||
|
monster: actBoss.monsterStats,
|
||||||
|
);
|
||||||
|
|
||||||
|
final taskResult = pq_logic.startTask(
|
||||||
|
progress,
|
||||||
|
l10n.taskDebugging(actBoss.monsterStats.name),
|
||||||
|
durationMillis,
|
||||||
|
);
|
||||||
|
|
||||||
|
final updatedProgress = taskResult.progress.copyWith(
|
||||||
|
currentTask: TaskInfo(
|
||||||
|
caption: taskResult.caption,
|
||||||
|
type: TaskType.kill,
|
||||||
|
monsterBaseName: actBoss.monsterStats.name,
|
||||||
|
monsterPart: '*',
|
||||||
|
monsterLevel: actBoss.monsterStats.level,
|
||||||
|
monsterGrade: MonsterGrade.boss,
|
||||||
|
monsterSize: getBossSizeForAct(state.progress.plotStageCount),
|
||||||
|
),
|
||||||
|
currentCombat: actBoss,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (progress: updatedProgress, queue: queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 일반 몬스터 전투 태스크 생성
|
||||||
|
({ProgressState progress, QueueState queue}) _createMonsterTask(
|
||||||
|
GameState state,
|
||||||
|
ProgressState progress,
|
||||||
|
QueueState queue,
|
||||||
|
) {
|
||||||
final level = state.traits.level;
|
final level = state.traits.level;
|
||||||
|
|
||||||
// 원본 Main.pas:548-551: 25% 확률로 Quest Monster 사용
|
// 퀘스트 몬스터 데이터 확인
|
||||||
// fQuest.Caption이 비어있지 않으면 해당 몬스터 데이터 전달
|
|
||||||
final questMonster = state.progress.currentQuestMonster;
|
final questMonster = state.progress.currentQuestMonster;
|
||||||
final questMonsterData = questMonster?.monsterData;
|
final questMonsterData = questMonster?.monsterData;
|
||||||
final questLevel = questMonsterData != null
|
final questLevel = questMonsterData != null
|
||||||
@@ -640,6 +808,7 @@ class ProgressService {
|
|||||||
0
|
0
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// 몬스터 생성
|
||||||
final monsterResult = pq_logic.monsterTask(
|
final monsterResult = pq_logic.monsterTask(
|
||||||
config,
|
config,
|
||||||
state.rng,
|
state.rng,
|
||||||
@@ -648,8 +817,7 @@ class ProgressService {
|
|||||||
questLevel,
|
questLevel,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 전투용 몬스터 레벨 조정 (밸런스)
|
// 몬스터 레벨 조정 (밸런스)
|
||||||
// Act별 최소 레벨과 플레이어 레벨 중 큰 값을 기준으로 ±3 범위 제한
|
|
||||||
final actMinLevel = ActMonsterLevel.forPlotStage(
|
final actMinLevel = ActMonsterLevel.forPlotStage(
|
||||||
state.progress.plotStageCount,
|
state.progress.plotStageCount,
|
||||||
);
|
);
|
||||||
@@ -658,7 +826,7 @@ class ProgressService {
|
|||||||
.clamp(math.max(1, baseLevel - 3), baseLevel + 3)
|
.clamp(math.max(1, baseLevel - 3), baseLevel + 3)
|
||||||
.toInt();
|
.toInt();
|
||||||
|
|
||||||
// 전투 스탯 생성 (Phase 12: 몬스터 레벨 기반 페널티 적용)
|
// 전투 스탯 생성
|
||||||
final playerCombatStats = CombatStats.fromStats(
|
final playerCombatStats = CombatStats.fromStats(
|
||||||
stats: state.stats,
|
stats: state.stats,
|
||||||
equipment: state.equipment,
|
equipment: state.equipment,
|
||||||
@@ -673,13 +841,12 @@ class ProgressService {
|
|||||||
plotStageCount: state.progress.plotStageCount,
|
plotStageCount: state.progress.plotStageCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 전투 상태 초기화
|
// 전투 상태 및 지속시간
|
||||||
final combatState = CombatState.start(
|
final combatState = CombatState.start(
|
||||||
playerStats: playerCombatStats,
|
playerStats: playerCombatStats,
|
||||||
monsterStats: monsterCombatStats,
|
monsterStats: monsterCombatStats,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 태스크 지속시간 계산 (CombatCalculator 기반)
|
|
||||||
final combatCalculator = CombatCalculator(rng: state.rng);
|
final combatCalculator = CombatCalculator(rng: state.rng);
|
||||||
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
||||||
player: playerCombatStats,
|
player: playerCombatStats,
|
||||||
@@ -692,14 +859,14 @@ class ProgressService {
|
|||||||
durationMillis,
|
durationMillis,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 몬스터 사이즈 결정 (Act 기반, Phase 13)
|
// 몬스터 사이즈 결정
|
||||||
final monsterSize = getMonsterSizeForAct(
|
final monsterSize = getMonsterSizeForAct(
|
||||||
plotStageCount: state.progress.plotStageCount,
|
plotStageCount: state.progress.plotStageCount,
|
||||||
grade: monsterResult.grade,
|
grade: monsterResult.grade,
|
||||||
rng: state.rng,
|
rng: state.rng,
|
||||||
);
|
);
|
||||||
|
|
||||||
progress = taskResult.progress.copyWith(
|
final updatedProgress = taskResult.progress.copyWith(
|
||||||
currentTask: TaskInfo(
|
currentTask: TaskInfo(
|
||||||
caption: taskResult.caption,
|
caption: taskResult.caption,
|
||||||
type: TaskType.kill,
|
type: TaskType.kill,
|
||||||
@@ -712,7 +879,7 @@ class ProgressService {
|
|||||||
currentCombat: combatState,
|
currentCombat: combatState,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (progress: progress, queue: queue);
|
return (progress: updatedProgress, queue: queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances quest completion, applies reward, and enqueues next quest task.
|
/// Advances quest completion, applies reward, and enqueues next quest task.
|
||||||
|
|||||||
Reference in New Issue
Block a user