update
- consumable items
This commit is contained in:
@@ -73,7 +73,7 @@ class BattleProvider with ChangeNotifier {
|
||||
final Random _random; // Injected Random instance
|
||||
|
||||
BattleProvider({required this.shopProvider, Random? random})
|
||||
: _random = random ?? Random() {
|
||||
: _random = random ?? Random() {
|
||||
// initializeBattle(); // Do not auto-start logic
|
||||
}
|
||||
|
||||
@@ -125,6 +125,15 @@ class BattleProvider with ChangeNotifier {
|
||||
player.addToInventory(ItemTable.weapons[5].createItem()); // Sunderer Axe
|
||||
player.addToInventory(ItemTable.shields[3].createItem()); // Cursed Shield
|
||||
|
||||
// Add Potions for Testing (Requested by User)
|
||||
var healPotion = ItemTable.get('potion_heal_small');
|
||||
var armorPotion = ItemTable.get('potion_armor_small');
|
||||
var strPotion = ItemTable.get('potion_strength_small');
|
||||
|
||||
if (healPotion != null) player.addToInventory(healPotion.createItem());
|
||||
if (armorPotion != null) player.addToInventory(armorPotion.createItem());
|
||||
if (strPotion != null) player.addToInventory(strPotion.createItem());
|
||||
|
||||
_prepareNextStage();
|
||||
_logManager.clear();
|
||||
_addLog("Game Started! Stage 1");
|
||||
@@ -222,7 +231,7 @@ class BattleProvider with ChangeNotifier {
|
||||
// _endPlayerTurn(); // Allow player to choose another action
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
isPlayerTurn = false;
|
||||
notifyListeners();
|
||||
|
||||
@@ -257,7 +266,11 @@ class BattleProvider with ChangeNotifier {
|
||||
if (result.success) {
|
||||
if (type == ActionType.attack) {
|
||||
// 1. Check for Dodge (Moved from _processAttackImpact)
|
||||
if (CombatCalculator.calculateDodge(enemy.totalDodge, random: _random)) { // Pass injected random
|
||||
if (CombatCalculator.calculateDodge(
|
||||
enemy.totalDodge,
|
||||
random: _random,
|
||||
)) {
|
||||
// Pass injected random
|
||||
_addLog("${enemy.name} dodged the attack!");
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
@@ -383,14 +396,22 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
// Recalculate value based on current stats
|
||||
if (intent.type == EnemyActionType.attack) {
|
||||
newValue = (enemy.totalAtk *
|
||||
CombatCalculator.getEfficiency(ActionType.attack, intent.risk))
|
||||
.toInt();
|
||||
newValue =
|
||||
(enemy.totalAtk *
|
||||
CombatCalculator.getEfficiency(
|
||||
ActionType.attack,
|
||||
intent.risk,
|
||||
))
|
||||
.toInt();
|
||||
if (newValue < 1 && enemy.totalAtk > 0) newValue = 1;
|
||||
} else {
|
||||
newValue = (enemy.totalDefense *
|
||||
CombatCalculator.getEfficiency(ActionType.defend, intent.risk))
|
||||
.toInt();
|
||||
newValue =
|
||||
(enemy.totalDefense *
|
||||
CombatCalculator.getEfficiency(
|
||||
ActionType.defend,
|
||||
intent.risk,
|
||||
))
|
||||
.toInt();
|
||||
if (newValue < 1 && enemy.totalDefense > 0) newValue = 1;
|
||||
}
|
||||
|
||||
@@ -520,7 +541,11 @@ class BattleProvider with ChangeNotifier {
|
||||
// Attack Action (Animating)
|
||||
if (intent.isSuccess) {
|
||||
// 1. Check for Dodge
|
||||
if (CombatCalculator.calculateDodge(player.totalDodge, random: _random)) { // Pass injected random
|
||||
if (CombatCalculator.calculateDodge(
|
||||
player.totalDodge,
|
||||
random: _random,
|
||||
)) {
|
||||
// Pass injected random
|
||||
_addLog("${player.name} dodged the attack!");
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
@@ -540,9 +565,13 @@ class BattleProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
// Recalculate damage to account for status changes (like Disarmed)
|
||||
int finalDamage = (enemy.totalAtk *
|
||||
CombatCalculator.getEfficiency(ActionType.attack, intent.risk))
|
||||
.toInt();
|
||||
int finalDamage =
|
||||
(enemy.totalAtk *
|
||||
CombatCalculator.getEfficiency(
|
||||
ActionType.attack,
|
||||
intent.risk,
|
||||
))
|
||||
.toInt();
|
||||
if (finalDamage < 1 && enemy.totalAtk > 0) finalDamage = 1;
|
||||
|
||||
final event = EffectEvent(
|
||||
@@ -579,17 +608,18 @@ class BattleProvider with ChangeNotifier {
|
||||
_effectEventController.sink.add(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (!canAct) { // If cannot act (stunned)
|
||||
_addLog("Enemy is stunned and cannot act!");
|
||||
int tid = _turnTransactionId;
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (tid != _turnTransactionId) return;
|
||||
_endEnemyTurn();
|
||||
});
|
||||
} else {
|
||||
_addLog("Enemy did nothing.");
|
||||
|
||||
}
|
||||
} else if (!canAct) {
|
||||
// If cannot act (stunned)
|
||||
_addLog("Enemy is stunned and cannot act!");
|
||||
int tid = _turnTransactionId;
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (tid != _turnTransactionId) return;
|
||||
_endEnemyTurn();
|
||||
});
|
||||
} else {
|
||||
_addLog("Enemy did nothing.");
|
||||
|
||||
int tid = _turnTransactionId;
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (tid != _turnTransactionId) return;
|
||||
@@ -777,6 +807,68 @@ class BattleProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Use a consumable item during battle (Free Action)
|
||||
void useConsumable(Item item) {
|
||||
if (item.slot != EquipmentSlot.consumable) {
|
||||
_addLog("Cannot use ${item.name}!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Apply Immediate Effects
|
||||
bool effectApplied = false;
|
||||
|
||||
// Heal
|
||||
if (item.hpBonus > 0) {
|
||||
int currentHp = player.hp;
|
||||
player.heal(item.hpBonus);
|
||||
int healedAmount = player.hp - currentHp;
|
||||
if (healedAmount > 0) {
|
||||
_addLog("Used ${item.name}. Recovered $healedAmount HP.");
|
||||
effectApplied = true;
|
||||
} else {
|
||||
_addLog("Used ${item.name}. HP is already full.");
|
||||
// Still consume? Yes, usually potions are lost even if full HP if used.
|
||||
// But maybe valid to just say "Recovered 0 HP".
|
||||
effectApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Armor
|
||||
if (item.armorBonus > 0) {
|
||||
player.armor += item.armorBonus;
|
||||
_addLog("Used ${item.name}. Gained ${item.armorBonus} Armor.");
|
||||
effectApplied = true;
|
||||
}
|
||||
|
||||
// 2. Apply Status Effects (Buffs)
|
||||
if (item.effects.isNotEmpty) {
|
||||
for (var effect in item.effects) {
|
||||
player.addStatusEffect(
|
||||
StatusEffect(
|
||||
type: effect.type,
|
||||
duration: effect.duration,
|
||||
value: effect.value,
|
||||
),
|
||||
);
|
||||
// Log handled? Character.addStatusEffect might need logging or we log here.
|
||||
// Let's add specific logs for known buffs
|
||||
if (effect.type == StatusEffectType.attackUp) {
|
||||
_addLog(
|
||||
"Used ${item.name}. Attack Up for ${effect.duration} turn(s)!",
|
||||
);
|
||||
} else {
|
||||
_addLog("Used ${item.name}. Applied ${effect.type.name}!");
|
||||
}
|
||||
}
|
||||
effectApplied = true;
|
||||
}
|
||||
|
||||
if (effectApplied) {
|
||||
player.inventory.remove(item);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Proceed to next stage from non-battle stages (Shop, Rest)
|
||||
void proceedToNextStage() {
|
||||
stage++;
|
||||
@@ -799,9 +891,11 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
// Decide Action Type
|
||||
// Check constraints
|
||||
bool canDefend = enemy.baseDefense > 0 &&
|
||||
bool canDefend =
|
||||
enemy.baseDefense > 0 &&
|
||||
!enemy.hasStatus(StatusEffectType.defenseForbidden);
|
||||
bool canAttack = true; // Attack is always possible, but strength is affected by status.
|
||||
bool canAttack =
|
||||
true; // Attack is always possible, but strength is affected by status.
|
||||
|
||||
bool isAttack = true; // Default to attack
|
||||
|
||||
@@ -981,7 +1075,7 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
// Try applying status effects
|
||||
_tryApplyStatusEffects(attacker, target);
|
||||
|
||||
|
||||
// If target is enemy, update intent to reflect potential status changes (e.g. Disarmed)
|
||||
if (target == enemy) {
|
||||
updateEnemyIntent();
|
||||
|
||||
@@ -3,10 +3,13 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SettingsProvider with ChangeNotifier {
|
||||
static const String _keyEnemyAnim = 'settings_enemy_anim';
|
||||
static const String _keyAttackAnimScale = 'settings_attack_anim_scale';
|
||||
|
||||
bool _enableEnemyAnimations = true; // Default: Enabled
|
||||
double _attackAnimScale = 5.0; // Default: 5.0
|
||||
|
||||
bool get enableEnemyAnimations => _enableEnemyAnimations;
|
||||
double get attackAnimScale => _attackAnimScale;
|
||||
|
||||
SettingsProvider() {
|
||||
_loadSettings();
|
||||
@@ -15,6 +18,7 @@ class SettingsProvider with ChangeNotifier {
|
||||
Future<void> _loadSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_enableEnemyAnimations = prefs.getBool(_keyEnemyAnim) ?? true;
|
||||
_attackAnimScale = prefs.getDouble(_keyAttackAnimScale) ?? 5.0;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -24,4 +28,11 @@ class SettingsProvider with ChangeNotifier {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_keyEnemyAnim, value);
|
||||
}
|
||||
|
||||
Future<void> setAttackAnimScale(double value) async {
|
||||
_attackAnimScale = value;
|
||||
notifyListeners();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble(_keyAttackAnimScale, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,50 @@ class ShopProvider with ChangeNotifier {
|
||||
currentTier = ItemTier.tier2;
|
||||
|
||||
availableItems = [];
|
||||
availableItems = [];
|
||||
|
||||
// 1. Generate 4 Random Equipment Items
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// Generate 4 items
|
||||
ItemTemplate? template = ItemTable.getRandomItem(tier: currentTier);
|
||||
if (template != null) {
|
||||
availableItems.add(template.createItem(stage: stage));
|
||||
// Exclude consumables from this pool if getRandomItem includes them by default (it does if we don't filter)
|
||||
// We need to implement slot exclusion or explicit slot inclusion in getRandomItem?
|
||||
// Or simply cycle slots?
|
||||
// ItemTable.getRandomItem picks from allItems which now includes consumables.
|
||||
// We should add filtering to getRandomItem logic OR filter here.
|
||||
// Let's filter here by retrying or explicitly asking for non-consumables.
|
||||
// Actually, ItemTable.getRandomItem accepts 'slot'. But we want ANY equipment.
|
||||
// Let's rely on type checking or add 'excludeSlot' to getRandomItem (too much change).
|
||||
// Simpler: Just pick random, if consumable, reroll? Or better:
|
||||
|
||||
// Let's update getRandomItem to support multiple allowed slots? No.
|
||||
// Let's just pick strictly by slot rotation or random filtering.
|
||||
// Let's try simple filtering loop.
|
||||
|
||||
while (true) {
|
||||
ItemTemplate? template = ItemTable.getRandomItem(tier: currentTier);
|
||||
if (template != null && template.slot != EquipmentSlot.consumable) {
|
||||
availableItems.add(template.createItem(stage: stage));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Generate 2 Random Consumables
|
||||
// Consumables might always be Tier 1 for now, or match current tier?
|
||||
// Let's match current tier (though we only defined Tier 1 potions).
|
||||
// If no potions at current tier, fallback to Tier 1?
|
||||
// ItemTable.consumables currently only has items.
|
||||
// Let's just pick from ItemTable.consumables directly for simplicity and safety.
|
||||
|
||||
if (ItemTable.consumables.isNotEmpty) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ItemTemplate? consTemplate = ItemTable.getRandomItem(
|
||||
tier: ItemTier.tier1, // Potions are Tier 1 for now
|
||||
slot: EquipmentSlot.consumable,
|
||||
);
|
||||
|
||||
if (consTemplate != null) {
|
||||
availableItems.add(consTemplate.createItem(stage: stage));
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
|
||||
Reference in New Issue
Block a user