refactor(engine): tick() 메서드 분할 (350→80 LOC)

- 8개 헬퍼 메서드로 책임 분리
- _generateNextTask() 35 LOC로 감소
This commit is contained in:
JiWoong Sul
2026-01-21 17:34:31 +09:00
parent 742b0d1773
commit 7f44e95163

View File

@@ -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,28 +276,32 @@ 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 uncapped = progress.task.position + elapsedMs;
final int newTaskPos = uncapped > progress.task.max final int newTaskPos = uncapped > progress.task.max
? progress.task.max ? progress.task.max
: uncapped; : uncapped;
// 킬 태스크 중 전투 진행 (CombatTickService 사용)
var updatedCombat = progress.currentCombat; var updatedCombat = progress.currentCombat;
var updatedSkillSystem = nextState.skillSystem; var updatedSkillSystem = state.skillSystem;
var updatedPotionInventory = nextState.potionInventory; var updatedPotionInventory = state.potionInventory;
var nextState = state;
// 킬 태스크 중 전투 진행
if (progress.currentTask.type == TaskType.kill && if (progress.currentTask.type == TaskType.kill &&
updatedCombat != null && updatedCombat != null &&
updatedCombat.isActive) { updatedCombat.isActive) {
final combatTickService = CombatTickService(rng: nextState.rng); final combatTickService = CombatTickService(rng: state.rng);
final combatResult = combatTickService.processTick( final combatResult = combatTickService.processTick(
state: nextState, state: state,
combat: updatedCombat, combat: updatedCombat,
skillSystem: updatedSkillSystem, skillSystem: updatedSkillSystem,
elapsedMs: clamped, elapsedMs: elapsedMs,
); );
updatedCombat = combatResult.combat; updatedCombat = combatResult.combat;
updatedSkillSystem = combatResult.skillSystem; updatedSkillSystem = combatResult.skillSystem;
@@ -234,11 +309,11 @@ class ProgressService {
updatedPotionInventory = combatResult.potionInventory!; updatedPotionInventory = combatResult.potionInventory!;
} }
// Phase 4: 플레이어 사망 체크 // 플레이어 사망 체크
if (!updatedCombat.playerStats.isAlive) { if (!updatedCombat.playerStats.isAlive) {
final monsterName = updatedCombat.monsterStats.name; final monsterName = updatedCombat.monsterStats.name;
nextState = _processPlayerDeath( nextState = _processPlayerDeath(
nextState, state,
killerName: monsterName, killerName: monsterName,
cause: DeathCause.monster, cause: DeathCause.monster,
); );
@@ -251,7 +326,7 @@ class ProgressService {
currentCombat: updatedCombat, currentCombat: updatedCombat,
); );
nextState = _recalculateEncumbrance( nextState = _recalculateEncumbrance(
nextState.copyWith( state.copyWith(
progress: progress, progress: progress,
skillSystem: updatedSkillSystem, skillSystem: updatedSkillSystem,
potionInventory: updatedPotionInventory, potionInventory: updatedPotionInventory,
@@ -260,28 +335,27 @@ class ProgressService {
return ProgressTickResult(state: nextState); return ProgressTickResult(state: nextState);
} }
final gain = progress.currentTask.type == TaskType.kill; /// 킬 태스크 완료 처리 (HP 회복, 전리품, 보스 처리)
final incrementSeconds = progress.task.max ~/ 1000; ({
GameState state,
ProgressState progress,
QueueState queue,
ProgressTickResult? earlyReturn,
}) _handleKillTaskCompletion(
GameState state,
ProgressState progress,
QueueState queue,
) {
var nextState = state;
// 몬스터 경험치 미리 저장 (currentCombat이 null되기 전) // 전투 후 HP 회복
final int monsterExpReward =
progress.currentCombat?.monsterStats.expReward ?? 0;
// 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
if (gain) {
// 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복
final combat = progress.currentCombat; final combat = progress.currentCombat;
if (combat != null && combat.isActive) { if (combat != null && combat.isActive) {
// 전투 중 남은 HP
final remainingHp = combat.playerStats.hpCurrent; final remainingHp = combat.playerStats.hpCurrent;
final maxHp = combat.playerStats.hpMax; final maxHp = combat.playerStats.hpMax;
// 전투 승리 시 HP 회복 (50% + CON/2 + 클래스 패시브)
// 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
final conBonus = nextState.stats.con ~/ 2; final conBonus = nextState.stats.con ~/ 2;
var healAmount = (maxHp * 0.5).round() + conBonus; var healAmount = (maxHp * 0.5).round() + conBonus;
// 클래스 패시브: 전투 후 HP 회복 (예: Garbage Collector +5%)
final klass = ClassData.findById(nextState.traits.classId); final klass = ClassData.findById(nextState.traits.classId);
if (klass != null) { if (klass != null) {
final postCombatHealRate = final postCombatHealRate =
@@ -292,17 +366,16 @@ class ProgressService {
} }
final newHp = (remainingHp + healAmount).clamp(0, maxHp); final newHp = (remainingHp + healAmount).clamp(0, maxHp);
nextState = nextState.copyWith( nextState = nextState.copyWith(
stats: nextState.stats.copyWith(hpCurrent: newHp), stats: nextState.stats.copyWith(hpCurrent: newHp),
); );
} }
// 전리품 획득 (원본 Main.pas:625-630) // 전리품 획득
final lootResult = _winLoot(nextState); final lootResult = _winLoot(nextState);
nextState = lootResult.state; nextState = lootResult.state;
// 물약 드랍 시 전투 로그에 이벤트 추가 // 물약 드랍 로그 추가
var combatForReset = progress.currentCombat; var combatForReset = progress.currentCombat;
if (lootResult.droppedPotion != null && combatForReset != null) { if (lootResult.droppedPotion != null && combatForReset != null) {
final potionDropEvent = CombatEvent.potionDrop( final potionDropEvent = CombatEvent.potionDrop(
@@ -319,9 +392,8 @@ class ProgressService {
progress = progress.copyWith(currentCombat: combatForReset); progress = progress.copyWith(currentCombat: combatForReset);
} }
// Boss 승리 처리: 시네마틱 트리거 // Boss 승리 처리
if (progress.pendingActCompletion) { if (progress.pendingActCompletion) {
// Act Boss를 처치했으므로 시네마틱 재생
final cinematicEntries = pq_logic.interplotCinematic( final cinematicEntries = pq_logic.interplotCinematic(
config, config,
nextState.rng, nextState.rng,
@@ -332,87 +404,107 @@ class ProgressService {
progress = progress.copyWith( progress = progress.copyWith(
currentCombat: null, currentCombat: null,
monstersKilled: progress.monstersKilled + 1, monstersKilled: progress.monstersKilled + 1,
pendingActCompletion: false, // Boss 처치 완료 pendingActCompletion: false,
); );
} else { } else {
// 일반 전투 종료
progress = progress.copyWith( progress = progress.copyWith(
currentCombat: null, currentCombat: null,
monstersKilled: progress.monstersKilled + 1, monstersKilled: progress.monstersKilled + 1,
); );
} }
nextState = nextState.copyWith( nextState = nextState.copyWith(progress: progress, queue: queue);
progress: progress,
queue: queue,
);
// 최종 보스 처치 체크 // 최종 보스 처치 체크
if (progress.finalBossState == FinalBossState.fighting) { if (progress.finalBossState == FinalBossState.fighting) {
// 글리치 갓 처치 완료 - 게임 클리어
progress = progress.copyWith(finalBossState: FinalBossState.defeated); progress = progress.copyWith(finalBossState: FinalBossState.defeated);
nextState = nextState.copyWith(progress: progress); nextState = nextState.copyWith(progress: progress);
// completeAct를 호출하여 게임 완료 처리
final actResult = completeAct(nextState); final actResult = completeAct(nextState);
nextState = actResult.state; return (
state: actResult.state,
return ProgressTickResult( progress: actResult.state.progress,
state: nextState, queue: actResult.state.queue,
leveledUp: false, earlyReturn: ProgressTickResult(
completedQuest: false, state: actResult.state,
completedAct: true, completedAct: true,
gameComplete: true, gameComplete: true,
),
); );
} }
return (
state: nextState,
progress: progress,
queue: queue,
earlyReturn: null,
);
} }
// 시장/판매/구매 태스크 완료 시 처리 (MarketService 사용) /// 시장/판매/구매 태스크 완료 처리
final marketService = MarketService(rng: nextState.rng); ({
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,
earlyReturn: null,
);
}
/// 경험치 획득 및 레벨업 처리
({GameState state, ProgressState progress, bool leveledUp}) _handleExpGain(
GameState state,
ProgressState progress,
int monsterExpReward,
) {
var nextState = state;
var leveledUp = false;
final race = RaceData.findById(nextState.traits.raceId); final race = RaceData.findById(nextState.traits.raceId);
final expMultiplier = race?.expMultiplier ?? 1.0; final expMultiplier = race?.expMultiplier ?? 1.0;
final adjustedExp = (monsterExpReward * expMultiplier).round(); final adjustedExp = (monsterExpReward * expMultiplier).round();
final newExpPos = progress.exp.position + adjustedExp; final newExpPos = progress.exp.position + adjustedExp;
// 레벨업 체크 (경험치가 필요량 이상일 때)
if (newExpPos >= progress.exp.max) { if (newExpPos >= progress.exp.max) {
// 초과 경험치 계산
final overflowExp = 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;
// 초과 경험치를 다음 레벨에 적용
if (overflowExp > 0 && nextState.traits.level < 100) { if (overflowExp > 0 && nextState.traits.level < 100) {
progress = progress.copyWith( progress = progress.copyWith(
exp: progress.exp.copyWith(position: overflowExp), exp: progress.exp.copyWith(position: overflowExp),
@@ -423,14 +515,32 @@ class ProgressService {
exp: progress.exp.copyWith(position: newExpPos), exp: progress.exp.copyWith(position: newExpPos),
); );
} }
return (state: nextState, progress: progress, leveledUp: leveledUp);
} }
// Advance quest bar after Act I. /// 퀘스트 진행 처리
({
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,68 +652,116 @@ 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);
}
// 2. 전환 태스크 (buying/heading)
if (_needsTransitionTask(oldTaskType)) {
return _createTransitionTask(state, progress, queue);
}
// 3. Act Boss 리트라이
if (state.progress.pendingActCompletion) {
return _createActBossRetryTask(state, progress, queue);
}
// 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( final taskResult = pq_logic.startTask(
progress, progress,
l10n.taskHeadingToMarket(), l10n.taskHeadingToMarket(),
4 * 1000, 4 * 1000,
); );
progress = taskResult.progress.copyWith( final updatedProgress = taskResult.progress.copyWith(
currentTask: TaskInfo( currentTask: TaskInfo(
caption: taskResult.caption, caption: taskResult.caption,
type: TaskType.market, type: TaskType.market,
), ),
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화 currentCombat: null,
); );
return (progress: progress, queue: queue); return (progress: updatedProgress, queue: queue);
} }
// 2. kill/heading/buying 태스크가 아니었으면 heading 또는 buying 태스크 실행 /// 전환 태스크 생성 (buying 또는 heading)
// (원본 670-677줄) - buying 완료 후 무한 루프 방지 ({ProgressState progress, QueueState queue}) _createTransitionTask(
if (oldTaskType != TaskType.kill && GameState state,
oldTaskType != TaskType.neutral && ProgressState progress,
oldTaskType != TaskType.buying) { QueueState queue,
// Gold가 충분하면 장비 구매 (Common 장비 가격 기준) ) {
// 실제 구매 가격과 동일한 공식 사용: level * 50
final gold = state.inventory.gold; final gold = state.inventory.gold;
final equipPrice = state.traits.level * 50; // Common 장비 1개 가격 final equipPrice = state.traits.level * 50;
// Gold 충분 시 장비 구매
if (gold > equipPrice) { if (gold > equipPrice) {
final taskResult = pq_logic.startTask( final taskResult = pq_logic.startTask(
progress, progress,
l10n.taskUpgradingHardware(), l10n.taskUpgradingHardware(),
5 * 1000, 5 * 1000,
); );
progress = taskResult.progress.copyWith( final updatedProgress = taskResult.progress.copyWith(
currentTask: TaskInfo( currentTask: TaskInfo(
caption: taskResult.caption, caption: taskResult.caption,
type: TaskType.buying, type: TaskType.buying,
), ),
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화 currentCombat: null,
); );
return (progress: progress, queue: queue); return (progress: updatedProgress, queue: queue);
} }
// Gold 부족하면 전장으로 이동 (원본 674-676줄) // Gold 부족 전장 이동
final taskResult = pq_logic.startTask( final taskResult = pq_logic.startTask(
progress, progress,
l10n.taskEnteringDebugZone(), l10n.taskEnteringDebugZone(),
4 * 1000, 4 * 1000,
); );
progress = taskResult.progress.copyWith( final updatedProgress = taskResult.progress.copyWith(
currentTask: TaskInfo( currentTask: TaskInfo(
caption: taskResult.caption, caption: taskResult.caption,
type: TaskType.neutral, type: TaskType.neutral,
), ),
currentCombat: null, // 비전투 태스크이므로 전투 상태 초기화 currentCombat: null,
); );
return (progress: progress, queue: queue); return (progress: updatedProgress, queue: queue);
} }
// 3. Act Boss 리트라이 체크 /// Act Boss 재도전 태스크 생성
// pendingActCompletion이 true면 Act Boss 재소환 ({ProgressState progress, QueueState queue}) _createActBossRetryTask(
if (state.progress.pendingActCompletion) { GameState state,
ProgressState progress,
QueueState queue,
) {
final actProgressionService = ActProgressionService(config: config); final actProgressionService = ActProgressionService(config: config);
final actBoss = actProgressionService.createActBoss(state); final actBoss = actProgressionService.createActBoss(state);
final combatCalculator = CombatCalculator(rng: state.rng); final combatCalculator = CombatCalculator(rng: state.rng);
@@ -595,12 +776,12 @@ class ProgressService {
durationMillis, durationMillis,
); );
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,
monsterBaseName: actBoss.monsterStats.name, monsterBaseName: actBoss.monsterStats.name,
monsterPart: '*', // Boss는 WinItem 드랍 monsterPart: '*',
monsterLevel: actBoss.monsterStats.level, monsterLevel: actBoss.monsterStats.level,
monsterGrade: MonsterGrade.boss, monsterGrade: MonsterGrade.boss,
monsterSize: getBossSizeForAct(state.progress.plotStageCount), monsterSize: getBossSizeForAct(state.progress.plotStageCount),
@@ -608,31 +789,18 @@ class ProgressService {
currentCombat: actBoss, currentCombat: actBoss,
); );
return (progress: progress, queue: queue); return (progress: updatedProgress, queue: queue);
} }
// 4. 최종 보스 전투 체크 /// 일반 몬스터 전투 태스크 생성
// finalBossState == fighting이면 Glitch God 스폰 ({ProgressState progress, QueueState queue}) _createMonsterTask(
// 단, 레벨링 모드 중이면 일반 몬스터로 레벨업 후 재도전 GameState state,
if (state.progress.finalBossState == FinalBossState.fighting) { ProgressState progress,
if (state.progress.isInBossLevelingMode) { QueueState queue,
// 레벨링 모드: 일반 몬스터 전투로 대체 (아래 MonsterTask로 진행) ) {
} else {
// 레벨링 모드 종료 또는 첫 도전: 보스전 시작
// 레벨링 모드가 끝났으면 타이머 초기화
if (state.progress.bossLevelingEndTime != null) {
progress = progress.copyWith(clearBossLevelingEndTime: true);
}
final actProgressionService = ActProgressionService(config: config);
return actProgressionService.startFinalBossFight(state, progress, queue);
}
}
// 5. MonsterTask 실행 (원본 678-684줄)
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.