feat(race-class): Phase 5 종족/클래스 특화 시스템 구현
- RaceTraits: 7종족 (Byte Human, Null Elf, Buffer Dwarf 등) - ClassTraits: 6클래스 (Bug Hunter, Compiler Mage 등) - StatCalculator: 종족/클래스 보정 계산 - CombatStats.fromStats: 종족/클래스 패시브 효과 통합 - 종족별 스탯 보정 및 패시브 (경험치, HP/MP, 크리티컬 등) - 클래스별 스탯 보정 및 패시브 (공격력, 방어력, 회피 등)
This commit is contained in:
185
lib/data/class_data.dart
Normal file
185
lib/data/class_data.dart
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import 'package:askiineverdie/src/core/model/class_traits.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
|
|
||||||
|
/// 클래스 데이터 정의 (class data)
|
||||||
|
///
|
||||||
|
/// 프로그래밍 테마의 6가지 클래스 정의
|
||||||
|
class ClassData {
|
||||||
|
ClassData._();
|
||||||
|
|
||||||
|
/// Bug Hunter: 전사형, 물리 공격 보너스
|
||||||
|
static const bugHunter = ClassTraits(
|
||||||
|
classId: 'bug_hunter',
|
||||||
|
name: 'Bug Hunter',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 2,
|
||||||
|
StatType.dex: 1,
|
||||||
|
},
|
||||||
|
startingSkills: ['power_strike'],
|
||||||
|
classSkills: ['power_strike', 'execute', 'berserker_rage'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.physicalDamageBonus,
|
||||||
|
value: 0.20,
|
||||||
|
description: '일반 공격 +20%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction.none,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Debugger Paladin: 탱커형, 방어/회복 보너스
|
||||||
|
static const debuggerPaladin = ClassTraits(
|
||||||
|
classId: 'debugger_paladin',
|
||||||
|
name: 'Debugger Paladin',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 1,
|
||||||
|
StatType.con: 2,
|
||||||
|
},
|
||||||
|
startingSkills: ['shield_bash'],
|
||||||
|
classSkills: ['shield_bash', 'holy_light', 'divine_protection'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.defenseBonus,
|
||||||
|
value: 0.15,
|
||||||
|
description: '방어력 +15%',
|
||||||
|
),
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.healingBonus,
|
||||||
|
value: 0.10,
|
||||||
|
description: '회복력 +10%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction(
|
||||||
|
armorWeight: ArmorWeight.heavy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Compiler Mage: 마법사형, 마법 데미지 보너스
|
||||||
|
static const compilerMage = ClassTraits(
|
||||||
|
classId: 'compiler_mage',
|
||||||
|
name: 'Compiler Mage',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.intelligence: 2,
|
||||||
|
StatType.wis: 1,
|
||||||
|
},
|
||||||
|
startingSkills: ['fireball'],
|
||||||
|
classSkills: ['fireball', 'ice_storm', 'arcane_blast', 'mana_shield'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.magicDamageBonus,
|
||||||
|
value: 0.25,
|
||||||
|
description: '마법 데미지 +25%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction(
|
||||||
|
armorWeight: ArmorWeight.light,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Refactor Monk: 민첩형, 회피/연속공격
|
||||||
|
static const refactorMonk = ClassTraits(
|
||||||
|
classId: 'refactor_monk',
|
||||||
|
name: 'Refactor Monk',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.dex: 2,
|
||||||
|
StatType.wis: 1,
|
||||||
|
},
|
||||||
|
startingSkills: ['flurry'],
|
||||||
|
classSkills: ['flurry', 'palm_strike', 'meditation'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.evasionBonus,
|
||||||
|
value: 0.15,
|
||||||
|
description: '회피율 +15%',
|
||||||
|
),
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.multiAttack,
|
||||||
|
value: 1.0,
|
||||||
|
description: '연속 공격 가능',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction(
|
||||||
|
armorWeight: ArmorWeight.light,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Pointer Assassin: 암살자형, 크리티컬/첫 공격 보너스
|
||||||
|
static const pointerAssassin = ClassTraits(
|
||||||
|
classId: 'pointer_assassin',
|
||||||
|
name: 'Pointer Assassin',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.dex: 2,
|
||||||
|
StatType.str: 1,
|
||||||
|
},
|
||||||
|
startingSkills: ['backstab'],
|
||||||
|
classSkills: ['backstab', 'poison_blade', 'shadow_step'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.criticalBonus,
|
||||||
|
value: 0.20,
|
||||||
|
description: '크리티컬 +20%',
|
||||||
|
),
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.firstStrikeBonus,
|
||||||
|
value: 2.0,
|
||||||
|
description: '첫 공격 2배',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction(
|
||||||
|
armorWeight: ArmorWeight.light,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Garbage Collector: 탱커형, HP/전투 후 회복
|
||||||
|
static const garbageCollector = ClassTraits(
|
||||||
|
classId: 'garbage_collector',
|
||||||
|
name: 'Garbage Collector',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.con: 2,
|
||||||
|
StatType.str: 1,
|
||||||
|
},
|
||||||
|
startingSkills: ['absorb'],
|
||||||
|
classSkills: ['absorb', 'recycle', 'memory_leak'],
|
||||||
|
passives: [
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.hpBonus,
|
||||||
|
value: 0.30,
|
||||||
|
description: 'HP +30%',
|
||||||
|
),
|
||||||
|
ClassPassive(
|
||||||
|
type: ClassPassiveType.postCombatHeal,
|
||||||
|
value: 0.10,
|
||||||
|
description: '전투 후 HP 10% 회복',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
restriction: EquipmentRestriction(
|
||||||
|
armorWeight: ArmorWeight.heavy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 모든 클래스 목록
|
||||||
|
static const List<ClassTraits> all = [
|
||||||
|
bugHunter,
|
||||||
|
debuggerPaladin,
|
||||||
|
compilerMage,
|
||||||
|
refactorMonk,
|
||||||
|
pointerAssassin,
|
||||||
|
garbageCollector,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// ID로 클래스 찾기
|
||||||
|
static ClassTraits? findById(String classId) {
|
||||||
|
for (final klass in all) {
|
||||||
|
if (klass.classId == classId) return klass;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 이름으로 클래스 찾기
|
||||||
|
static ClassTraits? findByName(String name) {
|
||||||
|
for (final klass in all) {
|
||||||
|
if (klass.name == name) return klass;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
167
lib/data/race_data.dart
Normal file
167
lib/data/race_data.dart
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
|
|
||||||
|
/// 종족 데이터 정의 (race data)
|
||||||
|
///
|
||||||
|
/// 프로그래밍 테마의 7가지 종족 정의
|
||||||
|
class RaceData {
|
||||||
|
RaceData._();
|
||||||
|
|
||||||
|
/// Byte Human: 균형형, 경험치 보너스
|
||||||
|
static const byteHuman = RaceTraits(
|
||||||
|
raceId: 'byte_human',
|
||||||
|
name: 'Byte Human',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.cha: 2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.expBonus,
|
||||||
|
value: 0.10,
|
||||||
|
description: '경험치 +10%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
expMultiplier: 1.10,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Null Elf: 민첩/지능형, 마법 데미지 보너스
|
||||||
|
static const nullElf = RaceTraits(
|
||||||
|
raceId: 'null_elf',
|
||||||
|
name: 'Null Elf',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: -1,
|
||||||
|
StatType.con: -1,
|
||||||
|
StatType.dex: 2,
|
||||||
|
StatType.intelligence: 2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.magicDamageBonus,
|
||||||
|
value: 0.15,
|
||||||
|
description: '마법 데미지 +15%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Buffer Dwarf: 힘/체력형, 방어력 보너스
|
||||||
|
static const bufferDwarf = RaceTraits(
|
||||||
|
raceId: 'buffer_dwarf',
|
||||||
|
name: 'Buffer Dwarf',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 2,
|
||||||
|
StatType.con: 2,
|
||||||
|
StatType.dex: -1,
|
||||||
|
StatType.intelligence: -1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.defenseBonus,
|
||||||
|
value: 0.10,
|
||||||
|
description: '방어력 +10%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Stack Goblin: 민첩형, 크리티컬 보너스
|
||||||
|
static const stackGoblin = RaceTraits(
|
||||||
|
raceId: 'stack_goblin',
|
||||||
|
name: 'Stack Goblin',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: -1,
|
||||||
|
StatType.con: -1,
|
||||||
|
StatType.dex: 3,
|
||||||
|
StatType.cha: 1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.criticalBonus,
|
||||||
|
value: 0.05,
|
||||||
|
description: '크리티컬 확률 +5%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Heap Troll: 체력형, HP 보너스
|
||||||
|
static const heapTroll = RaceTraits(
|
||||||
|
raceId: 'heap_troll',
|
||||||
|
name: 'Heap Troll',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 3,
|
||||||
|
StatType.con: 3,
|
||||||
|
StatType.dex: -2,
|
||||||
|
StatType.intelligence: -2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.hpBonus,
|
||||||
|
value: 0.20,
|
||||||
|
description: 'HP +20%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Pointer Fairy: 마법형, MP 보너스
|
||||||
|
static const pointerFairy = RaceTraits(
|
||||||
|
raceId: 'pointer_fairy',
|
||||||
|
name: 'Pointer Fairy',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: -2,
|
||||||
|
StatType.con: -2,
|
||||||
|
StatType.dex: 2,
|
||||||
|
StatType.intelligence: 2,
|
||||||
|
StatType.wis: 2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.mpBonus,
|
||||||
|
value: 0.20,
|
||||||
|
description: 'MP +20%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Coredump Undead: 탱커형, 사망 시 장비 보존
|
||||||
|
static const coredumpUndead = RaceTraits(
|
||||||
|
raceId: 'coredump_undead',
|
||||||
|
name: 'Coredump Undead',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 1,
|
||||||
|
StatType.con: 2,
|
||||||
|
StatType.dex: -1,
|
||||||
|
StatType.cha: -2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.deathEquipmentPreserve,
|
||||||
|
value: 1.0,
|
||||||
|
description: '사망 시 장비 1개 유지',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 모든 종족 목록
|
||||||
|
static const List<RaceTraits> all = [
|
||||||
|
byteHuman,
|
||||||
|
nullElf,
|
||||||
|
bufferDwarf,
|
||||||
|
stackGoblin,
|
||||||
|
heapTroll,
|
||||||
|
pointerFairy,
|
||||||
|
coredumpUndead,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// ID로 종족 찾기
|
||||||
|
static RaceTraits? findById(String raceId) {
|
||||||
|
for (final race in all) {
|
||||||
|
if (race.raceId == raceId) return race;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 이름으로 종족 찾기
|
||||||
|
static RaceTraits? findByName(String name) {
|
||||||
|
for (final race in all) {
|
||||||
|
if (race.name == name) return race;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
200
lib/src/core/engine/stat_calculator.dart
Normal file
200
lib/src/core/engine/stat_calculator.dart
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import 'package:askiineverdie/src/core/model/class_traits.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/combat_stats.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
|
|
||||||
|
/// 스탯 계산기 (stat calculator)
|
||||||
|
///
|
||||||
|
/// 기본 스탯에 종족/클래스 보정을 적용하여 최종 스탯 계산
|
||||||
|
class StatCalculator {
|
||||||
|
const StatCalculator();
|
||||||
|
|
||||||
|
/// 기본 스탯에 종족/클래스 보정 적용
|
||||||
|
///
|
||||||
|
/// [baseStats] 기본 캐릭터 스탯
|
||||||
|
/// [race] 종족 특성
|
||||||
|
/// [klass] 클래스 특성
|
||||||
|
Stats applyModifiers({
|
||||||
|
required Stats baseStats,
|
||||||
|
required RaceTraits race,
|
||||||
|
required ClassTraits klass,
|
||||||
|
}) {
|
||||||
|
// 종족 보정 적용
|
||||||
|
var str = baseStats.str + race.getModifier(StatType.str);
|
||||||
|
var con = baseStats.con + race.getModifier(StatType.con);
|
||||||
|
var dex = baseStats.dex + race.getModifier(StatType.dex);
|
||||||
|
var intel = baseStats.intelligence + race.getModifier(StatType.intelligence);
|
||||||
|
var wis = baseStats.wis + race.getModifier(StatType.wis);
|
||||||
|
var cha = baseStats.cha + race.getModifier(StatType.cha);
|
||||||
|
|
||||||
|
// 클래스 보정 적용
|
||||||
|
str += klass.getModifier(StatType.str);
|
||||||
|
con += klass.getModifier(StatType.con);
|
||||||
|
dex += klass.getModifier(StatType.dex);
|
||||||
|
intel += klass.getModifier(StatType.intelligence);
|
||||||
|
wis += klass.getModifier(StatType.wis);
|
||||||
|
cha += klass.getModifier(StatType.cha);
|
||||||
|
|
||||||
|
// HP/MP에 종족 패시브 적용
|
||||||
|
var hpMax = baseStats.hpMax;
|
||||||
|
var mpMax = baseStats.mpMax;
|
||||||
|
|
||||||
|
// 종족 HP 보너스 (Heap Troll: +20%)
|
||||||
|
final raceHpBonus = race.getPassiveValue(PassiveType.hpBonus);
|
||||||
|
if (raceHpBonus > 0) {
|
||||||
|
hpMax = (hpMax * (1 + raceHpBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 종족 MP 보너스 (Pointer Fairy: +20%)
|
||||||
|
final raceMpBonus = race.getPassiveValue(PassiveType.mpBonus);
|
||||||
|
if (raceMpBonus > 0) {
|
||||||
|
mpMax = (mpMax * (1 + raceMpBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 클래스 HP 보너스 (Garbage Collector: +30%)
|
||||||
|
final classHpBonus = klass.getPassiveValue(ClassPassiveType.hpBonus);
|
||||||
|
if (classHpBonus > 0) {
|
||||||
|
hpMax = (hpMax * (1 + classHpBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseStats.copyWith(
|
||||||
|
str: str,
|
||||||
|
con: con,
|
||||||
|
dex: dex,
|
||||||
|
intelligence: intel,
|
||||||
|
wis: wis,
|
||||||
|
cha: cha,
|
||||||
|
hpMax: hpMax,
|
||||||
|
mpMax: mpMax,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CombatStats에 종족/클래스 패시브 효과 적용
|
||||||
|
///
|
||||||
|
/// [combatStats] 기본 전투 스탯
|
||||||
|
/// [race] 종족 특성
|
||||||
|
/// [klass] 클래스 특성
|
||||||
|
CombatStats applyPassives({
|
||||||
|
required CombatStats combatStats,
|
||||||
|
required RaceTraits race,
|
||||||
|
required ClassTraits klass,
|
||||||
|
}) {
|
||||||
|
var atk = combatStats.atk;
|
||||||
|
var def = combatStats.def;
|
||||||
|
var magAtk = combatStats.magAtk;
|
||||||
|
var criRate = combatStats.criRate;
|
||||||
|
var evasion = combatStats.evasion;
|
||||||
|
|
||||||
|
// 종족 패시브 적용
|
||||||
|
|
||||||
|
// 마법 데미지 보너스 (Null Elf: +15%)
|
||||||
|
final raceMagicBonus = race.getPassiveValue(PassiveType.magicDamageBonus);
|
||||||
|
if (raceMagicBonus > 0) {
|
||||||
|
magAtk = (magAtk * (1 + raceMagicBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 방어력 보너스 (Buffer Dwarf: +10%)
|
||||||
|
final raceDefenseBonus = race.getPassiveValue(PassiveType.defenseBonus);
|
||||||
|
if (raceDefenseBonus > 0) {
|
||||||
|
def = (def * (1 + raceDefenseBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 크리티컬 보너스 (Stack Goblin: +5%)
|
||||||
|
final raceCritBonus = race.getPassiveValue(PassiveType.criticalBonus);
|
||||||
|
if (raceCritBonus > 0) {
|
||||||
|
criRate = (criRate + raceCritBonus).clamp(0.0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 클래스 패시브 적용
|
||||||
|
|
||||||
|
// 물리 공격력 보너스 (Bug Hunter: +20%)
|
||||||
|
final classPhysicalBonus = klass.getPassiveValue(ClassPassiveType.physicalDamageBonus);
|
||||||
|
if (classPhysicalBonus > 0) {
|
||||||
|
atk = (atk * (1 + classPhysicalBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 방어력 보너스 (Debugger Paladin: +15%)
|
||||||
|
final classDefenseBonus = klass.getPassiveValue(ClassPassiveType.defenseBonus);
|
||||||
|
if (classDefenseBonus > 0) {
|
||||||
|
def = (def * (1 + classDefenseBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마법 데미지 보너스 (Compiler Mage: +25%)
|
||||||
|
final classMagicBonus = klass.getPassiveValue(ClassPassiveType.magicDamageBonus);
|
||||||
|
if (classMagicBonus > 0) {
|
||||||
|
magAtk = (magAtk * (1 + classMagicBonus)).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 회피율 보너스 (Refactor Monk: +15%)
|
||||||
|
final classEvasionBonus = klass.getPassiveValue(ClassPassiveType.evasionBonus);
|
||||||
|
if (classEvasionBonus > 0) {
|
||||||
|
evasion = (evasion + classEvasionBonus).clamp(0.0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 크리티컬 보너스 (Pointer Assassin: +20%)
|
||||||
|
final classCritBonus = klass.getPassiveValue(ClassPassiveType.criticalBonus);
|
||||||
|
if (classCritBonus > 0) {
|
||||||
|
criRate = (criRate + classCritBonus).clamp(0.0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combatStats.copyWith(
|
||||||
|
atk: atk,
|
||||||
|
def: def,
|
||||||
|
magAtk: magAtk,
|
||||||
|
criRate: criRate,
|
||||||
|
evasion: evasion,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 경험치 배율 계산
|
||||||
|
///
|
||||||
|
/// 종족 경험치 배율 반환 (Byte Human: 1.10)
|
||||||
|
double calculateExpMultiplier(RaceTraits race) {
|
||||||
|
return race.expMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 전투 후 HP 회복량 계산 (Garbage Collector 패시브)
|
||||||
|
///
|
||||||
|
/// [klass] 클래스 특성
|
||||||
|
/// [maxHp] 최대 HP
|
||||||
|
/// Returns: 회복할 HP 양
|
||||||
|
int calculatePostCombatHeal({
|
||||||
|
required ClassTraits klass,
|
||||||
|
required int maxHp,
|
||||||
|
}) {
|
||||||
|
final healRate = klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
||||||
|
if (healRate <= 0) return 0;
|
||||||
|
return (maxHp * healRate).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 첫 공격 배율 계산 (Pointer Assassin 패시브)
|
||||||
|
///
|
||||||
|
/// [klass] 클래스 특성
|
||||||
|
/// Returns: 첫 공격 배율 (기본 1.0)
|
||||||
|
double calculateFirstStrikeMultiplier(ClassTraits klass) {
|
||||||
|
final bonus = klass.getPassiveValue(ClassPassiveType.firstStrikeBonus);
|
||||||
|
return bonus > 0 ? bonus : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 사망 시 보존할 장비 개수 (Coredump Undead 패시브)
|
||||||
|
///
|
||||||
|
/// [race] 종족 특성
|
||||||
|
/// Returns: 보존 장비 개수
|
||||||
|
int calculateDeathEquipmentPreserve(RaceTraits race) {
|
||||||
|
if (race.hasPassive(PassiveType.deathEquipmentPreserve)) {
|
||||||
|
return race.getPassiveValue(PassiveType.deathEquipmentPreserve).round();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 연속 공격 가능 여부 (Refactor Monk 패시브)
|
||||||
|
bool hasMultiAttack(ClassTraits klass) {
|
||||||
|
return klass.hasPassive(ClassPassiveType.multiAttack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 회복력 배율 계산 (Debugger Paladin 패시브)
|
||||||
|
double calculateHealingMultiplier(ClassTraits klass) {
|
||||||
|
final bonus = klass.getPassiveValue(ClassPassiveType.healingBonus);
|
||||||
|
return 1.0 + bonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
150
lib/src/core/model/class_traits.dart
Normal file
150
lib/src/core/model/class_traits.dart
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
|
|
||||||
|
/// 방어구 무게 등급 (armor weight class)
|
||||||
|
enum ArmorWeight {
|
||||||
|
/// 경갑 (light armor)
|
||||||
|
light,
|
||||||
|
|
||||||
|
/// 중갑 (heavy armor)
|
||||||
|
heavy,
|
||||||
|
|
||||||
|
/// 전체 가능 (all armor)
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 클래스 패시브 타입 (class passive type)
|
||||||
|
enum ClassPassiveType {
|
||||||
|
/// 물리 공격력 배율 보너스
|
||||||
|
physicalDamageBonus,
|
||||||
|
|
||||||
|
/// 방어력 배율 보너스
|
||||||
|
defenseBonus,
|
||||||
|
|
||||||
|
/// 회복력 배율 보너스
|
||||||
|
healingBonus,
|
||||||
|
|
||||||
|
/// 마법 데미지 배율 보너스
|
||||||
|
magicDamageBonus,
|
||||||
|
|
||||||
|
/// 회피율 보너스
|
||||||
|
evasionBonus,
|
||||||
|
|
||||||
|
/// 연속 공격 가능
|
||||||
|
multiAttack,
|
||||||
|
|
||||||
|
/// 크리티컬 확률 보너스
|
||||||
|
criticalBonus,
|
||||||
|
|
||||||
|
/// 첫 공격 배율 보너스
|
||||||
|
firstStrikeBonus,
|
||||||
|
|
||||||
|
/// HP 배율 보너스
|
||||||
|
hpBonus,
|
||||||
|
|
||||||
|
/// 전투 후 HP 회복 비율
|
||||||
|
postCombatHeal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 클래스 패시브 능력 (class passive ability)
|
||||||
|
class ClassPassive {
|
||||||
|
const ClassPassive({
|
||||||
|
required this.type,
|
||||||
|
required this.value,
|
||||||
|
this.description = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 패시브 타입
|
||||||
|
final ClassPassiveType type;
|
||||||
|
|
||||||
|
/// 효과 값 (배율의 경우 0.1 = 10%)
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
/// 설명
|
||||||
|
final String description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 장비 제한 사항 (equipment restriction)
|
||||||
|
class EquipmentRestriction {
|
||||||
|
const EquipmentRestriction({
|
||||||
|
this.armorWeight = ArmorWeight.all,
|
||||||
|
this.allowedWeaponSlots = const {},
|
||||||
|
this.disallowedSlots = const {},
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 허용 방어구 무게
|
||||||
|
final ArmorWeight armorWeight;
|
||||||
|
|
||||||
|
/// 허용 무기 슬롯 (비어있으면 모두 허용)
|
||||||
|
final Set<EquipmentSlot> allowedWeaponSlots;
|
||||||
|
|
||||||
|
/// 금지된 슬롯
|
||||||
|
final Set<EquipmentSlot> disallowedSlots;
|
||||||
|
|
||||||
|
/// 전체 허용 (제한 없음)
|
||||||
|
static const none = EquipmentRestriction();
|
||||||
|
|
||||||
|
/// 특정 슬롯이 허용되는지 확인
|
||||||
|
bool isSlotAllowed(EquipmentSlot slot) {
|
||||||
|
if (disallowedSlots.contains(slot)) return false;
|
||||||
|
|
||||||
|
// 무기 슬롯 제한 확인
|
||||||
|
if (slot == EquipmentSlot.weapon && allowedWeaponSlots.isNotEmpty) {
|
||||||
|
return allowedWeaponSlots.contains(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 클래스 특성 (class traits)
|
||||||
|
///
|
||||||
|
/// 각 클래스가 가진 고유한 스탯 보정, 스킬, 장비 제한
|
||||||
|
class ClassTraits {
|
||||||
|
const ClassTraits({
|
||||||
|
required this.classId,
|
||||||
|
required this.name,
|
||||||
|
required this.statModifiers,
|
||||||
|
this.startingSkills = const [],
|
||||||
|
this.classSkills = const [],
|
||||||
|
this.passives = const [],
|
||||||
|
this.restriction = EquipmentRestriction.none,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 클래스 식별자
|
||||||
|
final String classId;
|
||||||
|
|
||||||
|
/// 클래스 이름 (표시용)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// 스탯 보정치 맵
|
||||||
|
final Map<StatType, int> statModifiers;
|
||||||
|
|
||||||
|
/// 시작 스킬 ID 목록
|
||||||
|
final List<String> startingSkills;
|
||||||
|
|
||||||
|
/// 클래스 전용 스킬 ID 목록
|
||||||
|
final List<String> classSkills;
|
||||||
|
|
||||||
|
/// 패시브 능력 목록
|
||||||
|
final List<ClassPassive> passives;
|
||||||
|
|
||||||
|
/// 장비 제한
|
||||||
|
final EquipmentRestriction restriction;
|
||||||
|
|
||||||
|
/// 특정 스탯의 보정치 반환
|
||||||
|
int getModifier(StatType type) => statModifiers[type] ?? 0;
|
||||||
|
|
||||||
|
/// 특정 패시브 타입의 값 반환 (없으면 0)
|
||||||
|
double getPassiveValue(ClassPassiveType type) {
|
||||||
|
for (final passive in passives) {
|
||||||
|
if (passive.type == type) return passive.value;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 특정 패시브 보유 여부
|
||||||
|
bool hasPassive(ClassPassiveType type) {
|
||||||
|
return passives.any((p) => p.type == type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import 'package:askiineverdie/src/core/model/class_traits.dart';
|
||||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||||
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
|
|
||||||
/// 전투용 파생 스탯
|
/// 전투용 파생 스탯
|
||||||
///
|
///
|
||||||
@@ -200,41 +202,61 @@ class CombatStats {
|
|||||||
/// [stats] 캐릭터 기본 스탯
|
/// [stats] 캐릭터 기본 스탯
|
||||||
/// [equipment] 장착 장비 (장비 스탯 적용)
|
/// [equipment] 장착 장비 (장비 스탯 적용)
|
||||||
/// [level] 캐릭터 레벨 (스케일링용)
|
/// [level] 캐릭터 레벨 (스케일링용)
|
||||||
|
/// [race] 종족 특성 (선택사항, Phase 5)
|
||||||
|
/// [klass] 클래스 특성 (선택사항, Phase 5)
|
||||||
factory CombatStats.fromStats({
|
factory CombatStats.fromStats({
|
||||||
required Stats stats,
|
required Stats stats,
|
||||||
required Equipment equipment,
|
required Equipment equipment,
|
||||||
required int level,
|
required int level,
|
||||||
|
RaceTraits? race,
|
||||||
|
ClassTraits? klass,
|
||||||
}) {
|
}) {
|
||||||
// 장비 총 스탯 가져오기
|
// 장비 총 스탯 가져오기
|
||||||
final equipStats = equipment.totalStats;
|
final equipStats = equipment.totalStats;
|
||||||
|
|
||||||
// 장비 보너스가 적용된 기본 스탯
|
// 종족/클래스 스탯 보정 적용
|
||||||
final effectiveStr = stats.str + equipStats.strBonus;
|
final raceStr = race?.getModifier(StatType.str) ?? 0;
|
||||||
final effectiveCon = stats.con + equipStats.conBonus;
|
final raceCon = race?.getModifier(StatType.con) ?? 0;
|
||||||
final effectiveDex = stats.dex + equipStats.dexBonus;
|
final raceDex = race?.getModifier(StatType.dex) ?? 0;
|
||||||
final effectiveInt = stats.intelligence + equipStats.intBonus;
|
final raceInt = race?.getModifier(StatType.intelligence) ?? 0;
|
||||||
final effectiveWis = stats.wis + equipStats.wisBonus;
|
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
|
// 기본 공격력: STR 기반 + 레벨 보정 + 장비 ATK
|
||||||
final baseAtk = effectiveStr * 2 + level + equipStats.atk;
|
var baseAtk = effectiveStr * 2 + level + equipStats.atk;
|
||||||
|
|
||||||
// 기본 방어력: CON 기반 + 레벨 보정 + 장비 DEF
|
// 기본 방어력: CON 기반 + 레벨 보정 + 장비 DEF
|
||||||
final baseDef = effectiveCon + (level ~/ 2) + equipStats.def;
|
var baseDef = effectiveCon + (level ~/ 2) + equipStats.def;
|
||||||
|
|
||||||
// 마법 공격력: INT 기반 + 장비 MAG_ATK
|
// 마법 공격력: INT 기반 + 장비 MAG_ATK
|
||||||
final baseMagAtk = effectiveInt * 2 + level + equipStats.magAtk;
|
var baseMagAtk = effectiveInt * 2 + level + equipStats.magAtk;
|
||||||
|
|
||||||
// 마법 방어력: WIS 기반 + 장비 MAG_DEF
|
// 마법 방어력: WIS 기반 + 장비 MAG_DEF
|
||||||
final baseMagDef = effectiveWis + (level ~/ 2) + equipStats.magDef;
|
final baseMagDef = effectiveWis + (level ~/ 2) + equipStats.magDef;
|
||||||
|
|
||||||
// 크리티컬 확률: DEX 기반 + 장비 보너스 (0.05 ~ 0.5)
|
// 크리티컬 확률: DEX 기반 + 장비 보너스 (0.05 ~ 0.8)
|
||||||
final criRate = (0.05 + effectiveDex * 0.005 + equipStats.criRate).clamp(0.05, 0.5);
|
var criRate = 0.05 + effectiveDex * 0.005 + equipStats.criRate;
|
||||||
|
|
||||||
// 크리티컬 데미지: 기본 1.5배, DEX에 따라 증가 (최대 3.0)
|
// 크리티컬 데미지: 기본 1.5배, DEX에 따라 증가 (최대 3.0)
|
||||||
final criDamage = (1.5 + effectiveDex * 0.01).clamp(1.5, 3.0);
|
final criDamage = (1.5 + effectiveDex * 0.01).clamp(1.5, 3.0);
|
||||||
|
|
||||||
// 회피율: DEX 기반 + 장비 보너스 (0.0 ~ 0.5)
|
// 회피율: DEX 기반 + 장비 보너스 (0.0 ~ 0.6)
|
||||||
final evasion = (effectiveDex * 0.005 + equipStats.evasion).clamp(0.0, 0.5);
|
var evasion = effectiveDex * 0.005 + equipStats.evasion;
|
||||||
|
|
||||||
// 명중률: DEX 기반 (0.8 ~ 1.0)
|
// 명중률: DEX 기반 (0.8 ~ 1.0)
|
||||||
final accuracy = (0.8 + effectiveDex * 0.002).clamp(0.8, 1.0);
|
final accuracy = (0.8 + effectiveDex * 0.002).clamp(0.8, 1.0);
|
||||||
@@ -253,8 +275,80 @@ class CombatStats {
|
|||||||
final attackDelayMs = (1000 / speedModifier).round().clamp(357, 1500);
|
final attackDelayMs = (1000 / speedModifier).round().clamp(357, 1500);
|
||||||
|
|
||||||
// HP/MP: 기본 + 장비 보너스
|
// HP/MP: 기본 + 장비 보너스
|
||||||
final totalHpMax = stats.hpMax + equipStats.hpBonus;
|
var totalHpMax = stats.hpMax + equipStats.hpBonus;
|
||||||
final totalMpMax = stats.mpMax + equipStats.mpBonus;
|
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(
|
return CombatStats(
|
||||||
str: effectiveStr,
|
str: effectiveStr,
|
||||||
@@ -262,7 +356,7 @@ class CombatStats {
|
|||||||
dex: effectiveDex,
|
dex: effectiveDex,
|
||||||
intelligence: effectiveInt,
|
intelligence: effectiveInt,
|
||||||
wis: effectiveWis,
|
wis: effectiveWis,
|
||||||
cha: stats.cha + equipStats.chaBonus,
|
cha: effectiveCha,
|
||||||
atk: baseAtk,
|
atk: baseAtk,
|
||||||
def: baseDef,
|
def: baseDef,
|
||||||
magAtk: baseMagAtk,
|
magAtk: baseMagAtk,
|
||||||
|
|||||||
97
lib/src/core/model/race_traits.dart
Normal file
97
lib/src/core/model/race_traits.dart
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/// 스탯 타입 열거형 (stat type)
|
||||||
|
enum StatType {
|
||||||
|
str,
|
||||||
|
con,
|
||||||
|
dex,
|
||||||
|
intelligence,
|
||||||
|
wis,
|
||||||
|
cha,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 패시브 능력 타입 (passive ability type)
|
||||||
|
enum PassiveType {
|
||||||
|
/// 경험치 배율 보너스
|
||||||
|
expBonus,
|
||||||
|
|
||||||
|
/// 마법 데미지 배율 보너스
|
||||||
|
magicDamageBonus,
|
||||||
|
|
||||||
|
/// 방어력 배율 보너스
|
||||||
|
defenseBonus,
|
||||||
|
|
||||||
|
/// 크리티컬 확률 보너스
|
||||||
|
criticalBonus,
|
||||||
|
|
||||||
|
/// HP 배율 보너스
|
||||||
|
hpBonus,
|
||||||
|
|
||||||
|
/// MP 배율 보너스
|
||||||
|
mpBonus,
|
||||||
|
|
||||||
|
/// 사망 시 장비 보존
|
||||||
|
deathEquipmentPreserve,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 패시브 능력 (passive ability)
|
||||||
|
///
|
||||||
|
/// 종족이나 클래스가 제공하는 수동적 효과
|
||||||
|
class PassiveAbility {
|
||||||
|
const PassiveAbility({
|
||||||
|
required this.type,
|
||||||
|
required this.value,
|
||||||
|
this.description = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 패시브 타입
|
||||||
|
final PassiveType type;
|
||||||
|
|
||||||
|
/// 효과 값 (배율의 경우 0.1 = 10%)
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
/// 설명
|
||||||
|
final String description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 종족 특성 (race traits)
|
||||||
|
///
|
||||||
|
/// 각 종족이 가진 고유한 스탯 보정과 패시브 능력
|
||||||
|
class RaceTraits {
|
||||||
|
const RaceTraits({
|
||||||
|
required this.raceId,
|
||||||
|
required this.name,
|
||||||
|
required this.statModifiers,
|
||||||
|
this.passives = const [],
|
||||||
|
this.expMultiplier = 1.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 종족 식별자
|
||||||
|
final String raceId;
|
||||||
|
|
||||||
|
/// 종족 이름 (표시용)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// 스탯 보정치 맵
|
||||||
|
final Map<StatType, int> statModifiers;
|
||||||
|
|
||||||
|
/// 패시브 능력 목록
|
||||||
|
final List<PassiveAbility> passives;
|
||||||
|
|
||||||
|
/// 경험치 배율 (1.0 = 100%)
|
||||||
|
final double expMultiplier;
|
||||||
|
|
||||||
|
/// 특정 스탯의 보정치 반환
|
||||||
|
int getModifier(StatType type) => statModifiers[type] ?? 0;
|
||||||
|
|
||||||
|
/// 특정 패시브 타입의 값 반환 (없으면 0)
|
||||||
|
double getPassiveValue(PassiveType type) {
|
||||||
|
for (final passive in passives) {
|
||||||
|
if (passive.type == type) return passive.value;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 특정 패시브 보유 여부
|
||||||
|
bool hasPassive(PassiveType type) {
|
||||||
|
return passives.any((p) => p.type == type);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user