feat(phase5): 종족/클래스 시스템 균형 및 UI 통합
- 21개 종족 균형 재설계 (스탯 합계 = 0) - 18개 클래스 균형 재설계 (스탯 합계 = +3) - Traits에 raceId, classId 필드 추가 - 저장/불러오기에 종족/클래스 ID 추가 - 캐릭터 생성 UI에서 RaceData/ClassData 사용 - 선택 시 스탯 보정 및 패시브 정보 표시
This commit is contained in:
@@ -12,7 +12,7 @@ class ClassData {
|
|||||||
// 전사 계열 (STR 기반)
|
// 전사 계열 (STR 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Bug Hunter: STR + INT
|
/// Bug Hunter: STR + INT (스탯 합계: +3)
|
||||||
static const bugHunter = ClassTraits(
|
static const bugHunter = ClassTraits(
|
||||||
classId: 'bug_hunter',
|
classId: 'bug_hunter',
|
||||||
name: 'Bug Hunter',
|
name: 'Bug Hunter',
|
||||||
@@ -32,12 +32,13 @@ class ClassData {
|
|||||||
restriction: EquipmentRestriction.none,
|
restriction: EquipmentRestriction.none,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Overflow Warrior: STR
|
/// Overflow Warrior: STR + CON (스탯 합계: +3)
|
||||||
static const overflowWarrior = ClassTraits(
|
static const overflowWarrior = ClassTraits(
|
||||||
classId: 'overflow_warrior',
|
classId: 'overflow_warrior',
|
||||||
name: 'Overflow Warrior',
|
name: 'Overflow Warrior',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.str: 2,
|
StatType.str: 2,
|
||||||
|
StatType.con: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['power_strike'],
|
startingSkills: ['power_strike'],
|
||||||
classSkills: ['power_strike', 'overflow_slash', 'buffer_break'],
|
classSkills: ['power_strike', 'overflow_slash', 'buffer_break'],
|
||||||
@@ -51,7 +52,7 @@ class ClassData {
|
|||||||
restriction: EquipmentRestriction.none,
|
restriction: EquipmentRestriction.none,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Stack Crusher: STR + CON
|
/// Stack Crusher: STR + CON (스탯 합계: +3)
|
||||||
static const stackCrusher = ClassTraits(
|
static const stackCrusher = ClassTraits(
|
||||||
classId: 'stack_crusher',
|
classId: 'stack_crusher',
|
||||||
name: 'Stack Crusher',
|
name: 'Stack Crusher',
|
||||||
@@ -76,7 +77,7 @@ class ClassData {
|
|||||||
restriction: EquipmentRestriction.none,
|
restriction: EquipmentRestriction.none,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Assertion Knight: STR + WIS
|
/// Assertion Knight: STR + WIS (스탯 합계: +3)
|
||||||
static const assertionKnight = ClassTraits(
|
static const assertionKnight = ClassTraits(
|
||||||
classId: 'assertion_knight',
|
classId: 'assertion_knight',
|
||||||
name: 'Assertion Knight',
|
name: 'Assertion Knight',
|
||||||
@@ -102,7 +103,7 @@ class ClassData {
|
|||||||
// 탱커 계열 (CON 기반)
|
// 탱커 계열 (CON 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Debugger Paladin: WIS + CON
|
/// Debugger Paladin: WIS + CON (스탯 합계: +3)
|
||||||
static const debuggerPaladin = ClassTraits(
|
static const debuggerPaladin = ClassTraits(
|
||||||
classId: 'debugger_paladin',
|
classId: 'debugger_paladin',
|
||||||
name: 'Debugger Paladin',
|
name: 'Debugger Paladin',
|
||||||
@@ -129,12 +130,13 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Loop Breaker: CON
|
/// Loop Breaker: CON + STR (스탯 합계: +3)
|
||||||
static const loopBreaker = ClassTraits(
|
static const loopBreaker = ClassTraits(
|
||||||
classId: 'loop_breaker',
|
classId: 'loop_breaker',
|
||||||
name: 'Loop Breaker',
|
name: 'Loop Breaker',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.con: 2,
|
StatType.con: 2,
|
||||||
|
StatType.str: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['shield_bash'],
|
startingSkills: ['shield_bash'],
|
||||||
classSkills: ['shield_bash', 'infinite_guard', 'break_stance'],
|
classSkills: ['shield_bash', 'infinite_guard', 'break_stance'],
|
||||||
@@ -150,7 +152,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Garbage Collector: CON + STR
|
/// Garbage Collector: CON + STR (스탯 합계: +3)
|
||||||
static const garbageCollector = ClassTraits(
|
static const garbageCollector = ClassTraits(
|
||||||
classId: 'garbage_collector',
|
classId: 'garbage_collector',
|
||||||
name: 'Garbage Collector',
|
name: 'Garbage Collector',
|
||||||
@@ -181,12 +183,13 @@ class ClassData {
|
|||||||
// 마법사 계열 (INT 기반)
|
// 마법사 계열 (INT 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Compiler Mage: INT + MP Max
|
/// Compiler Mage: INT + WIS (스탯 합계: +3)
|
||||||
static const compilerMage = ClassTraits(
|
static const compilerMage = ClassTraits(
|
||||||
classId: 'compiler_mage',
|
classId: 'compiler_mage',
|
||||||
name: 'Compiler Mage',
|
name: 'Compiler Mage',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.intelligence: 2,
|
StatType.intelligence: 2,
|
||||||
|
StatType.wis: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['fireball'],
|
startingSkills: ['fireball'],
|
||||||
classSkills: ['fireball', 'compile_blast', 'syntax_storm'],
|
classSkills: ['fireball', 'compile_blast', 'syntax_storm'],
|
||||||
@@ -202,12 +205,13 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Recursion Master: INT
|
/// Recursion Master: INT + DEX (스탯 합계: +3)
|
||||||
static const recursionMaster = ClassTraits(
|
static const recursionMaster = ClassTraits(
|
||||||
classId: 'recursion_master',
|
classId: 'recursion_master',
|
||||||
name: 'Recursion Master',
|
name: 'Recursion Master',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.intelligence: 2,
|
StatType.intelligence: 2,
|
||||||
|
StatType.dex: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['fireball'],
|
startingSkills: ['fireball'],
|
||||||
classSkills: ['fireball', 'recursive_bolt', 'stack_overflow'],
|
classSkills: ['fireball', 'recursive_bolt', 'stack_overflow'],
|
||||||
@@ -223,7 +227,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Memory Leaker: INT + WIS
|
/// Memory Leaker: INT + WIS (스탯 합계: +3)
|
||||||
static const memoryLeaker = ClassTraits(
|
static const memoryLeaker = ClassTraits(
|
||||||
classId: 'memory_leaker',
|
classId: 'memory_leaker',
|
||||||
name: 'Memory Leaker',
|
name: 'Memory Leaker',
|
||||||
@@ -245,7 +249,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Type Caster: INT + CHA
|
/// Type Caster: INT + CHA (스탯 합계: +3)
|
||||||
static const typeCaster = ClassTraits(
|
static const typeCaster = ClassTraits(
|
||||||
classId: 'type_caster',
|
classId: 'type_caster',
|
||||||
name: 'Type Caster',
|
name: 'Type Caster',
|
||||||
@@ -267,7 +271,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// DevOps Shaman: CON + INT
|
/// DevOps Shaman: CON + INT (스탯 합계: +3)
|
||||||
static const devOpsShaman = ClassTraits(
|
static const devOpsShaman = ClassTraits(
|
||||||
classId: 'devops_shaman',
|
classId: 'devops_shaman',
|
||||||
name: 'DevOps Shaman',
|
name: 'DevOps Shaman',
|
||||||
@@ -298,12 +302,13 @@ class ClassData {
|
|||||||
// 민첩 계열 (DEX 기반)
|
// 민첩 계열 (DEX 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Refactor Monk: DEX
|
/// Refactor Monk: DEX + CON (스탯 합계: +3)
|
||||||
static const refactorMonk = ClassTraits(
|
static const refactorMonk = ClassTraits(
|
||||||
classId: 'refactor_monk',
|
classId: 'refactor_monk',
|
||||||
name: 'Refactor Monk',
|
name: 'Refactor Monk',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
|
StatType.con: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['flurry'],
|
startingSkills: ['flurry'],
|
||||||
classSkills: ['flurry', 'clean_code_strike', 'refactor_combo'],
|
classSkills: ['flurry', 'clean_code_strike', 'refactor_combo'],
|
||||||
@@ -324,12 +329,13 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Pointer Assassin: DEX
|
/// Pointer Assassin: DEX + STR (스탯 합계: +3)
|
||||||
static const pointerAssassin = ClassTraits(
|
static const pointerAssassin = ClassTraits(
|
||||||
classId: 'pointer_assassin',
|
classId: 'pointer_assassin',
|
||||||
name: 'Pointer Assassin',
|
name: 'Pointer Assassin',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
|
StatType.str: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['backstab'],
|
startingSkills: ['backstab'],
|
||||||
classSkills: ['backstab', 'null_strike', 'dereference_kill'],
|
classSkills: ['backstab', 'null_strike', 'dereference_kill'],
|
||||||
@@ -350,7 +356,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Callback Samurai: DEX + STR
|
/// Callback Samurai: DEX + STR (스탯 합계: +3)
|
||||||
static const callbackSamurai = ClassTraits(
|
static const callbackSamurai = ClassTraits(
|
||||||
classId: 'callback_samurai',
|
classId: 'callback_samurai',
|
||||||
name: 'Callback Samurai',
|
name: 'Callback Samurai',
|
||||||
@@ -375,7 +381,7 @@ class ClassData {
|
|||||||
restriction: EquipmentRestriction.none,
|
restriction: EquipmentRestriction.none,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Tester Jester: DEX + CHA
|
/// Tester Jester: DEX + CHA (스탯 합계: +3)
|
||||||
static const testerJester = ClassTraits(
|
static const testerJester = ClassTraits(
|
||||||
classId: 'tester_jester',
|
classId: 'tester_jester',
|
||||||
name: 'Tester Jester',
|
name: 'Tester Jester',
|
||||||
@@ -406,12 +412,13 @@ class ClassData {
|
|||||||
// 지혜 계열 (WIS 기반)
|
// 지혜 계열 (WIS 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Exception Handler: WIS
|
/// Exception Handler: WIS + INT (스탯 합계: +3)
|
||||||
static const exceptionHandler = ClassTraits(
|
static const exceptionHandler = ClassTraits(
|
||||||
classId: 'exception_handler',
|
classId: 'exception_handler',
|
||||||
name: 'Exception Handler',
|
name: 'Exception Handler',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
|
StatType.intelligence: 1,
|
||||||
},
|
},
|
||||||
startingSkills: ['heal'],
|
startingSkills: ['heal'],
|
||||||
classSkills: ['heal', 'try_catch', 'finally_heal'],
|
classSkills: ['heal', 'try_catch', 'finally_heal'],
|
||||||
@@ -427,7 +434,7 @@ class ClassData {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Null Checker: WIS + INT
|
/// Null Checker: WIS + INT (스탯 합계: +3)
|
||||||
static const nullChecker = ClassTraits(
|
static const nullChecker = ClassTraits(
|
||||||
classId: 'null_checker',
|
classId: 'null_checker',
|
||||||
name: 'Null Checker',
|
name: 'Null Checker',
|
||||||
|
|||||||
@@ -2,20 +2,42 @@ import 'package:askiineverdie/src/core/model/race_traits.dart';
|
|||||||
|
|
||||||
/// 종족 데이터 정의 (race data)
|
/// 종족 데이터 정의 (race data)
|
||||||
///
|
///
|
||||||
/// 프로그래밍 테마의 21가지 종족 정의
|
/// 21가지 종족 - 모든 종족의 스탯 합계 = 0 (균형)
|
||||||
/// pq_config_data.dart의 Races 데이터 기반
|
/// 패시브는 각 종족의 고유한 플레이스타일을 정의
|
||||||
class RaceData {
|
class RaceData {
|
||||||
RaceData._();
|
RaceData._();
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 기본 종족 (HP/밸런스형)
|
// HP/균형형 종족
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Byte Human: HP 보너스
|
/// Byte Human: 균형형 (스탯 합계: 0)
|
||||||
|
/// 특화 없이 경험치로 보상
|
||||||
static const byteHuman = RaceTraits(
|
static const byteHuman = RaceTraits(
|
||||||
raceId: 'byte_human',
|
raceId: 'byte_human',
|
||||||
name: 'Byte Human',
|
name: 'Byte Human',
|
||||||
statModifiers: {},
|
statModifiers: {},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.expBonus,
|
||||||
|
value: 0.05,
|
||||||
|
description: '경험치 +5%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
expMultiplier: 1.05,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Kernel Giant: 탱커형 (스탯 합계: 0)
|
||||||
|
/// STR/CON +2, DEX/INT -2
|
||||||
|
static const kernelGiant = RaceTraits(
|
||||||
|
raceId: 'kernel_giant',
|
||||||
|
name: 'Kernel Giant',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 2,
|
||||||
|
StatType.con: 2,
|
||||||
|
StatType.dex: -2,
|
||||||
|
StatType.intelligence: -2,
|
||||||
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.hpBonus,
|
type: PassiveType.hpBonus,
|
||||||
@@ -25,45 +47,32 @@ class RaceData {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Kernel Giant: STR + HP 보너스
|
|
||||||
static const kernelGiant = RaceTraits(
|
|
||||||
raceId: 'kernel_giant',
|
|
||||||
name: 'Kernel Giant',
|
|
||||||
statModifiers: {
|
|
||||||
StatType.str: 3,
|
|
||||||
StatType.dex: -2,
|
|
||||||
},
|
|
||||||
passives: [
|
|
||||||
PassiveAbility(
|
|
||||||
type: PassiveType.hpBonus,
|
|
||||||
value: 0.15,
|
|
||||||
description: 'HP +15%',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 지혜 종족 (WIS 기반)
|
// 지혜/마법 종족 (WIS 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Null Elf: WIS 보너스
|
/// Null Elf: 마법형 (스탯 합계: 0)
|
||||||
|
/// WIS/INT +2, STR/CON -2
|
||||||
static const nullElf = RaceTraits(
|
static const nullElf = RaceTraits(
|
||||||
raceId: 'null_elf',
|
raceId: 'null_elf',
|
||||||
name: 'Null Elf',
|
name: 'Null Elf',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
|
StatType.intelligence: 1,
|
||||||
|
StatType.str: -2,
|
||||||
StatType.con: -1,
|
StatType.con: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.magicDamageBonus,
|
type: PassiveType.magicDamageBonus,
|
||||||
value: 0.10,
|
value: 0.08,
|
||||||
description: '마법 데미지 +10%',
|
description: '마법 데미지 +8%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Recursive Sage: WIS + INT 보너스
|
/// Recursive Sage: 순수 마법형 (스탯 합계: 0)
|
||||||
|
/// WIS/INT +2, STR -2, DEX -1, CHA +1
|
||||||
static const recursiveSage = RaceTraits(
|
static const recursiveSage = RaceTraits(
|
||||||
raceId: 'recursive_sage',
|
raceId: 'recursive_sage',
|
||||||
name: 'Recursive Sage',
|
name: 'Recursive Sage',
|
||||||
@@ -71,6 +80,8 @@ class RaceData {
|
|||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
StatType.intelligence: 2,
|
StatType.intelligence: 2,
|
||||||
StatType.str: -2,
|
StatType.str: -2,
|
||||||
|
StatType.dex: -1,
|
||||||
|
StatType.con: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
@@ -81,47 +92,62 @@ class RaceData {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Callback Priest: WIS + CHA 보너스
|
/// Callback Priest: 지원형 (스탯 합계: 0)
|
||||||
|
/// WIS +2, CHA +1, STR -1, DEX -1, CON -1
|
||||||
static const callbackPriest = RaceTraits(
|
static const callbackPriest = RaceTraits(
|
||||||
raceId: 'callback_priest',
|
raceId: 'callback_priest',
|
||||||
name: 'Callback Priest',
|
name: 'Callback Priest',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
StatType.cha: 2,
|
StatType.cha: 1,
|
||||||
StatType.str: -1,
|
StatType.str: -1,
|
||||||
StatType.dex: -1,
|
StatType.dex: -1,
|
||||||
|
StatType.con: -1,
|
||||||
},
|
},
|
||||||
passives: [],
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.expBonus,
|
||||||
|
value: 0.03,
|
||||||
|
description: '경험치 +3%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
expMultiplier: 1.03,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 체력 종족 (CON 기반)
|
// 체력/방어 종족 (CON 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Buffer Dwarf: CON 보너스
|
/// Buffer Dwarf: 방어형 (스탯 합계: 0)
|
||||||
|
/// CON +2, STR +1, DEX -2, CHA -1
|
||||||
static const bufferDwarf = RaceTraits(
|
static const bufferDwarf = RaceTraits(
|
||||||
raceId: 'buffer_dwarf',
|
raceId: 'buffer_dwarf',
|
||||||
name: 'Buffer Dwarf',
|
name: 'Buffer Dwarf',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.con: 2,
|
StatType.con: 2,
|
||||||
StatType.dex: -1,
|
StatType.str: 1,
|
||||||
|
StatType.dex: -2,
|
||||||
|
StatType.cha: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.defenseBonus,
|
type: PassiveType.defenseBonus,
|
||||||
value: 0.05,
|
value: 0.08,
|
||||||
description: '방어력 +5%',
|
description: '방어력 +8%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Coredump Undead: CON 보너스
|
/// Coredump Undead: 생존형 (스탯 합계: 0)
|
||||||
|
/// CON +2, STR +1, CHA -2, DEX -1
|
||||||
static const coredumpUndead = RaceTraits(
|
static const coredumpUndead = RaceTraits(
|
||||||
raceId: 'coredump_undead',
|
raceId: 'coredump_undead',
|
||||||
name: 'Coredump Undead',
|
name: 'Coredump Undead',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.con: 2,
|
StatType.con: 2,
|
||||||
|
StatType.str: 1,
|
||||||
StatType.cha: -2,
|
StatType.cha: -2,
|
||||||
|
StatType.dex: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
@@ -133,16 +159,19 @@ class RaceData {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 민첩 종족 (DEX 기반)
|
// 민첩/크리티컬 종족 (DEX 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Bit Halfling: DEX 보너스
|
/// Bit Halfling: 민첩형 (스탯 합계: 0)
|
||||||
|
/// DEX +2, CHA +1, STR -2, CON -1
|
||||||
static const bitHalfling = RaceTraits(
|
static const bitHalfling = RaceTraits(
|
||||||
raceId: 'bit_halfling',
|
raceId: 'bit_halfling',
|
||||||
name: 'Bit Halfling',
|
name: 'Bit Halfling',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
StatType.str: -1,
|
StatType.cha: 1,
|
||||||
|
StatType.str: -2,
|
||||||
|
StatType.con: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
@@ -153,69 +182,81 @@ class RaceData {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Cache Imp: DEX 보너스
|
/// Cache Imp: 속도형 (스탯 합계: 0)
|
||||||
|
/// DEX +2, INT +1, CON -2, WIS -1
|
||||||
static const cacheImp = RaceTraits(
|
static const cacheImp = RaceTraits(
|
||||||
raceId: 'cache_imp',
|
raceId: 'cache_imp',
|
||||||
name: 'Cache Imp',
|
name: 'Cache Imp',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
StatType.con: -1,
|
StatType.intelligence: 1,
|
||||||
},
|
StatType.con: -2,
|
||||||
passives: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Iterator Rogue: DEX 보너스
|
|
||||||
static const iteratorRogue = RaceTraits(
|
|
||||||
raceId: 'iterator_rogue',
|
|
||||||
name: 'Iterator Rogue',
|
|
||||||
statModifiers: {
|
|
||||||
StatType.dex: 2,
|
|
||||||
StatType.wis: -1,
|
StatType.wis: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.criticalBonus,
|
type: PassiveType.criticalBonus,
|
||||||
value: 0.05,
|
value: 0.02,
|
||||||
description: '크리티컬 +5%',
|
description: '크리티컬 +2%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Iterator Rogue: 암살형 (스탯 합계: 0)
|
||||||
|
/// DEX +3, STR +1, CON -2, WIS -1, CHA -1
|
||||||
|
static const iteratorRogue = RaceTraits(
|
||||||
|
raceId: 'iterator_rogue',
|
||||||
|
name: 'Iterator Rogue',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.dex: 3,
|
||||||
|
StatType.str: 1,
|
||||||
|
StatType.con: -2,
|
||||||
|
StatType.wis: -1,
|
||||||
|
StatType.cha: -1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.criticalBonus,
|
||||||
|
value: 0.04,
|
||||||
|
description: '크리티컬 +4%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 힘 종족 (STR 기반)
|
// 힘/물리 종족 (STR 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Array Orc: STR 보너스
|
/// Array Orc: 공격형 (스탯 합계: 0)
|
||||||
|
/// STR +2, CON +1, INT -2, WIS -1
|
||||||
static const arrayOrc = RaceTraits(
|
static const arrayOrc = RaceTraits(
|
||||||
raceId: 'array_orc',
|
raceId: 'array_orc',
|
||||||
name: 'Array Orc',
|
name: 'Array Orc',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.str: 2,
|
StatType.str: 2,
|
||||||
StatType.intelligence: -1,
|
StatType.con: 1,
|
||||||
|
StatType.intelligence: -2,
|
||||||
|
StatType.wis: -1,
|
||||||
},
|
},
|
||||||
passives: [],
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.hpBonus,
|
||||||
|
value: 0.05,
|
||||||
|
description: 'HP +5%',
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Flag Knight: CHA + STR 보너스
|
/// Flag Knight: 전사형 (스탯 합계: 0)
|
||||||
|
/// STR +2, CHA +1, INT -2, WIS -1
|
||||||
static const flagKnight = RaceTraits(
|
static const flagKnight = RaceTraits(
|
||||||
raceId: 'flag_knight',
|
raceId: 'flag_knight',
|
||||||
name: 'Flag Knight',
|
name: 'Flag Knight',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.cha: 2,
|
|
||||||
StatType.str: 2,
|
StatType.str: 2,
|
||||||
|
StatType.cha: 1,
|
||||||
StatType.intelligence: -2,
|
StatType.intelligence: -2,
|
||||||
},
|
StatType.wis: -1,
|
||||||
passives: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Protocol Paladin: STR + CHA 보너스
|
|
||||||
static const protocolPaladin = RaceTraits(
|
|
||||||
raceId: 'protocol_paladin',
|
|
||||||
name: 'Protocol Paladin',
|
|
||||||
statModifiers: {
|
|
||||||
StatType.str: 2,
|
|
||||||
StatType.cha: 2,
|
|
||||||
StatType.dex: -2,
|
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
@@ -226,23 +267,53 @@ class RaceData {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Protocol Paladin: 수호자형 (스탯 합계: 0)
|
||||||
|
/// STR +2, CON +1, CHA +1, DEX -2, INT -2
|
||||||
|
static const protocolPaladin = RaceTraits(
|
||||||
|
raceId: 'protocol_paladin',
|
||||||
|
name: 'Protocol Paladin',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.str: 2,
|
||||||
|
StatType.con: 1,
|
||||||
|
StatType.cha: 1,
|
||||||
|
StatType.dex: -2,
|
||||||
|
StatType.intelligence: -2,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.defenseBonus,
|
||||||
|
value: 0.06,
|
||||||
|
description: '방어력 +6%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 복합 스탯 종족
|
// 복합 스탯 종족
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Stack Goblin: DEX + CON 보너스
|
/// Stack Goblin: 기동형 (스탯 합계: 0)
|
||||||
|
/// DEX +2, CON +1, STR -1, CHA -2
|
||||||
static const stackGoblin = RaceTraits(
|
static const stackGoblin = RaceTraits(
|
||||||
raceId: 'stack_goblin',
|
raceId: 'stack_goblin',
|
||||||
name: 'Stack Goblin',
|
name: 'Stack Goblin',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
StatType.con: 1,
|
StatType.con: 1,
|
||||||
|
StatType.str: -1,
|
||||||
StatType.cha: -2,
|
StatType.cha: -2,
|
||||||
},
|
},
|
||||||
passives: [],
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.criticalBonus,
|
||||||
|
value: 0.02,
|
||||||
|
description: '크리티컬 +2%',
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Heap Troll: CON + STR 보너스
|
/// Heap Troll: 중장갑형 (스탯 합계: 0)
|
||||||
|
/// CON +2, STR +2, INT -2, DEX -2
|
||||||
static const heapTroll = RaceTraits(
|
static const heapTroll = RaceTraits(
|
||||||
raceId: 'heap_troll',
|
raceId: 'heap_troll',
|
||||||
name: 'Heap Troll',
|
name: 'Heap Troll',
|
||||||
@@ -250,18 +321,19 @@ class RaceData {
|
|||||||
StatType.con: 2,
|
StatType.con: 2,
|
||||||
StatType.str: 2,
|
StatType.str: 2,
|
||||||
StatType.intelligence: -2,
|
StatType.intelligence: -2,
|
||||||
StatType.dex: -1,
|
StatType.dex: -2,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.hpBonus,
|
type: PassiveType.hpBonus,
|
||||||
value: 0.10,
|
value: 0.12,
|
||||||
description: 'HP +10%',
|
description: 'HP +12%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Index Ranger: DEX + CON 보너스
|
/// Index Ranger: 정찰형 (스탯 합계: 0)
|
||||||
|
/// DEX +2, CON +1, INT -1, CHA -2
|
||||||
static const indexRanger = RaceTraits(
|
static const indexRanger = RaceTraits(
|
||||||
raceId: 'index_ranger',
|
raceId: 'index_ranger',
|
||||||
name: 'Index Ranger',
|
name: 'Index Ranger',
|
||||||
@@ -269,21 +341,70 @@ class RaceData {
|
|||||||
StatType.dex: 2,
|
StatType.dex: 2,
|
||||||
StatType.con: 1,
|
StatType.con: 1,
|
||||||
StatType.intelligence: -1,
|
StatType.intelligence: -1,
|
||||||
|
StatType.cha: -2,
|
||||||
},
|
},
|
||||||
passives: [],
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.criticalBonus,
|
||||||
|
value: 0.03,
|
||||||
|
description: '크리티컬 +3%',
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 마법 종족 (MP/INT 기반)
|
// MP/주문 종족 (INT/MP 기반)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// Pointer Fairy: MP Max + WIS 보너스
|
/// Pointer Fairy: MP 특화형 (스탯 합계: 0)
|
||||||
|
/// WIS +2, INT +1, STR -2, CON -1
|
||||||
static const pointerFairy = RaceTraits(
|
static const pointerFairy = RaceTraits(
|
||||||
raceId: 'pointer_fairy',
|
raceId: 'pointer_fairy',
|
||||||
name: 'Pointer Fairy',
|
name: 'Pointer Fairy',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
|
StatType.intelligence: 1,
|
||||||
StatType.str: -2,
|
StatType.str: -2,
|
||||||
|
StatType.con: -1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.mpBonus,
|
||||||
|
value: 0.12,
|
||||||
|
description: 'MP +12%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Register Gnome: 지능형 (스탯 합계: 0)
|
||||||
|
/// INT +2, DEX +1, STR -2, CON -1
|
||||||
|
static const registerGnome = RaceTraits(
|
||||||
|
raceId: 'register_gnome',
|
||||||
|
name: 'Register Gnome',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.intelligence: 2,
|
||||||
|
StatType.dex: 1,
|
||||||
|
StatType.str: -2,
|
||||||
|
StatType.con: -1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.magicDamageBonus,
|
||||||
|
value: 0.05,
|
||||||
|
description: '마법 데미지 +5%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Thread Spirit: 영체형 (스탯 합계: 0)
|
||||||
|
/// INT +1, WIS +1, CON -2
|
||||||
|
static const threadSpirit = RaceTraits(
|
||||||
|
raceId: 'thread_spirit',
|
||||||
|
name: 'Thread Spirit',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.intelligence: 1,
|
||||||
|
StatType.wis: 1,
|
||||||
|
StatType.con: -2,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
@@ -294,57 +415,34 @@ class RaceData {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Register Gnome: INT 보너스
|
/// Loop Wizard: 순환 마법형 (스탯 합계: 0)
|
||||||
static const registerGnome = RaceTraits(
|
/// INT +2, WIS +1, STR -1, CON -1, DEX -1
|
||||||
raceId: 'register_gnome',
|
|
||||||
name: 'Register Gnome',
|
|
||||||
statModifiers: {
|
|
||||||
StatType.intelligence: 2,
|
|
||||||
StatType.str: -1,
|
|
||||||
},
|
|
||||||
passives: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Thread Spirit: MP Max 보너스
|
|
||||||
static const threadSpirit = RaceTraits(
|
|
||||||
raceId: 'thread_spirit',
|
|
||||||
name: 'Thread Spirit',
|
|
||||||
statModifiers: {
|
|
||||||
StatType.con: -1,
|
|
||||||
},
|
|
||||||
passives: [
|
|
||||||
PassiveAbility(
|
|
||||||
type: PassiveType.mpBonus,
|
|
||||||
value: 0.20,
|
|
||||||
description: 'MP +20%',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Loop Wizard: INT + MP Max 보너스
|
|
||||||
static const loopWizard = RaceTraits(
|
static const loopWizard = RaceTraits(
|
||||||
raceId: 'loop_wizard',
|
raceId: 'loop_wizard',
|
||||||
name: 'Loop Wizard',
|
name: 'Loop Wizard',
|
||||||
statModifiers: {
|
statModifiers: {
|
||||||
StatType.intelligence: 2,
|
StatType.intelligence: 2,
|
||||||
|
StatType.wis: 1,
|
||||||
StatType.str: -1,
|
StatType.str: -1,
|
||||||
StatType.con: -1,
|
StatType.con: -1,
|
||||||
|
StatType.dex: -1,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.mpBonus,
|
type: PassiveType.mpBonus,
|
||||||
value: 0.10,
|
value: 0.08,
|
||||||
description: 'MP +10%',
|
description: 'MP +8%',
|
||||||
),
|
),
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.magicDamageBonus,
|
type: PassiveType.magicDamageBonus,
|
||||||
value: 0.05,
|
value: 0.04,
|
||||||
description: '마법 데미지 +5%',
|
description: '마법 데미지 +4%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Lambda Druid: INT + WIS 보너스
|
/// Lambda Druid: 자연 마법형 (스탯 합계: 0)
|
||||||
|
/// INT +2, WIS +2, STR -2, CON -2
|
||||||
static const lambdaDruid = RaceTraits(
|
static const lambdaDruid = RaceTraits(
|
||||||
raceId: 'lambda_druid',
|
raceId: 'lambda_druid',
|
||||||
name: 'Lambda Druid',
|
name: 'Lambda Druid',
|
||||||
@@ -352,20 +450,25 @@ class RaceData {
|
|||||||
StatType.intelligence: 2,
|
StatType.intelligence: 2,
|
||||||
StatType.wis: 2,
|
StatType.wis: 2,
|
||||||
StatType.str: -2,
|
StatType.str: -2,
|
||||||
StatType.con: -1,
|
StatType.con: -2,
|
||||||
},
|
},
|
||||||
passives: [
|
passives: [
|
||||||
PassiveAbility(
|
PassiveAbility(
|
||||||
type: PassiveType.magicDamageBonus,
|
type: PassiveType.magicDamageBonus,
|
||||||
value: 0.10,
|
value: 0.06,
|
||||||
description: '마법 데미지 +10%',
|
description: '마법 데미지 +6%',
|
||||||
|
),
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.mpBonus,
|
||||||
|
value: 0.06,
|
||||||
|
description: 'MP +6%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 모든 종족 목록 (21개)
|
/// 모든 종족 목록 (21개)
|
||||||
static const List<RaceTraits> all = [
|
static const List<RaceTraits> all = [
|
||||||
// 기본/HP형
|
// 균형형
|
||||||
byteHuman,
|
byteHuman,
|
||||||
kernelGiant,
|
kernelGiant,
|
||||||
// 지혜형
|
// 지혜형
|
||||||
|
|||||||
@@ -296,15 +296,28 @@ class Traits {
|
|||||||
required this.level,
|
required this.level,
|
||||||
required this.motto,
|
required this.motto,
|
||||||
required this.guild,
|
required this.guild,
|
||||||
|
this.raceId = '',
|
||||||
|
this.classId = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
|
/// 종족 표시 이름 (예: "Kernel Giant")
|
||||||
final String race;
|
final String race;
|
||||||
|
|
||||||
|
/// 클래스 표시 이름 (예: "Bug Hunter")
|
||||||
final String klass;
|
final String klass;
|
||||||
|
|
||||||
final int level;
|
final int level;
|
||||||
final String motto;
|
final String motto;
|
||||||
final String guild;
|
final String guild;
|
||||||
|
|
||||||
|
/// 종족 ID (Phase 5, 예: "kernel_giant")
|
||||||
|
final String raceId;
|
||||||
|
|
||||||
|
/// 클래스 ID (Phase 5, 예: "bug_hunter")
|
||||||
|
final String classId;
|
||||||
|
|
||||||
factory Traits.empty() => const Traits(
|
factory Traits.empty() => const Traits(
|
||||||
name: '',
|
name: '',
|
||||||
race: '',
|
race: '',
|
||||||
@@ -312,6 +325,8 @@ class Traits {
|
|||||||
level: 1,
|
level: 1,
|
||||||
motto: '',
|
motto: '',
|
||||||
guild: '',
|
guild: '',
|
||||||
|
raceId: '',
|
||||||
|
classId: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
Traits copyWith({
|
Traits copyWith({
|
||||||
@@ -321,6 +336,8 @@ class Traits {
|
|||||||
int? level,
|
int? level,
|
||||||
String? motto,
|
String? motto,
|
||||||
String? guild,
|
String? guild,
|
||||||
|
String? raceId,
|
||||||
|
String? classId,
|
||||||
}) {
|
}) {
|
||||||
return Traits(
|
return Traits(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -329,6 +346,8 @@ class Traits {
|
|||||||
level: level ?? this.level,
|
level: level ?? this.level,
|
||||||
motto: motto ?? this.motto,
|
motto: motto ?? this.motto,
|
||||||
guild: guild ?? this.guild,
|
guild: guild ?? this.guild,
|
||||||
|
raceId: raceId ?? this.raceId,
|
||||||
|
classId: classId ?? this.classId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class GameSave {
|
|||||||
'level': traits.level,
|
'level': traits.level,
|
||||||
'motto': traits.motto,
|
'motto': traits.motto,
|
||||||
'guild': traits.guild,
|
'guild': traits.guild,
|
||||||
|
'raceId': traits.raceId,
|
||||||
|
'classId': traits.classId,
|
||||||
},
|
},
|
||||||
'stats': {
|
'stats': {
|
||||||
'str': stats.str,
|
'str': stats.str,
|
||||||
@@ -148,6 +150,8 @@ class GameSave {
|
|||||||
level: traitsJson['level'] as int? ?? 1,
|
level: traitsJson['level'] as int? ?? 1,
|
||||||
motto: traitsJson['motto'] as String? ?? '',
|
motto: traitsJson['motto'] as String? ?? '',
|
||||||
guild: traitsJson['guild'] as String? ?? '',
|
guild: traitsJson['guild'] as String? ?? '',
|
||||||
|
raceId: traitsJson['raceId'] as String? ?? '',
|
||||||
|
classId: traitsJson['classId'] as String? ?? '',
|
||||||
),
|
),
|
||||||
stats: Stats(
|
stats: Stats(
|
||||||
str: statsJson['str'] as int? ?? 0,
|
str: statsJson['str'] as int? ?? 0,
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import 'dart:math' as math;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:askiineverdie/data/class_data.dart';
|
||||||
|
import 'package:askiineverdie/data/race_data.dart';
|
||||||
import 'package:askiineverdie/l10n/app_localizations.dart';
|
import 'package:askiineverdie/l10n/app_localizations.dart';
|
||||||
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
|
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/pq_config.dart';
|
import 'package:askiineverdie/src/core/model/race_traits.dart';
|
||||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||||
import 'package:askiineverdie/src/core/util/pq_logic.dart';
|
import 'package:askiineverdie/src/core/util/pq_logic.dart';
|
||||||
|
|
||||||
@@ -21,12 +23,11 @@ class NewCharacterScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
||||||
final PqConfig _config = const PqConfig();
|
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
|
|
||||||
// 종족(races)과 직업(klasses) 목록
|
// 종족(races)과 직업(klasses) 목록 (Phase 5)
|
||||||
late final List<String> _races;
|
final List<RaceTraits> _races = RaceData.all;
|
||||||
late final List<String> _klasses;
|
final List<ClassTraits> _klasses = ClassData.all;
|
||||||
|
|
||||||
// 선택된 종족/직업 인덱스
|
// 선택된 종족/직업 인덱스
|
||||||
int _selectedRaceIndex = 0;
|
int _selectedRaceIndex = 0;
|
||||||
@@ -54,10 +55,6 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// 종족/직업 목록 로드 (name|attribute 형식에서 name만 추출)
|
|
||||||
_races = _config.races.map((e) => e.split('|').first).toList();
|
|
||||||
_klasses = _config.klasses.map((e) => e.split('|').first).toList();
|
|
||||||
|
|
||||||
// 초기 랜덤화
|
// 초기 랜덤화
|
||||||
final random = math.Random();
|
final random = math.Random();
|
||||||
_selectedRaceIndex = random.nextInt(_races.length);
|
_selectedRaceIndex = random.nextInt(_races.length);
|
||||||
@@ -150,6 +147,10 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 선택된 종족/클래스 (Phase 5)
|
||||||
|
final selectedRace = _races[_selectedRaceIndex];
|
||||||
|
final selectedClass = _klasses[_selectedKlassIndex];
|
||||||
|
|
||||||
// 게임에 사용할 새 RNG 생성
|
// 게임에 사용할 새 RNG 생성
|
||||||
final gameSeed = math.Random().nextInt(0x7FFFFFFF);
|
final gameSeed = math.Random().nextInt(0x7FFFFFFF);
|
||||||
|
|
||||||
@@ -174,11 +175,13 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
|
|
||||||
final traits = Traits(
|
final traits = Traits(
|
||||||
name: name,
|
name: name,
|
||||||
race: _races[_selectedRaceIndex],
|
race: selectedRace.name,
|
||||||
klass: _klasses[_selectedKlassIndex],
|
klass: selectedClass.name,
|
||||||
level: 1,
|
level: 1,
|
||||||
motto: '',
|
motto: '',
|
||||||
guild: '',
|
guild: '',
|
||||||
|
raceId: selectedRace.raceId,
|
||||||
|
classId: selectedClass.classId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 초기 게임 상태 생성
|
// 초기 게임 상태 생성
|
||||||
@@ -401,10 +404,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
itemCount: _races.length,
|
itemCount: _races.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isSelected = index == _selectedRaceIndex;
|
final isSelected = index == _selectedRaceIndex;
|
||||||
final raceName = GameDataL10n.getRaceName(
|
final race = _races[index];
|
||||||
context,
|
|
||||||
_races[index],
|
|
||||||
);
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
isSelected
|
isSelected
|
||||||
@@ -415,14 +415,15 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
raceName,
|
race.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: isSelected
|
fontWeight: isSelected
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dense: true,
|
subtitle: isSelected ? _buildRaceInfo(race) : null,
|
||||||
|
dense: !isSelected,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onTap: () => setState(() => _selectedRaceIndex = index),
|
onTap: () => setState(() => _selectedRaceIndex = index),
|
||||||
);
|
);
|
||||||
@@ -435,6 +436,48 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 종족 정보 표시 (Phase 5)
|
||||||
|
Widget _buildRaceInfo(RaceTraits race) {
|
||||||
|
final statMods = <String>[];
|
||||||
|
for (final entry in race.statModifiers.entries) {
|
||||||
|
final sign = entry.value > 0 ? '+' : '';
|
||||||
|
statMods.add('${_statName(entry.key)} $sign${entry.value}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final passiveDesc = race.passives.isNotEmpty
|
||||||
|
? race.passives.map((p) => p.description).join(', ')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (statMods.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
statMods.join(', '),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
if (passiveDesc.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
passiveDesc,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _statName(StatType type) {
|
||||||
|
return switch (type) {
|
||||||
|
StatType.str => 'STR',
|
||||||
|
StatType.con => 'CON',
|
||||||
|
StatType.dex => 'DEX',
|
||||||
|
StatType.intelligence => 'INT',
|
||||||
|
StatType.wis => 'WIS',
|
||||||
|
StatType.cha => 'CHA',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildKlassSection() {
|
Widget _buildKlassSection() {
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -450,10 +493,7 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
itemCount: _klasses.length,
|
itemCount: _klasses.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isSelected = index == _selectedKlassIndex;
|
final isSelected = index == _selectedKlassIndex;
|
||||||
final klassName = GameDataL10n.getKlassName(
|
final klass = _klasses[index];
|
||||||
context,
|
|
||||||
_klasses[index],
|
|
||||||
);
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
isSelected
|
isSelected
|
||||||
@@ -464,14 +504,15 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
klassName,
|
klass.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: isSelected
|
fontWeight: isSelected
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dense: true,
|
subtitle: isSelected ? _buildClassInfo(klass) : null,
|
||||||
|
dense: !isSelected,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onTap: () => setState(() => _selectedKlassIndex = index),
|
onTap: () => setState(() => _selectedKlassIndex = index),
|
||||||
);
|
);
|
||||||
@@ -483,4 +524,35 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 클래스 정보 표시 (Phase 5)
|
||||||
|
Widget _buildClassInfo(ClassTraits klass) {
|
||||||
|
final statMods = <String>[];
|
||||||
|
for (final entry in klass.statModifiers.entries) {
|
||||||
|
final sign = entry.value > 0 ? '+' : '';
|
||||||
|
statMods.add('${_statName(entry.key)} $sign${entry.value}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final passiveDesc = klass.passives.isNotEmpty
|
||||||
|
? klass.passives.map((p) => p.description).join(', ')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (statMods.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
statMods.join(', '),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
if (passiveDesc.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
passiveDesc,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user