update
This commit is contained in:
+372
-305
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/battle_provider.dart';
|
||||
|
||||
@@ -13,7 +14,11 @@ import '../widgets/battle/character_status_card.dart';
|
||||
import '../widgets/battle/battle_log_overlay.dart';
|
||||
import '../widgets/battle/floating_battle_texts.dart';
|
||||
import '../widgets/battle/stage_ui.dart';
|
||||
import '../widgets/battle/shake_widget.dart';
|
||||
import '../widgets/battle/battle_animation_widget.dart';
|
||||
import '../widgets/battle/explosion_widget.dart';
|
||||
import 'main_menu_screen.dart';
|
||||
import '../game/config/battle_config.dart';
|
||||
|
||||
class BattleScreen extends StatefulWidget {
|
||||
const BattleScreen({super.key});
|
||||
@@ -31,7 +36,13 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
final GlobalKey _playerKey = GlobalKey();
|
||||
final GlobalKey _enemyKey = GlobalKey();
|
||||
final GlobalKey _stackKey = GlobalKey();
|
||||
final GlobalKey<ShakeWidgetState> _shakeKey = GlobalKey<ShakeWidgetState>();
|
||||
final GlobalKey<BattleAnimationWidgetState> _playerAnimKey =
|
||||
GlobalKey<BattleAnimationWidgetState>();
|
||||
final GlobalKey<ExplosionWidgetState> _explosionKey =
|
||||
GlobalKey<ExplosionWidgetState>();
|
||||
bool _showLogs = true;
|
||||
bool _isPlayerAttacking = false; // Player Attack Animation State
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -104,7 +115,17 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
final Set<String> _processedEffectIds = {};
|
||||
|
||||
void _addFloatingEffect(EffectEvent event) {
|
||||
if (_processedEffectIds.contains(event.id)) {
|
||||
return;
|
||||
}
|
||||
_processedEffectIds.add(event.id);
|
||||
if (_processedEffectIds.length > 20) {
|
||||
_processedEffectIds.remove(_processedEffectIds.first);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -130,40 +151,78 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
position +
|
||||
Offset(renderBox.size.width / 2 - 30, renderBox.size.height / 2 - 30);
|
||||
|
||||
// feedbackType이 존재하면 해당 텍스트를 표시하고 기존 이펙트 아이콘은 건너뜜
|
||||
if (event.feedbackType != null) {
|
||||
String feedbackText;
|
||||
Color feedbackColor;
|
||||
switch (event.feedbackType) {
|
||||
case BattleFeedbackType.miss:
|
||||
feedbackText = "MISS";
|
||||
feedbackColor = Colors.grey;
|
||||
break;
|
||||
case BattleFeedbackType.failed:
|
||||
feedbackText = "FAILED";
|
||||
feedbackColor = Colors.redAccent;
|
||||
break;
|
||||
default:
|
||||
feedbackText = ""; // Should not happen with current enums
|
||||
feedbackColor = Colors.white;
|
||||
// 0. Prepare Effect Function
|
||||
void showEffect() {
|
||||
if (!mounted) return;
|
||||
|
||||
// feedbackType이 존재하면 해당 텍스트를 표시하고 기존 이펙트 아이콘은 건너뜜
|
||||
if (event.feedbackType != null) {
|
||||
String feedbackText;
|
||||
Color feedbackColor;
|
||||
switch (event.feedbackType) {
|
||||
case BattleFeedbackType.miss:
|
||||
feedbackText = "MISS";
|
||||
feedbackColor = Colors.grey;
|
||||
break;
|
||||
case BattleFeedbackType.failed:
|
||||
feedbackText = "FAILED";
|
||||
feedbackColor = Colors.redAccent;
|
||||
break;
|
||||
default:
|
||||
feedbackText = ""; // Should not happen with current enums
|
||||
feedbackColor = Colors.white;
|
||||
}
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.add(
|
||||
FeedbackTextData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingFeedbackText(
|
||||
key: ValueKey(id),
|
||||
feedback: feedbackText,
|
||||
color: feedbackColor,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return; // feedbackType이 있으면 아이콘 이펙트는 표시하지 않음
|
||||
}
|
||||
|
||||
// Use BattleConfig for Icon, Color, and Size
|
||||
IconData icon = BattleConfig.getIcon(event.type);
|
||||
Color color = BattleConfig.getColor(event.type, event.risk);
|
||||
double size = BattleConfig.getSize(event.risk);
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.add(
|
||||
FeedbackTextData(
|
||||
_floatingEffects.add(
|
||||
FloatingEffectData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingFeedbackText(
|
||||
child: FloatingEffect(
|
||||
key: ValueKey(id),
|
||||
feedback: feedbackText,
|
||||
color: feedbackColor,
|
||||
icon: icon,
|
||||
color: color,
|
||||
size: size,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.removeWhere((e) => e.id == id);
|
||||
_floatingEffects.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -172,67 +231,63 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
),
|
||||
);
|
||||
});
|
||||
return; // feedbackType이 있으면 아이콘 이펙트는 표시하지 않음
|
||||
}
|
||||
|
||||
IconData icon;
|
||||
Color color;
|
||||
double size;
|
||||
// 1. Attack Animation Trigger (All Risk Levels)
|
||||
if (event.type == ActionType.attack &&
|
||||
event.target == EffectTarget.enemy &&
|
||||
event.feedbackType == null) {
|
||||
// Calculate target position (Enemy) relative to Player
|
||||
final RenderBox? playerBox =
|
||||
_playerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final RenderBox? enemyBox =
|
||||
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (event.type == ActionType.attack) {
|
||||
if (event.risk == RiskLevel.risky) {
|
||||
icon = Icons.whatshot;
|
||||
color = Colors.redAccent;
|
||||
size = 60.0;
|
||||
} else if (event.risk == RiskLevel.normal) {
|
||||
icon = Icons.flash_on;
|
||||
color = Colors.orangeAccent;
|
||||
size = 40.0;
|
||||
} else {
|
||||
icon = Icons.close;
|
||||
color = Colors.grey;
|
||||
size = 30.0;
|
||||
if (playerBox != null && enemyBox != null) {
|
||||
final playerPos = playerBox.localToGlobal(Offset.zero);
|
||||
final enemyPos = enemyBox.localToGlobal(Offset.zero);
|
||||
|
||||
final offset = enemyPos - playerPos;
|
||||
|
||||
// Start Animation: Hide Stats
|
||||
setState(() {
|
||||
_isPlayerAttacking = true;
|
||||
});
|
||||
|
||||
_playerAnimKey.currentState
|
||||
?.animateAttack(offset, () {
|
||||
showEffect(); // Show Effect at Impact!
|
||||
// Shake and Explosion ONLY for Risky
|
||||
if (event.risk == RiskLevel.risky) {
|
||||
_shakeKey.currentState?.shake();
|
||||
|
||||
RenderBox? stackBox =
|
||||
_stackKey.currentContext?.findRenderObject()
|
||||
as RenderBox?;
|
||||
if (stackBox != null) {
|
||||
Offset localEnemyPos = stackBox.globalToLocal(enemyPos);
|
||||
// Center of the enemy card roughly
|
||||
localEnemyPos += Offset(
|
||||
enemyBox.size.width / 2,
|
||||
enemyBox.size.height / 2,
|
||||
);
|
||||
_explosionKey.currentState?.explode(localEnemyPos);
|
||||
}
|
||||
}
|
||||
}, event.risk)
|
||||
.then((_) {
|
||||
// End Animation: Show Stats
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isPlayerAttacking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
icon = Icons.shield;
|
||||
if (event.risk == RiskLevel.risky) {
|
||||
color = Colors.deepPurpleAccent;
|
||||
size = 60.0;
|
||||
} else if (event.risk == RiskLevel.normal) {
|
||||
color = Colors.blueAccent;
|
||||
size = 40.0;
|
||||
} else {
|
||||
color = Colors.greenAccent;
|
||||
size = 30.0;
|
||||
}
|
||||
// Not a player attack, show immediately
|
||||
showEffect();
|
||||
}
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
setState(() {
|
||||
_floatingEffects.add(
|
||||
FloatingEffectData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingEffect(
|
||||
key: ValueKey(id),
|
||||
icon: icon,
|
||||
color: color,
|
||||
size: size,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingEffects.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,254 +385,265 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
return RestUI(battleProvider: battleProvider);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
key: _stackKey,
|
||||
children: [
|
||||
// 1. Background (Black)
|
||||
Container(color: Colors.black87),
|
||||
return ShakeWidget(
|
||||
key: _shakeKey,
|
||||
child: Stack(
|
||||
key: _stackKey,
|
||||
children: [
|
||||
// 1. Background (Black)
|
||||
Container(color: Colors.black87),
|
||||
|
||||
// 2. Battle Content (Top Bar + Characters)
|
||||
Column(
|
||||
children: [
|
||||
// Top Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"Stage ${battleProvider.stage}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
// 2. Battle Content (Top Bar + Characters)
|
||||
Column(
|
||||
children: [
|
||||
// Top Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"Stage ${battleProvider.stage}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"Turn ${battleProvider.turnCount}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"Turn ${battleProvider.turnCount}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Battle Area (Characters) - Expanded to fill available space
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Enemy (Top Right)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.enemy,
|
||||
isPlayer: false,
|
||||
isTurn: !battleProvider.isPlayerTurn,
|
||||
key: _enemyKey,
|
||||
),
|
||||
),
|
||||
// Player (Bottom Left)
|
||||
Positioned(
|
||||
bottom: 80, // Space for FABs
|
||||
left: 0,
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.player,
|
||||
isPlayer: true,
|
||||
isTurn: battleProvider.isPlayerTurn,
|
||||
key: _playerKey,
|
||||
animationKey: _playerAnimKey,
|
||||
hideStats: _isPlayerAttacking,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 3. Logs Overlay
|
||||
if (_showLogs && battleProvider.logs.isNotEmpty)
|
||||
Positioned(
|
||||
top: 60,
|
||||
left: 16,
|
||||
right: 16,
|
||||
height: 150,
|
||||
child: BattleLogOverlay(logs: battleProvider.logs),
|
||||
),
|
||||
|
||||
// 4. Floating Action Buttons (Bottom Right)
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"ATK",
|
||||
Icons.whatshot,
|
||||
Colors.redAccent,
|
||||
ActionType.attack,
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"DEF",
|
||||
Icons.shield,
|
||||
Colors.blueAccent,
|
||||
ActionType.defend,
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 5. Log Toggle Button (Bottom Left)
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
child: FloatingActionButton(
|
||||
heroTag: "logToggle",
|
||||
mini: true,
|
||||
backgroundColor: Colors.grey[800],
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showLogs = !_showLogs;
|
||||
});
|
||||
},
|
||||
child: Icon(
|
||||
_showLogs ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Reward Popup
|
||||
if (battleProvider.showRewardPopup)
|
||||
Container(
|
||||
color: Colors.black54,
|
||||
child: Center(
|
||||
child: SimpleDialog(
|
||||
title: const Text("Victory! Choose a Reward"),
|
||||
children: battleProvider.rewardOptions.map((item) {
|
||||
return SimpleDialogOption(
|
||||
onPressed: () {
|
||||
battleProvider.selectReward(item);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[700],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: Colors.grey),
|
||||
),
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(item.slot),
|
||||
color: ItemUtils.getColor(item.slot),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
item.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildItemStatText(item),
|
||||
Text(
|
||||
item.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Battle Area (Characters) - Expanded to fill available space
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Stack(
|
||||
// Floating Effects
|
||||
..._floatingDamageTexts.map((e) => e.widget),
|
||||
..._floatingEffects.map((e) => e.widget),
|
||||
..._floatingFeedbackTexts.map((e) => e.widget),
|
||||
|
||||
// Explosion Layer
|
||||
ExplosionWidget(key: _explosionKey),
|
||||
|
||||
// Game Over Overlay
|
||||
if (battleProvider.player.isDead)
|
||||
Container(
|
||||
color: Colors.black87,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Enemy (Top Right)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.enemy,
|
||||
isPlayer: false,
|
||||
isTurn: !battleProvider.isPlayerTurn,
|
||||
key: _enemyKey,
|
||||
const Text(
|
||||
"DEFEAT",
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 4.0,
|
||||
),
|
||||
),
|
||||
// Player (Bottom Left)
|
||||
Positioned(
|
||||
bottom: 80, // Space for FABs
|
||||
left: 0,
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.player,
|
||||
isPlayer: true,
|
||||
isTurn: battleProvider.isPlayerTurn,
|
||||
key: _playerKey,
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MainMenuScreen(),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"Return to Main Menu",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 3. Logs Overlay
|
||||
if (_showLogs && battleProvider.logs.isNotEmpty)
|
||||
Positioned(
|
||||
top: 60,
|
||||
left: 16,
|
||||
right: 16,
|
||||
height: 150,
|
||||
child: BattleLogOverlay(logs: battleProvider.logs),
|
||||
),
|
||||
|
||||
// 4. Floating Action Buttons (Bottom Right)
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"ATK",
|
||||
Icons.whatshot,
|
||||
Colors.redAccent,
|
||||
ActionType.attack,
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"DEF",
|
||||
Icons.shield,
|
||||
Colors.blueAccent,
|
||||
ActionType.defend,
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 5. Log Toggle Button (Bottom Left)
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
child: FloatingActionButton(
|
||||
heroTag: "logToggle",
|
||||
mini: true,
|
||||
backgroundColor: Colors.grey[800],
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showLogs = !_showLogs;
|
||||
});
|
||||
},
|
||||
child: Icon(
|
||||
_showLogs ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Reward Popup
|
||||
if (battleProvider.showRewardPopup)
|
||||
Container(
|
||||
color: Colors.black54,
|
||||
child: Center(
|
||||
child: SimpleDialog(
|
||||
title: const Text("Victory! Choose a Reward"),
|
||||
children: battleProvider.rewardOptions.map((item) {
|
||||
return SimpleDialogOption(
|
||||
onPressed: () {
|
||||
battleProvider.selectReward(item);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[700],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: Colors.grey),
|
||||
),
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(item.slot),
|
||||
color: ItemUtils.getColor(item.slot),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
item.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildItemStatText(item),
|
||||
Text(
|
||||
item.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Floating Effects
|
||||
..._floatingDamageTexts.map((e) => e.widget),
|
||||
..._floatingEffects.map((e) => e.widget),
|
||||
..._floatingFeedbackTexts.map((e) => e.widget),
|
||||
|
||||
// Game Over Overlay
|
||||
if (battleProvider.player.isDead)
|
||||
Container(
|
||||
color: Colors.black87,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"DEFEAT",
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 4.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MainMenuScreen(),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"Return to Main Menu",
|
||||
style: TextStyle(color: Colors.white, fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -589,6 +655,7 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
if (item.atkBonus > 0) stats.add("+${item.atkBonus} ATK");
|
||||
if (item.hpBonus > 0) stats.add("+${item.hpBonus} HP");
|
||||
if (item.armorBonus > 0) stats.add("+${item.armorBonus} DEF");
|
||||
if (item.luck > 0) stats.add("+${item.luck} Luck");
|
||||
|
||||
List<String> effectTexts = item.effects.map((e) => e.description).toList();
|
||||
|
||||
|
||||
@@ -460,6 +460,11 @@ class InventoryScreen extends StatelessWidget {
|
||||
_buildStatChangeRow("Current HP", currentHp, newHp),
|
||||
_buildStatChangeRow("ATK", currentAtk, newAtk),
|
||||
_buildStatChangeRow("DEF", currentDef, newDef),
|
||||
_buildStatChangeRow(
|
||||
"LUCK",
|
||||
player.totalLuck,
|
||||
player.totalLuck - (oldItem?.luck ?? 0) + newItem.luck,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
|
||||
Reference in New Issue
Block a user