Compare commits
2 Commits
e020e44d8f
...
cb48d0db48
| Author | SHA1 | Date |
|---|---|---|
|
|
cb48d0db48 | |
|
|
3b9fa47f0a |
Binary file not shown.
|
After Width: | Height: | Size: 465 B |
Binary file not shown.
|
After Width: | Height: | Size: 556 B |
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,20 +33,34 @@ class ItemCardWidget extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
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(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
const SizedBox(height: 12),
|
||||||
child: Image.asset(
|
|
||||||
ItemUtils.getIconPath(item.slot),
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
item.name,
|
item.name,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -54,12 +68,17 @@ class ItemCardWidget extends StatelessWidget {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Show Item Stats (Fixed to show negative values)
|
const SizedBox(height: 4),
|
||||||
FittedBox(fit: BoxFit.scaleDown, child: _buildItemStatText(item)),
|
// Show Item Stats
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: _buildItemStatText(item),
|
||||||
|
),
|
||||||
if (showPrice) ...[
|
if (showPrice) ...[
|
||||||
const Spacer(),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
"${item.price} G",
|
"${item.price} G",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: canBuy
|
color: canBuy
|
||||||
? ThemeConfig.statGoldColor
|
? ThemeConfig.statGoldColor
|
||||||
|
|
@ -73,6 +92,9 @@ class ItemCardWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue