feat(ui): HP/MP 바 개선 및 전투 시스템 UI 업데이트
- HP/MP 변화 시 플래시 효과 및 변화량 표시 추가 - 전투 중 몬스터 HP 바 표시 기능 추가 - 몬스터 HP 바 Row 오버플로우 버그 수정 (Flexible 적용) - 전투 상태 및 이벤트 모델 개선 - 캐릭터 애니메이션 및 전투 컴포저 업데이트
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:askiineverdie/data/story_data.dart';
|
||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||
import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart';
|
||||
import 'package:askiineverdie/src/core/engine/story_service.dart';
|
||||
import 'package:askiineverdie/src/core/model/combat_event.dart';
|
||||
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/hall_of_fame.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:askiineverdie/src/features/game/game_session_controller.dart';
|
||||
import 'package:askiineverdie/src/features/hall_of_fame/hall_of_fame_screen.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/cinematic_view.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/combat_log.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/death_overlay.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/hp_mp_bar.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/notification_overlay.dart';
|
||||
import 'package:askiineverdie/src/features/game/widgets/skill_panel.dart';
|
||||
@@ -53,14 +55,22 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
int _lastQuestCount = 0;
|
||||
int _lastPlotStageCount = 0;
|
||||
|
||||
// 전투 이벤트 추적 (마지막 처리된 이벤트 수)
|
||||
int _lastProcessedEventCount = 0;
|
||||
|
||||
void _checkSpecialEvents(GameState state) {
|
||||
// Phase 8: 태스크 변경 시 로그 추가
|
||||
final currentCaption = state.progress.currentTask.caption;
|
||||
if (currentCaption.isNotEmpty && currentCaption != _lastTaskCaption) {
|
||||
_addCombatLog(currentCaption, CombatLogType.normal);
|
||||
_lastTaskCaption = currentCaption;
|
||||
// 새 태스크 시작 시 이벤트 카운터 리셋
|
||||
_lastProcessedEventCount = 0;
|
||||
}
|
||||
|
||||
// 전투 이벤트 처리 (Combat Events)
|
||||
_processCombatEvents(state);
|
||||
|
||||
// 레벨업 감지
|
||||
if (state.traits.level > _lastLevel && _lastLevel > 0) {
|
||||
_specialAnimation = AsciiAnimationType.levelUp;
|
||||
@@ -129,6 +139,69 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
}
|
||||
|
||||
/// 전투 이벤트를 로그로 변환 (Convert Combat Events to Log)
|
||||
void _processCombatEvents(GameState state) {
|
||||
final combat = state.progress.currentCombat;
|
||||
if (combat == null || !combat.isActive) {
|
||||
_lastProcessedEventCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
final events = combat.recentEvents;
|
||||
if (events.isEmpty || events.length <= _lastProcessedEventCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 새 이벤트만 처리
|
||||
final newEvents = events.skip(_lastProcessedEventCount);
|
||||
for (final event in newEvents) {
|
||||
final (message, type) = _formatCombatEvent(event);
|
||||
_addCombatLog(message, type);
|
||||
}
|
||||
|
||||
_lastProcessedEventCount = events.length;
|
||||
}
|
||||
|
||||
/// 전투 이벤트를 메시지와 타입으로 변환
|
||||
(String, CombatLogType) _formatCombatEvent(CombatEvent event) {
|
||||
return switch (event.type) {
|
||||
CombatEventType.playerAttack => event.isCritical
|
||||
? ('CRITICAL! ${event.damage} damage to ${event.targetName}!', CombatLogType.critical)
|
||||
: ('You hit ${event.targetName} for ${event.damage} damage', CombatLogType.damage),
|
||||
CombatEventType.monsterAttack => (
|
||||
'${event.targetName} hits you for ${event.damage} damage',
|
||||
CombatLogType.monsterAttack,
|
||||
),
|
||||
CombatEventType.playerEvade => (
|
||||
'You evaded ${event.targetName}\'s attack!',
|
||||
CombatLogType.evade,
|
||||
),
|
||||
CombatEventType.monsterEvade => (
|
||||
'${event.targetName} evaded your attack!',
|
||||
CombatLogType.evade,
|
||||
),
|
||||
CombatEventType.playerBlock => (
|
||||
'Blocked! Reduced to ${event.damage} damage',
|
||||
CombatLogType.block,
|
||||
),
|
||||
CombatEventType.playerParry => (
|
||||
'Parried! Reduced to ${event.damage} damage',
|
||||
CombatLogType.parry,
|
||||
),
|
||||
CombatEventType.playerSkill => event.isCritical
|
||||
? ('CRITICAL ${event.skillName}! ${event.damage} damage!', CombatLogType.critical)
|
||||
: ('${event.skillName}: ${event.damage} damage', CombatLogType.spell),
|
||||
CombatEventType.playerHeal => (
|
||||
'${event.skillName ?? "Heal"}: +${event.healAmount} HP',
|
||||
CombatLogType.heal,
|
||||
),
|
||||
CombatEventType.playerBuff => (
|
||||
'${event.skillName} activated!',
|
||||
CombatLogType.buff,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// Phase 9: Act 시네마틱 표시 (Show Act Cinematic)
|
||||
Future<void> _showCinematicForAct(StoryAct act) async {
|
||||
if (_showingCinematic) return;
|
||||
@@ -329,44 +402,60 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
],
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
body: Stack(
|
||||
children: [
|
||||
// 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트)
|
||||
TaskProgressPanel(
|
||||
progress: state.progress,
|
||||
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
|
||||
onSpeedCycle: () {
|
||||
widget.controller.loop?.cycleSpeed();
|
||||
setState(() {});
|
||||
},
|
||||
isPaused: !widget.controller.isRunning,
|
||||
onPauseToggle: () async {
|
||||
await widget.controller.togglePause();
|
||||
setState(() {});
|
||||
},
|
||||
specialAnimation: _specialAnimation,
|
||||
weaponName: state.equipment.weapon,
|
||||
shieldName: state.equipment.shield,
|
||||
characterLevel: state.traits.level,
|
||||
monsterLevel: state.progress.currentTask.monsterLevel,
|
||||
// 메인 게임 UI
|
||||
Column(
|
||||
children: [
|
||||
// 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트)
|
||||
TaskProgressPanel(
|
||||
progress: state.progress,
|
||||
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
|
||||
onSpeedCycle: () {
|
||||
widget.controller.loop?.cycleSpeed();
|
||||
setState(() {});
|
||||
},
|
||||
isPaused: !widget.controller.isRunning,
|
||||
onPauseToggle: () async {
|
||||
await widget.controller.togglePause();
|
||||
setState(() {});
|
||||
},
|
||||
specialAnimation: _specialAnimation,
|
||||
weaponName: state.equipment.weapon,
|
||||
shieldName: state.equipment.shield,
|
||||
characterLevel: state.traits.level,
|
||||
monsterLevel: state.progress.currentTask.monsterLevel,
|
||||
latestCombatEvent: state.progress.currentCombat?.recentEvents.lastOrNull,
|
||||
),
|
||||
|
||||
// 메인 3패널 영역
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 좌측 패널: Character Sheet
|
||||
Expanded(flex: 2, child: _buildCharacterPanel(state)),
|
||||
|
||||
// 중앙 패널: Equipment/Inventory
|
||||
Expanded(flex: 3, child: _buildEquipmentPanel(state)),
|
||||
|
||||
// 우측 패널: Plot/Quest
|
||||
Expanded(flex: 2, child: _buildQuestPanel(state)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 메인 3패널 영역
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 좌측 패널: Character Sheet
|
||||
Expanded(flex: 2, child: _buildCharacterPanel(state)),
|
||||
|
||||
// 중앙 패널: Equipment/Inventory
|
||||
Expanded(flex: 3, child: _buildEquipmentPanel(state)),
|
||||
|
||||
// 우측 패널: Plot/Quest
|
||||
Expanded(flex: 2, child: _buildQuestPanel(state)),
|
||||
],
|
||||
// Phase 4: 사망 오버레이 (Death Overlay)
|
||||
if (state.isDead && state.deathInfo != null)
|
||||
DeathOverlay(
|
||||
deathInfo: state.deathInfo!,
|
||||
traits: state.traits,
|
||||
onResurrect: () async {
|
||||
await widget.controller.resurrect();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -392,12 +481,22 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_buildSectionHeader(l10n.stats),
|
||||
Expanded(flex: 2, child: StatsPanel(stats: state.stats)),
|
||||
|
||||
// Phase 8: HP/MP 바 (사망 위험 시 깜빡임)
|
||||
// Phase 8: HP/MP 바 (사망 위험 시 깜빡임, 전투 중 몬스터 HP 표시)
|
||||
// 전투 중에는 전투 스탯의 HP/MP 사용, 비전투 시 기본 스탯 사용
|
||||
HpMpBar(
|
||||
hpCurrent: state.stats.hp,
|
||||
hpMax: state.stats.hpMax,
|
||||
mpCurrent: state.stats.mp,
|
||||
mpMax: state.stats.mpMax,
|
||||
hpCurrent: state.progress.currentCombat?.playerStats.hpCurrent ??
|
||||
state.stats.hp,
|
||||
hpMax: state.progress.currentCombat?.playerStats.hpMax ??
|
||||
state.stats.hpMax,
|
||||
mpCurrent: state.progress.currentCombat?.playerStats.mpCurrent ??
|
||||
state.stats.mp,
|
||||
mpMax: state.progress.currentCombat?.playerStats.mpMax ??
|
||||
state.stats.mpMax,
|
||||
// 전투 중일 때 몬스터 HP 정보 전달
|
||||
monsterHpCurrent:
|
||||
state.progress.currentCombat?.monsterStats.hpCurrent,
|
||||
monsterHpMax: state.progress.currentCombat?.monsterStats.hpMax,
|
||||
monsterName: state.progress.currentCombat?.monsterStats.name,
|
||||
),
|
||||
|
||||
// Experience 바
|
||||
|
||||
Reference in New Issue
Block a user