Compare commits

..

2 Commits

Author SHA1 Message Date
horoli cb48d0db48 update 2026-02-04 01:44:57 +09:00
horoli 3b9fa47f0a update 2026-02-04 01:44:38 +09:00
7 changed files with 126 additions and 100 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

View File

@ -15,6 +15,13 @@ import 'shop_provider.dart';
import '../game/logic.dart'; import '../game/logic.dart';
class TurnEffectResult {
final bool canAct;
final bool effectTriggered;
TurnEffectResult({required this.canAct, required this.effectTriggered});
}
class EnemyIntent { class EnemyIntent {
final EnemyActionType type; final EnemyActionType type;
final int value; final int value;
@ -225,8 +232,9 @@ class BattleProvider with ChangeNotifier {
/// Handle player's action choice /// Handle player's action choice
Future<void> playerAction(ActionType type, RiskLevel risk) async { Future<void> playerAction(ActionType type, RiskLevel risk) async {
if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) {
return; return;
}
// 0. Ensure Pre-emptive Enemy Defense is applied (if not already via animation) // 0. Ensure Pre-emptive Enemy Defense is applied (if not already via animation)
applyPendingEnemyDefense(); applyPendingEnemyDefense();
@ -245,14 +253,19 @@ class BattleProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
// 2. Process Start-of-Turn Effects (Stun, Bleed) // 2. Process Start-of-Turn Effects (Stun, Bleed)
bool canAct = _processStartTurnEffects(player); TurnEffectResult turnEffect = _processStartTurnEffects(player);
if (player.isDead) { if (player.isDead) {
await _onDefeat(); await _onDefeat();
return; 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 _endPlayerTurn(); // Skip turn if stunned
return; return;
} }
@ -474,11 +487,12 @@ class BattleProvider with ChangeNotifier {
} }
/// Check Status Effects at Start of Turn /// Check Status Effects at Start of Turn
bool _processStartTurnEffects(Character character) { TurnEffectResult _processStartTurnEffects(Character character) {
final result = CombatCalculator.processStartTurnEffects(character); final result = CombatCalculator.processStartTurnEffects(character);
int totalBleed = result['bleedDamage']; int totalBleed = result['bleedDamage'];
bool isStunned = result['isStunned']; bool isStunned = result['isStunned'];
bool effectTriggered = false;
// 1. Bleed Damage // 1. Bleed Damage
if (totalBleed > 0) { if (totalBleed > 0) {
@ -496,14 +510,19 @@ class BattleProvider with ChangeNotifier {
type: DamageType.bleed, type: DamageType.bleed,
), ),
); );
effectTriggered = true;
} }
// 2. Stun Check // 2. Stun Check
if (isStunned) { if (isStunned) {
_addLog("${character.name} is stunned!"); _addLog("${character.name} is stunned!");
effectTriggered = true;
} }
return !isStunned; return TurnEffectResult(
canAct: !isStunned,
effectTriggered: effectTriggered,
);
} }
// --- Turn Management Phases --- // --- Turn Management Phases ---
@ -522,14 +541,19 @@ class BattleProvider with ChangeNotifier {
} }
// Process Start-of-Turn Effects // Process Start-of-Turn Effects
bool canAct = _processStartTurnEffects(enemy); TurnEffectResult turnEffect = _processStartTurnEffects(enemy);
if (enemy.isDead) { if (enemy.isDead) {
_onVictory(); _onVictory();
return; 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!; final intent = currentEnemyIntent!;
if (intent.type == EnemyActionType.defend) { if (intent.type == EnemyActionType.defend) {
@ -617,7 +641,7 @@ class BattleProvider with ChangeNotifier {
return; return;
} }
} }
} else if (!canAct) { } else if (!turnEffect.canAct) {
// If cannot act (stunned) // If cannot act (stunned)
_addLog("Enemy is stunned and cannot act!"); _addLog("Enemy is stunned and cannot act!");
int tid = _turnTransactionId; int tid = _turnTransactionId;
@ -674,10 +698,11 @@ class BattleProvider with ChangeNotifier {
_addLog("Choose a reward."); _addLog("Choose a reward.");
ItemTier currentTier = ItemTier.tier1; ItemTier currentTier = ItemTier.tier1;
if (stage > GameConfig.tier2StageMax) if (stage > GameConfig.tier2StageMax) {
currentTier = ItemTier.tier3; currentTier = ItemTier.tier3;
else if (stage > GameConfig.tier1StageMax) } else if (stage > GameConfig.tier1StageMax) {
currentTier = ItemTier.tier2; currentTier = ItemTier.tier2;
}
rewardOptions = []; rewardOptions = [];
@ -910,16 +935,12 @@ class BattleProvider with ChangeNotifier {
if (canAttack && canDefend) { if (canAttack && canDefend) {
// Both options available: Use configured probability // Both options available: Use configured probability
isAttack = _random.nextDouble() < BattleConfig.enemyAttackChance; isAttack = _random.nextDouble() < BattleConfig.enemyAttackChance;
} else if (canAttack) {
// Must attack
isAttack = true;
} else if (canDefend) { } else if (canDefend) {
// Must defend // Must defend
isAttack = false; isAttack = false;
} else { } else {
// Both forbidden (Rare case, effectively stunned but not via Stun status) // Must attack (default) or both forbidden (defaults to attack currently)
// Default to Defend as a fallback, outcomes will be handled by stats/luck isAttack = true;
isAttack = false;
} }
// Decide Risk Level // Decide Risk Level
@ -1113,7 +1134,11 @@ class BattleProvider with ChangeNotifier {
} }
/// Tries to applyStatus effects from attacker's equipment to the target. /// 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<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects( List<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects(
attacker, attacker,
random: _random, // Pass injected random random: _random, // Pass injected random

View File

@ -21,15 +21,15 @@ class ItemUtils {
static String getIconPath(EquipmentSlot slot) { static String getIconPath(EquipmentSlot slot) {
switch (slot) { switch (slot) {
case EquipmentSlot.weapon: case EquipmentSlot.weapon:
return 'assets/data/icon/icon_weapon.png'; return 'assets/images/icons/weapons/sword_02.png';
case EquipmentSlot.shield: case EquipmentSlot.shield:
return 'assets/data/icon/icon_shield.png'; return 'assets/images/icons/subweapons/shield_01.png';
case EquipmentSlot.armor: case EquipmentSlot.armor:
return 'assets/data/icon/icon_armor.png'; return 'assets/images/icons/armors/armor_02.png';
case EquipmentSlot.accessory: case EquipmentSlot.accessory:
return 'assets/data/icon/icon_accessory.png'; return 'assets/images/icons/accessories/ring_02.png';
case EquipmentSlot.consumable: case EquipmentSlot.consumable:
return 'assets/data/icon/icon_accessory.png'; // Todo: Add potion icon return 'assets/images/icons/potions/potion_blue.png';
} }
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../game/config.dart'; import '../common/custom_icon_button.dart';
class BattleControls extends StatelessWidget { class BattleControls extends StatelessWidget {
final bool isAttackEnabled; final bool isAttackEnabled;
@ -18,57 +18,30 @@ class BattleControls extends StatelessWidget {
required this.onItemPressed, // New 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildFloatingActionButton( CustomIconButton(
label: "ATK", iconPath: 'assets/images/icons/weapons/sword_02.png',
color: ThemeConfig.btnActionActive,
iconPath: 'assets/data/icon/icon_weapon.png',
isEnabled: isAttackEnabled, isEnabled: isAttackEnabled,
onPressed: onAttackPressed, onTap: onAttackPressed,
iconColor: Colors.white,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildFloatingActionButton( CustomIconButton(
label: "DEF", iconPath: 'assets/images/icons/subweapons/shield_01.png',
color: ThemeConfig.btnDefendActive,
iconPath: 'assets/data/icon/icon_shield.png',
isEnabled: isDefendEnabled, isEnabled: isDefendEnabled,
onPressed: onDefendPressed, onTap: onDefendPressed,
iconColor: Colors.white,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildFloatingActionButton( CustomIconButton(
label: "ITEM", iconPath: 'assets/images/icons/potions/potion_blue.png',
color: Colors.indigoAccent, // Distinct color for Item isEnabled: isAttackEnabled, // Enabled when it's player turn
iconPath: onTap: onItemPressed,
'assets/data/icon/icon_accessory.png', // Placeholder for Bag iconColor: Colors.white,
isEnabled:
isAttackEnabled, // Enabled when it's player turn (same as attack)
onPressed: onItemPressed,
), ),
], ],
); );

View File

@ -24,7 +24,7 @@ class ItemCardWidget extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: Card( child: Card(
color: ThemeConfig.shopItemCardBg, // Configurable if needed color: ThemeConfig.shopItemCardBg, // Configurable if needed
shape: item.rarity != ItemRarity.magic shape: item.rarity != ItemRarity.normal
? RoundedRectangleBorder( ? RoundedRectangleBorder(
side: BorderSide( side: BorderSide(
color: ItemUtils.getRarityColor(item.rarity), color: ItemUtils.getRarityColor(item.rarity),
@ -33,44 +33,66 @@ class ItemCardWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
) )
: null, : null,
child: Padding( child: Stack(
padding: const EdgeInsets.all(4.0), fit: StackFit.expand,
child: Column( children: [
mainAxisAlignment: MainAxisAlignment.center, // Background Watermark/Silhouette Icon (Top-Left)
children: [ Positioned(
Expanded( left: 8,
child: Image.asset( top: 8,
ItemUtils.getIconPath(item.slot), child: Image.asset(
fit: BoxFit.contain, 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,
),
),
],
],
),
), ),
), ),
); );

View File

@ -30,3 +30,9 @@ flutter:
- assets/images/enemies/ - assets/images/enemies/
- assets/images/background/ - assets/images/background/
- assets/images/character/ - 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/