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:
292
lib/src/core/model/combat_stats.dart
Normal file
292
lib/src/core/model/combat_stats.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user