import 'package:askiineverdie/src/core/model/class_traits.dart'; import 'package:askiineverdie/src/core/model/game_state.dart'; import 'package:askiineverdie/src/core/model/race_traits.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] 캐릭터 레벨 (스케일링용) /// [race] 종족 특성 (선택사항, Phase 5) /// [klass] 클래스 특성 (선택사항, Phase 5) factory CombatStats.fromStats({ required Stats stats, required Equipment equipment, required int level, RaceTraits? race, ClassTraits? klass, }) { // 장비 총 스탯 가져오기 final equipStats = equipment.totalStats; // 종족/클래스 스탯 보정 적용 final raceStr = race?.getModifier(StatType.str) ?? 0; final raceCon = race?.getModifier(StatType.con) ?? 0; final raceDex = race?.getModifier(StatType.dex) ?? 0; final raceInt = race?.getModifier(StatType.intelligence) ?? 0; final raceWis = race?.getModifier(StatType.wis) ?? 0; final raceCha = race?.getModifier(StatType.cha) ?? 0; final classStr = klass?.getModifier(StatType.str) ?? 0; final classCon = klass?.getModifier(StatType.con) ?? 0; final classDex = klass?.getModifier(StatType.dex) ?? 0; final classInt = klass?.getModifier(StatType.intelligence) ?? 0; final classWis = klass?.getModifier(StatType.wis) ?? 0; final classCha = klass?.getModifier(StatType.cha) ?? 0; // 장비 보너스 + 종족/클래스 보정이 적용된 기본 스탯 final effectiveStr = stats.str + equipStats.strBonus + raceStr + classStr; final effectiveCon = stats.con + equipStats.conBonus + raceCon + classCon; final effectiveDex = stats.dex + equipStats.dexBonus + raceDex + classDex; final effectiveInt = stats.intelligence + equipStats.intBonus + raceInt + classInt; final effectiveWis = stats.wis + equipStats.wisBonus + raceWis + classWis; final effectiveCha = stats.cha + equipStats.chaBonus + raceCha + classCha; // 기본 공격력: STR 기반 + 레벨 보정 + 장비 ATK var baseAtk = effectiveStr * 2 + level + equipStats.atk; // 기본 방어력: CON 기반 + 레벨 보정 + 장비 DEF var baseDef = effectiveCon + (level ~/ 2) + equipStats.def; // 마법 공격력: INT 기반 + 장비 MAG_ATK var baseMagAtk = effectiveInt * 2 + level + equipStats.magAtk; // 마법 방어력: WIS 기반 + 장비 MAG_DEF final baseMagDef = effectiveWis + (level ~/ 2) + equipStats.magDef; // 크리티컬 확률: DEX 기반 + 장비 보너스 (0.05 ~ 0.8) var criRate = 0.05 + effectiveDex * 0.005 + equipStats.criRate; // 크리티컬 데미지: 기본 1.5배, DEX에 따라 증가 (최대 3.0) final criDamage = (1.5 + effectiveDex * 0.01).clamp(1.5, 3.0); // 회피율: DEX 기반 + 장비 보너스 (0.0 ~ 0.6) var evasion = effectiveDex * 0.005 + equipStats.evasion; // 명중률: DEX 기반 (0.8 ~ 1.0) final accuracy = (0.8 + effectiveDex * 0.002).clamp(0.8, 1.0); // 방패 방어율: 방패 장착 시 기본 + CON 보정 + 장비 보너스 (0.0 ~ 0.5) final hasShield = equipment.shield.isNotEmpty; final baseBlockRate = hasShield ? (0.1 + effectiveCon * 0.003) : 0.0; final blockRate = (baseBlockRate + equipStats.blockRate).clamp(0.0, 0.5); // 무기 쳐내기: DEX + STR 기반 + 장비 보너스 (0.0 ~ 0.4) final baseParryRate = (effectiveDex + effectiveStr) * 0.002; final parryRate = (baseParryRate + equipStats.parryRate).clamp(0.0, 0.4); // 공격 속도: 무기 기본 공속 + DEX 보정 // 무기 attackSpeed가 0이면 기본값 1000ms 사용 final weaponItem = equipment.items[0]; // 무기 슬롯 final weaponSpeed = weaponItem.stats.attackSpeed; final baseAttackSpeed = weaponSpeed > 0 ? weaponSpeed : 1000; final speedModifier = 1.0 + (effectiveDex - 10) * 0.02; final attackDelayMs = (baseAttackSpeed / speedModifier).round().clamp( 300, 2000, ); // HP/MP: 기본 + 장비 보너스 var totalHpMax = stats.hpMax + equipStats.hpBonus; var totalMpMax = stats.mpMax + equipStats.mpBonus; // ======================================================================== // 종족 패시브 적용 (Phase 5) // ======================================================================== // HP 보너스 (Heap Troll: +20%) final raceHpBonus = race?.getPassiveValue(PassiveType.hpBonus) ?? 0.0; if (raceHpBonus > 0) { totalHpMax = (totalHpMax * (1 + raceHpBonus)).round(); } // MP 보너스 (Pointer Fairy: +20%) final raceMpBonus = race?.getPassiveValue(PassiveType.mpBonus) ?? 0.0; if (raceMpBonus > 0) { totalMpMax = (totalMpMax * (1 + raceMpBonus)).round(); } // 마법 데미지 보너스 (Null Elf: +15%) final raceMagicBonus = race?.getPassiveValue(PassiveType.magicDamageBonus) ?? 0.0; if (raceMagicBonus > 0) { baseMagAtk = (baseMagAtk * (1 + raceMagicBonus)).round(); } // 방어력 보너스 (Buffer Dwarf: +10%) final raceDefBonus = race?.getPassiveValue(PassiveType.defenseBonus) ?? 0.0; if (raceDefBonus > 0) { baseDef = (baseDef * (1 + raceDefBonus)).round(); } // 크리티컬 보너스 (Stack Goblin: +5%) final raceCritBonus = race?.getPassiveValue(PassiveType.criticalBonus) ?? 0.0; criRate += raceCritBonus; // ======================================================================== // 클래스 패시브 적용 (Phase 5) // ======================================================================== // HP 보너스 (Garbage Collector: +30%) final classHpBonus = klass?.getPassiveValue(ClassPassiveType.hpBonus) ?? 0.0; if (classHpBonus > 0) { totalHpMax = (totalHpMax * (1 + classHpBonus)).round(); } // 물리 공격력 보너스 (Bug Hunter: +20%) final classPhysBonus = klass?.getPassiveValue(ClassPassiveType.physicalDamageBonus) ?? 0.0; if (classPhysBonus > 0) { baseAtk = (baseAtk * (1 + classPhysBonus)).round(); } // 방어력 보너스 (Debugger Paladin: +15%) final classDefBonus = klass?.getPassiveValue(ClassPassiveType.defenseBonus) ?? 0.0; if (classDefBonus > 0) { baseDef = (baseDef * (1 + classDefBonus)).round(); } // 마법 데미지 보너스 (Compiler Mage: +25%) final classMagBonus = klass?.getPassiveValue(ClassPassiveType.magicDamageBonus) ?? 0.0; if (classMagBonus > 0) { baseMagAtk = (baseMagAtk * (1 + classMagBonus)).round(); } // 회피율 보너스 (Refactor Monk: +15%) final classEvasionBonus = klass?.getPassiveValue(ClassPassiveType.evasionBonus) ?? 0.0; evasion += classEvasionBonus; // 크리티컬 보너스 (Pointer Assassin: +20%) final classCritBonus = klass?.getPassiveValue(ClassPassiveType.criticalBonus) ?? 0.0; criRate += classCritBonus; // 최종 클램핑 criRate = criRate.clamp(0.05, 0.8); evasion = evasion.clamp(0.0, 0.6); return CombatStats( str: effectiveStr, con: effectiveCon, dex: effectiveDex, intelligence: effectiveInt, wis: effectiveWis, cha: effectiveCha, atk: baseAtk, def: baseDef, magAtk: baseMagAtk, magDef: baseMagDef, criRate: criRate, criDamage: criDamage, evasion: evasion, accuracy: accuracy, blockRate: blockRate, parryRate: parryRate, attackDelayMs: attackDelayMs, hpMax: totalHpMax, hpCurrent: stats.hp.clamp(0, totalHpMax), mpMax: totalMpMax, mpCurrent: stats.mp.clamp(0, totalMpMax), ); } /// JSON으로 직렬화 Map toJson() { return { 'str': str, 'con': con, 'dex': dex, 'intelligence': intelligence, 'wis': wis, 'cha': cha, 'atk': atk, 'def': def, 'magAtk': magAtk, 'magDef': magDef, 'criRate': criRate, 'criDamage': criDamage, 'evasion': evasion, 'accuracy': accuracy, 'blockRate': blockRate, 'parryRate': parryRate, 'attackDelayMs': attackDelayMs, 'hpMax': hpMax, 'hpCurrent': hpCurrent, 'mpMax': mpMax, 'mpCurrent': mpCurrent, }; } /// JSON에서 역직렬화 factory CombatStats.fromJson(Map json) { return CombatStats( str: json['str'] as int, con: json['con'] as int, dex: json['dex'] as int, intelligence: json['intelligence'] as int, wis: json['wis'] as int, cha: json['cha'] as int, atk: json['atk'] as int, def: json['def'] as int, magAtk: json['magAtk'] as int, magDef: json['magDef'] as int, criRate: (json['criRate'] as num).toDouble(), criDamage: (json['criDamage'] as num).toDouble(), evasion: (json['evasion'] as num).toDouble(), accuracy: (json['accuracy'] as num).toDouble(), blockRate: (json['blockRate'] as num).toDouble(), parryRate: (json['parryRate'] as num).toDouble(), attackDelayMs: json['attackDelayMs'] as int, hpMax: json['hpMax'] as int, hpCurrent: json['hpCurrent'] as int, mpMax: json['mpMax'] as int, mpCurrent: json['mpCurrent'] as int, ); } /// 테스트/디버그용 기본값 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, ); }