diff --git a/assets/images/icons/accessories/ring_01.png b/assets/images/icons/accessories/ring_01.png new file mode 100644 index 0000000..c0d5dba Binary files /dev/null and b/assets/images/icons/accessories/ring_01.png differ diff --git a/assets/images/icons/accessories/ring_02.png b/assets/images/icons/accessories/ring_02.png new file mode 100644 index 0000000..8881039 Binary files /dev/null and b/assets/images/icons/accessories/ring_02.png differ diff --git a/lib/providers/battle_provider.dart b/lib/providers/battle_provider.dart index c848411..114cdc9 100644 --- a/lib/providers/battle_provider.dart +++ b/lib/providers/battle_provider.dart @@ -15,6 +15,13 @@ import 'shop_provider.dart'; import '../game/logic.dart'; +class TurnEffectResult { + final bool canAct; + final bool effectTriggered; + + TurnEffectResult({required this.canAct, required this.effectTriggered}); +} + class EnemyIntent { final EnemyActionType type; final int value; @@ -225,8 +232,9 @@ class BattleProvider with ChangeNotifier { /// Handle player's action choice Future playerAction(ActionType type, RiskLevel risk) async { - if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) + if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) { return; + } // 0. Ensure Pre-emptive Enemy Defense is applied (if not already via animation) applyPendingEnemyDefense(); @@ -245,14 +253,19 @@ class BattleProvider with ChangeNotifier { notifyListeners(); // 2. Process Start-of-Turn Effects (Stun, Bleed) - bool canAct = _processStartTurnEffects(player); + TurnEffectResult turnEffect = _processStartTurnEffects(player); if (player.isDead) { await _onDefeat(); return; } - if (!canAct) { + // If a visual effect occurred (bleed, stun), wait a bit before action + if (turnEffect.effectTriggered) { + await Future.delayed(const Duration(milliseconds: 800)); + } + + if (!turnEffect.canAct) { _endPlayerTurn(); // Skip turn if stunned return; } @@ -474,11 +487,12 @@ class BattleProvider with ChangeNotifier { } /// Check Status Effects at Start of Turn - bool _processStartTurnEffects(Character character) { + TurnEffectResult _processStartTurnEffects(Character character) { final result = CombatCalculator.processStartTurnEffects(character); int totalBleed = result['bleedDamage']; bool isStunned = result['isStunned']; + bool effectTriggered = false; // 1. Bleed Damage if (totalBleed > 0) { @@ -496,14 +510,19 @@ class BattleProvider with ChangeNotifier { type: DamageType.bleed, ), ); + effectTriggered = true; } // 2. Stun Check if (isStunned) { _addLog("${character.name} is stunned!"); + effectTriggered = true; } - return !isStunned; + return TurnEffectResult( + canAct: !isStunned, + effectTriggered: effectTriggered, + ); } // --- Turn Management Phases --- @@ -522,14 +541,19 @@ class BattleProvider with ChangeNotifier { } // Process Start-of-Turn Effects - bool canAct = _processStartTurnEffects(enemy); + TurnEffectResult turnEffect = _processStartTurnEffects(enemy); if (enemy.isDead) { _onVictory(); return; } - if (canAct && currentEnemyIntent != null) { + // If a visual effect occurred (bleed, stun), wait a bit before action + if (turnEffect.effectTriggered) { + await Future.delayed(const Duration(milliseconds: 800)); + } + + if (turnEffect.canAct && currentEnemyIntent != null) { final intent = currentEnemyIntent!; if (intent.type == EnemyActionType.defend) { @@ -617,7 +641,7 @@ class BattleProvider with ChangeNotifier { return; } } - } else if (!canAct) { + } else if (!turnEffect.canAct) { // If cannot act (stunned) _addLog("Enemy is stunned and cannot act!"); int tid = _turnTransactionId; @@ -674,10 +698,11 @@ class BattleProvider with ChangeNotifier { _addLog("Choose a reward."); ItemTier currentTier = ItemTier.tier1; - if (stage > GameConfig.tier2StageMax) + if (stage > GameConfig.tier2StageMax) { currentTier = ItemTier.tier3; - else if (stage > GameConfig.tier1StageMax) + } else if (stage > GameConfig.tier1StageMax) { currentTier = ItemTier.tier2; + } rewardOptions = []; @@ -910,16 +935,12 @@ class BattleProvider with ChangeNotifier { if (canAttack && canDefend) { // Both options available: Use configured probability isAttack = _random.nextDouble() < BattleConfig.enemyAttackChance; - } else if (canAttack) { - // Must attack - isAttack = true; } else if (canDefend) { // Must defend isAttack = false; } else { - // Both forbidden (Rare case, effectively stunned but not via Stun status) - // Default to Defend as a fallback, outcomes will be handled by stats/luck - isAttack = false; + // Must attack (default) or both forbidden (defaults to attack currently) + isAttack = true; } // Decide Risk Level @@ -1113,7 +1134,11 @@ class BattleProvider with ChangeNotifier { } /// Tries to applyStatus effects from attacker's equipment to the target. - void _tryApplyStatusEffects(Character attacker, Character target, int damageToHp) { + void _tryApplyStatusEffects( + Character attacker, + Character target, + int damageToHp, + ) { List effectsToApply = CombatCalculator.getAppliedEffects( attacker, random: _random, // Pass injected random diff --git a/lib/utils/item_utils.dart b/lib/utils/item_utils.dart index e7aba5a..c01dd4b 100644 --- a/lib/utils/item_utils.dart +++ b/lib/utils/item_utils.dart @@ -21,15 +21,15 @@ class ItemUtils { static String getIconPath(EquipmentSlot slot) { switch (slot) { case EquipmentSlot.weapon: - return 'assets/data/icon/icon_weapon.png'; + return 'assets/images/icons/weapons/sword_02.png'; case EquipmentSlot.shield: - return 'assets/data/icon/icon_shield.png'; + return 'assets/images/icons/subweapons/shield_01.png'; case EquipmentSlot.armor: - return 'assets/data/icon/icon_armor.png'; + return 'assets/images/icons/armors/armor_01.png'; case EquipmentSlot.accessory: - return 'assets/data/icon/icon_accessory.png'; + return 'assets/images/icons/accessories/ring_02.png'; case EquipmentSlot.consumable: - return 'assets/data/icon/icon_accessory.png'; // Todo: Add potion icon + return 'assets/images/icons/potions/potion_blue.png'; } } } diff --git a/lib/widgets/battle/battle_controls.dart b/lib/widgets/battle/battle_controls.dart index b021b5f..9a39c78 100644 --- a/lib/widgets/battle/battle_controls.dart +++ b/lib/widgets/battle/battle_controls.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../game/config.dart'; +import '../common/custom_icon_button.dart'; class BattleControls extends StatelessWidget { final bool isAttackEnabled; @@ -18,57 +18,30 @@ class BattleControls extends StatelessWidget { required this.onItemPressed, // New }); - Widget _buildFloatingActionButton({ - required String label, - required Color color, - required String iconPath, // Changed from ActionType to String - required bool isEnabled, - required VoidCallback onPressed, - }) { - return FloatingActionButton( - heroTag: label, - onPressed: isEnabled ? onPressed : null, - backgroundColor: isEnabled ? color : ThemeConfig.btnDisabled, - child: Image.asset( - iconPath, - width: 32, - height: 32, - color: ThemeConfig.textColorWhite, // Tint icon white - fit: BoxFit.contain, - filterQuality: FilterQuality.high, - ), - ); - } - @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ - _buildFloatingActionButton( - label: "ATK", - color: ThemeConfig.btnActionActive, - iconPath: 'assets/data/icon/icon_weapon.png', + CustomIconButton( + iconPath: 'assets/images/icons/weapons/sword_02.png', isEnabled: isAttackEnabled, - onPressed: onAttackPressed, + onTap: onAttackPressed, + iconColor: Colors.white, ), const SizedBox(height: 16), - _buildFloatingActionButton( - label: "DEF", - color: ThemeConfig.btnDefendActive, - iconPath: 'assets/data/icon/icon_shield.png', + CustomIconButton( + iconPath: 'assets/images/icons/subweapons/shield_01.png', isEnabled: isDefendEnabled, - onPressed: onDefendPressed, + onTap: onDefendPressed, + iconColor: Colors.white, ), const SizedBox(height: 16), - _buildFloatingActionButton( - label: "ITEM", - color: Colors.indigoAccent, // Distinct color for Item - iconPath: - 'assets/data/icon/icon_accessory.png', // Placeholder for Bag - isEnabled: - isAttackEnabled, // Enabled when it's player turn (same as attack) - onPressed: onItemPressed, + CustomIconButton( + iconPath: 'assets/images/icons/potions/potion_blue.png', + isEnabled: isAttackEnabled, // Enabled when it's player turn + onTap: onItemPressed, + iconColor: Colors.white, ), ], ); diff --git a/lib/widgets/common/item_card_widget.dart b/lib/widgets/common/item_card_widget.dart index 33eee6e..b4dc476 100644 --- a/lib/widgets/common/item_card_widget.dart +++ b/lib/widgets/common/item_card_widget.dart @@ -24,7 +24,7 @@ class ItemCardWidget extends StatelessWidget { onTap: onTap, child: Card( color: ThemeConfig.shopItemCardBg, // Configurable if needed - shape: item.rarity != ItemRarity.magic + shape: item.rarity != ItemRarity.normal ? RoundedRectangleBorder( side: BorderSide( color: ItemUtils.getRarityColor(item.rarity), @@ -33,44 +33,66 @@ class ItemCardWidget extends StatelessWidget { borderRadius: BorderRadius.circular(8.0), ) : null, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Image.asset( - ItemUtils.getIconPath(item.slot), - fit: BoxFit.contain, + child: Stack( + fit: StackFit.expand, + children: [ + // Background Watermark/Silhouette Icon (Top-Left) + Positioned( + left: 8, + top: 8, + child: Image.asset( + ItemUtils.getIconPath(item.slot), + width: 32, + height: 32, + fit: BoxFit.contain, + color: Colors.black12, // Shadow silhouette + ), + ), + // Main Content (Centered) + Center( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 12), + Text( + item.name, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.bold, + color: ItemUtils.getRarityColor(item.rarity), + fontSize: 12, + ), + ), + const SizedBox(height: 4), + // Show Item Stats + FittedBox( + fit: BoxFit.scaleDown, + child: _buildItemStatText(item), + ), + if (showPrice) ...[ + const SizedBox(height: 4), + Text( + "${item.price} G", + textAlign: TextAlign.center, + style: TextStyle( + color: canBuy + ? ThemeConfig.statGoldColor + : ThemeConfig.textColorGrey, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ], ), ), - Text( - item.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: FontWeight.bold, - color: ItemUtils.getRarityColor(item.rarity), - fontSize: 12, - ), - ), - // Show Item Stats (Fixed to show negative values) - FittedBox(fit: BoxFit.scaleDown, child: _buildItemStatText(item)), - if (showPrice) ...[ - const Spacer(), - Text( - "${item.price} G", - style: TextStyle( - color: canBuy - ? ThemeConfig.statGoldColor - : ThemeConfig.textColorGrey, - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ], - ], - ), + ), + ], ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index ebd7a15..d06d8f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,3 +30,9 @@ flutter: - assets/images/enemies/ - assets/images/background/ - assets/images/character/ + - assets/images/icons/borders/ + - assets/images/icons/weapons/ + - assets/images/icons/subweapons/ + - assets/images/icons/accessories/ + - assets/images/icons/potions/ + - assets/images/icons/armors/