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

@@ -6,6 +6,7 @@ import 'package:askiineverdie/src/core/engine/combat_calculator.dart';
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
import 'package:askiineverdie/src/core/engine/reward_service.dart';
import 'package:askiineverdie/src/core/engine/skill_service.dart';
import 'package:askiineverdie/src/core/model/combat_event.dart';
import 'package:askiineverdie/src/core/model/combat_state.dart';
import 'package:askiineverdie/src/core/model/combat_stats.dart';
import 'package:askiineverdie/src/core/model/equipment_item.dart';
@@ -225,11 +226,19 @@ class ProgressService {
// 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
if (gain) {
// 전투 결과에 따라 플레이어 HP 업데이트
// 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복
final combat = progress.currentCombat;
if (combat != null && combat.isActive) {
// 전투 중 데미지를 실제 Stats에 반영
final newHp = combat.playerStats.hpCurrent;
// 전투 중 HP
final remainingHp = combat.playerStats.hpCurrent;
final maxHp = combat.playerStats.hpMax;
// 전투 승리 시 HP 회복 (50% + CON/2)
// 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
final conBonus = nextState.stats.con ~/ 2;
final healAmount = (maxHp * 0.5).round() + conBonus;
final newHp = (remainingHp + healAmount).clamp(0, maxHp);
nextState = nextState.copyWith(
stats: nextState.stats.copyWith(hpCurrent: newHp),
);
@@ -456,9 +465,17 @@ class ProgressService {
level: level,
);
// 전투용 몬스터 레벨 조정 (밸런스)
// config의 raw 레벨이 플레이어보다 너무 높으면 전투가 불가능
// 플레이어 레벨 ±3 범위로 제한 (최소 1)
final effectiveMonsterLevel = monsterResult.level.clamp(
math.max(1, level - 3),
level + 3,
).toInt();
final monsterCombatStats = MonsterCombatStats.fromLevel(
name: monsterResult.displayName,
level: monsterResult.level,
level: effectiveMonsterLevel,
speedType: MonsterCombatStats.inferSpeedType(monsterResult.baseName),
);
@@ -875,6 +892,10 @@ class ProgressService {
var turnsElapsed = combat.turnsElapsed;
var updatedSkillSystem = skillSystem;
// 새 전투 이벤트 수집
final newEvents = <CombatEvent>[];
final timestamp = updatedSkillSystem.elapsedMs;
// 플레이어 공격 체크
if (playerAccumulator >= playerStats.attackDelayMs) {
// 스킬 자동 선택
@@ -905,6 +926,14 @@ class ProgressService {
monsterStats = skillResult.updatedMonster;
totalDamageDealt += skillResult.result.damage;
updatedSkillSystem = skillResult.updatedSkillSystem;
// 스킬 공격 이벤트 생성
newEvents.add(CombatEvent.playerSkill(
timestamp: timestamp,
skillName: selectedSkill.name,
damage: skillResult.result.damage,
targetName: monsterStats.name,
));
} else if (selectedSkill != null && selectedSkill.isHeal) {
// 회복 스킬 사용
final skillResult = skillService.useHealSkill(
@@ -914,6 +943,13 @@ class ProgressService {
);
playerStats = skillResult.updatedPlayer;
updatedSkillSystem = skillResult.updatedSkillSystem;
// 회복 이벤트 생성
newEvents.add(CombatEvent.playerHeal(
timestamp: timestamp,
healAmount: skillResult.result.healedAmount,
skillName: selectedSkill.name,
));
} else if (selectedSkill != null && selectedSkill.isBuff) {
// 버프 스킬 사용
final skillResult = skillService.useBuffSkill(
@@ -923,6 +959,12 @@ class ProgressService {
);
playerStats = skillResult.updatedPlayer;
updatedSkillSystem = skillResult.updatedSkillSystem;
// 버프 이벤트 생성
newEvents.add(CombatEvent.playerBuff(
timestamp: timestamp,
skillName: selectedSkill.name,
));
} else {
// 일반 공격
final attackResult = calculator.playerAttackMonster(
@@ -931,6 +973,22 @@ class ProgressService {
);
monsterStats = attackResult.updatedDefender;
totalDamageDealt += attackResult.result.damage;
// 일반 공격 이벤트 생성
final result = attackResult.result;
if (result.isEvaded) {
newEvents.add(CombatEvent.monsterEvade(
timestamp: timestamp,
targetName: monsterStats.name,
));
} else {
newEvents.add(CombatEvent.playerAttack(
timestamp: timestamp,
damage: result.damage,
targetName: monsterStats.name,
isCritical: result.isCritical,
));
}
}
playerAccumulator -= playerStats.attackDelayMs;
@@ -946,11 +1004,44 @@ class ProgressService {
playerStats = attackResult.updatedDefender;
totalDamageTaken += attackResult.result.damage;
monsterAccumulator -= monsterStats.attackDelayMs;
// 몬스터 공격 이벤트 생성
final result = attackResult.result;
if (result.isEvaded) {
newEvents.add(CombatEvent.playerEvade(
timestamp: timestamp,
attackerName: monsterStats.name,
));
} else if (result.isBlocked) {
newEvents.add(CombatEvent.playerBlock(
timestamp: timestamp,
reducedDamage: result.damage,
attackerName: monsterStats.name,
));
} else if (result.isParried) {
newEvents.add(CombatEvent.playerParry(
timestamp: timestamp,
reducedDamage: result.damage,
attackerName: monsterStats.name,
));
} else {
newEvents.add(CombatEvent.monsterAttack(
timestamp: timestamp,
damage: result.damage,
attackerName: monsterStats.name,
));
}
}
// 전투 종료 체크
final isActive = playerStats.isAlive && monsterStats.isAlive;
// 기존 이벤트와 합쳐서 최대 10개 유지
final combinedEvents = [...combat.recentEvents, ...newEvents];
final recentEvents = combinedEvents.length > 10
? combinedEvents.sublist(combinedEvents.length - 10)
: combinedEvents;
return (
combat: combat.copyWith(
playerStats: playerStats,
@@ -961,6 +1052,7 @@ class ProgressService {
totalDamageTaken: totalDamageTaken,
turnsElapsed: turnsElapsed,
isActive: isActive,
recentEvents: recentEvents,
),
skillSystem: updatedSkillSystem,
);
@@ -977,6 +1069,10 @@ class ProgressService {
// 상실할 장비 개수 계산
final lostCount = state.equipment.equippedItems.length;
// 사망 직전 전투 이벤트 저장 (최대 10개)
final lastCombatEvents =
state.progress.currentCombat?.recentEvents ?? const [];
// 빈 장비 생성 (기본 무기만 유지)
final emptyEquipment = Equipment(
items: [
@@ -995,7 +1091,7 @@ class ProgressService {
bestIndex: 0,
);
// 사망 정보 생성
// 사망 정보 생성 (전투 로그 포함)
final deathInfo = DeathInfo(
cause: cause,
killerName: killerName,
@@ -1003,6 +1099,7 @@ class ProgressService {
goldAtDeath: state.inventory.gold,
levelAtDeath: state.traits.level,
timestamp: state.skillSystem.elapsedMs,
lastCombatEvents: lastCombatEvents,
);
// 전투 상태 초기화