feat(game): 포션 시스템 및 UI 패널 추가

- 포션 시스템 구현 (PotionService, Potion 모델)
- 포션 인벤토리 패널 위젯
- 활성 버프 패널 위젯
- 장비 스탯 패널 위젯
- 스킬 시스템 확장
- 일본어 번역 추가
- 전투 이벤트/상태 모델 개선
This commit is contained in:
JiWoong Sul
2025-12-21 23:53:27 +09:00
parent eb71d2a199
commit 7cd8be88df
25 changed files with 5174 additions and 261 deletions

View File

@@ -0,0 +1,238 @@
import 'package:flutter/material.dart';
import 'package:askiineverdie/data/potion_data.dart';
import 'package:askiineverdie/src/core/model/potion.dart';
/// 물약 인벤토리 패널
///
/// 보유 중인 물약 목록과 수량을 표시.
/// HP 물약은 빨간색, MP 물약은 파란색으로 구분.
class PotionInventoryPanel extends StatelessWidget {
const PotionInventoryPanel({
super.key,
required this.inventory,
this.usedInBattle = const {},
});
final PotionInventory inventory;
final Set<PotionType> usedInBattle;
@override
Widget build(BuildContext context) {
final potionEntries = _buildPotionEntries();
if (potionEntries.isEmpty) {
return const Center(
child: Text(
'No potions',
style: TextStyle(
fontSize: 11,
color: Colors.grey,
fontStyle: FontStyle.italic,
),
),
);
}
return ListView.builder(
itemCount: potionEntries.length,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
itemBuilder: (context, index) {
final entry = potionEntries[index];
return _PotionRow(
potion: entry.potion,
quantity: entry.quantity,
isUsedThisBattle: usedInBattle.contains(entry.potion.type),
);
},
);
}
/// 물약 엔트리 목록 생성
///
/// HP 물약 먼저, MP 물약 나중에 정렬
List<_PotionEntry> _buildPotionEntries() {
final entries = <_PotionEntry>[];
for (final potionId in inventory.potions.keys) {
final quantity = inventory.potions[potionId] ?? 0;
if (quantity <= 0) continue;
final potion = PotionData.getById(potionId);
if (potion == null) continue;
entries.add(_PotionEntry(potion: potion, quantity: quantity));
}
// HP 물약 우선, 같은 타입 내에서는 티어순
entries.sort((a, b) {
final typeCompare = a.potion.type.index.compareTo(b.potion.type.index);
if (typeCompare != 0) return typeCompare;
return a.potion.tier.compareTo(b.potion.tier);
});
return entries;
}
}
/// 물약 엔트리
class _PotionEntry {
const _PotionEntry({required this.potion, required this.quantity});
final Potion potion;
final int quantity;
}
/// 물약 행 위젯
class _PotionRow extends StatelessWidget {
const _PotionRow({
required this.potion,
required this.quantity,
this.isUsedThisBattle = false,
});
final Potion potion;
final int quantity;
final bool isUsedThisBattle;
@override
Widget build(BuildContext context) {
final color = _getPotionColor();
final opacity = isUsedThisBattle ? 0.5 : 1.0;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Opacity(
opacity: opacity,
child: Row(
children: [
// 물약 아이콘
_PotionIcon(type: potion.type, tier: potion.tier),
const SizedBox(width: 4),
// 물약 이름
Expanded(
child: Text(
potion.name,
style: TextStyle(
fontSize: 11,
color: color,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
// 회복량 표시
_HealBadge(potion: potion),
const SizedBox(width: 4),
// 수량
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'x$quantity',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: color,
),
),
),
// 전투 중 사용 불가 표시
if (isUsedThisBattle) ...[
const SizedBox(width: 4),
const Icon(
Icons.block,
size: 12,
color: Colors.grey,
),
],
],
),
),
);
}
Color _getPotionColor() {
return switch (potion.type) {
PotionType.hp => Colors.red.shade700,
PotionType.mp => Colors.blue.shade700,
};
}
}
/// 물약 아이콘
class _PotionIcon extends StatelessWidget {
const _PotionIcon({required this.type, required this.tier});
final PotionType type;
final int tier;
@override
Widget build(BuildContext context) {
final color = type == PotionType.hp
? Colors.red.shade400
: Colors.blue.shade400;
// 티어에 따른 아이콘 크기 조절
final size = 12.0 + tier * 1.0;
return Container(
width: 18,
height: 18,
alignment: Alignment.center,
child: Icon(
type == PotionType.hp ? Icons.favorite : Icons.bolt,
size: size.clamp(12, 18),
color: color,
),
);
}
}
/// 회복량 배지
class _HealBadge extends StatelessWidget {
const _HealBadge({required this.potion});
final Potion potion;
@override
Widget build(BuildContext context) {
final healText = _buildHealText();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(4),
),
child: Text(
healText,
style: TextStyle(
fontSize: 9,
color: Colors.grey.shade700,
),
),
);
}
String _buildHealText() {
final parts = <String>[];
if (potion.healAmount > 0) {
parts.add('+${potion.healAmount}');
}
if (potion.healPercent > 0) {
final percent = (potion.healPercent * 100).round();
parts.add('+$percent%');
}
return parts.join(' ');
}
}