feat(combat): Phase 1 핵심 전투 시스템 구현

신규 파일:
- combat_stats.dart: 플레이어 전투 파생 스탯 (ATK, DEF, CRI 등)
- monster_combat_stats.dart: 몬스터 전투 스탯 (레벨 기반 스케일링)
- combat_result.dart: 전투 결과 타입 (AttackResult, CombatTurnResult)
- combat_state.dart: 전투 상태 관리 (HP, 누적 시간, 턴 수)
- combat_calculator.dart: 전투 계산 서비스 (데미지, 명중, 크리티컬)

수정 파일:
- game_state.dart: ProgressState에 currentCombat 필드 추가
- progress_service.dart: 킬 태스크 시 전투 로직 통합
  - CombatStats/MonsterCombatStats 기반 전투 시간 계산
  - 틱마다 전투 턴 처리 (_processCombatTick)
  - 전투 완료 시 플레이어 HP 반영
This commit is contained in:
JiWoong Sul
2025-12-17 16:31:52 +09:00
parent 9ad0cf4b74
commit c62687f7bd
7 changed files with 1148 additions and 7 deletions

View File

@@ -0,0 +1,292 @@
import 'package:askiineverdie/src/core/model/game_state.dart';
/// 전투용 파생 스탯
///
/// 기본 Stats와 Equipment를 기반으로 계산되는 전투 관련 수치.
/// 불변(immutable) 객체로 설계되어 상태 변경 시 새 인스턴스 생성.
class CombatStats {
const CombatStats({
// 기본 스탯 (Stats에서 복사)
required this.str,
required this.con,
required this.dex,
required this.intelligence,
required this.wis,
required this.cha,
// 파생 스탯
required this.atk,
required this.def,
required this.magAtk,
required this.magDef,
required this.criRate,
required this.criDamage,
required this.evasion,
required this.accuracy,
required this.blockRate,
required this.parryRate,
required this.attackDelayMs,
// 자원
required this.hpMax,
required this.hpCurrent,
required this.mpMax,
required this.mpCurrent,
});
// ============================================================================
// 기본 스탯
// ============================================================================
/// 힘: 물리 공격력 보정
final int str;
/// 체력: HP, 방어력 보정
final int con;
/// 민첩: 회피율, 크리티컬율, 명중률, 공격 속도
final int dex;
/// 지능: 마법 공격력, MP
final int intelligence;
/// 지혜: 마법 방어력, MP 회복
final int wis;
/// 매력: 상점 가격, 드롭률 보정
final int cha;
// ============================================================================
// 파생 스탯 (전투용)
// ============================================================================
/// 물리 공격력
final int atk;
/// 물리 방어력
final int def;
/// 마법 공격력
final int magAtk;
/// 마법 방어력
final int magDef;
/// 크리티컬 확률 (0.0 ~ 1.0)
final double criRate;
/// 크리티컬 데미지 배율 (1.5 ~ 3.0)
final double criDamage;
/// 회피율 (0.0 ~ 0.5)
final double evasion;
/// 명중률 (0.8 ~ 1.0)
final double accuracy;
/// 방패 방어율 (0.0 ~ 0.4)
final double blockRate;
/// 무기로 쳐내기 확률 (0.0 ~ 0.3)
final double parryRate;
/// 공격 딜레이 (밀리초)
final int attackDelayMs;
// ============================================================================
// 자원
// ============================================================================
/// 최대 HP
final int hpMax;
/// 현재 HP
final int hpCurrent;
/// 최대 MP
final int mpMax;
/// 현재 MP
final int mpCurrent;
// ============================================================================
// 유틸리티
// ============================================================================
/// HP 비율 (0.0 ~ 1.0)
double get hpRatio => hpMax > 0 ? hpCurrent / hpMax : 0.0;
/// MP 비율 (0.0 ~ 1.0)
double get mpRatio => mpMax > 0 ? mpCurrent / mpMax : 0.0;
/// 생존 여부
bool get isAlive => hpCurrent > 0;
/// 사망 여부
bool get isDead => hpCurrent <= 0;
/// HP 변경된 새 인스턴스 반환
CombatStats withHp(int newHp) {
return copyWith(hpCurrent: newHp.clamp(0, hpMax));
}
/// MP 변경된 새 인스턴스 반환
CombatStats withMp(int newMp) {
return copyWith(mpCurrent: newMp.clamp(0, mpMax));
}
/// 데미지 적용된 새 인스턴스 반환
CombatStats applyDamage(int damage) {
return withHp(hpCurrent - damage);
}
/// 힐 적용된 새 인스턴스 반환
CombatStats applyHeal(int amount) {
return withHp(hpCurrent + amount);
}
CombatStats copyWith({
int? str,
int? con,
int? dex,
int? intelligence,
int? wis,
int? cha,
int? atk,
int? def,
int? magAtk,
int? magDef,
double? criRate,
double? criDamage,
double? evasion,
double? accuracy,
double? blockRate,
double? parryRate,
int? attackDelayMs,
int? hpMax,
int? hpCurrent,
int? mpMax,
int? mpCurrent,
}) {
return CombatStats(
str: str ?? this.str,
con: con ?? this.con,
dex: dex ?? this.dex,
intelligence: intelligence ?? this.intelligence,
wis: wis ?? this.wis,
cha: cha ?? this.cha,
atk: atk ?? this.atk,
def: def ?? this.def,
magAtk: magAtk ?? this.magAtk,
magDef: magDef ?? this.magDef,
criRate: criRate ?? this.criRate,
criDamage: criDamage ?? this.criDamage,
evasion: evasion ?? this.evasion,
accuracy: accuracy ?? this.accuracy,
blockRate: blockRate ?? this.blockRate,
parryRate: parryRate ?? this.parryRate,
attackDelayMs: attackDelayMs ?? this.attackDelayMs,
hpMax: hpMax ?? this.hpMax,
hpCurrent: hpCurrent ?? this.hpCurrent,
mpMax: mpMax ?? this.mpMax,
mpCurrent: mpCurrent ?? this.mpCurrent,
);
}
// ============================================================================
// 팩토리 메서드
// ============================================================================
/// Stats와 Equipment에서 CombatStats 생성
///
/// [stats] 캐릭터 기본 스탯
/// [equipment] 장착 장비 (향후 장비 스탯 적용 시 사용)
/// [level] 캐릭터 레벨 (스케일링용)
factory CombatStats.fromStats({
required Stats stats,
required Equipment equipment,
required int level,
}) {
// 기본 공격력: STR 기반 + 레벨 보정
final baseAtk = stats.str * 2 + level;
// 기본 방어력: CON 기반 + 레벨 보정
final baseDef = stats.con + (level ~/ 2);
// 마법 공격력: INT 기반
final baseMagAtk = stats.intelligence * 2 + level;
// 마법 방어력: WIS 기반
final baseMagDef = stats.wis + (level ~/ 2);
// 크리티컬 확률: DEX 기반 (0.05 ~ 0.5)
final criRate = (0.05 + stats.dex * 0.005).clamp(0.05, 0.5);
// 크리티컬 데미지: 기본 1.5배, DEX에 따라 증가 (최대 3.0)
final criDamage = (1.5 + stats.dex * 0.01).clamp(1.5, 3.0);
// 회피율: DEX 기반 (0.0 ~ 0.5)
final evasion = (stats.dex * 0.005).clamp(0.0, 0.5);
// 명중률: DEX 기반 (0.8 ~ 1.0)
final accuracy = (0.8 + stats.dex * 0.002).clamp(0.8, 1.0);
// 방패 방어율: 방패 장착 여부에 따라 (0.0 ~ 0.4)
final hasShield = equipment.shield.isNotEmpty;
final blockRate = hasShield ? (0.1 + stats.con * 0.003).clamp(0.1, 0.4) : 0.0;
// 무기 쳐내기: DEX + STR 기반 (0.0 ~ 0.3)
final parryRate = ((stats.dex + stats.str) * 0.002).clamp(0.0, 0.3);
// 공격 속도: DEX 기반 (기본 1000ms, 최소 357ms)
final speedModifier = 1.0 + (stats.dex - 10) * 0.02;
final attackDelayMs = (1000 / speedModifier).round().clamp(357, 1500);
return CombatStats(
str: stats.str,
con: stats.con,
dex: stats.dex,
intelligence: stats.intelligence,
wis: stats.wis,
cha: stats.cha,
atk: baseAtk,
def: baseDef,
magAtk: baseMagAtk,
magDef: baseMagDef,
criRate: criRate,
criDamage: criDamage,
evasion: evasion,
accuracy: accuracy,
blockRate: blockRate,
parryRate: parryRate,
attackDelayMs: attackDelayMs,
hpMax: stats.hpMax,
hpCurrent: stats.hp,
mpMax: stats.mpMax,
mpCurrent: stats.mp,
);
}
/// 테스트/디버그용 기본값
factory CombatStats.empty() => const CombatStats(
str: 10,
con: 10,
dex: 10,
intelligence: 10,
wis: 10,
cha: 10,
atk: 20,
def: 10,
magAtk: 20,
magDef: 10,
criRate: 0.05,
criDamage: 1.5,
evasion: 0.05,
accuracy: 0.85,
blockRate: 0.0,
parryRate: 0.0,
attackDelayMs: 1000,
hpMax: 100,
hpCurrent: 100,
mpMax: 50,
mpCurrent: 50,
);
}