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 = []; // 장착된 아이템의 슬롯 인덱스 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( 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 protectedSlots, }) { // 보존할 아이템 추출 final protectedItems = {}; 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, ), ); } }