feat(phase5): 종족/클래스 시스템 균형 및 UI 통합

- 21개 종족 균형 재설계 (스탯 합계 = 0)
- 18개 클래스 균형 재설계 (스탯 합계 = +3)
- Traits에 raceId, classId 필드 추가
- 저장/불러오기에 종족/클래스 ID 추가
- 캐릭터 생성 UI에서 RaceData/ClassData 사용
- 선택 시 스탯 보정 및 패시브 정보 표시
This commit is contained in:
JiWoong Sul
2025-12-17 17:42:27 +09:00
parent e451703161
commit ec27389e9b
5 changed files with 364 additions and 159 deletions

View File

@@ -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',

View File

@@ -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,
// 지혜형 // 지혜형

View File

@@ -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,
); );
} }
} }

View File

@@ -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,

View File

@@ -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,
),
),
],
);
}
} }