This commit is contained in:
2025-12-08 18:57:17 +09:00
parent eab99eee6f
commit f49902c067
6 changed files with 223 additions and 254 deletions
+101 -178
View File
@@ -266,65 +266,8 @@ class BattleProvider with ChangeNotifier {
if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup)
return;
// 0. Apply Enemy Pre-emptive Defense (Just-in-Time)
if (currentEnemyIntent?.type == EnemyActionType.defend &&
!currentEnemyIntent!.isApplied) {
final intent = currentEnemyIntent!;
if (intent.isSuccess) {
enemy.armor += intent.finalValue;
_addLog(
"Enemy raises shield just in time! (+${intent.finalValue} Armor)",
);
// Visual Effect for Enemy Defense Success
final event = EffectEvent(
id:
DateTime.now().millisecondsSinceEpoch.toString() +
Random().nextInt(1000).toString(),
type: ActionType.defend,
risk: intent.risk,
target: EffectTarget.enemy,
feedbackType: null,
attacker: enemy,
targetEntity: enemy,
armorGained: intent.finalValue,
isSuccess: true,
);
_effectEventController.sink.add(event);
}
if (!intent.isSuccess) {
_addLog("Enemy tried to raise shield but fumbled!");
print("[Logic Debug] Enemy Defense Fumbled Event"); // Debug Log
// Visual Effect for Enemy Defense Failure
final event = EffectEvent(
id:
DateTime.now().millisecondsSinceEpoch.toString() +
Random().nextInt(1000).toString(),
type: ActionType.defend,
risk: intent.risk,
target: EffectTarget.enemy,
feedbackType: BattleFeedbackType.failed,
attacker: enemy,
targetEntity: enemy,
isSuccess: false,
);
_effectEventController.sink.add(event);
}
intent.isApplied = true;
notifyListeners();
// Re-add delay to show the defense before player attack lands
int tid = _turnTransactionId;
await Future.delayed(const Duration(milliseconds: 500));
if (tid != _turnTransactionId) return;
}
// 0. Apply Enemy Pre-emptive Defense - REMOVED (Standard Turn-Based Logic)
// Defense now happens on Enemy's Turn.
// Update Enemy Status Effects at the start of Player's turn (user request)
@@ -433,7 +376,6 @@ class BattleProvider with ChangeNotifier {
event,
); // Send event for miss/fail feedback
_addLog("${player.name}'s ${type.name} ${feedbackType.name}!");
print("[Logic Debug] Player Action Failed Event"); // Debug Log
// handleImpact(event); // REMOVED: Driven by UI
}
@@ -469,18 +411,90 @@ class BattleProvider with ChangeNotifier {
// --- Turn Management Phases ---
// Phase 4: Start Player Turn
void _startPlayerTurn() {
// Player Turn Start Logic
// Armor decay (Player)
if (player.armor > 0) {
player.armor = (player.armor * GameConfig.armorDecayRate).toInt();
_addLog("Player's armor decayed to ${player.armor}.");
}
if (player.isDead) {
_onDefeat();
return;
}
// [New] Apply Pre-emptive Enemy Intent (Defense/Buffs)
if (currentEnemyIntent != null) {
final intent = currentEnemyIntent!;
if (intent.type == EnemyActionType.defend) {
if (intent.isSuccess) {
enemy.armor += intent.finalValue;
_addLog(
"${enemy.name} assumes a defensive stance (+${intent.finalValue} Armor).",
);
} else {
_addLog("${enemy.name} tried to defend but failed.");
}
intent.isApplied = true; // Mark as applied so we don't do it again
}
// Add other pre-emptive intent types here if needed (e.g., Buffs)
}
isPlayerTurn = true;
turnCount++;
notifyListeners();
}
void _addLog(String log) {
_logManager.addLog(log);
notifyListeners();
}
/// Check Status Effects at Start of Turn
bool _processStartTurnEffects(Character character) {
final result = CombatCalculator.processStartTurnEffects(character);
int totalBleed = result['bleedDamage'];
bool isStunned = result['isStunned'];
// 1. Bleed Damage
if (totalBleed > 0) {
character.hp -= totalBleed;
if (character.hp < 0) character.hp = 0;
_addLog("${character.name} takes $totalBleed bleed damage!");
// Emit DamageEvent for bleed
_damageEventController.sink.add(
DamageEvent(
damage: totalBleed,
target: (character == player)
? DamageTarget.player
: DamageTarget.enemy,
type: DamageType.bleed,
),
);
}
// 2. Stun Check
if (isStunned) {
_addLog("${character.name} is stunned!");
}
return !isStunned;
}
// --- Turn Management Phases ---
// Phase 1: Enemy Action Phase
Future<void> _startEnemyTurn() async {
_turnTransactionId++; // Start of Enemy Turn Phase
if (!isPlayerTurn && (player.isDead || enemy.isDead)) return;
_addLog("Enemy's turn...");
// REMOVED: Initial delay for faster pacing
// await Future.delayed(
// const Duration(milliseconds: GameConfig.animDelayEnemyTurn),
// );
// Armor decay
// Armor decay (Enemy)
if (enemy.armor > 0) {
enemy.armor = (enemy.armor * GameConfig.armorDecayRate).toInt();
_addLog("Enemy's armor decayed to ${enemy.armor}.");
@@ -498,33 +512,18 @@ class BattleProvider with ChangeNotifier {
final intent = currentEnemyIntent!;
if (intent.type == EnemyActionType.defend) {
// Defensive Action (Non-animating)
// Defensive Action (Pre-applied at start of Player Turn)
// Just show a log or maintain stance visual
_addLog("${enemy.name} maintains defensive stance.");
// Check if already applied in Phase 3 of previous turn
if (intent.isApplied) {
_addLog("Enemy maintains defensive stance.");
// Proceed manually
int tid = _turnTransactionId;
int delay = skipAnimations ? 500 : 1000; // Faster if animations off
Future.delayed(Duration(milliseconds: delay), () {
if (tid != _turnTransactionId) return;
_endEnemyTurn();
});
return;
}
if (intent.isSuccess) {
// ... (success logic) ...
} else {
// ... (failure logic) ...
}
// For defense (if not applied), we proceed manually
// IMPORTANT: We still need to end the turn sequence properly.
// Since no animation is needed (or a very short one), we can just delay slightly.
int tid = _turnTransactionId;
int delay = skipAnimations ? 500 : 1500; // Faster if animations off
Future.delayed(Duration(milliseconds: delay), () {
Future.delayed(const Duration(milliseconds: 500), () {
if (tid != _turnTransactionId) return;
_endEnemyTurn();
});
return;
} else {
// Attack Action (Animating)
if (intent.isSuccess) {
@@ -542,10 +541,8 @@ class BattleProvider with ChangeNotifier {
isSuccess: true,
);
_effectEventController.sink.add(event);
// CRITICAL: We DO NOT call _endEnemyTurn here.
// The UI will play the animation, then call handleImpact.
// handleImpact will trigger _endEnemyTurn.
return; // Exit _startEnemyTurn after emitting event for UI to handle
// UI monitors event -> animates -> calls handleImpact -> _endEnemyTurn
return;
} else {
// Missed Attack
_addLog("Enemy's ${intent.risk.name} attack missed!");
@@ -562,7 +559,7 @@ class BattleProvider with ChangeNotifier {
isSuccess: false,
);
_effectEventController.sink.add(event);
return; // Exit _startEnemyTurn after emitting event for UI to handle
return;
}
}
} else if (!canAct) {
@@ -594,42 +591,14 @@ class BattleProvider with ChangeNotifier {
// Phase 3: Middle Turn (Apply Defense Effects)
Future<void> _processMiddleTurn() async {
// Apply Intent Effects (Pre-emptive Defense)
// Phase 3 Middle Turn - Reduced functionality as Defense is now Phase 1.
int tid = _turnTransactionId;
await Future.delayed(const Duration(milliseconds: 500));
await Future.delayed(const Duration(milliseconds: 200)); // Short pause
if (tid != _turnTransactionId) return;
_applyEnemyIntentEffects();
// REMOVED: Delay for faster pacing
// Small pause to let the player see the enemy's new stance
_startPlayerTurn();
}
// Phase 4: Start Player Turn
void _startPlayerTurn() {
// Player Turn Start Logic
// Armor decay
if (player.armor > 0) {
player.armor = (player.armor * GameConfig.armorDecayRate).toInt();
_addLog("Player's armor decayed to ${player.armor}.");
}
if (player.isDead) {
_onDefeat();
return;
}
isPlayerTurn = true;
turnCount++;
notifyListeners();
}
void _addLog(String message) {
_logManager.addLog(message);
notifyListeners();
}
void _onVictory() {
// Calculate Gold Reward
// Base 10 + (Stage * 5) + Random variance
@@ -897,21 +866,14 @@ class BattleProvider with ChangeNotifier {
/// Applies the effects of the enemy's intent (specifically Defense)
/// This should be called just before the Player's turn starts.
void _applyEnemyIntentEffects() {
if (currentEnemyIntent == null || enemy.isDead) return;
// Prevent duplicate application
if (currentEnemyIntent!.isApplied) return;
if (currentEnemyIntent!.type == EnemyActionType.defend) {
// Logic moved to playerAction for "Just-in-Time" defense.
// Nothing to do here except maybe log intent (optional, but Intent UI covers it).
return;
}
// No pre-emptive effects in Standard Turn-Based model.
// Logic cleared.
}
// New public method to be called by UI at impact moment
void handleImpact(EffectEvent event) {
if (event.isSuccess == false || event.feedbackType != null) {
if ((event.isSuccess == false || event.feedbackType != null) &&
event.type != ActionType.defend) {
// If it's a miss/fail/feedback, just log and return
// Logging and feedback text should already be handled when event created
notifyListeners(); // Ensure UI updates for log
@@ -920,11 +882,8 @@ class BattleProvider with ChangeNotifier {
if (event.attacker == player) {
_endPlayerTurn();
} else if (event.attacker == enemy) {
// Special Case: Do NOT call _endEnemyTurn for Enemy Defense (Phase 1 & 3).
// Phase 1 relies on manual timer. Phase 3 relies on _processMiddleTurn sequence.
if (event.type != ActionType.defend) {
_endEnemyTurn();
}
// Special Case: Phase 1 relies on manual timer. Phase 3 relies on _processMiddleTurn sequence.
_endEnemyTurn();
}
return;
}
@@ -970,10 +929,10 @@ class BattleProvider with ChangeNotifier {
// FIX: Update _startEnemyTurn (Phase 1) to ALSO apply armor directly and make the event visual-only.
// Then we can globally block Enemy Defend in handleImpact.
// Step 1: Modifying handleImpact to block ALL Enemy Defend logic.
if (event.attacker == enemy && event.type == ActionType.defend) {
return;
}
// REMOVED: Blocking Enemy Defend. Now we want to process it.
// if (event.attacker == enemy && event.type == ActionType.defend) {
// return;
// }
// Only process actual attack or defend impacts here
_processAttackImpact(event);
@@ -1068,40 +1027,6 @@ class BattleProvider with ChangeNotifier {
notifyListeners();
}
/// Process effects that happen at the start of the turn (Bleed, Stun).
/// Returns true if the character can act, false if stunned.
bool _processStartTurnEffects(Character character) {
final result = CombatCalculator.processStartTurnEffects(character);
int totalBleed = result['bleedDamage'];
bool isStunned = result['isStunned'];
// 1. Bleed Damage
if (totalBleed > 0) {
character.hp -= totalBleed;
if (character.hp < 0) character.hp = 0;
_addLog("${character.name} takes $totalBleed bleed damage!");
// Emit DamageEvent for bleed
_damageEventController.sink.add(
DamageEvent(
damage: totalBleed,
target: (character == player)
? DamageTarget.player
: DamageTarget.enemy,
type: DamageType.bleed,
),
);
}
// 2. Stun Check
if (isStunned) {
_addLog("${character.name} is stunned!");
}
return !isStunned;
}
/// Tries to apply status effects from attacker's equipment to the target.
void _tryApplyStatusEffects(Character attacker, Character target) {
List<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects(
@@ -1113,6 +1038,4 @@ class BattleProvider with ChangeNotifier {
_addLog("Applied ${effect.type.name} to ${target.name}!");
}
}
/// Process effects that happen at the start of the turn (Bleed, Stun).
}
+2 -2
View File
@@ -4,7 +4,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class SettingsProvider with ChangeNotifier {
static const String _keyEnemyAnim = 'settings_enemy_anim';
bool _enableEnemyAnimations = false; // Default: Disabled
bool _enableEnemyAnimations = true; // Default: Enabled
bool get enableEnemyAnimations => _enableEnemyAnimations;
@@ -14,7 +14,7 @@ class SettingsProvider with ChangeNotifier {
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
_enableEnemyAnimations = prefs.getBool(_keyEnemyAnim) ?? false;
_enableEnemyAnimations = prefs.getBool(_keyEnemyAnim) ?? true;
notifyListeners();
}