update
This commit is contained in:
@@ -70,8 +70,10 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
// Dependency injection
|
||||
final ShopProvider shopProvider;
|
||||
final Random _random; // Injected Random instance
|
||||
|
||||
BattleProvider({required this.shopProvider}) {
|
||||
BattleProvider({required this.shopProvider, Random? random})
|
||||
: _random = random ?? Random() {
|
||||
// initializeBattle(); // Do not auto-start logic
|
||||
}
|
||||
|
||||
@@ -134,6 +136,9 @@ class BattleProvider with ChangeNotifier {
|
||||
// Save Game at the start of each stage
|
||||
SaveManager.saveGame(this);
|
||||
|
||||
// Reset Player Armor at start of new stage
|
||||
player.armor = 0;
|
||||
|
||||
StageType type;
|
||||
|
||||
// Stage Type Logic
|
||||
@@ -214,10 +219,10 @@ class BattleProvider with ChangeNotifier {
|
||||
player.hasStatus(StatusEffectType.defenseForbidden)) {
|
||||
_addLog("Cannot defend! You are under Defense Forbidden status.");
|
||||
notifyListeners(); // 상태 변경을 알림
|
||||
_endPlayerTurn();
|
||||
// _endPlayerTurn(); // Allow player to choose another action
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
isPlayerTurn = false;
|
||||
notifyListeners();
|
||||
|
||||
@@ -246,28 +251,48 @@ class BattleProvider with ChangeNotifier {
|
||||
risk: risk,
|
||||
luck: player.totalLuck,
|
||||
baseValue: baseValue,
|
||||
random: _random, // Pass injected random
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
if (type == ActionType.attack) {
|
||||
int damage = result.value;
|
||||
// 1. Check for Dodge (Moved from _processAttackImpact)
|
||||
if (CombatCalculator.calculateDodge(enemy.totalDodge, random: _random)) { // Pass injected random
|
||||
_addLog("${enemy.name} dodged the attack!");
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
_random.nextInt(1000).toString(), // Use injected random
|
||||
type: ActionType.attack,
|
||||
risk: risk,
|
||||
target: EffectTarget.enemy,
|
||||
feedbackType: BattleFeedbackType.dodge, // Dodge feedback
|
||||
attacker: player,
|
||||
targetEntity: enemy,
|
||||
damageValue: 0,
|
||||
isSuccess:
|
||||
false, // Treated as fail for animation purposes (or custom)
|
||||
);
|
||||
_effectEventController.sink.add(event);
|
||||
} else {
|
||||
// 2. Hit Success
|
||||
int damage = result.value;
|
||||
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
Random().nextInt(1000).toString(),
|
||||
type: ActionType.attack,
|
||||
risk: risk,
|
||||
target: EffectTarget.enemy,
|
||||
feedbackType: null,
|
||||
attacker: player,
|
||||
targetEntity: enemy,
|
||||
damageValue: damage,
|
||||
isSuccess: true,
|
||||
);
|
||||
_effectEventController.sink.add(
|
||||
event,
|
||||
); // No Future.delayed here, BattleScreen will trigger impact
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
_random.nextInt(1000).toString(), // Use injected random
|
||||
type: ActionType.attack,
|
||||
risk: risk,
|
||||
target: EffectTarget.enemy,
|
||||
feedbackType: null,
|
||||
attacker: player,
|
||||
targetEntity: enemy,
|
||||
damageValue: damage,
|
||||
isSuccess: true,
|
||||
);
|
||||
_effectEventController.sink.add(event);
|
||||
}
|
||||
} else {
|
||||
// Defense Success - Impact is immediate, so process it directly
|
||||
final event = EffectEvent(
|
||||
@@ -348,6 +373,40 @@ class BattleProvider with ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
/// Recalculates the current enemy intent value based on current stats.
|
||||
/// Used to update UI when enemy stats change (e.g. Disarmed applied).
|
||||
void updateEnemyIntent() {
|
||||
if (currentEnemyIntent == null || enemy.isDead) return;
|
||||
|
||||
final intent = currentEnemyIntent!;
|
||||
int newValue = 0;
|
||||
|
||||
// Recalculate value based on current stats
|
||||
if (intent.type == EnemyActionType.attack) {
|
||||
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();
|
||||
if (newValue < 1 && enemy.totalDefense > 0) newValue = 1;
|
||||
}
|
||||
|
||||
// Replace intent with updated value, keeping other properties
|
||||
currentEnemyIntent = EnemyIntent(
|
||||
type: intent.type,
|
||||
value: newValue,
|
||||
risk: intent.risk,
|
||||
description: "$newValue (${intent.risk.name})",
|
||||
isSuccess: intent.isSuccess,
|
||||
finalValue: newValue,
|
||||
isApplied: intent.isApplied,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// --- Turn Management Phases ---
|
||||
|
||||
// Phase 4: Start Player Turn
|
||||
@@ -364,6 +423,9 @@ class BattleProvider with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update Intent if stats changed (e.g. status effects expired)
|
||||
updateEnemyIntent();
|
||||
|
||||
// [New] Apply Pre-emptive Enemy Intent (Defense/Buffs)
|
||||
// MOVED: Logic moved to applyPendingEnemyDefense() to sync with animation.
|
||||
// We just check intent existence here but do NOT apply effects yet.
|
||||
@@ -430,7 +492,8 @@ class BattleProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
// Process Start-of-Turn Effects
|
||||
bool canAct = _processStartTurnEffects(enemy);
|
||||
final result = CombatCalculator.processStartTurnEffects(enemy);
|
||||
bool canAct = !result['isStunned'];
|
||||
|
||||
if (enemy.isDead) {
|
||||
_onVictory();
|
||||
@@ -456,17 +519,43 @@ class BattleProvider with ChangeNotifier {
|
||||
} else {
|
||||
// Attack Action (Animating)
|
||||
if (intent.isSuccess) {
|
||||
// 1. Check for Dodge
|
||||
if (CombatCalculator.calculateDodge(player.totalDodge, random: _random)) { // Pass injected random
|
||||
_addLog("${player.name} dodged the attack!");
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
_random.nextInt(1000).toString(), // Use injected random
|
||||
type: ActionType.attack,
|
||||
risk: intent.risk,
|
||||
target: EffectTarget.player,
|
||||
feedbackType: BattleFeedbackType.dodge,
|
||||
attacker: enemy,
|
||||
targetEntity: player,
|
||||
damageValue: 0,
|
||||
isSuccess: false,
|
||||
);
|
||||
_effectEventController.sink.add(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate damage to account for status changes (like Disarmed)
|
||||
int finalDamage = (enemy.totalAtk *
|
||||
CombatCalculator.getEfficiency(ActionType.attack, intent.risk))
|
||||
.toInt();
|
||||
if (finalDamage < 1 && enemy.totalAtk > 0) finalDamage = 1;
|
||||
|
||||
final event = EffectEvent(
|
||||
id:
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
Random().nextInt(1000).toString(),
|
||||
_random.nextInt(1000).toString(), // Use injected random
|
||||
type: ActionType.attack,
|
||||
risk: intent.risk,
|
||||
target: EffectTarget.player,
|
||||
feedbackType: null,
|
||||
attacker: enemy,
|
||||
targetEntity: player,
|
||||
damageValue: intent.finalValue,
|
||||
damageValue: finalDamage,
|
||||
isSuccess: true,
|
||||
);
|
||||
_effectEventController.sink.add(event);
|
||||
@@ -490,16 +579,17 @@ class BattleProvider with ChangeNotifier {
|
||||
_effectEventController.sink.add(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (!canAct) {
|
||||
_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;
|
||||
@@ -512,6 +602,9 @@ class BattleProvider with ChangeNotifier {
|
||||
void _endEnemyTurn() {
|
||||
if (player.isDead) return; // Game Over check
|
||||
|
||||
// Update enemy status at the end of their turn
|
||||
enemy.updateStatusEffects();
|
||||
|
||||
// Generate NEXT intent
|
||||
_generateEnemyIntent();
|
||||
|
||||
@@ -690,29 +783,45 @@ class BattleProvider with ChangeNotifier {
|
||||
_prepareNextStage();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
void generateEnemyIntent() {
|
||||
_generateEnemyIntent();
|
||||
}
|
||||
|
||||
void _generateEnemyIntent() {
|
||||
if (enemy.isDead) {
|
||||
currentEnemyIntent = null;
|
||||
return;
|
||||
}
|
||||
|
||||
final random = Random();
|
||||
// Use the injected _random field
|
||||
// final random = Random(); // Removed
|
||||
|
||||
// Decide Action Type
|
||||
bool canDefend = enemy.baseDefense > 0;
|
||||
if (enemy.hasStatus(StatusEffectType.defenseForbidden)) {
|
||||
canDefend = false;
|
||||
}
|
||||
bool isAttack = true;
|
||||
// Check constraints
|
||||
bool canDefend = enemy.baseDefense > 0 &&
|
||||
!enemy.hasStatus(StatusEffectType.defenseForbidden);
|
||||
bool canAttack = true; // Attack is always possible, but strength is affected by status.
|
||||
|
||||
if (canDefend) {
|
||||
isAttack = random.nextDouble() < BattleConfig.enemyAttackChance;
|
||||
} else {
|
||||
bool isAttack = true; // Default to attack
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Decide Risk Level
|
||||
RiskLevel risk = RiskLevel.values[random.nextInt(RiskLevel.values.length)];
|
||||
RiskLevel risk = RiskLevel.values[_random.nextInt(RiskLevel.values.length)];
|
||||
|
||||
CombatResult result;
|
||||
if (isAttack) {
|
||||
@@ -872,6 +981,11 @@ 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();
|
||||
}
|
||||
} else if (event.type == ActionType.defend) {
|
||||
// Defense Impact is immediate (no anim delay from UI)
|
||||
if (event.isSuccess!) {
|
||||
@@ -900,6 +1014,7 @@ class BattleProvider with ChangeNotifier {
|
||||
void _tryApplyStatusEffects(Character attacker, Character target) {
|
||||
List<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects(
|
||||
attacker,
|
||||
random: _random, // Pass injected random
|
||||
);
|
||||
|
||||
for (var effect in effectsToApply) {
|
||||
|
||||
Reference in New Issue
Block a user