feat(death): Phase 4 사망/부활 시스템 구현

- DeathInfo, DeathCause 클래스 정의 (game_state.dart)
  - 사망 원인, 상실 장비 수, 사망 시점 정보 기록
- ShopService 구현 (shop_service.dart)
  - 장비 가격 계산 (레벨 * 50 * 희귀도 배율)
  - 슬롯별 장비 생성 (프로그래밍 테마)
  - 자동 구매 (빈 슬롯에 Common 장비)
- ResurrectionService 구현 (resurrection_service.dart)
  - 사망 처리: 모든 장비 상실, 기본 무기만 유지
  - 부활 처리: HP/MP 회복, 자동 장비 구매
- progress_service.dart 사망 판정 로직 추가
  - 전투 중 HP <= 0 시 사망 처리
  - ProgressTickResult에 playerDied 플래그 추가
- progress_loop.dart 사망 시 루프 정지
  - onPlayerDied 콜백 추가
  - 사망 상태에서 틱 진행 방지
- DeathOverlay 위젯 구현 (death_overlay.dart)
  - ASCII 스컬 아트, 사망 원인, 상실 정보 표시
  - 부활 버튼
- GameSessionController 사망/부활 상태 관리
  - GameSessionStatus.dead 상태 추가
  - resurrect() 메서드로 부활 처리
This commit is contained in:
JiWoong Sul
2025-12-17 17:15:22 +09:00
parent 517bf54a56
commit 21bf057cfc
7 changed files with 974 additions and 3 deletions

View File

@@ -0,0 +1,182 @@
import 'package:askiineverdie/src/core/engine/shop_service.dart';
import 'package:askiineverdie/src/core/model/equipment_item.dart';
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
/// 부활 시스템 서비스 (Phase 4)
///
/// 사망 처리, 부활 처리, 장비 상실 등을 담당
class ResurrectionService {
const ResurrectionService({required this.shopService});
final ShopService shopService;
// ============================================================================
// 사망 처리
// ============================================================================
/// 플레이어 사망 처리
///
/// 1. 모든 장비 제거 (인벤토리로 이동하지 않음 - 상실)
/// 2. 전투 상태 초기화
/// 3. 사망 정보 기록
GameState processDeath({
required GameState state,
required String killerName,
required DeathCause cause,
}) {
// 상실할 장비 개수 계산
final lostCount = state.equipment.equippedItems.length;
// 빈 장비 생성 (기본 무기만 유지)
final emptyEquipment = Equipment(
items: [
EquipmentItem.defaultWeapon(), // 무기 슬롯에 기본 Keyboard
EquipmentItem.empty(EquipmentSlot.shield),
EquipmentItem.empty(EquipmentSlot.helm),
EquipmentItem.empty(EquipmentSlot.hauberk),
EquipmentItem.empty(EquipmentSlot.brassairts),
EquipmentItem.empty(EquipmentSlot.vambraces),
EquipmentItem.empty(EquipmentSlot.gauntlets),
EquipmentItem.empty(EquipmentSlot.gambeson),
EquipmentItem.empty(EquipmentSlot.cuisses),
EquipmentItem.empty(EquipmentSlot.greaves),
EquipmentItem.empty(EquipmentSlot.sollerets),
],
bestIndex: 0,
);
// 사망 정보 생성
final deathInfo = DeathInfo(
cause: cause,
killerName: killerName,
lostEquipmentCount: lostCount,
goldAtDeath: state.inventory.gold,
levelAtDeath: state.traits.level,
timestamp: state.skillSystem.elapsedMs,
);
// 전투 상태 초기화
final progress = state.progress.copyWith(
currentCombat: null,
);
return state.copyWith(
equipment: emptyEquipment,
progress: progress,
deathInfo: deathInfo,
);
}
// ============================================================================
// 부활 처리
// ============================================================================
/// 플레이어 부활 처리
///
/// 1. HP/MP 전체 회복
/// 2. 골드로 구매 가능한 장비 자동 구매
/// 3. 사망 상태 해제
/// 4. 안전 지역으로 이동 태스크 설정
GameState processResurrection(GameState state) {
if (!state.isDead) return state;
// HP/MP 전체 회복
var nextState = state.copyWith(
stats: state.stats.copyWith(
hpCurrent: state.stats.hpMax,
mpCurrent: state.stats.mpMax,
),
);
// 빈 슬롯에 자동 장비 구매
final autoBuyResult = shopService.autoBuyForEmptySlots(
playerLevel: nextState.traits.level,
currentGold: nextState.inventory.gold,
currentEquipment: nextState.equipment,
);
// 결과 적용
nextState = nextState.copyWith(
equipment: autoBuyResult.updatedEquipment,
inventory: nextState.inventory.copyWith(
gold: autoBuyResult.remainingGold,
),
clearDeathInfo: true, // 사망 상태 해제
);
// 스킬 쿨타임 초기화
nextState = nextState.copyWith(
skillSystem: SkillSystemState.empty().copyWith(
elapsedMs: nextState.skillSystem.elapsedMs,
),
);
return nextState;
}
// ============================================================================
// 유틸리티
// ============================================================================
/// 부활 비용 계산 (향후 확장용)
///
/// 현재는 무료 부활, 향후 비용 도입 가능
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,
),
);
}
}