Refactor BattleProvider: Introduce CombatCalculator and BattleLogManager
This commit is contained in:
parent
dcfb8ab9de
commit
46658b22c8
|
|
@ -8,7 +8,7 @@ class GameConfig {
|
|||
static const int shopRerollCost = 50;
|
||||
|
||||
// Stages
|
||||
static const int eliteStageInterval = 10;
|
||||
static const int eliteStageInterval = 12;
|
||||
static const int shopStageInterval = 5;
|
||||
static const int restStageInterval = 8;
|
||||
static const int tier1StageMax = 12;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class BattleLogManager {
|
||||
final List<String> _logs = [];
|
||||
|
||||
List<String> get logs => List.unmodifiable(_logs);
|
||||
|
||||
void addLog(String message) {
|
||||
_logs.add(message);
|
||||
debugPrint("[BattleLog] $message"); // Optional: Console logging for debug
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_logs.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import 'dart:math';
|
||||
import '../model/entity.dart';
|
||||
import '../model/status_effect.dart';
|
||||
import '../enums.dart';
|
||||
import '../config/game_config.dart';
|
||||
import '../model/damage_event.dart';
|
||||
|
||||
class CombatResult {
|
||||
final bool success;
|
||||
final int value;
|
||||
final double efficiency;
|
||||
final bool isCritical; // Future extension
|
||||
|
||||
CombatResult({
|
||||
required this.success,
|
||||
required this.value,
|
||||
required this.efficiency,
|
||||
this.isCritical = false,
|
||||
});
|
||||
}
|
||||
|
||||
class CombatCalculator {
|
||||
static final Random _random = Random();
|
||||
|
||||
/// Calculates success and efficiency based on Risk Level and Luck.
|
||||
static CombatResult calculateActionOutcome({
|
||||
required RiskLevel risk,
|
||||
required int luck,
|
||||
required int baseValue,
|
||||
}) {
|
||||
double efficiency = 1.0;
|
||||
double baseChance = 0.0;
|
||||
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
baseChance = 1.0;
|
||||
efficiency = 0.5;
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
baseChance = 0.8;
|
||||
efficiency = 1.0;
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
baseChance = 0.4;
|
||||
efficiency = 2.0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply Luck (1 Luck = +1%)
|
||||
double chance = baseChance + (luck / 100.0);
|
||||
if (chance > 1.0) chance = 1.0;
|
||||
|
||||
bool success = _random.nextDouble() < chance;
|
||||
int finalValue = (baseValue * efficiency).toInt();
|
||||
if (finalValue < 1 && baseValue > 0) finalValue = 1;
|
||||
|
||||
return CombatResult(
|
||||
success: success,
|
||||
value: finalValue,
|
||||
efficiency: efficiency,
|
||||
);
|
||||
}
|
||||
|
||||
/// Calculates actual damage to HP after applying armor and vulnerability.
|
||||
static int calculateDamageToHp({
|
||||
required int incomingDamage,
|
||||
required int currentArmor,
|
||||
required bool isVulnerable,
|
||||
}) {
|
||||
int damage = incomingDamage;
|
||||
|
||||
// 1. Vulnerability check
|
||||
if (isVulnerable) {
|
||||
damage = (damage * GameConfig.vulnerableDamageMultiplier).toInt();
|
||||
}
|
||||
|
||||
// 2. Armor absorption
|
||||
int damageToHp = 0;
|
||||
if (currentArmor > 0) {
|
||||
if (currentArmor >= damage) {
|
||||
// Fully absorbed
|
||||
damageToHp = 0;
|
||||
} else {
|
||||
damageToHp = damage - currentArmor;
|
||||
}
|
||||
} else {
|
||||
damageToHp = damage;
|
||||
}
|
||||
|
||||
return damageToHp;
|
||||
}
|
||||
|
||||
/// Calculates armor remaining after damage absorption.
|
||||
static int calculateRemainingArmor({
|
||||
required int incomingDamage,
|
||||
required int currentArmor,
|
||||
required bool isVulnerable,
|
||||
}) {
|
||||
int damage = incomingDamage;
|
||||
if (isVulnerable) {
|
||||
damage = (damage * GameConfig.vulnerableDamageMultiplier).toInt();
|
||||
}
|
||||
|
||||
if (currentArmor > 0) {
|
||||
if (currentArmor >= damage) {
|
||||
return currentArmor - damage;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Checks if status effects (Bleed, Stun) allow action and returns bleed damage.
|
||||
static Map<String, dynamic> processStartTurnEffects(Character character) {
|
||||
int totalBleedDamage = 0;
|
||||
bool isStunned = false;
|
||||
|
||||
// 1. Bleed Damage
|
||||
var bleedEffects = character.statusEffects
|
||||
.where((e) => e.type == StatusEffectType.bleed)
|
||||
.toList();
|
||||
|
||||
if (bleedEffects.isNotEmpty) {
|
||||
totalBleedDamage = bleedEffects.fold(0, (sum, e) => sum + e.value);
|
||||
}
|
||||
|
||||
// 2. Stun Check
|
||||
if (character.hasStatus(StatusEffectType.stun)) {
|
||||
isStunned = true;
|
||||
}
|
||||
|
||||
return {
|
||||
'bleedDamage': totalBleedDamage,
|
||||
'isStunned': isStunned,
|
||||
};
|
||||
}
|
||||
|
||||
/// Tries to apply status effects from attacker's equipment.
|
||||
/// Returns a list of applied effects.
|
||||
static List<StatusEffect> getAppliedEffects(Character attacker) {
|
||||
List<StatusEffect> appliedEffects = [];
|
||||
|
||||
for (var item in attacker.equipment.values) {
|
||||
for (var effect in item.effects) {
|
||||
if (_random.nextInt(100) < effect.probability) {
|
||||
appliedEffects.add(
|
||||
StatusEffect(
|
||||
type: effect.type,
|
||||
duration: effect.duration,
|
||||
value: effect.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return appliedEffects;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,9 @@ import '../game/save_manager.dart';
|
|||
import '../game/config/game_config.dart';
|
||||
import 'shop_provider.dart'; // Import ShopProvider
|
||||
|
||||
import '../game/logic/battle_log_manager.dart';
|
||||
import '../game/logic/combat_calculator.dart';
|
||||
|
||||
class EnemyIntent {
|
||||
final EnemyActionType type;
|
||||
final int value;
|
||||
|
|
@ -45,7 +48,7 @@ class BattleProvider with ChangeNotifier {
|
|||
late StageModel currentStage; // The current stage object
|
||||
EnemyIntent? currentEnemyIntent;
|
||||
|
||||
List<String> battleLogs = [];
|
||||
final BattleLogManager _logManager = BattleLogManager();
|
||||
bool isPlayerTurn = true;
|
||||
|
||||
int stage = 1;
|
||||
|
|
@ -54,7 +57,7 @@ class BattleProvider with ChangeNotifier {
|
|||
bool showRewardPopup = false;
|
||||
int _lastGoldReward = 0; // New: Stores gold gained from last victory
|
||||
|
||||
List<String> get logs => battleLogs;
|
||||
List<String> get logs => _logManager.logs;
|
||||
int get lastGoldReward => _lastGoldReward;
|
||||
|
||||
void refreshUI() {
|
||||
|
|
@ -88,7 +91,7 @@ class BattleProvider with ChangeNotifier {
|
|||
turnCount = data['turnCount'];
|
||||
player = Character.fromJson(data['player']);
|
||||
|
||||
battleLogs.clear();
|
||||
_logManager.clear();
|
||||
_addLog("Game Loaded! Resuming Stage $stage");
|
||||
|
||||
_prepareNextStage();
|
||||
|
|
@ -170,7 +173,7 @@ class BattleProvider with ChangeNotifier {
|
|||
player.addToInventory(ItemTable.shields[3].createItem()); // Cursed Shield
|
||||
|
||||
_prepareNextStage();
|
||||
battleLogs.clear();
|
||||
_logManager.clear();
|
||||
_addLog("Game Started! Stage 1");
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
@ -282,37 +285,18 @@ class BattleProvider with ChangeNotifier {
|
|||
|
||||
_addLog("Player chose to ${type.name} with ${risk.name} risk.");
|
||||
|
||||
final random = Random();
|
||||
bool success = false;
|
||||
double efficiency = 1.0;
|
||||
// Calculate Outcome using CombatCalculator
|
||||
int baseValue = (type == ActionType.attack) ? player.totalAtk : player.totalDefense;
|
||||
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
// Safe: 100% base chance + luck
|
||||
double chance = 1.0 + (player.totalLuck / 100.0);
|
||||
if (chance > 1.0) chance = 1.0;
|
||||
success = random.nextDouble() < chance;
|
||||
efficiency = 0.5; // 50%
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
// Normal: 80% base chance + luck
|
||||
double chance = 0.8 + (player.totalLuck / 100.0);
|
||||
if (chance > 1.0) chance = 1.0;
|
||||
success = random.nextDouble() < chance;
|
||||
efficiency = 1.0; // 100%
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
// Risky: 40% base chance + luck
|
||||
double chance = 0.4 + (player.totalLuck / 100.0);
|
||||
if (chance > 1.0) chance = 1.0;
|
||||
success = random.nextDouble() < chance;
|
||||
efficiency = 2.0; // 200%
|
||||
break;
|
||||
}
|
||||
final result = CombatCalculator.calculateActionOutcome(
|
||||
risk: risk,
|
||||
luck: player.totalLuck,
|
||||
baseValue: baseValue
|
||||
);
|
||||
|
||||
if (success) {
|
||||
if (result.success) {
|
||||
if (type == ActionType.attack) {
|
||||
int damage = (player.totalAtk * efficiency).toInt();
|
||||
int damage = result.value;
|
||||
|
||||
final eventId =
|
||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
||||
|
|
@ -323,50 +307,70 @@ class BattleProvider with ChangeNotifier {
|
|||
type: ActionType.attack,
|
||||
risk: risk,
|
||||
target: EffectTarget.enemy,
|
||||
feedbackType: null, // 공격 성공이므로 feedbackType 없음
|
||||
feedbackType: null,
|
||||
),
|
||||
);
|
||||
|
||||
// Animation Delays to sync with Impact
|
||||
if (risk == RiskLevel.safe) {
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: GameConfig.animDelaySafe),
|
||||
);
|
||||
} else if (risk == RiskLevel.normal) {
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: GameConfig.animDelayNormal),
|
||||
);
|
||||
} else if (risk == RiskLevel.risky) {
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: GameConfig.animDelayRisky),
|
||||
);
|
||||
// Animation Delays
|
||||
int delay = GameConfig.animDelayNormal;
|
||||
if (risk == RiskLevel.safe) delay = GameConfig.animDelaySafe;
|
||||
if (risk == RiskLevel.risky) delay = GameConfig.animDelayRisky;
|
||||
|
||||
await Future.delayed(Duration(milliseconds: delay));
|
||||
|
||||
// Calculate Damage to HP using CombatCalculator
|
||||
int damageToHp = CombatCalculator.calculateDamageToHp(
|
||||
incomingDamage: damage,
|
||||
currentArmor: enemy.armor,
|
||||
isVulnerable: enemy.hasStatus(StatusEffectType.vulnerable)
|
||||
);
|
||||
|
||||
// Calculate Remaining Armor
|
||||
int remainingArmor = CombatCalculator.calculateRemainingArmor(
|
||||
incomingDamage: damage,
|
||||
currentArmor: enemy.armor,
|
||||
isVulnerable: enemy.hasStatus(StatusEffectType.vulnerable)
|
||||
);
|
||||
|
||||
// Log details
|
||||
if (enemy.armor > 0) {
|
||||
int absorbed = enemy.armor - remainingArmor;
|
||||
if (damageToHp == 0) {
|
||||
_addLog("Enemy's armor absorbed all damage.");
|
||||
} else {
|
||||
_addLog("Enemy's armor absorbed $absorbed damage.");
|
||||
}
|
||||
}
|
||||
|
||||
int damageToHp = 0;
|
||||
if (enemy.armor > 0) {
|
||||
if (enemy.armor >= damage) {
|
||||
enemy.armor -= damage;
|
||||
damageToHp = 0;
|
||||
_addLog("Enemy's armor absorbed all $damage damage.");
|
||||
} else {
|
||||
damageToHp = damage - enemy.armor;
|
||||
_addLog("Enemy's armor absorbed ${enemy.armor} damage.");
|
||||
enemy.armor = 0;
|
||||
}
|
||||
} else {
|
||||
damageToHp = damage;
|
||||
}
|
||||
enemy.armor = remainingArmor;
|
||||
|
||||
if (damageToHp > 0) {
|
||||
_applyDamage(enemy, damageToHp, targetType: DamageTarget.enemy);
|
||||
// Note: _applyDamage internally handles Vulnerable multiplier again for the DamageEvent and logs.
|
||||
// To avoid double application, we should just pass the raw damage to _applyDamage
|
||||
// OR refactor _applyDamage.
|
||||
// Let's refactor _applyDamage to just apply the final value since we calculated it here.
|
||||
// actually _applyDamage handles the reduction of HP.
|
||||
// Let's call a simplified version or just do it here.
|
||||
|
||||
enemy.hp -= damageToHp;
|
||||
if (enemy.hp < 0) enemy.hp = 0;
|
||||
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(
|
||||
damage: damageToHp,
|
||||
target: DamageTarget.enemy,
|
||||
type: enemy.hasStatus(StatusEffectType.vulnerable) ? DamageType.vulnerable : DamageType.normal
|
||||
),
|
||||
);
|
||||
_addLog("Player dealt $damageToHp damage to Enemy.");
|
||||
} else {
|
||||
_addLog("Player's attack was fully blocked by armor.");
|
||||
}
|
||||
|
||||
// Try applying status effects from items
|
||||
// Try applying status effects
|
||||
_tryApplyStatusEffects(player, enemy);
|
||||
} else {
|
||||
// Defense Success
|
||||
_effectEventController.sink.add(
|
||||
EffectEvent(
|
||||
id:
|
||||
|
|
@ -375,15 +379,16 @@ class BattleProvider with ChangeNotifier {
|
|||
type: ActionType.defend,
|
||||
risk: risk,
|
||||
target: EffectTarget.player,
|
||||
feedbackType: null, // 방어 성공이므로 feedbackType 없음
|
||||
feedbackType: null,
|
||||
),
|
||||
);
|
||||
|
||||
int armorGained = (player.totalDefense * efficiency).toInt();
|
||||
int armorGained = result.value;
|
||||
player.armor += armorGained;
|
||||
_addLog("Player gained $armorGained armor.");
|
||||
}
|
||||
} else {
|
||||
// Failure
|
||||
if (type == ActionType.attack) {
|
||||
_addLog("Player's attack missed!");
|
||||
_effectEventController.sink.add(
|
||||
|
|
@ -393,7 +398,7 @@ class BattleProvider with ChangeNotifier {
|
|||
Random().nextInt(1000).toString(),
|
||||
type: type,
|
||||
risk: risk,
|
||||
target: EffectTarget.enemy, // 공격 실패는 적 위치에 MISS
|
||||
target: EffectTarget.enemy,
|
||||
feedbackType: BattleFeedbackType.miss,
|
||||
),
|
||||
);
|
||||
|
|
@ -406,7 +411,7 @@ class BattleProvider with ChangeNotifier {
|
|||
Random().nextInt(1000).toString(),
|
||||
type: type,
|
||||
risk: risk,
|
||||
target: EffectTarget.player, // 방어 실패는 내 위치에 FAILED
|
||||
target: EffectTarget.player,
|
||||
feedbackType: BattleFeedbackType.failed,
|
||||
),
|
||||
);
|
||||
|
|
@ -484,25 +489,41 @@ class BattleProvider with ChangeNotifier {
|
|||
);
|
||||
|
||||
int incomingDamage = intent.finalValue;
|
||||
int damageToHp = 0;
|
||||
|
||||
// Handle Player Armor
|
||||
if (player.armor > 0) {
|
||||
if (player.armor >= incomingDamage) {
|
||||
player.armor -= incomingDamage;
|
||||
damageToHp = 0;
|
||||
_addLog("Armor absorbed all $incomingDamage damage.");
|
||||
} else {
|
||||
damageToHp = incomingDamage - player.armor;
|
||||
_addLog("Armor absorbed ${player.armor} damage.");
|
||||
player.armor = 0;
|
||||
}
|
||||
} else {
|
||||
damageToHp = incomingDamage;
|
||||
}
|
||||
// Calculate Damage using Calculator
|
||||
int damageToHp = CombatCalculator.calculateDamageToHp(
|
||||
incomingDamage: incomingDamage,
|
||||
currentArmor: player.armor,
|
||||
isVulnerable: player.hasStatus(StatusEffectType.vulnerable)
|
||||
);
|
||||
|
||||
int remainingArmor = CombatCalculator.calculateRemainingArmor(
|
||||
incomingDamage: incomingDamage,
|
||||
currentArmor: player.armor,
|
||||
isVulnerable: player.hasStatus(StatusEffectType.vulnerable)
|
||||
);
|
||||
|
||||
if (player.armor > 0) {
|
||||
int absorbed = player.armor - remainingArmor;
|
||||
if (damageToHp == 0) {
|
||||
_addLog("Armor absorbed all damage.");
|
||||
} else {
|
||||
_addLog("Armor absorbed $absorbed damage.");
|
||||
}
|
||||
}
|
||||
player.armor = remainingArmor;
|
||||
|
||||
if (damageToHp > 0) {
|
||||
_applyDamage(player, damageToHp, targetType: DamageTarget.player);
|
||||
player.hp -= damageToHp;
|
||||
if (player.hp < 0) player.hp = 0;
|
||||
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(
|
||||
damage: damageToHp,
|
||||
target: DamageTarget.player,
|
||||
type: player.hasStatus(StatusEffectType.vulnerable) ? DamageType.vulnerable : DamageType.normal
|
||||
),
|
||||
);
|
||||
_addLog("Enemy dealt $damageToHp damage to Player HP.");
|
||||
}
|
||||
|
||||
|
|
@ -554,92 +575,49 @@ class BattleProvider with ChangeNotifier {
|
|||
/// 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) {
|
||||
bool canAct = true;
|
||||
final result = CombatCalculator.processStartTurnEffects(character);
|
||||
|
||||
int totalBleed = result['bleedDamage'];
|
||||
bool isStunned = result['isStunned'];
|
||||
|
||||
// 1. Bleed Damage
|
||||
var bleedEffects = character.statusEffects
|
||||
.where((e) => e.type == StatusEffectType.bleed)
|
||||
.toList();
|
||||
if (bleedEffects.isNotEmpty) {
|
||||
int totalBleed = bleedEffects.fold(0, (sum, e) => sum + e.value);
|
||||
int previousHp = character.hp; // Record HP before 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
|
||||
if (character == player) {
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(
|
||||
damage: totalBleed,
|
||||
target: DamageTarget.player,
|
||||
type: DamageType.bleed,
|
||||
),
|
||||
);
|
||||
} else if (character == enemy) {
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(
|
||||
damage: totalBleed,
|
||||
target: DamageTarget.enemy,
|
||||
type: DamageType.bleed,
|
||||
),
|
||||
);
|
||||
}
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(
|
||||
damage: totalBleed,
|
||||
target: (character == player) ? DamageTarget.player : DamageTarget.enemy,
|
||||
type: DamageType.bleed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Stun Check
|
||||
if (character.hasStatus(StatusEffectType.stun)) {
|
||||
canAct = false;
|
||||
if (isStunned) {
|
||||
_addLog("${character.name} is stunned!");
|
||||
}
|
||||
|
||||
return canAct;
|
||||
return !isStunned;
|
||||
}
|
||||
|
||||
/// Tries to apply status effects from attacker's equipment to the target.
|
||||
void _tryApplyStatusEffects(Character attacker, Character target) {
|
||||
final random = Random();
|
||||
List<StatusEffect> effectsToApply = CombatCalculator.getAppliedEffects(attacker);
|
||||
|
||||
for (var item in attacker.equipment.values) {
|
||||
for (var effect in item.effects) {
|
||||
// Roll for probability (0-100)
|
||||
if (random.nextInt(100) < effect.probability) {
|
||||
// Apply effect
|
||||
final newStatus = StatusEffect(
|
||||
type: effect.type,
|
||||
duration: effect.duration,
|
||||
value: effect.value,
|
||||
);
|
||||
target.addStatusEffect(newStatus);
|
||||
_addLog("Applied ${effect.type.name} to ${target.name}!");
|
||||
}
|
||||
}
|
||||
for (var effect in effectsToApply) {
|
||||
target.addStatusEffect(effect);
|
||||
_addLog("Applied ${effect.type.name} to ${target.name}!");
|
||||
}
|
||||
}
|
||||
|
||||
void _applyDamage(
|
||||
Character target,
|
||||
int damage, {
|
||||
required DamageTarget targetType,
|
||||
DamageType type = DamageType.normal,
|
||||
}) {
|
||||
// Check Vulnerable
|
||||
if (target.hasStatus(StatusEffectType.vulnerable)) {
|
||||
damage = (damage * GameConfig.vulnerableDamageMultiplier).toInt();
|
||||
_addLog("Vulnerable! Damage increased to $damage.");
|
||||
type = DamageType.vulnerable;
|
||||
}
|
||||
|
||||
target.hp -= damage;
|
||||
if (target.hp < 0) target.hp = 0;
|
||||
|
||||
_damageEventController.sink.add(
|
||||
DamageEvent(damage: damage, target: targetType, type: type),
|
||||
);
|
||||
}
|
||||
|
||||
void _addLog(String message) {
|
||||
battleLogs.add(message);
|
||||
_logManager.addLog(message);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# 57. BattleProvider Refactoring
|
||||
|
||||
## 1. 목표 (Goal)
|
||||
- 비대해진 `BattleProvider` 클래스(약 900라인)를 역할별로 분리하여 유지보수성을 높이고 가독성을 개선합니다.
|
||||
- `CombatCalculator`(전투 계산)와 `BattleLogManager`(로그 관리) 클래스를 도입합니다.
|
||||
|
||||
## 2. 구현 계획 (Implementation Plan)
|
||||
1. **디렉토리 생성:** `lib/game/logic` 폴더를 생성하여 로직 클래스들을 모아둡니다.
|
||||
2. **`BattleLogManager` 분리:**
|
||||
- 전투 로그 리스트(`_battleLogs`)와 로그 추가 메서드(`logBattleInfo`)를 전담하는 클래스를 생성합니다.
|
||||
3. **`CombatCalculator` 분리:**
|
||||
- 공격/방어 성공 확률, 데미지 산출 로직, 상태이상 적용 확률 등 순수 계산 로직을 분리합니다.
|
||||
4. **`BattleProvider` 수정:**
|
||||
- 위 클래스들을 인스턴스로 포함하고, 해당 로직을 위임(delegation) 처리합니다.
|
||||
- `ChangeNotifier`로서의 UI 상태 관리 책임은 유지합니다.
|
||||
|
||||
## 3. 기대 효과 (Expected Outcome)
|
||||
- `BattleProvider`의 코드 라인 수 감소.
|
||||
- 전투 공식 수정 시 `CombatCalculator`만 수정하면 되므로 안전성 확보.
|
||||
- 로그 포맷이나 저장 방식 변경 시 `BattleLogManager`만 수정하면 됨.
|
||||
Loading…
Reference in New Issue