import 'dart:async'; 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/features/game/widgets/combat_log.dart'; /// 아레나 전투 상태 (Arena Battle State) /// /// 컨트롤러가 관리하는 전투 상태 스냅샷 class ArenaBattleState { ArenaBattleState({ required this.currentTurn, required this.challengerHp, required this.challengerHpMax, required this.challengerMp, required this.challengerMpMax, required this.opponentHp, required this.opponentHpMax, required this.opponentMp, required this.opponentMpMax, required this.battleLog, required this.isFinished, this.result, this.latestCombatEvent, this.currentEventIcon, this.currentSkillName, this.challengerHpChange = 0, this.opponentHpChange = 0, this.battleStartTime, }); final int currentTurn; final int challengerHp; final int challengerHpMax; final int challengerMp; final int challengerMpMax; final int opponentHp; final int opponentHpMax; final int opponentMp; final int opponentMpMax; final List battleLog; final bool isFinished; final ArenaMatchResult? result; final CombatEvent? latestCombatEvent; final CombatEventType? currentEventIcon; final String? currentSkillName; final int challengerHpChange; final int opponentHpChange; final DateTime? battleStartTime; } /// 아레나 전투 컨트롤러 (Arena Battle Controller) /// /// 전투 시뮬레이션 스트림 구독, 턴 처리, 로그 생성을 담당 class ArenaBattleController { ArenaBattleController({required this.match}); final ArenaMatch match; final ArenaService _arenaService = ArenaService(); // 상태 (State) int _currentTurn = 0; DateTime? _battleStartTime; late int _challengerHp; late int _challengerHpMax; late int _challengerMp; late int _challengerMpMax; late int _opponentHp; late int _opponentHpMax; late int _opponentMp; late int _opponentMpMax; final List _battleLog = []; ArenaMatchResult? _result; CombatEvent? _latestCombatEvent; CombatEventType? _currentEventIcon; String? _currentSkillName; int _challengerHpChange = 0; int _opponentHpChange = 0; bool _isFinished = false; StreamSubscription? _combatSubscription; Timer? _eventIconTimer; /// 상태 변경 콜백 (setState 대체) void Function()? onStateChanged; /// HP 변화 콜백 (애니메이션 트리거용) /// challenger: true = 도전자, false = 상대 void Function(bool challenger)? onHpChanged; /// 현재 상태 스냅샷 ArenaBattleState get state => ArenaBattleState( currentTurn: _currentTurn, challengerHp: _challengerHp, challengerHpMax: _challengerHpMax, challengerMp: _challengerMp, challengerMpMax: _challengerMpMax, opponentHp: _opponentHp, opponentHpMax: _opponentHpMax, opponentMp: _opponentMp, opponentMpMax: _opponentMpMax, battleLog: _battleLog, isFinished: _isFinished, result: _result, latestCombatEvent: _latestCombatEvent, currentEventIcon: _currentEventIcon, currentSkillName: _currentSkillName, challengerHpChange: _challengerHpChange, opponentHpChange: _opponentHpChange, battleStartTime: _battleStartTime, ); /// HP/MP 초기화 void initialize() { _challengerHpMax = match.challenger.finalStats?.hpMax ?? 100; _challengerHp = _challengerHpMax; _challengerMpMax = match.challenger.finalStats?.mpMax ?? 50; _challengerMp = _challengerMpMax; _opponentHpMax = match.opponent.finalStats?.hpMax ?? 100; _opponentHp = _opponentHpMax; _opponentMpMax = match.opponent.finalStats?.mpMax ?? 50; _opponentMp = _opponentMpMax; } /// 전투 시작 void startBattle() { _battleStartTime = DateTime.now(); _combatSubscription = _arenaService .simulateCombat(match) .listen(_processTurn, onDone: _endBattle); } /// 턴 처리 (Turn Processing) void _processTurn(ArenaCombatTurn turn) { final oldChallengerHp = _challengerHp; final oldOpponentHp = _opponentHp; _currentTurn++; _challengerHp = turn.challengerHp; _opponentHp = turn.opponentHp; _challengerMp = turn.challengerMp ?? _challengerMp; _opponentMp = turn.opponentMp ?? _opponentMp; // 도전자 HP 변화 감지 if (oldChallengerHp != _challengerHp) { _challengerHpChange = _challengerHp - oldChallengerHp; onHpChanged?.call(true); } // 상대 HP 변화 감지 if (oldOpponentHp != _opponentHp) { _opponentHpChange = _opponentHp - oldOpponentHp; onHpChanged?.call(false); } // 전투 로그 생성 _addTurnLogs(turn); // 전투 이벤트 생성 (테두리 이펙트용) _latestCombatEvent = _createCombatEvent(turn); // 전투 이벤트 아이콘 표시 _showEventIcon(turn); onStateChanged?.call(); } /// 턴 로그 생성 (Turn Log Generation) void _addTurnLogs(ArenaCombatTurn turn) { final challengerName = match.challenger.characterName; final opponentName = match.opponent.characterName; // 도전자 스킬 사용 로그 if (turn.challengerSkillUsed != null) { _battleLog.add( CombatLogEntry( message: '$challengerName uses ${turn.challengerSkillUsed}!', timestamp: DateTime.now(), type: CombatLogType.skill, ), ); } // 도전자 회복 로그 if (turn.challengerHealAmount != null && turn.challengerHealAmount! > 0) { _battleLog.add( CombatLogEntry( message: '$challengerName heals ${turn.challengerHealAmount} HP!', timestamp: DateTime.now(), type: CombatLogType.heal, ), ); } // 도전자 데미지 로그 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: '$challengerName deals ${turn.challengerDamage}' '$critText$skillText', timestamp: DateTime.now(), type: type, ), ); } // 상대 회피/블록 이벤트 if (turn.isOpponentEvaded) { _battleLog.add( CombatLogEntry( message: '$opponentName evaded!', timestamp: DateTime.now(), type: CombatLogType.evade, ), ); } if (turn.isOpponentBlocked) { _battleLog.add( CombatLogEntry( message: '$opponentName blocked!', timestamp: DateTime.now(), type: CombatLogType.block, ), ); } // 상대 스킬 사용 로그 if (turn.opponentSkillUsed != null) { _battleLog.add( CombatLogEntry( message: '$opponentName uses ${turn.opponentSkillUsed}!', timestamp: DateTime.now(), type: CombatLogType.skill, ), ); } // 상대 회복 로그 if (turn.opponentHealAmount != null && turn.opponentHealAmount! > 0) { _battleLog.add( CombatLogEntry( message: '$opponentName 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: '$opponentName deals ${turn.opponentDamage}$critText', timestamp: DateTime.now(), type: type, ), ); } // 도전자 회피/블록 이벤트 if (turn.isChallengerEvaded) { _battleLog.add( CombatLogEntry( message: '$challengerName evaded!', timestamp: DateTime.now(), type: CombatLogType.evade, ), ); } if (turn.isChallengerBlocked) { _battleLog.add( CombatLogEntry( message: '$challengerName blocked!', timestamp: DateTime.now(), type: CombatLogType.block, ), ); } } /// 전투 이벤트 아이콘 표시 (일정 시간 후 사라짐) 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; // 800ms 후 아이콘 숨김 _eventIconTimer = Timer(const Duration(milliseconds: 800), () { _currentEventIcon = null; _currentSkillName = null; onStateChanged?.call(); }); } /// ArenaCombatTurn에서 CombatEvent 생성 (테두리 이펙트용) CombatEvent? _createCombatEvent(ArenaCombatTurn turn) { final timestamp = DateTime.now().millisecondsSinceEpoch; final challengerName = match.challenger.characterName; final opponentName = match.opponent.characterName; // 도전자 스킬 사용 (보라색 테두리) if (turn.challengerSkillUsed != null && turn.challengerDamage != null) { return CombatEvent.playerSkill( timestamp: timestamp, skillName: turn.challengerSkillUsed!, damage: turn.challengerDamage!, targetName: opponentName, isCritical: turn.isChallengerCritical, ); } // 도전자 공격 이벤트 if (turn.challengerDamage != null) { return CombatEvent.playerAttack( timestamp: timestamp, damage: turn.challengerDamage!, targetName: opponentName, 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: opponentName, ); } if (turn.isChallengerBlocked) { return CombatEvent.playerBlock( timestamp: timestamp, reducedDamage: turn.opponentDamage ?? 0, attackerName: opponentName, ); } // 상대 공격 이벤트 if (turn.opponentDamage != null) { return CombatEvent.monsterAttack( timestamp: timestamp, damage: turn.opponentDamage!, attackerName: challengerName, ); } return null; } /// 전투 종료 처리 void _endBattle() { _result = _arenaService.createResultFromSimulation( match: match, challengerHp: _challengerHp, opponentHp: _opponentHp, turns: _currentTurn, ); _isFinished = true; onStateChanged?.call(); } /// 리소스 해제 void dispose() { _combatSubscription?.cancel(); _eventIconTimer?.cancel(); } }