feat(game): 게임 시스템 전면 개편 및 다국어 지원 확장
## 스킬 시스템 개선 - skill_data.dart: 스킬 데이터 구조 전면 개편 (+1176 라인) - skill_service.dart: 스킬 발동 로직 확장 및 버프 시스템 연동 - skill.dart: 스킬 모델 개선, 쿨다운/효과 타입 추가 ## Canvas 애니메이션 리팩토링 - battle_composer.dart 삭제 (레거시 위젯 기반 렌더러) - monster_colors.dart 삭제 (AsciiCell 색상 시스템으로 통합) - canvas_battle_composer.dart: z-index 정렬 (몬스터 z=1, 캐릭터 z=2, 이펙트 z=3) - ascii_cell.dart, ascii_layer.dart: 코드 정리 ## UI/UX 개선 - hp_mp_bar.dart: l10n 적용, 몬스터 HP 바 컴팩트화 - death_overlay.dart: 사망 화면 개선 - equipment_stats_panel.dart: 장비 스탯 표시 확장 - active_buff_panel.dart: 버프 패널 개선 - notification_overlay.dart: 알림 시스템 개선 ## 다국어 지원 확장 - game_text_l10n.dart: 게임 텍스트 통합 (+758 라인) - 한국어/일본어/영어/중국어 번역 업데이트 - ARB 파일 동기화 ## 게임 로직 개선 - progress_service.dart: 진행 로직 리팩토링 - combat_calculator.dart: 전투 계산 로직 개선 - stat_calculator.dart: 스탯 계산 시스템 개선 - story_service.dart: 스토리 진행 로직 개선 ## 기타 - theme_preferences.dart 삭제 (미사용) - 테스트 파일 업데이트 - class_data.dart: 클래스 데이터 정리
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/model/skill.dart';
|
||||
|
||||
/// 활성 버프 패널 위젯
|
||||
@@ -18,10 +19,10 @@ class ActiveBuffPanel extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (activeBuffs.isEmpty) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Text(
|
||||
'No active buffs',
|
||||
style: TextStyle(
|
||||
l10n.uiNoActiveBuffs,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey,
|
||||
fontStyle: FontStyle.italic,
|
||||
@@ -43,10 +44,7 @@ class ActiveBuffPanel extends StatelessWidget {
|
||||
|
||||
/// 개별 버프 행 위젯
|
||||
class _BuffRow extends StatelessWidget {
|
||||
const _BuffRow({
|
||||
required this.buff,
|
||||
required this.currentMs,
|
||||
});
|
||||
const _BuffRow({required this.buff, required this.currentMs});
|
||||
|
||||
final ActiveBuff buff;
|
||||
final int currentMs;
|
||||
@@ -66,11 +64,7 @@ class _BuffRow extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
// 버프 아이콘
|
||||
const Icon(
|
||||
Icons.trending_up,
|
||||
size: 14,
|
||||
color: Colors.lightBlue,
|
||||
),
|
||||
const Icon(Icons.trending_up, size: 14, color: Colors.lightBlue),
|
||||
const SizedBox(width: 4),
|
||||
|
||||
// 버프 이름
|
||||
@@ -92,8 +86,9 @@ class _BuffRow extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: remainingMs < 3000 ? Colors.orange : Colors.grey,
|
||||
fontWeight:
|
||||
remainingMs < 3000 ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: remainingMs < 3000
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -117,11 +112,7 @@ class _BuffRow extends StatelessWidget {
|
||||
// 효과 목록
|
||||
if (modifiers.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 2,
|
||||
children: modifiers,
|
||||
),
|
||||
Wrap(spacing: 6, runSpacing: 2, children: modifiers),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -134,35 +125,43 @@ class _BuffRow extends StatelessWidget {
|
||||
final effect = buff.effect;
|
||||
|
||||
if (effect.atkModifier != 0) {
|
||||
modifiers.add(_ModifierChip(
|
||||
label: 'ATK',
|
||||
value: effect.atkModifier,
|
||||
isPositive: effect.atkModifier > 0,
|
||||
));
|
||||
modifiers.add(
|
||||
_ModifierChip(
|
||||
label: 'ATK',
|
||||
value: effect.atkModifier,
|
||||
isPositive: effect.atkModifier > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (effect.defModifier != 0) {
|
||||
modifiers.add(_ModifierChip(
|
||||
label: 'DEF',
|
||||
value: effect.defModifier,
|
||||
isPositive: effect.defModifier > 0,
|
||||
));
|
||||
modifiers.add(
|
||||
_ModifierChip(
|
||||
label: 'DEF',
|
||||
value: effect.defModifier,
|
||||
isPositive: effect.defModifier > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (effect.criRateModifier != 0) {
|
||||
modifiers.add(_ModifierChip(
|
||||
label: 'CRI',
|
||||
value: effect.criRateModifier,
|
||||
isPositive: effect.criRateModifier > 0,
|
||||
));
|
||||
modifiers.add(
|
||||
_ModifierChip(
|
||||
label: 'CRI',
|
||||
value: effect.criRateModifier,
|
||||
isPositive: effect.criRateModifier > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (effect.evasionModifier != 0) {
|
||||
modifiers.add(_ModifierChip(
|
||||
label: 'EVA',
|
||||
value: effect.evasionModifier,
|
||||
isPositive: effect.evasionModifier > 0,
|
||||
));
|
||||
modifiers.add(
|
||||
_ModifierChip(
|
||||
label: 'EVA',
|
||||
value: effect.evasionModifier,
|
||||
isPositive: effect.evasionModifier > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
|
||||
@@ -117,6 +117,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
// 전투 이벤트 동기화용 (Phase 5)
|
||||
int? _lastEventTimestamp;
|
||||
bool _showCriticalEffect = false;
|
||||
bool _showBlockEffect = false;
|
||||
bool _showParryEffect = false;
|
||||
bool _showSkillEffect = false;
|
||||
|
||||
// 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
|
||||
// specialAnimationFrameCounts 상수 사용
|
||||
@@ -177,33 +180,114 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
// 전투 모드가 아니면 무시
|
||||
if (_animationMode != AnimationMode.battle) return;
|
||||
|
||||
// 이벤트 타입에 따라 페이즈 강제 전환
|
||||
final (targetPhase, isCritical) = switch (event.type) {
|
||||
// 이벤트 타입에 따라 페이즈 및 효과 결정
|
||||
final (
|
||||
targetPhase,
|
||||
isCritical,
|
||||
isBlock,
|
||||
isParry,
|
||||
isSkill,
|
||||
) = switch (event.type) {
|
||||
// 플레이어 공격 → attack 페이즈
|
||||
CombatEventType.playerAttack => (BattlePhase.attack, event.isCritical),
|
||||
CombatEventType.playerSkill => (BattlePhase.attack, event.isCritical),
|
||||
CombatEventType.playerAttack => (
|
||||
BattlePhase.attack,
|
||||
event.isCritical,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
// 스킬 사용 → attack 페이즈 + 스킬 이펙트
|
||||
CombatEventType.playerSkill => (
|
||||
BattlePhase.attack,
|
||||
event.isCritical,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
|
||||
// 몬스터 공격/플레이어 피격 → hit 페이즈
|
||||
CombatEventType.monsterAttack => (BattlePhase.hit, false),
|
||||
CombatEventType.playerBlock => (BattlePhase.hit, false),
|
||||
CombatEventType.playerParry => (BattlePhase.hit, false),
|
||||
// 몬스터 공격 → hit 페이즈
|
||||
CombatEventType.monsterAttack => (
|
||||
BattlePhase.hit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
// 블록 → hit 페이즈 + 블록 이펙트
|
||||
CombatEventType.playerBlock => (
|
||||
BattlePhase.hit,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
// 패리 → hit 페이즈 + 패리 이펙트
|
||||
CombatEventType.playerParry => (
|
||||
BattlePhase.hit,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
|
||||
// 회피 → recover 페이즈 (빠른 회피 동작)
|
||||
CombatEventType.playerEvade => (BattlePhase.recover, false),
|
||||
CombatEventType.monsterEvade => (BattlePhase.idle, false),
|
||||
CombatEventType.playerEvade => (
|
||||
BattlePhase.recover,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
CombatEventType.monsterEvade => (
|
||||
BattlePhase.idle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
|
||||
// 회복/버프 → idle 페이즈 유지
|
||||
CombatEventType.playerHeal => (BattlePhase.idle, false),
|
||||
CombatEventType.playerBuff => (BattlePhase.idle, false),
|
||||
CombatEventType.playerHeal => (
|
||||
BattlePhase.idle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
CombatEventType.playerBuff => (
|
||||
BattlePhase.idle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
|
||||
// DOT 틱 → attack 페이즈 (지속 피해)
|
||||
CombatEventType.dotTick => (BattlePhase.attack, false),
|
||||
CombatEventType.dotTick => (
|
||||
BattlePhase.attack,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
|
||||
// 물약 사용 → idle 페이즈 유지
|
||||
CombatEventType.playerPotion => (BattlePhase.idle, false),
|
||||
CombatEventType.playerPotion => (
|
||||
BattlePhase.idle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
|
||||
// 물약 드랍 → idle 페이즈 유지
|
||||
CombatEventType.potionDrop => (BattlePhase.idle, false),
|
||||
CombatEventType.potionDrop => (
|
||||
BattlePhase.idle,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
setState(() {
|
||||
@@ -211,6 +295,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
_battleSubFrame = 0;
|
||||
_phaseFrameCount = 0;
|
||||
_showCriticalEffect = isCritical;
|
||||
_showBlockEffect = isBlock;
|
||||
_showParryEffect = isParry;
|
||||
_showSkillEffect = isSkill;
|
||||
|
||||
// 페이즈 인덱스 동기화
|
||||
_phaseIndex = _battlePhaseSequence.indexWhere((p) => p.$1 == targetPhase);
|
||||
@@ -322,8 +409,11 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
_phaseIndex = (_phaseIndex + 1) % _battlePhaseSequence.length;
|
||||
_phaseFrameCount = 0;
|
||||
_battleSubFrame = 0;
|
||||
// 크리티컬 이펙트 리셋 (페이즈 전환 시)
|
||||
// 이펙트 리셋 (페이즈 전환 시)
|
||||
_showCriticalEffect = false;
|
||||
_showBlockEffect = false;
|
||||
_showParryEffect = false;
|
||||
_showSkillEffect = false;
|
||||
} else {
|
||||
_battleSubFrame++;
|
||||
}
|
||||
@@ -340,21 +430,22 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
/// 현재 애니메이션 레이어 생성
|
||||
List<AsciiLayer> _composeLayers() {
|
||||
return switch (_animationMode) {
|
||||
AnimationMode.battle => _battleComposer?.composeLayers(
|
||||
_battlePhase,
|
||||
_battleSubFrame,
|
||||
widget.monsterBaseName,
|
||||
_environment,
|
||||
_globalTick,
|
||||
) ??
|
||||
[AsciiLayer.empty()],
|
||||
AnimationMode.battle =>
|
||||
_battleComposer?.composeLayers(
|
||||
_battlePhase,
|
||||
_battleSubFrame,
|
||||
widget.monsterBaseName,
|
||||
_environment,
|
||||
_globalTick,
|
||||
) ??
|
||||
[AsciiLayer.empty()],
|
||||
AnimationMode.walking => _walkingComposer.composeLayers(_globalTick),
|
||||
AnimationMode.town => _townComposer.composeLayers(_globalTick),
|
||||
AnimationMode.special => _specialComposer.composeLayers(
|
||||
_currentSpecialAnimation ?? AsciiAnimationType.levelUp,
|
||||
_currentFrame,
|
||||
_globalTick,
|
||||
),
|
||||
_currentSpecialAnimation ?? AsciiAnimationType.levelUp,
|
||||
_currentFrame,
|
||||
_globalTick,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -363,17 +454,38 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
||||
// Phase 7: 고정 4색 팔레트 사용 (colorTheme 무시)
|
||||
const bgColor = AsciiColors.background;
|
||||
|
||||
// 테두리 효과 결정 (특수 애니메이션 또는 크리티컬 히트)
|
||||
// 테두리 효과 결정 (전투 이벤트 또는 특수 애니메이션)
|
||||
final isSpecial = _currentSpecialAnimation != null;
|
||||
Border? borderEffect;
|
||||
if (_showCriticalEffect) {
|
||||
// 크리티컬 히트: 노란색 테두리 (Phase 5)
|
||||
borderEffect =
|
||||
Border.all(color: Colors.yellow.withValues(alpha: 0.8), width: 2);
|
||||
// 크리티컬 히트: 노란색 테두리
|
||||
borderEffect = Border.all(
|
||||
color: Colors.yellow.withValues(alpha: 0.8),
|
||||
width: 2,
|
||||
);
|
||||
} else if (_showBlockEffect) {
|
||||
// 블록 (방패 방어): 파란색 테두리
|
||||
borderEffect = Border.all(
|
||||
color: Colors.blue.withValues(alpha: 0.8),
|
||||
width: 2,
|
||||
);
|
||||
} else if (_showParryEffect) {
|
||||
// 패리 (무기 쳐내기): 주황색 테두리
|
||||
borderEffect = Border.all(
|
||||
color: Colors.orange.withValues(alpha: 0.8),
|
||||
width: 2,
|
||||
);
|
||||
} else if (_showSkillEffect) {
|
||||
// 스킬 사용: 마젠타 테두리
|
||||
borderEffect = Border.all(
|
||||
color: Colors.purple.withValues(alpha: 0.8),
|
||||
width: 2,
|
||||
);
|
||||
} else if (isSpecial) {
|
||||
// 특수 애니메이션: 시안 테두리
|
||||
borderEffect =
|
||||
Border.all(color: AsciiColors.positive.withValues(alpha: 0.5));
|
||||
borderEffect = Border.all(
|
||||
color: AsciiColors.positive.withValues(alpha: 0.5),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/data/story_data.dart';
|
||||
|
||||
/// 시네마틱 뷰 위젯 (Phase 9: Cinematic UI)
|
||||
@@ -162,12 +163,9 @@ class _CinematicViewState extends State<CinematicView>
|
||||
right: 16,
|
||||
child: TextButton(
|
||||
onPressed: _skip,
|
||||
child: const Text(
|
||||
'SKIP',
|
||||
style: TextStyle(
|
||||
color: Colors.white54,
|
||||
fontSize: 14,
|
||||
),
|
||||
child: Text(
|
||||
l10n.uiSkip,
|
||||
style: const TextStyle(color: Colors.white54, fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -178,7 +176,7 @@ class _CinematicViewState extends State<CinematicView>
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
'Tap to continue',
|
||||
l10n.uiTapToContinue,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
fontSize: 12,
|
||||
@@ -246,8 +244,8 @@ class _ProgressDots extends StatelessWidget {
|
||||
color: isActive
|
||||
? Colors.cyan
|
||||
: isPast
|
||||
? Colors.cyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.2),
|
||||
? Colors.cyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.2),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -148,7 +148,10 @@ class _LogEntryTile extends StatelessWidget {
|
||||
(Color?, IconData?) _getStyleForType(CombatLogType type) {
|
||||
return switch (type) {
|
||||
CombatLogType.normal => (null, null),
|
||||
CombatLogType.damage => (Colors.red.shade300, Icons.local_fire_department),
|
||||
CombatLogType.damage => (
|
||||
Colors.red.shade300,
|
||||
Icons.local_fire_department,
|
||||
),
|
||||
CombatLogType.heal => (Colors.green.shade300, Icons.healing),
|
||||
CombatLogType.levelUp => (Colors.amber, Icons.arrow_upward),
|
||||
CombatLogType.questComplete => (Colors.blue.shade300, Icons.check_circle),
|
||||
@@ -158,7 +161,10 @@ class _LogEntryTile extends StatelessWidget {
|
||||
CombatLogType.evade => (Colors.cyan.shade300, Icons.directions_run),
|
||||
CombatLogType.block => (Colors.blueGrey.shade300, Icons.shield),
|
||||
CombatLogType.parry => (Colors.teal.shade300, Icons.sports_kabaddi),
|
||||
CombatLogType.monsterAttack => (Colors.deepOrange.shade300, Icons.dangerous),
|
||||
CombatLogType.monsterAttack => (
|
||||
Colors.deepOrange.shade300,
|
||||
Icons.dangerous,
|
||||
),
|
||||
CombatLogType.buff => (Colors.lightBlue.shade300, Icons.trending_up),
|
||||
CombatLogType.dotTick => (Colors.deepPurple.shade300, Icons.whatshot),
|
||||
CombatLogType.potion => (Colors.pink.shade300, Icons.local_drink),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
|
||||
import 'package:askiineverdie/src/core/model/combat_event.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
|
||||
@@ -109,7 +111,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'YOU DIED',
|
||||
l10n.deathYouDied,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -133,7 +135,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Level ${deathInfo.levelAtDeath} ${traits.klass}',
|
||||
'Level ${deathInfo.levelAtDeath} ${GameDataL10n.getKlassName(context, traits.klass)}',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -174,9 +176,9 @@ class DeathOverlay extends StatelessWidget {
|
||||
|
||||
String _getDeathCauseText() {
|
||||
return switch (deathInfo.cause) {
|
||||
DeathCause.monster => 'Killed by ${deathInfo.killerName}',
|
||||
DeathCause.selfDamage => 'Self-inflicted damage',
|
||||
DeathCause.environment => 'Environmental hazard',
|
||||
DeathCause.monster => l10n.deathKilledBy(deathInfo.killerName),
|
||||
DeathCause.selfDamage => l10n.deathSelfInflicted,
|
||||
DeathCause.environment => l10n.deathEnvironmentalHazard,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,7 +212,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Sacrificed to Resurrect',
|
||||
l10n.deathSacrificedToResurrect,
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -234,8 +236,8 @@ class DeathOverlay extends StatelessWidget {
|
||||
_buildInfoRow(
|
||||
context,
|
||||
icon: Icons.check_circle_outline,
|
||||
label: 'Equipment',
|
||||
value: 'No sacrifice needed',
|
||||
label: l10n.deathEquipment,
|
||||
value: l10n.deathNoSacrificeNeeded,
|
||||
isNegative: false,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -243,7 +245,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
_buildInfoRow(
|
||||
context,
|
||||
icon: Icons.monetization_on_outlined,
|
||||
label: 'Gold Remaining',
|
||||
label: l10n.deathGoldRemaining,
|
||||
value: _formatGold(deathInfo.goldAtDeath),
|
||||
isNegative: false,
|
||||
),
|
||||
@@ -306,7 +308,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
child: FilledButton.icon(
|
||||
onPressed: onResurrect,
|
||||
icon: const Icon(Icons.replay),
|
||||
label: const Text('Resurrect'),
|
||||
label: Text(l10n.deathResurrect),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
@@ -324,7 +326,7 @@ class DeathOverlay extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Combat Log',
|
||||
l10n.deathCombatLog,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -378,69 +380,70 @@ class DeathOverlay extends StatelessWidget {
|
||||
|
||||
/// 전투 이벤트를 아이콘, 색상, 메시지로 포맷
|
||||
(IconData, Color, String) _formatCombatEvent(CombatEvent event) {
|
||||
final target = event.targetName ?? '';
|
||||
return switch (event.type) {
|
||||
CombatEventType.playerAttack => (
|
||||
event.isCritical ? Icons.flash_on : Icons.local_fire_department,
|
||||
event.isCritical ? Colors.yellow.shade300 : Colors.green.shade300,
|
||||
event.isCritical
|
||||
? 'CRITICAL! ${event.damage} damage to ${event.targetName}'
|
||||
: 'Hit ${event.targetName} for ${event.damage} damage',
|
||||
),
|
||||
event.isCritical ? Icons.flash_on : Icons.local_fire_department,
|
||||
event.isCritical ? Colors.yellow.shade300 : Colors.green.shade300,
|
||||
event.isCritical
|
||||
? l10n.combatCritical(event.damage, target)
|
||||
: l10n.combatYouHit(target, event.damage),
|
||||
),
|
||||
CombatEventType.monsterAttack => (
|
||||
Icons.dangerous,
|
||||
Colors.red.shade300,
|
||||
'${event.targetName} hits you for ${event.damage} damage',
|
||||
),
|
||||
Icons.dangerous,
|
||||
Colors.red.shade300,
|
||||
l10n.combatMonsterHitsYou(target, event.damage),
|
||||
),
|
||||
CombatEventType.playerEvade => (
|
||||
Icons.directions_run,
|
||||
Colors.cyan.shade300,
|
||||
'Evaded attack from ${event.targetName}',
|
||||
),
|
||||
Icons.directions_run,
|
||||
Colors.cyan.shade300,
|
||||
l10n.combatEvadedAttackFrom(target),
|
||||
),
|
||||
CombatEventType.monsterEvade => (
|
||||
Icons.directions_run,
|
||||
Colors.orange.shade300,
|
||||
'${event.targetName} evaded your attack',
|
||||
),
|
||||
Icons.directions_run,
|
||||
Colors.orange.shade300,
|
||||
l10n.combatMonsterEvaded(target),
|
||||
),
|
||||
CombatEventType.playerBlock => (
|
||||
Icons.shield,
|
||||
Colors.blueGrey.shade300,
|
||||
'Blocked ${event.targetName}\'s attack (${event.damage} reduced)',
|
||||
),
|
||||
Icons.shield,
|
||||
Colors.blueGrey.shade300,
|
||||
l10n.combatBlockedAttack(target, event.damage),
|
||||
),
|
||||
CombatEventType.playerParry => (
|
||||
Icons.sports_kabaddi,
|
||||
Colors.teal.shade300,
|
||||
'Parried ${event.targetName}\'s attack (${event.damage} reduced)',
|
||||
),
|
||||
Icons.sports_kabaddi,
|
||||
Colors.teal.shade300,
|
||||
l10n.combatParriedAttack(target, event.damage),
|
||||
),
|
||||
CombatEventType.playerSkill => (
|
||||
Icons.auto_fix_high,
|
||||
Colors.purple.shade300,
|
||||
'${event.skillName} deals ${event.damage} damage',
|
||||
),
|
||||
Icons.auto_fix_high,
|
||||
Colors.purple.shade300,
|
||||
l10n.combatSkillDamage(event.skillName ?? '', event.damage),
|
||||
),
|
||||
CombatEventType.playerHeal => (
|
||||
Icons.healing,
|
||||
Colors.green.shade300,
|
||||
'Healed for ${event.healAmount} HP',
|
||||
),
|
||||
Icons.healing,
|
||||
Colors.green.shade300,
|
||||
l10n.combatHealedFor(event.healAmount),
|
||||
),
|
||||
CombatEventType.playerBuff => (
|
||||
Icons.trending_up,
|
||||
Colors.lightBlue.shade300,
|
||||
'${event.skillName} activated',
|
||||
),
|
||||
Icons.trending_up,
|
||||
Colors.lightBlue.shade300,
|
||||
l10n.combatBuffActivated(event.skillName ?? ''),
|
||||
),
|
||||
CombatEventType.dotTick => (
|
||||
Icons.whatshot,
|
||||
Colors.deepOrange.shade300,
|
||||
'${event.skillName} ticks for ${event.damage} damage',
|
||||
),
|
||||
Icons.whatshot,
|
||||
Colors.deepOrange.shade300,
|
||||
l10n.combatDotTick(event.skillName ?? '', event.damage),
|
||||
),
|
||||
CombatEventType.playerPotion => (
|
||||
Icons.local_drink,
|
||||
Colors.lightGreen.shade300,
|
||||
'${event.skillName}: +${event.healAmount} ${event.targetName}',
|
||||
),
|
||||
Icons.local_drink,
|
||||
Colors.lightGreen.shade300,
|
||||
l10n.combatPotionUsed(event.skillName ?? '', event.healAmount, target),
|
||||
),
|
||||
CombatEventType.potionDrop => (
|
||||
Icons.card_giftcard,
|
||||
Colors.lime.shade300,
|
||||
'Dropped: ${event.skillName}',
|
||||
),
|
||||
Icons.card_giftcard,
|
||||
Colors.lime.shade300,
|
||||
l10n.combatPotionDrop(event.skillName ?? ''),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/engine/item_service.dart';
|
||||
import 'package:askiineverdie/src/core/model/equipment_item.dart';
|
||||
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
||||
@@ -135,7 +136,7 @@ class _EmptySlotTile extends StatelessWidget {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
leading: _SlotIcon(slot: slot, isEmpty: true),
|
||||
title: Text(
|
||||
'[${_getSlotName(slot)}] (empty)',
|
||||
'[${_getSlotName(slot)}] ${l10n.uiEmpty}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade600,
|
||||
@@ -222,10 +223,7 @@ class _TotalScoreHeader extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.blueGrey.shade700,
|
||||
Colors.blueGrey.shade600,
|
||||
],
|
||||
colors: [Colors.blueGrey.shade700, Colors.blueGrey.shade600],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
@@ -239,11 +237,7 @@ class _TotalScoreHeader extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
// 장비 아이콘
|
||||
const Icon(
|
||||
Icons.shield,
|
||||
size: 20,
|
||||
color: Colors.white70,
|
||||
),
|
||||
const Icon(Icons.shield, size: 20, color: Colors.white70),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// 총합 점수
|
||||
@@ -251,12 +245,9 @@ class _TotalScoreHeader extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Equipment Score',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white70,
|
||||
),
|
||||
Text(
|
||||
l10n.uiEquipmentScore,
|
||||
style: const TextStyle(fontSize: 10, color: Colors.white70),
|
||||
),
|
||||
Text(
|
||||
'$totalScore',
|
||||
@@ -304,46 +295,80 @@ class _StatsGrid extends StatelessWidget {
|
||||
final entries = <_StatEntry>[];
|
||||
|
||||
// 공격 스탯
|
||||
if (stats.atk > 0) entries.add(_StatEntry('ATK', '+${stats.atk}'));
|
||||
if (stats.magAtk > 0) entries.add(_StatEntry('MATK', '+${stats.magAtk}'));
|
||||
if (stats.atk > 0) entries.add(_StatEntry(l10n.statAtk, '+${stats.atk}'));
|
||||
if (stats.magAtk > 0) {
|
||||
entries.add(_StatEntry(l10n.statMAtk, '+${stats.magAtk}'));
|
||||
}
|
||||
if (stats.criRate > 0) {
|
||||
entries.add(_StatEntry('CRI', '${(stats.criRate * 100).toStringAsFixed(1)}%'));
|
||||
entries.add(
|
||||
_StatEntry(l10n.statCri, '${(stats.criRate * 100).toStringAsFixed(1)}%'),
|
||||
);
|
||||
}
|
||||
if (stats.parryRate > 0) {
|
||||
entries.add(_StatEntry('PARRY', '${(stats.parryRate * 100).toStringAsFixed(1)}%'));
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.statParry,
|
||||
'${(stats.parryRate * 100).toStringAsFixed(1)}%',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 방어 스탯
|
||||
if (stats.def > 0) entries.add(_StatEntry('DEF', '+${stats.def}'));
|
||||
if (stats.magDef > 0) entries.add(_StatEntry('MDEF', '+${stats.magDef}'));
|
||||
if (stats.def > 0) entries.add(_StatEntry(l10n.statDef, '+${stats.def}'));
|
||||
if (stats.magDef > 0) {
|
||||
entries.add(_StatEntry(l10n.statMDef, '+${stats.magDef}'));
|
||||
}
|
||||
if (stats.blockRate > 0) {
|
||||
entries.add(_StatEntry('BLOCK', '${(stats.blockRate * 100).toStringAsFixed(1)}%'));
|
||||
entries.add(
|
||||
_StatEntry(
|
||||
l10n.statBlock,
|
||||
'${(stats.blockRate * 100).toStringAsFixed(1)}%',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (stats.evasion > 0) {
|
||||
entries.add(_StatEntry('EVA', '${(stats.evasion * 100).toStringAsFixed(1)}%'));
|
||||
entries.add(
|
||||
_StatEntry(l10n.statEva, '${(stats.evasion * 100).toStringAsFixed(1)}%'),
|
||||
);
|
||||
}
|
||||
|
||||
// 자원 스탯
|
||||
if (stats.hpBonus > 0) entries.add(_StatEntry('HP', '+${stats.hpBonus}'));
|
||||
if (stats.mpBonus > 0) entries.add(_StatEntry('MP', '+${stats.mpBonus}'));
|
||||
if (stats.hpBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statHp, '+${stats.hpBonus}'));
|
||||
}
|
||||
if (stats.mpBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statMp, '+${stats.mpBonus}'));
|
||||
}
|
||||
|
||||
// 능력치 보너스
|
||||
if (stats.strBonus > 0) entries.add(_StatEntry('STR', '+${stats.strBonus}'));
|
||||
if (stats.conBonus > 0) entries.add(_StatEntry('CON', '+${stats.conBonus}'));
|
||||
if (stats.dexBonus > 0) entries.add(_StatEntry('DEX', '+${stats.dexBonus}'));
|
||||
if (stats.intBonus > 0) entries.add(_StatEntry('INT', '+${stats.intBonus}'));
|
||||
if (stats.wisBonus > 0) entries.add(_StatEntry('WIS', '+${stats.wisBonus}'));
|
||||
if (stats.chaBonus > 0) entries.add(_StatEntry('CHA', '+${stats.chaBonus}'));
|
||||
if (stats.strBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statStr, '+${stats.strBonus}'));
|
||||
}
|
||||
if (stats.conBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statCon, '+${stats.conBonus}'));
|
||||
}
|
||||
if (stats.dexBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statDex, '+${stats.dexBonus}'));
|
||||
}
|
||||
if (stats.intBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statInt, '+${stats.intBonus}'));
|
||||
}
|
||||
if (stats.wisBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statWis, '+${stats.wisBonus}'));
|
||||
}
|
||||
if (stats.chaBonus > 0) {
|
||||
entries.add(_StatEntry(l10n.statCha, '+${stats.chaBonus}'));
|
||||
}
|
||||
|
||||
// 무기 공속
|
||||
if (slot == EquipmentSlot.weapon && stats.attackSpeed > 0) {
|
||||
entries.add(_StatEntry('SPEED', '${stats.attackSpeed}ms'));
|
||||
entries.add(_StatEntry(l10n.statSpeed, '${stats.attackSpeed}ms'));
|
||||
}
|
||||
|
||||
if (entries.isEmpty) {
|
||||
return const Text(
|
||||
'No bonus stats',
|
||||
style: TextStyle(fontSize: 10, color: Colors.grey),
|
||||
return Text(
|
||||
l10n.uiNoBonusStats,
|
||||
style: const TextStyle(fontSize: 10, color: Colors.grey),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -406,7 +431,7 @@ class _ItemMetaRow extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
'Lv.${item.level}',
|
||||
l10n.uiLevel(item.level),
|
||||
style: const TextStyle(fontSize: 9, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -420,7 +445,7 @@ class _ItemMetaRow extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Wt.${item.weight}',
|
||||
l10n.uiWeight(item.weight),
|
||||
style: const TextStyle(fontSize: 9, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
@@ -441,16 +466,16 @@ class _ItemMetaRow extends StatelessWidget {
|
||||
/// 슬롯 이름 반환
|
||||
String _getSlotName(EquipmentSlot slot) {
|
||||
return switch (slot) {
|
||||
EquipmentSlot.weapon => 'Weapon',
|
||||
EquipmentSlot.shield => 'Shield',
|
||||
EquipmentSlot.helm => 'Helm',
|
||||
EquipmentSlot.hauberk => 'Hauberk',
|
||||
EquipmentSlot.brassairts => 'Brassairts',
|
||||
EquipmentSlot.vambraces => 'Vambraces',
|
||||
EquipmentSlot.gauntlets => 'Gauntlets',
|
||||
EquipmentSlot.gambeson => 'Gambeson',
|
||||
EquipmentSlot.cuisses => 'Cuisses',
|
||||
EquipmentSlot.greaves => 'Greaves',
|
||||
EquipmentSlot.sollerets => 'Sollerets',
|
||||
EquipmentSlot.weapon => l10n.slotWeapon,
|
||||
EquipmentSlot.shield => l10n.slotShield,
|
||||
EquipmentSlot.helm => l10n.slotHelm,
|
||||
EquipmentSlot.hauberk => l10n.slotHauberk,
|
||||
EquipmentSlot.brassairts => l10n.slotBrassairts,
|
||||
EquipmentSlot.vambraces => l10n.slotVambraces,
|
||||
EquipmentSlot.gauntlets => l10n.slotGauntlets,
|
||||
EquipmentSlot.gambeson => l10n.slotGambeson,
|
||||
EquipmentSlot.cuisses => l10n.slotCuisses,
|
||||
EquipmentSlot.greaves => l10n.slotGreaves,
|
||||
EquipmentSlot.sollerets => l10n.slotSollerets,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
|
||||
/// HP/MP 바 위젯 (Phase 8: 변화 시 시각 효과)
|
||||
///
|
||||
/// - HP가 20% 미만일 때 빨간색 깜빡임
|
||||
@@ -151,7 +153,8 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
final hpRatio = widget.hpMax > 0 ? widget.hpCurrent / widget.hpMax : 0.0;
|
||||
final mpRatio = widget.mpMax > 0 ? widget.mpCurrent / widget.mpMax : 0.0;
|
||||
|
||||
final hasMonster = widget.monsterHpCurrent != null &&
|
||||
final hasMonster =
|
||||
widget.monsterHpCurrent != null &&
|
||||
widget.monsterHpMax != null &&
|
||||
widget.monsterHpMax! > 0;
|
||||
|
||||
@@ -162,7 +165,7 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
children: [
|
||||
// HP 바 (플래시 효과 포함)
|
||||
_buildAnimatedBar(
|
||||
label: 'HP',
|
||||
label: l10n.statHp,
|
||||
current: widget.hpCurrent,
|
||||
max: widget.hpMax,
|
||||
ratio: hpRatio,
|
||||
@@ -176,7 +179,7 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
|
||||
// MP 바 (플래시 효과 포함)
|
||||
_buildAnimatedBar(
|
||||
label: 'MP',
|
||||
label: l10n.statMp,
|
||||
current: widget.mpCurrent,
|
||||
max: widget.mpMax,
|
||||
ratio: mpRatio,
|
||||
@@ -188,10 +191,7 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
),
|
||||
|
||||
// 몬스터 HP 바 (전투 중일 때만)
|
||||
if (hasMonster) ...[
|
||||
const SizedBox(height: 8),
|
||||
_buildMonsterBar(),
|
||||
],
|
||||
if (hasMonster) ...[const SizedBox(height: 8), _buildMonsterBar()],
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -228,7 +228,13 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildBar(label: label, current: current, max: max, ratio: ratio, color: color),
|
||||
_buildBar(
|
||||
label: label,
|
||||
current: current,
|
||||
max: max,
|
||||
ratio: ratio,
|
||||
color: color,
|
||||
),
|
||||
|
||||
// 플로팅 변화량 텍스트 (위로 떠오르며 사라짐)
|
||||
if (change != 0 && flashController.value > 0.05)
|
||||
@@ -340,8 +346,9 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
||||
child: LinearProgressIndicator(
|
||||
value: ratio.clamp(0.0, 1.0),
|
||||
backgroundColor: Colors.orange.withValues(alpha: 0.2),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation<Color>(Colors.orange),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Colors.orange,
|
||||
),
|
||||
minHeight: 8,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -40,20 +40,21 @@ class _NotificationOverlayState extends State<NotificationOverlay>
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, -1),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeOutBack,
|
||||
));
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, -1), end: Offset.zero).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeIn),
|
||||
);
|
||||
|
||||
_notificationSub =
|
||||
widget.notificationService.notifications.listen(_onNotification);
|
||||
_notificationSub = widget.notificationService.notifications.listen(
|
||||
_onNotification,
|
||||
);
|
||||
_dismissSub = widget.notificationService.dismissals.listen(_onDismiss);
|
||||
}
|
||||
|
||||
@@ -184,35 +185,35 @@ class _NotificationCard extends StatelessWidget {
|
||||
(Color, IconData, Color) _getStyleForType(NotificationType type) {
|
||||
return switch (type) {
|
||||
NotificationType.levelUp => (
|
||||
const Color(0xFF1565C0),
|
||||
Icons.trending_up,
|
||||
Colors.amber,
|
||||
),
|
||||
const Color(0xFF1565C0),
|
||||
Icons.trending_up,
|
||||
Colors.amber,
|
||||
),
|
||||
NotificationType.questComplete => (
|
||||
const Color(0xFF2E7D32),
|
||||
Icons.check_circle,
|
||||
Colors.lightGreen,
|
||||
),
|
||||
const Color(0xFF2E7D32),
|
||||
Icons.check_circle,
|
||||
Colors.lightGreen,
|
||||
),
|
||||
NotificationType.actComplete => (
|
||||
const Color(0xFF6A1B9A),
|
||||
Icons.flag,
|
||||
Colors.purpleAccent,
|
||||
),
|
||||
const Color(0xFF6A1B9A),
|
||||
Icons.flag,
|
||||
Colors.purpleAccent,
|
||||
),
|
||||
NotificationType.newSpell => (
|
||||
const Color(0xFF4527A0),
|
||||
Icons.auto_fix_high,
|
||||
Colors.deepPurpleAccent,
|
||||
),
|
||||
const Color(0xFF4527A0),
|
||||
Icons.auto_fix_high,
|
||||
Colors.deepPurpleAccent,
|
||||
),
|
||||
NotificationType.newEquipment => (
|
||||
const Color(0xFFE65100),
|
||||
Icons.shield,
|
||||
Colors.orange,
|
||||
),
|
||||
const Color(0xFFE65100),
|
||||
Icons.shield,
|
||||
Colors.orange,
|
||||
),
|
||||
NotificationType.bossDefeat => (
|
||||
const Color(0xFFC62828),
|
||||
Icons.whatshot,
|
||||
Colors.redAccent,
|
||||
),
|
||||
const Color(0xFFC62828),
|
||||
Icons.whatshot,
|
||||
Colors.redAccent,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/data/potion_data.dart';
|
||||
import 'package:askiineverdie/src/core/model/potion.dart';
|
||||
|
||||
@@ -22,10 +23,10 @@ class PotionInventoryPanel extends StatelessWidget {
|
||||
final potionEntries = _buildPotionEntries();
|
||||
|
||||
if (potionEntries.isEmpty) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Text(
|
||||
'No potions',
|
||||
style: TextStyle(
|
||||
l10n.uiNoPotions,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey,
|
||||
fontStyle: FontStyle.italic,
|
||||
@@ -146,11 +147,7 @@ class _PotionRow extends StatelessWidget {
|
||||
// 전투 중 사용 불가 표시
|
||||
if (isUsedThisBattle) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.block,
|
||||
size: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const Icon(Icons.block, size: 12, color: Colors.grey),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -213,10 +210,7 @@ class _HealBadge extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
healText,
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
style: TextStyle(fontSize: 9, color: Colors.grey.shade700),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/data/skill_data.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/skill.dart';
|
||||
@@ -90,8 +91,8 @@ class _SkillPanelState extends State<SkillPanel>
|
||||
final skillStates = widget.skillSystem.skillStates;
|
||||
|
||||
if (skillStates.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('No skills', style: TextStyle(fontSize: 11)),
|
||||
return Center(
|
||||
child: Text(l10n.uiNoSkills, style: const TextStyle(fontSize: 11)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ class _SkillRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cooldownText = isReady
|
||||
? 'Ready'
|
||||
? l10n.uiReady
|
||||
: '${(remainingMs / 1000).toStringAsFixed(1)}s';
|
||||
|
||||
final skillIcon = _getSkillIcon(skill.type);
|
||||
@@ -192,9 +193,9 @@ class _SkillRow extends StatelessWidget {
|
||||
color: elementColor.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: const Text(
|
||||
'DOT',
|
||||
style: TextStyle(fontSize: 7, color: Colors.white70),
|
||||
child: Text(
|
||||
l10n.uiDot,
|
||||
style: const TextStyle(fontSize: 7, color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@@ -202,7 +203,7 @@ class _SkillRow extends StatelessWidget {
|
||||
|
||||
// 랭크
|
||||
Text(
|
||||
'Lv.$rank',
|
||||
l10n.uiLevel(rank),
|
||||
style: const TextStyle(fontSize: 9, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@@ -233,7 +234,9 @@ class _SkillRow extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: skillColor.withValues(alpha: glowAnimation.value * 0.5),
|
||||
color: skillColor.withValues(
|
||||
alpha: glowAnimation.value * 0.5,
|
||||
),
|
||||
blurRadius: 8 * glowAnimation.value,
|
||||
spreadRadius: 2 * glowAnimation.value,
|
||||
),
|
||||
@@ -332,11 +335,7 @@ class _ElementBadge extends StatelessWidget {
|
||||
? Border.all(color: color.withValues(alpha: 0.7), width: 1)
|
||||
: null,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 10,
|
||||
color: color,
|
||||
),
|
||||
child: Icon(icon, size: 10, color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,9 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
child: Text(
|
||||
'${speedMultiplier}x',
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
speedMultiplier > 1 ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: speedMultiplier > 1
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: speedMultiplier > 1
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
@@ -157,8 +158,9 @@ class TaskProgressPanel extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: LinearProgressIndicator(
|
||||
value: progressValue,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary.withValues(alpha: 0.2),
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.2),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user