refactor(engine): 서비스 로직 정리
- ArenaService, PotionService, ProgressService 개선 - ResurrectionService, SkillService 정리
This commit is contained in:
@@ -19,7 +19,7 @@ import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||
/// - 장비 교환
|
||||
class ArenaService {
|
||||
ArenaService({DeterministicRandom? rng})
|
||||
: _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch);
|
||||
: _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
final DeterministicRandom _rng;
|
||||
|
||||
@@ -309,7 +309,9 @@ class ArenaService {
|
||||
elapsedMs += tickMs;
|
||||
|
||||
// 스킬 시스템 시간 업데이트
|
||||
challengerSkillSystem = challengerSkillSystem.copyWith(elapsedMs: elapsedMs);
|
||||
challengerSkillSystem = challengerSkillSystem.copyWith(
|
||||
elapsedMs: elapsedMs,
|
||||
);
|
||||
opponentSkillSystem = opponentSkillSystem.copyWith(elapsedMs: elapsedMs);
|
||||
|
||||
int? challengerDamage;
|
||||
@@ -469,10 +471,13 @@ class ArenaService {
|
||||
challengerSkillSystem = skillResult.updatedSkillSystem;
|
||||
final debuffEffect = skillResult.debuffEffect;
|
||||
if (debuffEffect != null) {
|
||||
opponentDebuffs = opponentDebuffs
|
||||
.where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id)
|
||||
.toList()
|
||||
..add(debuffEffect);
|
||||
opponentDebuffs =
|
||||
opponentDebuffs
|
||||
.where(
|
||||
(ActiveBuff d) => d.effect.id != debuffEffect.effect.id,
|
||||
)
|
||||
.toList()
|
||||
..add(debuffEffect);
|
||||
}
|
||||
challengerSkillUsed = selectedSkill.name;
|
||||
} else {
|
||||
@@ -585,10 +590,13 @@ class ArenaService {
|
||||
opponentSkillSystem = skillResult.updatedSkillSystem;
|
||||
final debuffEffect = skillResult.debuffEffect;
|
||||
if (debuffEffect != null) {
|
||||
challengerDebuffs = challengerDebuffs
|
||||
.where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id)
|
||||
.toList()
|
||||
..add(debuffEffect);
|
||||
challengerDebuffs =
|
||||
challengerDebuffs
|
||||
.where(
|
||||
(ActiveBuff d) => d.effect.id != debuffEffect.effect.id,
|
||||
)
|
||||
.toList()
|
||||
..add(debuffEffect);
|
||||
}
|
||||
opponentSkillUsed = selectedSkill.name;
|
||||
} else {
|
||||
@@ -628,7 +636,8 @@ class ArenaService {
|
||||
}
|
||||
|
||||
// 액션이 발생했을 때만 턴 전송
|
||||
final hasAction = challengerDamage != null ||
|
||||
final hasAction =
|
||||
challengerDamage != null ||
|
||||
opponentDamage != null ||
|
||||
challengerHealAmount != null ||
|
||||
opponentHealAmount != null ||
|
||||
@@ -722,12 +731,14 @@ class ArenaService {
|
||||
required bool isVictory,
|
||||
}) {
|
||||
// 도전자 장비 목록 복사
|
||||
final challengerEquipment =
|
||||
List<EquipmentItem>.from(match.challenger.finalEquipment ?? []);
|
||||
final challengerEquipment = List<EquipmentItem>.from(
|
||||
match.challenger.finalEquipment ?? [],
|
||||
);
|
||||
|
||||
// 상대 장비 목록 복사
|
||||
final opponentEquipment =
|
||||
List<EquipmentItem>.from(match.opponent.finalEquipment ?? []);
|
||||
final opponentEquipment = List<EquipmentItem>.from(
|
||||
match.opponent.finalEquipment ?? [],
|
||||
);
|
||||
|
||||
if (isVictory) {
|
||||
// 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득
|
||||
@@ -766,7 +777,9 @@ class ArenaService {
|
||||
|
||||
/// 슬롯으로 장비 찾기
|
||||
EquipmentItem _findItemBySlot(
|
||||
List<EquipmentItem> equipment, EquipmentSlot slot) {
|
||||
List<EquipmentItem> equipment,
|
||||
EquipmentSlot slot,
|
||||
) {
|
||||
for (final item in equipment) {
|
||||
if (item.slot == slot) return item;
|
||||
}
|
||||
|
||||
@@ -375,8 +375,10 @@ class PotionService {
|
||||
required int typeRoll,
|
||||
}) {
|
||||
// 기본 드랍 확률 계산
|
||||
var dropChance = (baseDropChance + playerLevel * dropChancePerLevel)
|
||||
.clamp(baseDropChance, maxDropChance);
|
||||
var dropChance = (baseDropChance + playerLevel * dropChancePerLevel).clamp(
|
||||
baseDropChance,
|
||||
maxDropChance,
|
||||
);
|
||||
|
||||
// 몬스터 등급 보너스 (Elite +5%, Boss +15%)
|
||||
dropChance += monsterGrade.potionDropBonus;
|
||||
|
||||
@@ -104,8 +104,10 @@ class ProgressService {
|
||||
);
|
||||
|
||||
// ExpBar 초기화 (원본 743-746줄)
|
||||
final expBar =
|
||||
ProgressBarState(position: 0, max: ExpConstants.requiredExp(1));
|
||||
final expBar = ProgressBarState(
|
||||
position: 0,
|
||||
max: ExpConstants.requiredExp(1),
|
||||
);
|
||||
|
||||
// PlotBar 초기화 - Prologue 5분 (300초)
|
||||
final plotBar = const ProgressBarState(position: 0, max: 300);
|
||||
@@ -299,23 +301,40 @@ class ProgressService {
|
||||
progress = progress.copyWith(currentCombat: combatForReset);
|
||||
}
|
||||
|
||||
// 전투 상태 초기화, 몬스터 처치 수 증가 및 물약 사용 기록 초기화
|
||||
progress = progress.copyWith(
|
||||
currentCombat: null,
|
||||
monstersKilled: progress.monstersKilled + 1,
|
||||
);
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
final resetPotionInventory = nextState.potionInventory.resetBattleUsage();
|
||||
nextState = nextState.copyWith(
|
||||
progress: progress,
|
||||
queue: queue,
|
||||
potionInventory: resetPotionInventory,
|
||||
);
|
||||
|
||||
// 최종 보스 처치 체크
|
||||
if (progress.finalBossState == FinalBossState.fighting) {
|
||||
// 글리치 갓 처치 완료 - 게임 클리어
|
||||
progress = progress.copyWith(
|
||||
finalBossState: FinalBossState.defeated,
|
||||
);
|
||||
progress = progress.copyWith(finalBossState: FinalBossState.defeated);
|
||||
nextState = nextState.copyWith(progress: progress);
|
||||
|
||||
// completeAct를 호출하여 게임 완료 처리
|
||||
@@ -406,22 +425,22 @@ class ProgressService {
|
||||
}
|
||||
}
|
||||
|
||||
// 플롯(plot) 바가 완료되면 InterplotCinematic 트리거
|
||||
// (원본 Main.pas:1301-1304)
|
||||
// 플롯(plot) 바가 완료되면 Act Boss 소환
|
||||
// (개선: Boss 처치 → 시네마틱 → Act 전환 순서)
|
||||
if (gain &&
|
||||
progress.plot.max > 0 &&
|
||||
progress.plot.position >= progress.plot.max) {
|
||||
// InterplotCinematic을 호출하여 시네마틱 이벤트 큐에 추가
|
||||
final cinematicEntries = pq_logic.interplotCinematic(
|
||||
config,
|
||||
nextState.rng,
|
||||
nextState.traits.level,
|
||||
nextState.progress.plotStageCount,
|
||||
progress.plot.position >= progress.plot.max &&
|
||||
!progress.pendingActCompletion) {
|
||||
// Act Boss 소환 및 플래그 설정
|
||||
final actBoss = _createActBoss(nextState);
|
||||
progress = progress.copyWith(
|
||||
plot: progress.plot.copyWith(position: 0), // Plot bar 리셋
|
||||
currentCombat: actBoss,
|
||||
pendingActCompletion: true, // Boss 처치 대기 플래그
|
||||
);
|
||||
queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
|
||||
// 플롯 바를 0으로 리셋하지 않음 - completeAct에서 처리됨
|
||||
} else if (progress.currentTask.type != TaskType.load &&
|
||||
progress.plot.max > 0) {
|
||||
progress.plot.max > 0 &&
|
||||
!progress.pendingActCompletion) {
|
||||
final uncappedPlot = progress.plot.position + incrementSeconds;
|
||||
final int newPlotPos = uncappedPlot > progress.plot.max
|
||||
? progress.plot.max
|
||||
@@ -531,13 +550,44 @@ class ProgressService {
|
||||
return (progress: progress, queue: queue);
|
||||
}
|
||||
|
||||
// 3. 최종 보스 전투 체크
|
||||
// 3. Act Boss 리트라이 체크
|
||||
// pendingActCompletion이 true면 Act Boss 재소환
|
||||
if (state.progress.pendingActCompletion) {
|
||||
final actBoss = _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,
|
||||
);
|
||||
|
||||
progress = taskResult.progress.copyWith(
|
||||
currentTask: TaskInfo(
|
||||
caption: taskResult.caption,
|
||||
type: TaskType.kill,
|
||||
monsterBaseName: actBoss.monsterStats.name,
|
||||
monsterPart: '*', // Boss는 WinItem 드랍
|
||||
monsterLevel: actBoss.monsterStats.level,
|
||||
monsterGrade: MonsterGrade.boss,
|
||||
),
|
||||
currentCombat: actBoss,
|
||||
);
|
||||
|
||||
return (progress: progress, queue: queue);
|
||||
}
|
||||
|
||||
// 4. 최종 보스 전투 체크
|
||||
// finalBossState == fighting이면 Glitch God 스폰
|
||||
if (state.progress.finalBossState == FinalBossState.fighting) {
|
||||
return _startFinalBossFight(state, progress, queue);
|
||||
}
|
||||
|
||||
// 4. MonsterTask 실행 (원본 678-684줄)
|
||||
// 5. MonsterTask 실행 (원본 678-684줄)
|
||||
final level = state.traits.level;
|
||||
|
||||
// 원본 Main.pas:548-551: 25% 확률로 Quest Monster 사용
|
||||
@@ -878,7 +928,8 @@ class ProgressService {
|
||||
var nextState = state;
|
||||
|
||||
// 현재 레벨이 목표 레벨보다 낮으면 레벨업 (최대 100레벨)
|
||||
while (nextState.traits.level < targetLevel && nextState.traits.level < 100) {
|
||||
while (nextState.traits.level < targetLevel &&
|
||||
nextState.traits.level < 100) {
|
||||
nextState = _levelUp(nextState);
|
||||
}
|
||||
|
||||
@@ -1390,10 +1441,13 @@ class ProgressService {
|
||||
|
||||
// 디버프 효과 추가 (기존 같은 디버프 제거 후)
|
||||
if (skillResult.debuffEffect != null) {
|
||||
activeDebuffs = activeDebuffs
|
||||
.where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
|
||||
.toList()
|
||||
..add(skillResult.debuffEffect!);
|
||||
activeDebuffs =
|
||||
activeDebuffs
|
||||
.where(
|
||||
(d) => d.effect.id != skillResult.debuffEffect!.effect.id,
|
||||
)
|
||||
.toList()
|
||||
..add(skillResult.debuffEffect!);
|
||||
}
|
||||
|
||||
// 디버프 이벤트 생성
|
||||
@@ -1533,6 +1587,55 @@ class ProgressService {
|
||||
);
|
||||
}
|
||||
|
||||
/// Act Boss 생성 (Act 완료 시)
|
||||
///
|
||||
/// 보스 레벨 = min(플레이어 레벨, Act 최소 레벨)로 설정하여
|
||||
/// 플레이어가 이길 수 있는 수준 보장
|
||||
CombatState _createActBoss(GameState state) {
|
||||
final plotStage = state.progress.plotStageCount;
|
||||
final actNumber = plotStage + 1;
|
||||
|
||||
// 보스 레벨 = min(플레이어 레벨, Act 최소 레벨)
|
||||
// → 플레이어가 현재 레벨보다 높은 보스를 만나지 않도록 보장
|
||||
final actMinLevel = ActMonsterLevel.forPlotStage(actNumber);
|
||||
final bossLevel = math.min(state.traits.level, actMinLevel);
|
||||
|
||||
// Named monster 생성 (pq_logic.namedMonster 활용)
|
||||
final bossName = pq_logic.namedMonster(config, state.rng, bossLevel);
|
||||
|
||||
final bossStats = MonsterBaseStats.forLevel(bossLevel);
|
||||
|
||||
// 플레이어 전투 스탯 생성
|
||||
final playerCombatStats = CombatStats.fromStats(
|
||||
stats: state.stats,
|
||||
equipment: state.equipment,
|
||||
level: state.traits.level,
|
||||
monsterLevel: bossLevel,
|
||||
);
|
||||
|
||||
// Boss 몬스터 스탯 생성 (일반 몬스터 대비 강화)
|
||||
final monsterCombatStats = MonsterCombatStats(
|
||||
name: bossName,
|
||||
level: bossLevel,
|
||||
atk: (bossStats.atk * 1.5).round(), // Boss 보정 (1.5배)
|
||||
def: (bossStats.def * 1.5).round(),
|
||||
hpMax: (bossStats.hp * 2.0).round(), // HP는 2.0배 (보스다운 전투 시간)
|
||||
hpCurrent: (bossStats.hp * 2.0).round(),
|
||||
criRate: 0.05,
|
||||
criDamage: 1.5,
|
||||
evasion: 0.0,
|
||||
accuracy: 0.8,
|
||||
attackDelayMs: 1000,
|
||||
expReward: (bossStats.exp * 2.5).round(), // 경험치 보상 증가
|
||||
);
|
||||
|
||||
// 전투 상태 초기화
|
||||
return CombatState.start(
|
||||
playerStats: playerCombatStats,
|
||||
monsterStats: monsterCombatStats,
|
||||
);
|
||||
}
|
||||
|
||||
/// 플레이어 사망 처리 (Phase 4)
|
||||
///
|
||||
/// 모든 장비 상실 및 사망 정보 기록
|
||||
@@ -1578,9 +1681,11 @@ class ProgressService {
|
||||
);
|
||||
|
||||
// 전투 상태 초기화 및 사망 횟수 증가
|
||||
// pendingActCompletion 플래그는 유지 (Boss 리트라이를 위해)
|
||||
final progress = state.progress.copyWith(
|
||||
currentCombat: null,
|
||||
deathCount: state.progress.deathCount + 1,
|
||||
// pendingActCompletion은 copyWith에서 명시하지 않으면 기존 값 유지
|
||||
);
|
||||
|
||||
return state.copyWith(
|
||||
|
||||
@@ -167,10 +167,7 @@ class ResurrectionService {
|
||||
caption: firstTask.caption,
|
||||
type: firstTask.taskType,
|
||||
),
|
||||
task: ProgressBarState(
|
||||
position: 0,
|
||||
max: firstTask.durationMillis,
|
||||
),
|
||||
task: ProgressBarState(position: 0, max: firstTask.durationMillis),
|
||||
currentCombat: null, // 전투 상태 명시적 초기화
|
||||
),
|
||||
);
|
||||
|
||||
@@ -446,10 +446,12 @@ class SkillService {
|
||||
|
||||
// ATK 증가량 기준 정렬
|
||||
buffSkills.sort((a, b) {
|
||||
final aValue = (a.buff?.atkModifier ?? 0) +
|
||||
final aValue =
|
||||
(a.buff?.atkModifier ?? 0) +
|
||||
(a.buff?.defModifier ?? 0) * 0.5 +
|
||||
(a.buff?.criRateModifier ?? 0) * 0.3;
|
||||
final bValue = (b.buff?.atkModifier ?? 0) +
|
||||
final bValue =
|
||||
(b.buff?.atkModifier ?? 0) +
|
||||
(b.buff?.defModifier ?? 0) * 0.5 +
|
||||
(b.buff?.criRateModifier ?? 0) * 0.3;
|
||||
return bValue.compareTo(aValue);
|
||||
@@ -470,9 +472,11 @@ class SkillService {
|
||||
|
||||
// 디버프 효과 크기 기준 정렬 (음수 값이므로 절대값으로 비교)
|
||||
debuffSkills.sort((a, b) {
|
||||
final aValue = (a.buff?.atkModifier ?? 0).abs() +
|
||||
final aValue =
|
||||
(a.buff?.atkModifier ?? 0).abs() +
|
||||
(a.buff?.defModifier ?? 0).abs() * 0.5;
|
||||
final bValue = (b.buff?.atkModifier ?? 0).abs() +
|
||||
final bValue =
|
||||
(b.buff?.atkModifier ?? 0).abs() +
|
||||
(b.buff?.defModifier ?? 0).abs() * 0.5;
|
||||
return bValue.compareTo(aValue);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user