import 'dart:async'; import 'package:flutter/material.dart'; import 'package:asciineverdie/src/core/engine/arena_service.dart'; import 'package:asciineverdie/src/core/model/arena_match.dart'; import 'package:asciineverdie/src/core/model/combat_event.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; import 'package:asciineverdie/src/core/animation/race_character_frames.dart'; import 'package:asciineverdie/src/features/arena/widgets/arena_combat_log.dart'; import 'package:asciineverdie/src/features/arena/widgets/arena_result_panel.dart'; import 'package:asciineverdie/src/shared/widgets/ascii_disintegrate_widget.dart'; import 'package:asciineverdie/src/features/game/widgets/ascii_animation_card.dart'; import 'package:asciineverdie/src/features/game/widgets/combat_log.dart'; import 'package:asciineverdie/src/shared/retro_colors.dart'; // 임시 문자열 (추후 l10n으로 이동) const _battleTitle = 'ARENA BATTLE'; const _hpLabel = 'HP'; /// 아레나 전투 화면 /// /// ASCII 애니메이션 기반 턴제 전투 표시 /// 레트로 RPG 스타일 HP 바 (세그먼트) class ArenaBattleScreen extends StatefulWidget { const ArenaBattleScreen({ super.key, required this.match, required this.onBattleComplete, }); /// 대전 정보 final ArenaMatch match; /// 전투 완료 콜백 final void Function(ArenaMatchResult) onBattleComplete; @override State createState() => _ArenaBattleScreenState(); } class _ArenaBattleScreenState extends State with TickerProviderStateMixin { final ArenaService _arenaService = ArenaService(); /// 현재 턴 int _currentTurn = 0; /// 전투 시작 시간 (경과 시간 계산용) DateTime? _battleStartTime; /// 도전자 HP/MP late int _challengerHp; late int _challengerHpMax; late int _challengerMp; late int _challengerMpMax; /// 상대 HP/MP late int _opponentHp; late int _opponentHpMax; late int _opponentMp; late int _opponentMpMax; /// 전투 로그 (CombatLogEntry 사용) final List _battleLog = []; /// 전투 시뮬레이션 스트림 구독 StreamSubscription? _combatSubscription; /// 최종 결과 ArenaMatchResult? _result; // HP 변화 애니메이션 late AnimationController _challengerFlashController; late AnimationController _opponentFlashController; late Animation _challengerFlashAnimation; late Animation _opponentFlashAnimation; // 변화량 표시용 int _challengerHpChange = 0; int _opponentHpChange = 0; /// 최신 전투 이벤트 (테두리 이펙트용) CombatEvent? _latestCombatEvent; /// 전투 이벤트 아이콘 타이머 (페이드 아웃용) Timer? _eventIconTimer; /// 현재 표시 중인 이벤트 아이콘 타입 CombatEventType? _currentEventIcon; /// 현재 표시 중인 스킬 이름 String? _currentSkillName; /// 전투 종료 여부 (결과 패널 표시용) bool _isFinished = false; @override void initState() { super.initState(); // HP/MP 초기화 _challengerHpMax = widget.match.challenger.finalStats?.hpMax ?? 100; _challengerHp = _challengerHpMax; _challengerMpMax = widget.match.challenger.finalStats?.mpMax ?? 50; _challengerMp = _challengerMpMax; _opponentHpMax = widget.match.opponent.finalStats?.hpMax ?? 100; _opponentHp = _opponentHpMax; _opponentMpMax = widget.match.opponent.finalStats?.mpMax ?? 50; _opponentMp = _opponentMpMax; // 플래시 애니메이션 초기화 _challengerFlashController = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, ); _challengerFlashAnimation = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation( parent: _challengerFlashController, curve: Curves.easeOut, ), ); _opponentFlashController = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, ); _opponentFlashAnimation = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _opponentFlashController, curve: Curves.easeOut), ); // 전투 시작 (딜레이 후) Future.delayed(const Duration(milliseconds: 500), _startBattle); } @override void dispose() { _combatSubscription?.cancel(); _eventIconTimer?.cancel(); _challengerFlashController.dispose(); _opponentFlashController.dispose(); super.dispose(); } void _startBattle() { _battleStartTime = DateTime.now(); _combatSubscription = _arenaService .simulateCombat(widget.match) .listen( (turn) { _processTurn(turn); }, onDone: () { _endBattle(); }, ); } void _processTurn(ArenaCombatTurn turn) { final oldChallengerHp = _challengerHp; final oldOpponentHp = _opponentHp; setState(() { _currentTurn++; _challengerHp = turn.challengerHp; _opponentHp = turn.opponentHp; _challengerMp = turn.challengerMp ?? _challengerMp; _opponentMp = turn.opponentMp ?? _opponentMp; // 도전자 HP 변화 감지 if (oldChallengerHp != _challengerHp) { _challengerHpChange = _challengerHp - oldChallengerHp; _challengerFlashController.forward(from: 0.0); } // 상대 HP 변화 감지 if (oldOpponentHp != _opponentHp) { _opponentHpChange = _opponentHp - oldOpponentHp; _opponentFlashController.forward(from: 0.0); } // 도전자 스킬 사용 로그 if (turn.challengerSkillUsed != null) { _battleLog.add( CombatLogEntry( message: '${widget.match.challenger.characterName} uses ' '${turn.challengerSkillUsed}!', timestamp: DateTime.now(), type: CombatLogType.skill, ), ); } // 도전자 회복 로그 if (turn.challengerHealAmount != null && turn.challengerHealAmount! > 0) { _battleLog.add( CombatLogEntry( message: '${widget.match.challenger.characterName} heals ' '${turn.challengerHealAmount} HP!', timestamp: DateTime.now(), type: CombatLogType.heal, ), ); } // 로그 추가 (CombatLogEntry 사용) if (turn.challengerDamage != null) { final type = turn.isChallengerCritical ? CombatLogType.critical : CombatLogType.damage; final critText = turn.isChallengerCritical ? ' CRITICAL!' : ''; final skillText = turn.challengerSkillUsed != null ? '' : ''; _battleLog.add( CombatLogEntry( message: '${widget.match.challenger.characterName} deals ' '${turn.challengerDamage}$critText$skillText', timestamp: DateTime.now(), type: type, ), ); } // 상대 회피/블록 이벤트 if (turn.isOpponentEvaded) { _battleLog.add( CombatLogEntry( message: '${widget.match.opponent.characterName} evaded!', timestamp: DateTime.now(), type: CombatLogType.evade, ), ); } if (turn.isOpponentBlocked) { _battleLog.add( CombatLogEntry( message: '${widget.match.opponent.characterName} blocked!', timestamp: DateTime.now(), type: CombatLogType.block, ), ); } // 상대 스킬 사용 로그 if (turn.opponentSkillUsed != null) { _battleLog.add( CombatLogEntry( message: '${widget.match.opponent.characterName} uses ' '${turn.opponentSkillUsed}!', timestamp: DateTime.now(), type: CombatLogType.skill, ), ); } // 상대 회복 로그 if (turn.opponentHealAmount != null && turn.opponentHealAmount! > 0) { _battleLog.add( CombatLogEntry( message: '${widget.match.opponent.characterName} heals ' '${turn.opponentHealAmount} HP!', timestamp: DateTime.now(), type: CombatLogType.heal, ), ); } if (turn.opponentDamage != null) { final type = turn.isOpponentCritical ? CombatLogType.critical : CombatLogType.monsterAttack; final critText = turn.isOpponentCritical ? ' CRITICAL!' : ''; _battleLog.add( CombatLogEntry( message: '${widget.match.opponent.characterName} deals ' '${turn.opponentDamage}$critText', timestamp: DateTime.now(), type: type, ), ); } // 도전자 회피/블록 이벤트 if (turn.isChallengerEvaded) { _battleLog.add( CombatLogEntry( message: '${widget.match.challenger.characterName} evaded!', timestamp: DateTime.now(), type: CombatLogType.evade, ), ); } if (turn.isChallengerBlocked) { _battleLog.add( CombatLogEntry( message: '${widget.match.challenger.characterName} blocked!', timestamp: DateTime.now(), type: CombatLogType.block, ), ); } // 전투 이벤트 생성 (테두리 이펙트용) _latestCombatEvent = _createCombatEvent(turn); // 전투 이벤트 아이콘 표시 _showEventIcon(turn); }); } /// 전투 이벤트 아이콘 표시 (일정 시간 후 사라짐) void _showEventIcon(ArenaCombatTurn turn) { // 이전 타이머 취소 _eventIconTimer?.cancel(); // 스킬 이름 저장 _currentSkillName = turn.challengerSkillUsed ?? turn.opponentSkillUsed; // 이벤트 타입 결정 (우선순위: 스킬 > 크리티컬 > 블록 > 회피 > 일반공격) CombatEventType? eventType; if (_currentSkillName != null) { eventType = CombatEventType.playerSkill; } else if (turn.isChallengerCritical || turn.isOpponentCritical) { eventType = CombatEventType.playerAttack; // 크리티컬 표시용 } else if (turn.isChallengerBlocked || turn.isOpponentBlocked) { eventType = CombatEventType.playerBlock; } else if (turn.isChallengerEvaded || turn.isOpponentEvaded) { eventType = CombatEventType.playerEvade; } else if (turn.challengerDamage != null || turn.opponentDamage != null) { eventType = CombatEventType.playerAttack; } _currentEventIcon = eventType; // 1초 후 아이콘 숨김 _eventIconTimer = Timer(const Duration(milliseconds: 800), () { if (mounted) { setState(() { _currentEventIcon = null; _currentSkillName = null; }); } }); } /// ArenaCombatTurn에서 CombatEvent 생성 (테두리 이펙트용) CombatEvent? _createCombatEvent(ArenaCombatTurn turn) { final timestamp = DateTime.now().millisecondsSinceEpoch; // 도전자 스킬 사용 (보라색 테두리) if (turn.challengerSkillUsed != null && turn.challengerDamage != null) { return CombatEvent.playerSkill( timestamp: timestamp, skillName: turn.challengerSkillUsed!, damage: turn.challengerDamage!, targetName: widget.match.opponent.characterName, isCritical: turn.isChallengerCritical, ); } // 도전자 공격 이벤트 (우선순위: 크리티컬 > 일반 공격) if (turn.challengerDamage != null) { return CombatEvent.playerAttack( timestamp: timestamp, damage: turn.challengerDamage!, targetName: widget.match.opponent.characterName, isCritical: turn.isChallengerCritical, ); } // 도전자 회복 이벤트 if (turn.challengerHealAmount != null && turn.challengerSkillUsed != null) { return CombatEvent.playerHeal( timestamp: timestamp, healAmount: turn.challengerHealAmount!, skillName: turn.challengerSkillUsed, ); } // 도전자 방어 이벤트 (회피/블록) if (turn.isChallengerEvaded) { return CombatEvent.playerEvade( timestamp: timestamp, attackerName: widget.match.opponent.characterName, ); } if (turn.isChallengerBlocked) { return CombatEvent.playerBlock( timestamp: timestamp, reducedDamage: turn.opponentDamage ?? 0, attackerName: widget.match.opponent.characterName, ); } // 상대 공격 이벤트 (몬스터 공격으로 처리) if (turn.opponentDamage != null) { return CombatEvent.monsterAttack( timestamp: timestamp, damage: turn.opponentDamage!, attackerName: widget.match.opponent.characterName, ); } return null; } void _endBattle() { // 시뮬레이션 HP 결과를 기반으로 최종 결과 계산 _result = _arenaService.createResultFromSimulation( match: widget.match, challengerHp: _challengerHp, opponentHp: _opponentHp, turns: _currentTurn, ); // 전투 종료 상태로 전환 (인라인 결과 패널 표시) setState(() { _isFinished = true; }); } /// Continue 버튼 콜백 void _handleContinue() { if (_result != null) { widget.onBattleComplete(_result!); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: RetroColors.backgroundOf(context), appBar: AppBar( title: Text( _battleTitle, style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 12), ), centerTitle: true, backgroundColor: RetroColors.panelBgOf(context), automaticallyImplyLeading: false, ), body: SafeArea( child: Column( children: [ // 턴 표시 _buildTurnIndicator(), // HP 바 (레트로 세그먼트 스타일) _buildRetroHpBars(), // 전투 이벤트 아이콘 (HP 바와 애니메이션 사이) _buildCombatEventIcons(), // ASCII 애니메이션 (전투 중 / 종료 분기) _buildBattleArea(), // 로그 영역 (남은 공간 채움) Expanded(child: _buildBattleLog()), // 결과 패널 (전투 종료 시) if (_isFinished && _result != null) ArenaResultPanel( result: _result!, turnCount: _currentTurn, onContinue: _handleContinue, battleLog: _battleLog, ), ], ), ), ); } /// 방패 장착 여부 확인 bool _hasShield(HallOfFameEntry entry) { final equipment = entry.finalEquipment; if (equipment == null) return false; return equipment.any((item) => item.slot.name == 'shield'); } /// 전투 영역 (전투 중 / 종료 분기) Widget _buildBattleArea() { if (_isFinished && _result != null) { return _buildFinishedBattleArea(); } return _buildActiveBattleArea(); } /// 활성 전투 영역 (기존 AsciiAnimationCard) Widget _buildActiveBattleArea() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: SizedBox( height: 120, child: AsciiAnimationCard( taskType: TaskType.kill, raceId: widget.match.challenger.raceId, shieldName: _hasShield(widget.match.challenger) ? 'shield' : null, opponentRaceId: widget.match.opponent.raceId, opponentHasShield: _hasShield(widget.match.opponent), latestCombatEvent: _latestCombatEvent, ), ), ); } /// 종료된 전투 영역 (승자 유지 + 패자 분해) Widget _buildFinishedBattleArea() { final isVictory = _result!.isVictory; final winnerRaceId = isVictory ? widget.match.challenger.raceId : widget.match.opponent.raceId; final loserRaceId = isVictory ? widget.match.opponent.raceId : widget.match.challenger.raceId; // 패자 캐릭터 프레임 (idle 첫 프레임) final loserFrameData = RaceCharacterFrames.get(loserRaceId) ?? RaceCharacterFrames.defaultFrames; final loserLines = loserFrameData.idle.first.lines; // 승자 캐릭터 프레임 (idle 첫 프레임) final winnerFrameData = RaceCharacterFrames.get(winnerRaceId) ?? RaceCharacterFrames.defaultFrames; final winnerLines = winnerFrameData.idle.first.lines; return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: SizedBox( height: 120, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 좌측: 도전자 (승자면 유지, 패자면 분해) Expanded( child: Center( child: isVictory ? _buildStaticCharacter(winnerLines, false) : AsciiDisintegrateWidget( characterLines: _mirrorLines(loserLines), ), ), ), // 중앙 VS Text( 'VS', style: TextStyle( fontFamily: 'PressStart2P', fontSize: 10, color: RetroColors.goldOf(context).withValues(alpha: 0.5), ), ), // 우측: 상대 (승자면 유지, 패자면 분해) Expanded( child: Center( child: isVictory ? AsciiDisintegrateWidget(characterLines: loserLines) : _buildStaticCharacter(_mirrorLines(winnerLines), false), ), ), ], ), ), ); } /// 정적 ASCII 캐릭터 표시 Widget _buildStaticCharacter(List lines, bool mirrored) { final textColor = RetroColors.textPrimaryOf(context); return Column( mainAxisSize: MainAxisSize.min, children: lines .map( (line) => Text( line, style: TextStyle( fontFamily: 'JetBrainsMono', fontSize: 10, color: textColor, height: 1.2, ), ), ) .toList(), ); } /// ASCII 문자열 미러링 (좌우 대칭) List _mirrorLines(List lines) { return lines.map((line) { final chars = line.split(''); return chars.reversed.map(_mirrorChar).join(); }).toList(); } /// 개별 문자 미러링 String _mirrorChar(String char) { return switch (char) { '/' => r'\', r'\' => '/', '(' => ')', ')' => '(', '[' => ']', ']' => '[', '{' => '}', '}' => '{', '<' => '>', '>' => '<', 'd' => 'b', 'b' => 'd', 'q' => 'p', 'p' => 'q', _ => char, }; } Widget _buildTurnIndicator() { // 경과 시간 계산 (분:초 형식) String elapsedTime = '00:00'; if (_battleStartTime != null) { final elapsed = DateTime.now().difference(_battleStartTime!); final minutes = elapsed.inMinutes; final seconds = elapsed.inSeconds % 60; elapsedTime = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; } return Container( padding: const EdgeInsets.symmetric(vertical: 8), color: RetroColors.panelBgOf(context).withValues(alpha: 0.5), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.timer, color: RetroColors.goldOf(context), size: 16), const SizedBox(width: 8), Text( elapsedTime, style: TextStyle( fontFamily: 'PressStart2P', fontSize: 10, color: RetroColors.goldOf(context), ), ), ], ), ); } /// 레트로 스타일 HP 바 (좌우 대칭) Widget _buildRetroHpBars() { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( color: RetroColors.panelBgOf(context), border: Border( bottom: BorderSide(color: RetroColors.borderOf(context), width: 2), ), ), child: Row( children: [ // 도전자 HP (좌측, 파란색) Expanded( child: _buildRetroHpBar( name: widget.match.challenger.characterName, hp: _challengerHp, hpMax: _challengerHpMax, fillColor: RetroColors.mpBlue, accentColor: Colors.blue, flashAnimation: _challengerFlashAnimation, hpChange: _challengerHpChange, isReversed: false, ), ), // VS 구분자 Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( 'VS', style: TextStyle( fontFamily: 'PressStart2P', fontSize: 8, color: RetroColors.goldOf(context), fontWeight: FontWeight.bold, ), ), ), // 상대 HP (우측, 빨간색) Expanded( child: _buildRetroHpBar( name: widget.match.opponent.characterName, hp: _opponentHp, hpMax: _opponentHpMax, fillColor: RetroColors.hpRed, accentColor: Colors.red, flashAnimation: _opponentFlashAnimation, hpChange: _opponentHpChange, isReversed: true, ), ), ], ), ); } /// 레트로 세그먼트 HP 바 Widget _buildRetroHpBar({ required String name, required int hp, required int hpMax, required Color fillColor, required Color accentColor, required Animation flashAnimation, required int hpChange, required bool isReversed, }) { final hpRatio = hpMax > 0 ? hp / hpMax : 0.0; final isLow = hpRatio < 0.2 && hpRatio > 0; return AnimatedBuilder( animation: flashAnimation, builder: (context, child) { // 플래시 색상 (데미지=빨강) final isDamage = hpChange < 0; final flashColor = isDamage ? RetroColors.hpRed.withValues(alpha: flashAnimation.value * 0.4) : RetroColors.expGreen.withValues( alpha: flashAnimation.value * 0.4, ); return Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: flashAnimation.value > 0.1 ? flashColor : accentColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all(color: accentColor, width: 2), ), child: Stack( clipBehavior: Clip.none, children: [ Column( crossAxisAlignment: isReversed ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ // 이름 Text( name, style: TextStyle( fontFamily: 'PressStart2P', fontSize: 6, color: RetroColors.textPrimaryOf(context), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // HP 세그먼트 바 _buildSegmentBar( ratio: hpRatio, fillColor: fillColor, isLow: isLow, isReversed: isReversed, ), const SizedBox(height: 2), // HP 수치 Row( mainAxisAlignment: isReversed ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ Text( _hpLabel, style: TextStyle( fontFamily: 'PressStart2P', fontSize: 5, color: accentColor.withValues(alpha: 0.8), ), ), const SizedBox(width: 4), Text( '$hp/$hpMax', style: TextStyle( fontFamily: 'PressStart2P', fontSize: 6, color: isLow ? RetroColors.hpRed : fillColor, ), ), ], ), ], ), // 플로팅 데미지 텍스트 if (hpChange != 0 && flashAnimation.value > 0.05) Positioned( left: isReversed ? null : 0, right: isReversed ? 0 : null, top: -12, child: Transform.translate( offset: Offset(0, -12 * (1 - flashAnimation.value)), child: Opacity( opacity: flashAnimation.value, child: Text( hpChange > 0 ? '+$hpChange' : '$hpChange', style: TextStyle( fontFamily: 'PressStart2P', fontSize: 8, fontWeight: FontWeight.bold, color: isDamage ? RetroColors.hpRed : RetroColors.expGreen, shadows: const [ Shadow(color: Colors.black, blurRadius: 3), Shadow(color: Colors.black, blurRadius: 6), ], ), ), ), ), ), ], ), ); }, ); } /// 세그먼트 바 (8-bit 스타일) Widget _buildSegmentBar({ required double ratio, required Color fillColor, required bool isLow, required bool isReversed, }) { const segmentCount = 10; final filledSegments = (ratio.clamp(0.0, 1.0) * segmentCount).round(); final segments = List.generate(segmentCount, (index) { final isFilled = isReversed ? index >= segmentCount - filledSegments : index < filledSegments; return Expanded( child: Container( height: 8, decoration: BoxDecoration( color: isFilled ? (isLow ? RetroColors.hpRed : fillColor) : fillColor.withValues(alpha: 0.2), border: Border( right: index < segmentCount - 1 ? BorderSide( color: RetroColors.borderOf( context, ).withValues(alpha: 0.3), width: 1, ) : BorderSide.none, ), ), ), ); }); return Container( decoration: BoxDecoration( border: Border.all(color: RetroColors.borderOf(context), width: 1), ), child: Row(children: isReversed ? segments.reversed.toList() : segments), ); } Widget _buildBattleLog() { return Container( margin: const EdgeInsets.all(12), decoration: BoxDecoration( color: RetroColors.panelBgOf(context), borderRadius: BorderRadius.circular(8), border: Border.all(color: RetroColors.borderOf(context)), ), child: ArenaCombatLog(entries: _battleLog), ); } /// 전투 이벤트 아이콘 영역 (HP 바와 애니메이션 사이) /// /// 메인 게임의 _buildBuffIcons() 스타일을 따름 /// 스킬 사용, 크리티컬, 블록, 회피 표시 Widget _buildCombatEventIcons() { // 스킬 사용 또는 특수 액션만 표시 final hasSpecialEvent = _currentSkillName != null || _latestCombatEvent?.isCritical == true || _currentEventIcon == CombatEventType.playerBlock || _currentEventIcon == CombatEventType.playerEvade || _currentEventIcon == CombatEventType.playerParry || _currentEventIcon == CombatEventType.playerSkill; if (!hasSpecialEvent) { return const SizedBox(height: 28); } // 이벤트 타입에 따른 아이콘/색상 결정 final (icon, color) = _getEventIconData(); return AnimatedOpacity( opacity: _currentEventIcon != null ? 1.0 : 0.0, duration: const Duration(milliseconds: 200), child: SizedBox( height: 28, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 버프 아이콘 스타일 (CircularProgressIndicator) Stack( alignment: Alignment.center, children: [ // 원형 진행률 표시 (펄스 효과용) SizedBox( width: 24, height: 24, child: CircularProgressIndicator( value: 1.0, strokeWidth: 2, backgroundColor: Colors.grey.shade700, valueColor: AlwaysStoppedAnimation(color), ), ), // 아이콘 Icon(icon, size: 12, color: color), ], ), // 스킬 이름 표시 if (_currentSkillName != null) ...[ const SizedBox(width: 6), Text( _currentSkillName!, style: TextStyle( fontFamily: 'PressStart2P', fontSize: 6, color: color, ), ), ], ], ), ), ); } /// 이벤트 타입에 따른 아이콘, 색상 반환 (IconData, Color) _getEventIconData() { // 스킬 사용 if (_currentSkillName != null || _currentEventIcon == CombatEventType.playerSkill) { return (Icons.auto_fix_high, Colors.purple); } // 크리티컬 체크 (latestCombatEvent에서) if (_latestCombatEvent?.isCritical == true) { return (Icons.flash_on, Colors.yellow.shade600); } return switch (_currentEventIcon) { CombatEventType.playerBlock => (Icons.shield, Colors.blue), CombatEventType.playerEvade => (Icons.directions_run, Colors.cyan), CombatEventType.playerParry => (Icons.sports_kabaddi, Colors.purple), _ => (Icons.trending_up, Colors.lightBlue), }; } }