feat(game): 포션 시스템 및 UI 패널 추가
- 포션 시스템 구현 (PotionService, Potion 모델) - 포션 인벤토리 패널 위젯 - 활성 버프 패널 위젯 - 장비 스탯 패널 위젯 - 스킬 시스템 확장 - 일본어 번역 추가 - 전투 이벤트/상태 모델 개선
This commit is contained in:
238
lib/src/features/game/widgets/potion_inventory_panel.dart
Normal file
238
lib/src/features/game/widgets/potion_inventory_panel.dart
Normal 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(' ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user