refactor(engine): 엔진 서비스 분리 및 리팩토링
- progress_service에서 death_handler, loot_handler, task_generator 분리 - combat_tick_service에서 player_attack_processor 분리 - arena_service에서 arena_combat_simulator 분리 - skill_service에서 skill_auto_selector 분리
This commit is contained in:
172
lib/src/core/engine/death_handler.dart
Normal file
172
lib/src/core/engine/death_handler.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
||||
|
||||
/// 플레이어 사망 처리 서비스
|
||||
///
|
||||
/// ProgressService에서 분리된 사망 관련 로직 담당:
|
||||
/// - 장비 손실 계산
|
||||
/// - 사망 정보 기록
|
||||
/// - 보스전 레벨링 모드 진입
|
||||
class DeathHandler {
|
||||
const DeathHandler();
|
||||
|
||||
/// 플레이어 사망 처리 (Phase 4)
|
||||
///
|
||||
/// 모든 장비 상실 및 사망 정보 기록.
|
||||
/// 보스전 사망 시: 장비 보호 + 5분 레벨링 모드 진입.
|
||||
GameState processPlayerDeath(
|
||||
GameState state, {
|
||||
required String killerName,
|
||||
required DeathCause cause,
|
||||
}) {
|
||||
// 사망 직전 전투 이벤트 저장 (최대 10개)
|
||||
final lastCombatEvents =
|
||||
state.progress.currentCombat?.recentEvents ?? const [];
|
||||
|
||||
// 보스전 사망 여부 확인 (최종 보스 fighting 상태)
|
||||
final isBossDeath =
|
||||
state.progress.finalBossState == FinalBossState.fighting;
|
||||
|
||||
// 보스전 사망이 아닐 경우에만 장비 손실
|
||||
var newEquipment = state.equipment;
|
||||
var lostCount = 0;
|
||||
String? lostItemName;
|
||||
EquipmentSlot? lostItemSlot;
|
||||
ItemRarity? lostItemRarity;
|
||||
EquipmentItem? lostEquipmentItem; // 광고 부활 시 복구용
|
||||
|
||||
if (!isBossDeath) {
|
||||
final lossResult = _calculateEquipmentLoss(state);
|
||||
newEquipment = lossResult.equipment;
|
||||
lostCount = lossResult.lostCount;
|
||||
lostItemName = lossResult.lostItemName;
|
||||
lostItemSlot = lossResult.lostItemSlot;
|
||||
lostItemRarity = lossResult.lostItemRarity;
|
||||
lostEquipmentItem = lossResult.lostItem;
|
||||
}
|
||||
|
||||
// 사망 정보 생성 (전투 로그 포함)
|
||||
final deathInfo = DeathInfo(
|
||||
cause: cause,
|
||||
killerName: killerName,
|
||||
lostEquipmentCount: lostCount,
|
||||
lostItemName: lostItemName,
|
||||
lostItemSlot: lostItemSlot,
|
||||
lostItemRarity: lostItemRarity,
|
||||
lostItem: lostEquipmentItem,
|
||||
goldAtDeath: state.inventory.gold,
|
||||
levelAtDeath: state.traits.level,
|
||||
timestamp: state.skillSystem.elapsedMs,
|
||||
lastCombatEvents: lastCombatEvents,
|
||||
);
|
||||
|
||||
// 보스전 사망 시 5분 레벨링 모드 진입
|
||||
final bossLevelingEndTime = isBossDeath
|
||||
? DateTime.now().millisecondsSinceEpoch +
|
||||
(5 * 60 * 1000) // 5분
|
||||
: null;
|
||||
|
||||
// 전투 상태 초기화 및 사망 횟수 증가
|
||||
final progress = state.progress.copyWith(
|
||||
currentCombat: null,
|
||||
deathCount: state.progress.deathCount + 1,
|
||||
bossLevelingEndTime: bossLevelingEndTime,
|
||||
);
|
||||
|
||||
return state.copyWith(
|
||||
equipment: newEquipment,
|
||||
progress: progress,
|
||||
deathInfo: deathInfo,
|
||||
);
|
||||
}
|
||||
|
||||
/// 장비 손실 계산
|
||||
({
|
||||
Equipment equipment,
|
||||
int lostCount,
|
||||
String? lostItemName,
|
||||
EquipmentSlot? lostItemSlot,
|
||||
ItemRarity? lostItemRarity,
|
||||
EquipmentItem? lostItem,
|
||||
})
|
||||
_calculateEquipmentLoss(GameState state) {
|
||||
var newEquipment = state.equipment;
|
||||
|
||||
// 레벨 기반 장비 손실 확률 계산
|
||||
// Lv 1: 20%, Lv 5: ~56%, Lv 10+: 100%
|
||||
// 공식: 20 + (level - 1) * 80 / 9
|
||||
final level = state.traits.level;
|
||||
final lossChancePercent = level >= 10
|
||||
? 100
|
||||
: (20 + ((level - 1) * 80 ~/ 9)).clamp(0, 100);
|
||||
final roll = state.rng.nextInt(100); // 0~99
|
||||
final shouldLoseEquipment = roll < lossChancePercent;
|
||||
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[Death] Lv$level lossChance=$lossChancePercent% roll=$roll '
|
||||
'shouldLose=$shouldLoseEquipment',
|
||||
);
|
||||
|
||||
if (!shouldLoseEquipment) {
|
||||
return (
|
||||
equipment: newEquipment,
|
||||
lostCount: 0,
|
||||
lostItemName: null,
|
||||
lostItemSlot: null,
|
||||
lostItemRarity: null,
|
||||
lostItem: null,
|
||||
);
|
||||
}
|
||||
|
||||
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
||||
final equippedNonWeaponSlots = <int>[];
|
||||
for (var i = 1; i < Equipment.slotCount; i++) {
|
||||
final item = state.equipment.getItemByIndex(i);
|
||||
if (item.isNotEmpty) {
|
||||
equippedNonWeaponSlots.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (equippedNonWeaponSlots.isEmpty) {
|
||||
return (
|
||||
equipment: newEquipment,
|
||||
lostCount: 0,
|
||||
lostItemName: null,
|
||||
lostItemSlot: null,
|
||||
lostItemRarity: null,
|
||||
lostItem: null,
|
||||
);
|
||||
}
|
||||
|
||||
// 랜덤하게 1개 슬롯 선택
|
||||
final sacrificeIndex =
|
||||
equippedNonWeaponSlots[state.rng.nextInt(
|
||||
equippedNonWeaponSlots.length,
|
||||
)];
|
||||
|
||||
// 제물로 바칠 아이템 정보 저장
|
||||
final lostItem = state.equipment.getItemByIndex(sacrificeIndex);
|
||||
final lostItemSlot = EquipmentSlot.values[sacrificeIndex];
|
||||
|
||||
// 해당 슬롯을 빈 장비로 교체
|
||||
newEquipment = newEquipment.setItemByIndex(
|
||||
sacrificeIndex,
|
||||
EquipmentItem.empty(lostItemSlot),
|
||||
);
|
||||
|
||||
// ignore: avoid_print
|
||||
print('[Death] Lost item: ${lostItem.name} (slot: $lostItemSlot)');
|
||||
|
||||
return (
|
||||
equipment: newEquipment,
|
||||
lostCount: 1,
|
||||
lostItemName: lostItem.name,
|
||||
lostItemSlot: lostItemSlot,
|
||||
lostItemRarity: lostItem.rarity,
|
||||
lostItem: lostItem,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user