Files
asciinevrdie/lib/src/core/engine/resurrection_service.dart
JiWoong Sul e679abd0d8 refactor(core): 코어 엔진 및 모델 개선
- 애니메이션 시스템 개선
- 오디오 서비스 개선
- 전투/스킬/포션 서비스 개선
- 스토리지 및 저장 시스템 개선
- 모델 클래스 타입 안정성 강화
2025-12-31 17:46:53 +09:00

289 lines
9.2 KiB
Dart

import 'dart:math';
import 'package:asciineverdie/data/class_data.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
import 'package:asciineverdie/data/race_data.dart';
import 'package:asciineverdie/src/core/engine/shop_service.dart';
import 'package:asciineverdie/src/core/model/class_traits.dart';
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/race_traits.dart';
/// 부활 시스템 서비스 (Phase 4)
///
/// 사망 처리, 부활 처리, 장비 상실 등을 담당
class ResurrectionService {
const ResurrectionService({required this.shopService});
final ShopService shopService;
// ============================================================================
// 사망 처리
// ============================================================================
/// 플레이어 사망 처리
///
/// 1. 1개의 랜덤 장비 제거 (제물로 바침)
/// 2. 전투 상태 초기화
/// 3. 사망 정보 기록
GameState processDeath({
required GameState state,
required String killerName,
required DeathCause cause,
}) {
// 제물로 바칠 아이템 선택 (무기 제외, 장착된 아이템 중 랜덤 1개)
final equippedItems = <int>[]; // 장착된 아이템의 슬롯 인덱스
for (var i = 0; i < Equipment.slotCount; i++) {
final slot = EquipmentSlot.values[i];
// 무기 슬롯은 제외
if (slot == EquipmentSlot.weapon) continue;
final item = state.equipment.getItemByIndex(i);
// 빈 슬롯 제외
if (item.isNotEmpty) {
equippedItems.add(i);
}
}
String? lostItemName;
var newEquipment = state.equipment;
if (equippedItems.isNotEmpty) {
// 랜덤하게 1개 슬롯 선택
final random = Random();
final slotIndex = equippedItems[random.nextInt(equippedItems.length)];
final lostItem = state.equipment.getItemByIndex(slotIndex);
lostItemName = lostItem.name;
// 해당 슬롯만 빈 아이템으로 교체
final slot = EquipmentSlot.values[slotIndex];
newEquipment = state.equipment.setItemByIndex(
slotIndex,
EquipmentItem.empty(slot),
);
}
// 사망 정보 생성
final deathInfo = DeathInfo(
cause: cause,
killerName: killerName,
lostEquipmentCount: lostItemName != null ? 1 : 0,
lostItemName: lostItemName,
goldAtDeath: state.inventory.gold,
levelAtDeath: state.traits.level,
timestamp: state.skillSystem.elapsedMs,
);
// 전투 상태 초기화
final progress = state.progress.copyWith(currentCombat: null);
return state.copyWith(
equipment: newEquipment,
progress: progress,
deathInfo: deathInfo,
);
}
// ============================================================================
// 부활 처리
// ============================================================================
/// 플레이어 부활 처리
///
/// 1. 골드로 구매 가능한 장비 자동 구매
/// 2. HP/MP 전체 회복 (장비/종족/클래스 보너스 포함)
/// 3. 사망 상태 해제
/// 4. 안전 지역으로 이동 태스크 설정
GameState processResurrection(GameState state) {
if (!state.isDead) return state;
// 1. 먼저 장비 구매 (HP 계산에 필요)
final autoBuyResult = shopService.autoBuyForEmptySlots(
playerLevel: state.traits.level,
currentGold: state.inventory.gold,
currentEquipment: state.equipment,
);
// 장비 적용
var nextState = state.copyWith(
equipment: autoBuyResult.updatedEquipment,
inventory: state.inventory.copyWith(gold: autoBuyResult.remainingGold),
);
// 2. 전체 HP/MP 계산 (장비 + 종족 + 클래스 보너스 포함)
final totalHpMax = _calculateTotalHpMax(nextState);
final totalMpMax = _calculateTotalMpMax(nextState);
// HP/MP 전체 회복
nextState = nextState.copyWith(
stats: nextState.stats.copyWith(
hpCurrent: totalHpMax,
mpCurrent: totalMpMax,
),
clearDeathInfo: true, // 사망 상태 해제
);
// 스킬 쿨타임 초기화
nextState = nextState.copyWith(
skillSystem: SkillSystemState.empty().copyWith(
elapsedMs: nextState.skillSystem.elapsedMs,
),
);
// 4. 부활 후 태스크 시퀀스 설정 (큐에 추가)
// 순서: 마을 귀환 → 샵 정비 → 사냥터 이동 → 전투
final resurrectionQueue = <QueueEntry>[
QueueEntry(
kind: QueueKind.task,
durationMillis: 3000, // 3초
caption: l10n.taskReturningToTown,
taskType: TaskType.neutral, // 걷기 애니메이션
),
QueueEntry(
kind: QueueKind.task,
durationMillis: 3000, // 3초
caption: l10n.taskRestockingAtShop,
taskType: TaskType.market, // town 애니메이션
),
QueueEntry(
kind: QueueKind.task,
durationMillis: 2000, // 2초
caption: l10n.taskHeadingToHuntingGrounds,
taskType: TaskType.neutral, // 걷기 애니메이션
),
];
// 기존 큐 초기화 후 부활 시퀀스만 설정
// 첫 번째 태스크를 현재 태스크로 설정하여 즉시 표시
final firstTask = resurrectionQueue.removeAt(0);
nextState = nextState.copyWith(
queue: QueueState(
entries: resurrectionQueue, // 나머지 큐
),
// 첫 번째 태스크를 현재 태스크로 직접 설정
progress: nextState.progress.copyWith(
currentTask: TaskInfo(
caption: firstTask.caption,
type: firstTask.taskType,
),
task: ProgressBarState(
position: 0,
max: firstTask.durationMillis,
),
currentCombat: null, // 전투 상태 명시적 초기화
),
);
return nextState;
}
/// 장비/종족/클래스 보너스를 포함한 전체 HP 계산
int _calculateTotalHpMax(GameState state) {
// 기본 HP + 장비 보너스
var totalHp = state.stats.hpMax + state.equipment.totalStats.hpBonus;
// 종족 HP 보너스 (예: Heap Troll +20%)
final race = RaceData.findById(state.traits.raceId);
if (race != null) {
final raceHpBonus = race.getPassiveValue(PassiveType.hpBonus);
if (raceHpBonus > 0) {
totalHp = (totalHp * (1 + raceHpBonus)).round();
}
}
// 클래스 HP 보너스 (예: Garbage Collector +30%)
final klass = ClassData.findById(state.traits.classId);
if (klass != null) {
final classHpBonus = klass.getPassiveValue(ClassPassiveType.hpBonus);
if (classHpBonus > 0) {
totalHp = (totalHp * (1 + classHpBonus)).round();
}
}
return totalHp;
}
/// 장비/종족/클래스 보너스를 포함한 전체 MP 계산
int _calculateTotalMpMax(GameState state) {
// 기본 MP + 장비 보너스
var totalMp = state.stats.mpMax + state.equipment.totalStats.mpBonus;
// 종족 MP 보너스 (예: Pointer Fairy +20%)
final race = RaceData.findById(state.traits.raceId);
if (race != null) {
final raceMpBonus = race.getPassiveValue(PassiveType.mpBonus);
if (raceMpBonus > 0) {
totalMp = (totalMp * (1 + raceMpBonus)).round();
}
}
return totalMp;
}
// ============================================================================
// 유틸리티
// ============================================================================
/// 부활 비용 계산 (향후 확장용)
///
/// 현재는 무료 부활, 향후 비용 도입 가능
int calculateResurrectionCost(int playerLevel) {
// 기본: 무료
return 0;
}
/// 부활 가능 여부 확인
bool canResurrect(GameState state) {
if (!state.isDead) return false;
final cost = calculateResurrectionCost(state.traits.level);
return state.inventory.gold >= cost;
}
/// 장비 보존 아이템 적용 (향후 확장용)
///
/// [protectedSlots] 보존할 슬롯 인덱스 목록
GameState processDeathWithProtection({
required GameState state,
required String killerName,
required DeathCause cause,
required List<int> protectedSlots,
}) {
// 보존할 아이템 추출
final protectedItems = <int, EquipmentItem>{};
for (final slotIndex in protectedSlots) {
if (slotIndex >= 0 && slotIndex < Equipment.slotCount) {
final item = state.equipment.getItemByIndex(slotIndex);
if (item.isNotEmpty) {
protectedItems[slotIndex] = item;
}
}
}
// 기본 사망 처리
var nextState = processDeath(
state: state,
killerName: killerName,
cause: cause,
);
// 보존된 아이템 복원
var equipment = nextState.equipment;
for (final entry in protectedItems.entries) {
equipment = equipment.setItemByIndex(entry.key, entry.value);
}
// 상실 개수 재계산
final actualLostCount =
state.equipment.equippedItems.length - protectedItems.length;
return nextState.copyWith(
equipment: equipment,
deathInfo: nextState.deathInfo?.copyWith(
lostEquipmentCount: actualLostCount,
),
);
}
}