Compare commits

..

2 Commits

Author SHA1 Message Date
JiWoong Sul
93f29f6c33 feat(ui): 사망 화면 잃은 아이템 희귀도 색상 표시
- DeathInfo에 lostItemRarity 필드 추가
- 사망 처리 시 아이템 희귀도 저장
- 사망 오버레이에서 희귀도별 색상 적용
  - Common: 회색, Uncommon: 녹색, Rare: 파랑
  - Epic: 보라, Legendary: 주황
2026-01-16 00:13:24 +09:00
JiWoong Sul
a2b5bb7dc0 refactor(ui): 물약 글로벌 쿨타임 적용 UI 정리
- usedPotionTypes/usedInBattle 파라미터 제거
- 전투당 타입별 제한 → 시간 기반 쿨타임 전환
- PotionInventoryPanel 불투명도 로직 제거
2026-01-16 00:12:43 +09:00
8 changed files with 78 additions and 68 deletions

View File

@@ -7,6 +7,7 @@ import 'package:asciineverdie/src/core/model/combat_state.dart';
import 'package:asciineverdie/src/core/model/combat_stats.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
import 'package:asciineverdie/src/core/model/potion.dart';
import 'package:asciineverdie/src/core/model/skill.dart';
import 'package:asciineverdie/src/core/util/deterministic_random.dart';

View File

@@ -16,6 +16,7 @@ import 'package:asciineverdie/src/core/model/combat_stats.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/game_state.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';
import 'package:asciineverdie/src/core/model/monster_combat_stats.dart';
import 'package:asciineverdie/src/core/model/monster_grade.dart';
import 'package:asciineverdie/src/core/model/potion.dart';
@@ -970,6 +971,7 @@ class ProgressService {
var lostCount = 0;
String? lostItemName;
EquipmentSlot? lostItemSlot;
ItemRarity? lostItemRarity;
if (!isBossDeath) {
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
@@ -999,6 +1001,7 @@ class ProgressService {
final lostItem = state.equipment.getItemByIndex(sacrificeIndex);
lostItemName = lostItem.name;
lostItemSlot = EquipmentSlot.values[sacrificeIndex];
lostItemRarity = lostItem.rarity;
// 해당 슬롯을 빈 장비로 교체
newEquipment = newEquipment.setItemByIndex(
@@ -1015,6 +1018,7 @@ class ProgressService {
lostEquipmentCount: lostCount,
lostItemName: lostItemName,
lostItemSlot: lostItemSlot,
lostItemRarity: lostItemRarity,
goldAtDeath: state.inventory.gold,
levelAtDeath: state.traits.level,
timestamp: state.skillSystem.elapsedMs,

View File

@@ -132,6 +132,7 @@ class DeathInfo {
required this.timestamp,
this.lostItemName,
this.lostItemSlot,
this.lostItemRarity,
this.lastCombatEvents = const [],
});
@@ -150,6 +151,9 @@ class DeathInfo {
/// 제물로 바친 아이템 슬롯 (null이면 없음)
final EquipmentSlot? lostItemSlot;
/// 제물로 바친 아이템 희귀도 (null이면 없음)
final ItemRarity? lostItemRarity;
/// 사망 시점 골드
final int goldAtDeath;
@@ -168,6 +172,7 @@ class DeathInfo {
int? lostEquipmentCount,
String? lostItemName,
EquipmentSlot? lostItemSlot,
ItemRarity? lostItemRarity,
int? goldAtDeath,
int? levelAtDeath,
int? timestamp,
@@ -179,6 +184,7 @@ class DeathInfo {
lostEquipmentCount: lostEquipmentCount ?? this.lostEquipmentCount,
lostItemName: lostItemName ?? this.lostItemName,
lostItemSlot: lostItemSlot ?? this.lostItemSlot,
lostItemRarity: lostItemRarity ?? this.lostItemRarity,
goldAtDeath: goldAtDeath ?? this.goldAtDeath,
levelAtDeath: levelAtDeath ?? this.levelAtDeath,
timestamp: timestamp ?? this.timestamp,

View File

@@ -935,8 +935,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
Expanded(
child: PotionInventoryPanel(
inventory: state.potionInventory,
usedInBattle:
state.progress.currentCombat?.usedPotionTypes ?? const {},
),
),

View File

@@ -754,9 +754,6 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
inventory: state.inventory,
potionInventory: state.potionInventory,
encumbrance: state.progress.encumbrance,
usedPotionTypes:
state.progress.currentCombat?.usedPotionTypes ??
const {},
),
// 2: 장비

View File

@@ -16,13 +16,11 @@ class InventoryPage extends StatelessWidget {
required this.inventory,
required this.potionInventory,
required this.encumbrance,
this.usedPotionTypes = const {},
});
final Inventory inventory;
final PotionInventory potionInventory;
final ProgressBarState encumbrance;
final Set<PotionType> usedPotionTypes;
@override
Widget build(BuildContext context) {
@@ -41,7 +39,6 @@ class InventoryPage extends StatelessWidget {
flex: 2,
child: PotionInventoryPanel(
inventory: potionInventory,
usedInBattle: usedPotionTypes,
),
),

View File

@@ -5,6 +5,7 @@ import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/combat_event.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart';
/// 사망 오버레이 위젯 (Phase 4)
@@ -294,10 +295,8 @@ class DeathOverlay extends StatelessWidget {
final expColor = RetroColors.expOf(context);
final gold = RetroColors.goldOf(context);
// 슬롯명 + 아이템명 조합
final lostItemDisplay = hasLostItem
? '[${_getSlotName(deathInfo.lostItemSlot)}] ${deathInfo.lostItemName}'
: null;
// 희귀도에 따른 아이템 이름 색상
final itemRarityColor = _getRarityColor(deathInfo.lostItemRarity);
return Column(
children: [
@@ -326,12 +325,23 @@ class DeathOverlay extends StatelessWidget {
),
),
const SizedBox(height: 4),
Text(
lostItemDisplay!,
style: TextStyle(
// 슬롯명 (회색) + 아이템명 (희귀도 색상)
Text.rich(
TextSpan(
children: [
TextSpan(
text: '[${_getSlotName(deathInfo.lostItemSlot)}] ',
style: TextStyle(color: muted),
),
TextSpan(
text: deathInfo.lostItemName!,
style: TextStyle(color: itemRarityColor),
),
],
),
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 13,
color: hpColor,
),
),
],
@@ -695,4 +705,16 @@ class DeathOverlay extends StatelessWidget {
EquipmentSlot.sollerets => l10n.slotSollerets,
};
}
/// 희귀도에 따른 색상 반환
Color _getRarityColor(ItemRarity? rarity) {
if (rarity == null) return Colors.grey;
return switch (rarity) {
ItemRarity.common => Colors.grey,
ItemRarity.uncommon => Colors.green,
ItemRarity.rare => Colors.blue,
ItemRarity.epic => Colors.purple,
ItemRarity.legendary => Colors.orange,
};
}
}

View File

@@ -12,11 +12,9 @@ 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) {
@@ -43,7 +41,6 @@ class PotionInventoryPanel extends StatelessWidget {
return _PotionRow(
potion: entry.potion,
quantity: entry.quantity,
isUsedThisBattle: usedInBattle.contains(entry.potion.type),
);
},
);
@@ -88,69 +85,57 @@ 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),
child: Row(
children: [
// 물약 아이콘
_PotionIcon(type: potion.type, tier: potion.tier),
const SizedBox(width: 4),
// 물약 이름 (번역 적용)
Expanded(
child: Text(
l10n.translateSpell(potion.name),
style: TextStyle(
fontSize: 16,
color: color,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
// 물약 이름 (번역 적용)
Expanded(
child: Text(
l10n.translateSpell(potion.name),
style: TextStyle(
fontSize: 16,
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: 15,
fontWeight: FontWeight.bold,
color: color,
),
),
// 회복량 표시
_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: 15,
fontWeight: FontWeight.bold,
color: color,
),
),
),
// 전투 중 사용 불가 표시
if (isUsedThisBattle) ...[
const SizedBox(width: 4),
const Icon(Icons.block, size: 12, color: Colors.grey),
],
],
),
),
],
),
);
}