feat(ui): HP/MP 바 개선 및 전투 시스템 UI 업데이트

- HP/MP 변화 시 플래시 효과 및 변화량 표시 추가
- 전투 중 몬스터 HP 바 표시 기능 추가
- 몬스터 HP 바 Row 오버플로우 버그 수정 (Flexible 적용)
- 전투 상태 및 이벤트 모델 개선
- 캐릭터 애니메이션 및 전투 컴포저 업데이트
This commit is contained in:
JiWoong Sul
2025-12-18 18:10:22 +09:00
parent 45147da5ec
commit cf8fdaecde
14 changed files with 1220 additions and 153 deletions

View File

@@ -1,7 +1,13 @@
import 'dart:math';
import 'package:askiineverdie/data/class_data.dart';
import 'package:askiineverdie/data/race_data.dart';
import 'package:askiineverdie/src/core/engine/shop_service.dart';
import 'package:askiineverdie/src/core/model/class_traits.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';
import 'package:askiineverdie/src/core/model/race_traits.dart';
/// 부활 시스템 서비스 (Phase 4)
///
@@ -17,7 +23,7 @@ class ResurrectionService {
/// 플레이어 사망 처리
///
/// 1. 모든 장비 제거 (인벤토리로 이동하지 않음 - 상실)
/// 1. 1개의 랜덤 장비 제거 (제물로 바침)
/// 2. 전투 상태 초기화
/// 3. 사망 정보 기록
GameState processDeath({
@@ -25,32 +31,40 @@ class ResurrectionService {
required String killerName,
required DeathCause cause,
}) {
// 상실할 장비 개수 계산
final lostCount = state.equipment.equippedItems.length;
// 제물로 바칠 아이템 선택 (장착된 아이템 중 랜덤 1개)
final equippedItems = <int>[]; // 장착된 아이템의 슬롯 인덱스
for (var i = 0; i < Equipment.slotCount; i++) {
final item = state.equipment.getItemByIndex(i);
// 빈 슬롯과 기본 무기(Keyboard) 제외
if (item.isNotEmpty && item.name != 'Keyboard') {
equippedItems.add(i);
}
}
// 빈 장비 생성 (기본 무기만 유지)
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,
);
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: lostCount,
lostEquipmentCount: lostItemName != null ? 1 : 0,
lostItemName: lostItemName,
goldAtDeath: state.inventory.gold,
levelAtDeath: state.traits.level,
timestamp: state.skillSystem.elapsedMs,
@@ -62,7 +76,7 @@ class ResurrectionService {
);
return state.copyWith(
equipment: emptyEquipment,
equipment: newEquipment,
progress: progress,
deathInfo: deathInfo,
);
@@ -74,33 +88,37 @@ class ResurrectionService {
/// 플레이어 부활 처리
///
/// 1. HP/MP 전체 회복
/// 2. 골드로 구매 가능한 장비 자동 구매
/// 1. 골드로 구매 가능한 장비 자동 구매
/// 2. HP/MP 전체 회복 (장비/종족/클래스 보너스 포함)
/// 3. 사망 상태 해제
/// 4. 안전 지역으로 이동 태스크 설정
GameState processResurrection(GameState state) {
if (!state.isDead) return state;
// HP/MP 전체 회복
// 1. 먼저 장비 구매 (HP 계산에 필요)
final autoBuyResult = shopService.autoBuyForEmptySlots(
playerLevel: state.traits.level,
currentGold: state.inventory.gold,
currentEquipment: state.equipment,
);
// 장비 적용
var nextState = state.copyWith(
stats: state.stats.copyWith(
hpCurrent: state.stats.hpMax,
mpCurrent: state.stats.mpMax,
equipment: autoBuyResult.updatedEquipment,
inventory: state.inventory.copyWith(
gold: autoBuyResult.remainingGold,
),
);
// 빈 슬롯에 자동 장비 구매
final autoBuyResult = shopService.autoBuyForEmptySlots(
playerLevel: nextState.traits.level,
currentGold: nextState.inventory.gold,
currentEquipment: nextState.equipment,
);
// 2. 전체 HP/MP 계산 (장비 + 종족 + 클래스 보너스 포함)
final totalHpMax = _calculateTotalHpMax(nextState);
final totalMpMax = _calculateTotalMpMax(nextState);
// 결과 적용
// HP/MP 전체 회복
nextState = nextState.copyWith(
equipment: autoBuyResult.updatedEquipment,
inventory: nextState.inventory.copyWith(
gold: autoBuyResult.remainingGold,
stats: nextState.stats.copyWith(
hpCurrent: totalHpMax,
mpCurrent: totalMpMax,
),
clearDeathInfo: true, // 사망 상태 해제
);
@@ -115,6 +133,49 @@ class ResurrectionService {
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;
}
// ============================================================================
// 유틸리티
// ============================================================================