feat(ui): 아레나 결과 패널 및 애니메이션 카드 개선
This commit is contained in:
@@ -10,6 +10,7 @@ import 'package:asciineverdie/src/core/engine/item_service.dart';
|
|||||||
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
import 'package:asciineverdie/src/core/model/arena_match.dart';
|
||||||
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
import 'package:asciineverdie/src/core/model/equipment_item.dart';
|
||||||
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
|
||||||
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
import 'package:asciineverdie/src/core/model/item_stats.dart';
|
||||||
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
||||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||||
@@ -103,6 +104,9 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
final fileName = 'arena_${challenger}_vs_${opponent}_$timestamp.json';
|
final fileName = 'arena_${challenger}_vs_${opponent}_$timestamp.json';
|
||||||
final file = File('${directory.path}/$fileName');
|
final file = File('${directory.path}/$fileName');
|
||||||
|
|
||||||
|
// 전투 통계 계산
|
||||||
|
final stats = _calculateBattleStats();
|
||||||
|
|
||||||
final jsonData = {
|
final jsonData = {
|
||||||
'match': {
|
'match': {
|
||||||
'challenger': challenger,
|
'challenger': challenger,
|
||||||
@@ -111,6 +115,11 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
'turnCount': widget.turnCount,
|
'turnCount': widget.turnCount,
|
||||||
'timestamp': DateTime.now().toIso8601String(),
|
'timestamp': DateTime.now().toIso8601String(),
|
||||||
},
|
},
|
||||||
|
'characters': {
|
||||||
|
'challenger': _characterToJson(widget.result.match.challenger),
|
||||||
|
'opponent': _characterToJson(widget.result.match.opponent),
|
||||||
|
},
|
||||||
|
'stats': stats,
|
||||||
'battleLog': widget.battleLog!.map((e) => e.toJson()).toList(),
|
'battleLog': widget.battleLog!.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,6 +155,126 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 캐릭터 정보를 JSON으로 변환
|
||||||
|
Map<String, dynamic> _characterToJson(HallOfFameEntry entry) {
|
||||||
|
return {
|
||||||
|
'name': entry.characterName,
|
||||||
|
'level': entry.level,
|
||||||
|
'race': entry.race,
|
||||||
|
'class': entry.klass,
|
||||||
|
'combatStats': entry.finalStats?.toJson(),
|
||||||
|
'equipment': entry.finalEquipment
|
||||||
|
?.map((EquipmentItem e) => {
|
||||||
|
'slot': e.slot.name,
|
||||||
|
'name': e.name,
|
||||||
|
'level': e.level,
|
||||||
|
'rarity': e.rarity.name,
|
||||||
|
'stats': e.stats.toJson(),
|
||||||
|
})
|
||||||
|
.toList(),
|
||||||
|
'skills': entry.finalSkills,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 배틀 로그에서 전투 통계 계산
|
||||||
|
Map<String, dynamic> _calculateBattleStats() {
|
||||||
|
if (widget.battleLog == null || widget.battleLog!.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int challengerTotalDamage = 0;
|
||||||
|
int opponentTotalDamage = 0;
|
||||||
|
int challengerTotalHeal = 0;
|
||||||
|
int opponentTotalHeal = 0;
|
||||||
|
int challengerCriticals = 0;
|
||||||
|
int opponentCriticals = 0;
|
||||||
|
int challengerBlocks = 0;
|
||||||
|
int opponentBlocks = 0;
|
||||||
|
int challengerEvades = 0;
|
||||||
|
int opponentEvades = 0;
|
||||||
|
int challengerSkillsUsed = 0;
|
||||||
|
int opponentSkillsUsed = 0;
|
||||||
|
|
||||||
|
final challenger = widget.result.match.challenger.characterName;
|
||||||
|
|
||||||
|
for (final entry in widget.battleLog!) {
|
||||||
|
final msg = entry.message;
|
||||||
|
final isChallenger = msg.startsWith(challenger);
|
||||||
|
|
||||||
|
switch (entry.type) {
|
||||||
|
case CombatLogType.damage:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalDamage += dmg;
|
||||||
|
}
|
||||||
|
case CombatLogType.monsterAttack:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
opponentTotalDamage += dmg;
|
||||||
|
case CombatLogType.critical:
|
||||||
|
final dmg = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalDamage += dmg;
|
||||||
|
challengerCriticals++;
|
||||||
|
} else {
|
||||||
|
opponentTotalDamage += dmg;
|
||||||
|
opponentCriticals++;
|
||||||
|
}
|
||||||
|
case CombatLogType.heal:
|
||||||
|
final heal = _extractNumber(msg);
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerTotalHeal += heal;
|
||||||
|
} else {
|
||||||
|
opponentTotalHeal += heal;
|
||||||
|
}
|
||||||
|
case CombatLogType.block:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerBlocks++;
|
||||||
|
} else {
|
||||||
|
opponentBlocks++;
|
||||||
|
}
|
||||||
|
case CombatLogType.evade:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerEvades++;
|
||||||
|
} else {
|
||||||
|
opponentEvades++;
|
||||||
|
}
|
||||||
|
case CombatLogType.skill:
|
||||||
|
if (isChallenger) {
|
||||||
|
challengerSkillsUsed++;
|
||||||
|
} else {
|
||||||
|
opponentSkillsUsed++;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'challenger': {
|
||||||
|
'totalDamage': challengerTotalDamage,
|
||||||
|
'totalHeal': challengerTotalHeal,
|
||||||
|
'criticals': challengerCriticals,
|
||||||
|
'blocks': challengerBlocks,
|
||||||
|
'evades': challengerEvades,
|
||||||
|
'skillsUsed': challengerSkillsUsed,
|
||||||
|
},
|
||||||
|
'opponent': {
|
||||||
|
'totalDamage': opponentTotalDamage,
|
||||||
|
'totalHeal': opponentTotalHeal,
|
||||||
|
'criticals': opponentCriticals,
|
||||||
|
'blocks': opponentBlocks,
|
||||||
|
'evades': opponentEvades,
|
||||||
|
'skillsUsed': opponentSkillsUsed,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 메시지에서 숫자 추출
|
||||||
|
int _extractNumber(String msg) {
|
||||||
|
final match = RegExp(r'(\d+)').firstMatch(msg);
|
||||||
|
return match != null ? int.tryParse(match.group(1)!) ?? 0 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isVictory = widget.result.isVictory;
|
final isVictory = widget.result.isVictory;
|
||||||
|
|||||||
@@ -226,13 +226,11 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_showDeathAnimation = true;
|
_showDeathAnimation = true;
|
||||||
});
|
});
|
||||||
return; // 사망 애니메이션 중에는 다른 업데이트 무시
|
// 분해 애니메이션은 오버레이로 표시되므로
|
||||||
|
// 백그라운드 상태 업데이트는 계속 진행 (20배속 대응)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사망 애니메이션 중에는 다른 업데이트 무시
|
|
||||||
if (_showDeathAnimation) return;
|
|
||||||
|
|
||||||
// 전투 이벤트 동기화 (Phase 5)
|
// 전투 이벤트 동기화 (Phase 5)
|
||||||
if (widget.latestCombatEvent != null &&
|
if (widget.latestCombatEvent != null &&
|
||||||
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
||||||
@@ -253,7 +251,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
oldWidget.weaponRarity != widget.weaponRarity ||
|
oldWidget.weaponRarity != widget.weaponRarity ||
|
||||||
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
||||||
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
||||||
oldWidget.isInCombat != widget.isInCombat) {
|
oldWidget.isInCombat != widget.isInCombat ||
|
||||||
|
oldWidget.monsterDied != widget.monsterDied) {
|
||||||
_updateAnimation();
|
_updateAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user