Compare commits
4 Commits
525e231c06
...
7e1936b34f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e1936b34f | ||
|
|
9599a33a8f | ||
|
|
c41d15405f | ||
|
|
b0913a24ff |
@@ -17,31 +17,34 @@ class SkillData {
|
|||||||
name: 'Stack Trace',
|
name: 'Stack Trace',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 1,
|
tier: 1,
|
||||||
mpCost: 50,
|
damageType: DamageType.physical,
|
||||||
|
mpCost: 30, // T1 기본
|
||||||
cooldownMs: 3000,
|
cooldownMs: 3000,
|
||||||
power: 15,
|
power: 15,
|
||||||
damageMultiplier: 2.0,
|
damageMultiplier: 2.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Core Dump - 중급 공격
|
/// Core Dump - 중급 마법 공격 (메모리 덤프)
|
||||||
static const coreDump = Skill(
|
static const coreDump = Skill(
|
||||||
id: 'core_dump',
|
id: 'core_dump',
|
||||||
name: 'Core Dump',
|
name: 'Core Dump',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 180,
|
damageType: DamageType.magical,
|
||||||
|
mpCost: 135, // T3 × 1.5 (mult 3.0)
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 30,
|
power: 30,
|
||||||
damageMultiplier: 3.0,
|
damageMultiplier: 3.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Memory Dump - DOT 공격
|
/// Memory Dump - DOT 마법 공격
|
||||||
static const memoryDump = Skill(
|
static const memoryDump = Skill(
|
||||||
id: 'memory_dump',
|
id: 'memory_dump',
|
||||||
name: 'Memory Dump',
|
name: 'Memory Dump',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 130,
|
damageType: DamageType.magical,
|
||||||
|
mpCost: 110, // T3 DOT (6틱 × 10dmg)
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 0,
|
power: 0,
|
||||||
element: SkillElement.memory,
|
element: SkillElement.memory,
|
||||||
@@ -51,63 +54,68 @@ class SkillData {
|
|||||||
baseDotTickMs: 1000,
|
baseDotTickMs: 1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Kernel Panic - 최강 공격 (자해 데미지)
|
/// Kernel Panic - 최강 마법 공격 (자해 데미지)
|
||||||
static const kernelPanic = Skill(
|
static const kernelPanic = Skill(
|
||||||
id: 'kernel_panic',
|
id: 'kernel_panic',
|
||||||
name: 'Kernel Panic',
|
name: 'Kernel Panic',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 5,
|
tier: 5,
|
||||||
mpCost: 400,
|
damageType: DamageType.magical,
|
||||||
|
mpCost: 300, // T5 × 2.0 (mult 4.0, 자해 보상)
|
||||||
cooldownMs: 45000,
|
cooldownMs: 45000,
|
||||||
power: 60,
|
power: 60,
|
||||||
damageMultiplier: 4.0,
|
damageMultiplier: 4.0,
|
||||||
selfDamagePercent: 0.1,
|
selfDamagePercent: 0.1,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Blue Screen - 강력 공격 (긴 쿨타임)
|
/// Blue Screen - 강력 마법 공격 (긴 쿨타임)
|
||||||
static const blueScreen = Skill(
|
static const blueScreen = Skill(
|
||||||
id: 'blue_screen',
|
id: 'blue_screen',
|
||||||
name: 'Blue Screen',
|
name: 'Blue Screen',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 300,
|
damageType: DamageType.magical,
|
||||||
|
mpCost: 210, // T4 × 1.75 (mult 3.5)
|
||||||
cooldownMs: 30000,
|
cooldownMs: 30000,
|
||||||
power: 50,
|
power: 50,
|
||||||
damageMultiplier: 3.5,
|
damageMultiplier: 3.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Inject Code - 방어 무시 공격
|
/// Inject Code - 방어 무시 마법 공격
|
||||||
static const injectCode = Skill(
|
static const injectCode = Skill(
|
||||||
id: 'inject_code',
|
id: 'inject_code',
|
||||||
name: 'Inject Code',
|
name: 'Inject Code',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 200,
|
damageType: DamageType.magical,
|
||||||
|
mpCost: 190, // T4 × 1.25 × 방감 1.25
|
||||||
cooldownMs: 18000,
|
cooldownMs: 18000,
|
||||||
power: 35,
|
power: 35,
|
||||||
damageMultiplier: 2.5,
|
damageMultiplier: 2.5,
|
||||||
targetDefReduction: 0.5,
|
targetDefReduction: 0.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Spawn Shell - 3연타 공격
|
/// Spawn Shell - 3연타 물리 공격
|
||||||
static const spawnShell = Skill(
|
static const spawnShell = Skill(
|
||||||
id: 'spawn_shell',
|
id: 'spawn_shell',
|
||||||
name: 'Spawn Shell',
|
name: 'Spawn Shell',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 150,
|
damageType: DamageType.physical,
|
||||||
|
mpCost: 120, // T3 × 3타 보정 1.3
|
||||||
cooldownMs: 10000,
|
cooldownMs: 10000,
|
||||||
power: 12,
|
power: 12,
|
||||||
damageMultiplier: 2.0,
|
damageMultiplier: 2.0,
|
||||||
hitCount: 3,
|
hitCount: 3,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Thread Pool - 5연타 공격
|
/// Thread Pool - 5연타 물리 공격
|
||||||
static const threadPool = Skill(
|
static const threadPool = Skill(
|
||||||
id: 'thread_pool',
|
id: 'thread_pool',
|
||||||
name: 'Thread Pool',
|
name: 'Thread Pool',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 230,
|
mpCost: 230,
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 10,
|
power: 10,
|
||||||
@@ -115,12 +123,13 @@ class SkillData {
|
|||||||
hitCount: 5,
|
hitCount: 5,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Exfiltrate Data - HP 흡수 공격
|
/// Exfiltrate Data - HP 흡수 하이브리드 공격
|
||||||
static const exfiltrateData = Skill(
|
static const exfiltrateData = Skill(
|
||||||
id: 'exfiltrate_data',
|
id: 'exfiltrate_data',
|
||||||
name: 'Exfiltrate Data',
|
name: 'Exfiltrate Data',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.hybrid,
|
||||||
mpCost: 180,
|
mpCost: 180,
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 25,
|
power: 25,
|
||||||
@@ -128,12 +137,13 @@ class SkillData {
|
|||||||
lifestealPercent: 0.3,
|
lifestealPercent: 0.3,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Fuzzing - 랜덤 데미지 공격
|
/// Fuzzing - 랜덤 데미지 하이브리드 공격
|
||||||
static const fuzzing = Skill(
|
static const fuzzing = Skill(
|
||||||
id: 'fuzzing',
|
id: 'fuzzing',
|
||||||
name: 'Fuzzing',
|
name: 'Fuzzing',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
|
damageType: DamageType.hybrid,
|
||||||
mpCost: 100,
|
mpCost: 100,
|
||||||
cooldownMs: 8000,
|
cooldownMs: 8000,
|
||||||
power: 20,
|
power: 20,
|
||||||
@@ -141,12 +151,13 @@ class SkillData {
|
|||||||
element: SkillElement.chaos,
|
element: SkillElement.chaos,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Chaos Monkey - 랜덤 효과 공격
|
/// Chaos Monkey - 랜덤 효과 하이브리드 공격
|
||||||
static const chaosMonkey = Skill(
|
static const chaosMonkey = Skill(
|
||||||
id: 'chaos_monkey',
|
id: 'chaos_monkey',
|
||||||
name: 'Chaos Monkey',
|
name: 'Chaos Monkey',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 5,
|
tier: 5,
|
||||||
|
damageType: DamageType.hybrid,
|
||||||
mpCost: 250,
|
mpCost: 250,
|
||||||
cooldownMs: 25000,
|
cooldownMs: 25000,
|
||||||
power: 40,
|
power: 40,
|
||||||
@@ -154,12 +165,13 @@ class SkillData {
|
|||||||
element: SkillElement.chaos,
|
element: SkillElement.chaos,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Saga Pattern - 3회 연속 공격
|
/// Saga Pattern - 3회 연속 물리 공격
|
||||||
static const sagaPattern = Skill(
|
static const sagaPattern = Skill(
|
||||||
id: 'saga_pattern',
|
id: 'saga_pattern',
|
||||||
name: 'Saga Pattern',
|
name: 'Saga Pattern',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 280,
|
mpCost: 280,
|
||||||
cooldownMs: 20000,
|
cooldownMs: 20000,
|
||||||
power: 18,
|
power: 18,
|
||||||
@@ -167,12 +179,13 @@ class SkillData {
|
|||||||
hitCount: 3,
|
hitCount: 3,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Event Store - 차지 공격 (DOT로 표현)
|
/// Event Store - 차지 마법 공격 (DOT로 표현)
|
||||||
static const eventStore = Skill(
|
static const eventStore = Skill(
|
||||||
id: 'event_store',
|
id: 'event_store',
|
||||||
name: 'Event Store',
|
name: 'Event Store',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.magical,
|
||||||
mpCost: 200,
|
mpCost: 200,
|
||||||
cooldownMs: 18000,
|
cooldownMs: 18000,
|
||||||
power: 0,
|
power: 0,
|
||||||
@@ -183,24 +196,26 @@ class SkillData {
|
|||||||
baseDotTickMs: 3000,
|
baseDotTickMs: 3000,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Auto Scale - HP비례 공격
|
/// Auto Scale - HP비례 하이브리드 공격
|
||||||
static const autoScale = Skill(
|
static const autoScale = Skill(
|
||||||
id: 'auto_scale',
|
id: 'auto_scale',
|
||||||
name: 'Auto Scale',
|
name: 'Auto Scale',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.hybrid,
|
||||||
mpCost: 230,
|
mpCost: 230,
|
||||||
cooldownMs: 20000,
|
cooldownMs: 20000,
|
||||||
power: 30,
|
power: 30,
|
||||||
damageMultiplier: 2.5,
|
damageMultiplier: 2.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Disassemble - 방어감소+공격
|
/// Disassemble - 방어감소 + 물리 공격
|
||||||
static const disassemble = Skill(
|
static const disassemble = Skill(
|
||||||
id: 'disassemble',
|
id: 'disassemble',
|
||||||
name: 'Disassemble',
|
name: 'Disassemble',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 150,
|
mpCost: 150,
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 22,
|
power: 22,
|
||||||
@@ -208,36 +223,39 @@ class SkillData {
|
|||||||
targetDefReduction: 0.3,
|
targetDefReduction: 0.3,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Decompile - 약점 공격 (높은 크리)
|
/// Decompile - 약점 물리 공격 (높은 크리)
|
||||||
static const decompile = Skill(
|
static const decompile = Skill(
|
||||||
id: 'decompile',
|
id: 'decompile',
|
||||||
name: 'Decompile',
|
name: 'Decompile',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 130,
|
mpCost: 130,
|
||||||
cooldownMs: 10000,
|
cooldownMs: 10000,
|
||||||
power: 20,
|
power: 20,
|
||||||
damageMultiplier: 2.2,
|
damageMultiplier: 2.2,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Canary Release - 테스트 공격
|
/// Canary Release - 테스트 물리 공격
|
||||||
static const canaryRelease = Skill(
|
static const canaryRelease = Skill(
|
||||||
id: 'canary_release',
|
id: 'canary_release',
|
||||||
name: 'Canary Release',
|
name: 'Canary Release',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 1,
|
tier: 1,
|
||||||
mpCost: 80,
|
damageType: DamageType.physical,
|
||||||
|
mpCost: 35, // T1 기본
|
||||||
cooldownMs: 6000,
|
cooldownMs: 6000,
|
||||||
power: 12,
|
power: 12,
|
||||||
damageMultiplier: 2.0,
|
damageMultiplier: 2.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// A/B Test - 이중 공격
|
/// A/B Test - 이중 물리 공격
|
||||||
static const abTest = Skill(
|
static const abTest = Skill(
|
||||||
id: 'ab_test',
|
id: 'ab_test',
|
||||||
name: 'A/B Test',
|
name: 'A/B Test',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 180,
|
mpCost: 180,
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 15,
|
power: 15,
|
||||||
@@ -245,12 +263,13 @@ class SkillData {
|
|||||||
hitCount: 2,
|
hitCount: 2,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Pivot Network - 네트워크 공격
|
/// Pivot Network - 네트워크 마법 공격
|
||||||
static const pivotNetwork = Skill(
|
static const pivotNetwork = Skill(
|
||||||
id: 'pivot_network',
|
id: 'pivot_network',
|
||||||
name: 'Pivot Network',
|
name: 'Pivot Network',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
|
damageType: DamageType.magical,
|
||||||
mpCost: 150,
|
mpCost: 150,
|
||||||
cooldownMs: 10000,
|
cooldownMs: 10000,
|
||||||
power: 25,
|
power: 25,
|
||||||
@@ -258,24 +277,26 @@ class SkillData {
|
|||||||
element: SkillElement.network,
|
element: SkillElement.network,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Async Await - 딜레이 공격
|
/// Async Await - 딜레이 마법 공격
|
||||||
static const asyncAwait = Skill(
|
static const asyncAwait = Skill(
|
||||||
id: 'async_await',
|
id: 'async_await',
|
||||||
name: 'Async Await',
|
name: 'Async Await',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.magical,
|
||||||
mpCost: 180,
|
mpCost: 180,
|
||||||
cooldownMs: 14000,
|
cooldownMs: 14000,
|
||||||
power: 35,
|
power: 35,
|
||||||
damageMultiplier: 3.0,
|
damageMultiplier: 3.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Event Source - DOT 공격
|
/// Event Source - DOT 마법 공격
|
||||||
static const eventSource = Skill(
|
static const eventSource = Skill(
|
||||||
id: 'event_source',
|
id: 'event_source',
|
||||||
name: 'Event Source',
|
name: 'Event Source',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
|
damageType: DamageType.magical,
|
||||||
mpCost: 150,
|
mpCost: 150,
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 0,
|
power: 0,
|
||||||
@@ -286,12 +307,13 @@ class SkillData {
|
|||||||
baseDotTickMs: 800,
|
baseDotTickMs: 800,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// CQRS Split - 분리 공격
|
/// CQRS Split - 분리 물리 공격
|
||||||
static const cqrsSplit = Skill(
|
static const cqrsSplit = Skill(
|
||||||
id: 'cqrs_split',
|
id: 'cqrs_split',
|
||||||
name: 'CQRS Split',
|
name: 'CQRS Split',
|
||||||
type: SkillType.attack,
|
type: SkillType.attack,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
|
damageType: DamageType.physical,
|
||||||
mpCost: 200,
|
mpCost: 200,
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 20,
|
power: 20,
|
||||||
@@ -464,190 +486,190 @@ class SkillData {
|
|||||||
name: 'Debug Mode',
|
name: 'Debug Mode',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 100,
|
mpCost: 140, // 100 → 140
|
||||||
cooldownMs: 20000,
|
cooldownMs: 20000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'debug_mode_buff',
|
id: 'debug_mode_buff',
|
||||||
name: 'Debug Mode',
|
name: 'Debug Mode',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
atkModifier: 0.25,
|
atkModifier: 0.15, // 25% → 15%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Safe Mode - DEF +30%
|
/// Safe Mode - DEF 증가
|
||||||
static const safeMode = Skill(
|
static const safeMode = Skill(
|
||||||
id: 'safe_mode',
|
id: 'safe_mode',
|
||||||
name: 'Safe Mode',
|
name: 'Safe Mode',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 130,
|
mpCost: 160, // 130 → 160
|
||||||
cooldownMs: 25000,
|
cooldownMs: 25000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'safe_mode_buff',
|
id: 'safe_mode_buff',
|
||||||
name: 'Safe Mode',
|
name: 'Safe Mode',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
defModifier: 0.3,
|
defModifier: 0.18, // 30% → 18%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Memory Optimization - 전스탯 +10%
|
/// Memory Optimization - 전스탯 증가
|
||||||
static const memoryOptimization = Skill(
|
static const memoryOptimization = Skill(
|
||||||
id: 'memory_optimization',
|
id: 'memory_optimization',
|
||||||
name: 'Memory Optimization',
|
name: 'Memory Optimization',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 150,
|
mpCost: 180, // 150 → 180
|
||||||
cooldownMs: 30000,
|
cooldownMs: 30000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'memory_optimization_buff',
|
id: 'memory_optimization_buff',
|
||||||
name: 'Optimized',
|
name: 'Optimized',
|
||||||
durationMs: 15000,
|
durationMs: 12000, // 15초 → 12초
|
||||||
atkModifier: 0.1,
|
atkModifier: 0.07, // 10% → 7%
|
||||||
defModifier: 0.1,
|
defModifier: 0.07, // 10% → 7%
|
||||||
criRateModifier: 0.05,
|
criRateModifier: 0.03, // 5% → 3%
|
||||||
evasionModifier: 0.05,
|
evasionModifier: 0.03, // 5% → 3%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Breakpoint - 다음 공격 크리티컬
|
/// Breakpoint - 크리율 증가 (하이브리드 밸런스)
|
||||||
static const breakpoint = Skill(
|
static const breakpoint = Skill(
|
||||||
id: 'breakpoint',
|
id: 'breakpoint',
|
||||||
name: 'Breakpoint',
|
name: 'Breakpoint',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 80,
|
mpCost: 120, // 70 → 120
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'breakpoint_buff',
|
id: 'breakpoint_buff',
|
||||||
name: 'Breakpoint',
|
name: 'Breakpoint',
|
||||||
durationMs: 5000,
|
durationMs: 5000,
|
||||||
criRateModifier: 0.5,
|
criRateModifier: 0.20, // 35% → 20%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Watch Variable - 회피율 +20%
|
/// Watch Variable - 회피율 증가
|
||||||
static const watchVariable = Skill(
|
static const watchVariable = Skill(
|
||||||
id: 'watch_variable',
|
id: 'watch_variable',
|
||||||
name: 'Watch Variable',
|
name: 'Watch Variable',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 90,
|
mpCost: 120, // 90 → 120
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'watch_variable_buff',
|
id: 'watch_variable_buff',
|
||||||
name: 'Watching',
|
name: 'Watching',
|
||||||
durationMs: 8000,
|
durationMs: 6000, // 8초 → 6초
|
||||||
evasionModifier: 0.2,
|
evasionModifier: 0.12, // 20% → 12%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Step Into - 공격속도 +30% (ATK 버프로 표현)
|
/// Step Into - 공격속도 증가 (ATK 버프로 표현)
|
||||||
static const stepInto = Skill(
|
static const stepInto = Skill(
|
||||||
id: 'step_into',
|
id: 'step_into',
|
||||||
name: 'Step Into',
|
name: 'Step Into',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 1,
|
tier: 1,
|
||||||
mpCost: 80,
|
mpCost: 100, // 80 → 100
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'step_into_buff',
|
id: 'step_into_buff',
|
||||||
name: 'Step Into',
|
name: 'Step Into',
|
||||||
durationMs: 6000,
|
durationMs: 6000,
|
||||||
atkModifier: 0.2,
|
atkModifier: 0.12, // 20% → 12%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Profile Run - 크리율 +30%
|
/// Profile Run - 크리율 증가
|
||||||
static const profileRun = Skill(
|
static const profileRun = Skill(
|
||||||
id: 'profile_run',
|
id: 'profile_run',
|
||||||
name: 'Profile Run',
|
name: 'Profile Run',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 100,
|
mpCost: 150, // 100 → 150
|
||||||
cooldownMs: 18000,
|
cooldownMs: 18000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'profile_run_buff',
|
id: 'profile_run_buff',
|
||||||
name: 'Profiling',
|
name: 'Profiling',
|
||||||
durationMs: 8000,
|
durationMs: 6000, // 8초 → 6초
|
||||||
criRateModifier: 0.3,
|
criRateModifier: 0.18, // 30% → 18%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Benchmark - 데미지 +40%
|
/// Benchmark - 데미지 증가
|
||||||
static const benchmark = Skill(
|
static const benchmark = Skill(
|
||||||
id: 'benchmark',
|
id: 'benchmark',
|
||||||
name: 'Benchmark',
|
name: 'Benchmark',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 130,
|
mpCost: 180, // 130 → 180
|
||||||
cooldownMs: 20000,
|
cooldownMs: 20000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'benchmark_buff',
|
id: 'benchmark_buff',
|
||||||
name: 'Benchmarking',
|
name: 'Benchmarking',
|
||||||
durationMs: 5000,
|
durationMs: 5000,
|
||||||
atkModifier: 0.4,
|
atkModifier: 0.25, // 40% → 25%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Elevate Privilege - 전스탯 +20%
|
/// Elevate Privilege - 전스탯 증가 (하이브리드 밸런스)
|
||||||
static const elevatePrivilege = Skill(
|
static const elevatePrivilege = Skill(
|
||||||
id: 'elevate_privilege',
|
id: 'elevate_privilege',
|
||||||
name: 'Elevate Privilege',
|
name: 'Elevate Privilege',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 5,
|
tier: 5,
|
||||||
mpCost: 200,
|
mpCost: 280, // 220 → 280
|
||||||
cooldownMs: 35000,
|
cooldownMs: 35000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'elevate_privilege_buff',
|
id: 'elevate_privilege_buff',
|
||||||
name: 'Elevated',
|
name: 'Elevated',
|
||||||
durationMs: 8000,
|
durationMs: 6000, // 8초 → 6초
|
||||||
atkModifier: 0.2,
|
atkModifier: 0.08, // 12% → 8%
|
||||||
defModifier: 0.2,
|
defModifier: 0.08, // 12% → 8%
|
||||||
criRateModifier: 0.1,
|
criRateModifier: 0.04, // 6% → 4%
|
||||||
evasionModifier: 0.1,
|
evasionModifier: 0.04, // 6% → 4%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Scale Up - ATK +50%
|
/// Scale Up - ATK 증가 (하이브리드 밸런스)
|
||||||
static const scaleUp = Skill(
|
static const scaleUp = Skill(
|
||||||
id: 'scale_up',
|
id: 'scale_up',
|
||||||
name: 'Scale Up',
|
name: 'Scale Up',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 5,
|
tier: 5,
|
||||||
mpCost: 180,
|
mpCost: 280, // 220 → 280
|
||||||
cooldownMs: 30000,
|
cooldownMs: 30000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'scale_up_buff',
|
id: 'scale_up_buff',
|
||||||
name: 'Scaled Up',
|
name: 'Scaled Up',
|
||||||
durationMs: 6000,
|
durationMs: 5000, // 6초 → 5초
|
||||||
atkModifier: 0.5,
|
atkModifier: 0.20, // 30% → 20%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Failover - 치명타 방지 (DEF 증가로 표현)
|
/// Failover - DEF 증가 (하이브리드 밸런스)
|
||||||
static const failover = Skill(
|
static const failover = Skill(
|
||||||
id: 'failover',
|
id: 'failover',
|
||||||
name: 'Failover',
|
name: 'Failover',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 5,
|
tier: 5,
|
||||||
mpCost: 150,
|
mpCost: 350, // 300 → 350
|
||||||
cooldownMs: 45000,
|
cooldownMs: 45000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'failover_buff',
|
id: 'failover_buff',
|
||||||
name: 'Failover Ready',
|
name: 'Failover Ready',
|
||||||
durationMs: 30000,
|
durationMs: 15000, // 20초 → 15초
|
||||||
defModifier: 0.4,
|
defModifier: 0.18, // 25% → 18%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -657,14 +679,14 @@ class SkillData {
|
|||||||
name: 'Containerize',
|
name: 'Containerize',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 3,
|
tier: 3,
|
||||||
mpCost: 130,
|
mpCost: 170, // 130 → 170
|
||||||
cooldownMs: 20000,
|
cooldownMs: 20000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'containerize_buff',
|
id: 'containerize_buff',
|
||||||
name: 'Containerized',
|
name: 'Containerized',
|
||||||
durationMs: 12000,
|
durationMs: 10000, // 12초 → 10초
|
||||||
defModifier: 0.35,
|
defModifier: 0.20, // 35% → 20%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -674,16 +696,16 @@ class SkillData {
|
|||||||
name: 'Orchestrate',
|
name: 'Orchestrate',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 230,
|
mpCost: 260, // 230 → 260
|
||||||
cooldownMs: 40000,
|
cooldownMs: 40000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'orchestrate_buff',
|
id: 'orchestrate_buff',
|
||||||
name: 'Orchestrated',
|
name: 'Orchestrated',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
atkModifier: 0.15,
|
atkModifier: 0.10, // 15% → 10%
|
||||||
defModifier: 0.15,
|
defModifier: 0.10, // 15% → 10%
|
||||||
criRateModifier: 0.1,
|
criRateModifier: 0.06, // 10% → 6%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -693,14 +715,14 @@ class SkillData {
|
|||||||
name: 'Promise Resolve',
|
name: 'Promise Resolve',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 100,
|
mpCost: 130, // 100 → 130
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'promise_resolve_buff',
|
id: 'promise_resolve_buff',
|
||||||
name: 'Resolved',
|
name: 'Resolved',
|
||||||
durationMs: 8000,
|
durationMs: 6000, // 8초 → 6초
|
||||||
atkModifier: 0.25,
|
atkModifier: 0.15, // 25% → 15%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -710,32 +732,32 @@ class SkillData {
|
|||||||
name: 'Feature Toggle',
|
name: 'Feature Toggle',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 1,
|
tier: 1,
|
||||||
mpCost: 80,
|
mpCost: 110, // 80 → 110
|
||||||
cooldownMs: 10000,
|
cooldownMs: 10000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'feature_toggle_buff',
|
id: 'feature_toggle_buff',
|
||||||
name: 'Toggled',
|
name: 'Toggled',
|
||||||
durationMs: 12000,
|
durationMs: 10000, // 12초 → 10초
|
||||||
atkModifier: 0.15,
|
atkModifier: 0.10, // 15% → 10%
|
||||||
defModifier: 0.15,
|
defModifier: 0.10, // 15% → 10%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Dark Launch - 은신 (회피+100% → 50%로 조정)
|
/// Dark Launch - 은신 (하이브리드 밸런스)
|
||||||
static const darkLaunch = Skill(
|
static const darkLaunch = Skill(
|
||||||
id: 'dark_launch',
|
id: 'dark_launch',
|
||||||
name: 'Dark Launch',
|
name: 'Dark Launch',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 180,
|
mpCost: 180, // 150 → 180
|
||||||
cooldownMs: 30000,
|
cooldownMs: 30000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'dark_launch_buff',
|
id: 'dark_launch_buff',
|
||||||
name: 'Hidden',
|
name: 'Hidden',
|
||||||
durationMs: 3000,
|
durationMs: 3000,
|
||||||
evasionModifier: 0.5,
|
evasionModifier: 0.20, // 30% → 20%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -745,14 +767,14 @@ class SkillData {
|
|||||||
name: 'Static Analysis',
|
name: 'Static Analysis',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 1,
|
tier: 1,
|
||||||
mpCost: 80,
|
mpCost: 100, // 80 → 100
|
||||||
cooldownMs: 12000,
|
cooldownMs: 12000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'static_analysis_buff',
|
id: 'static_analysis_buff',
|
||||||
name: 'Analyzed',
|
name: 'Analyzed',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
criRateModifier: 0.15,
|
criRateModifier: 0.10, // 15% → 10%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -762,15 +784,15 @@ class SkillData {
|
|||||||
name: 'Dynamic Analysis',
|
name: 'Dynamic Analysis',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 130,
|
mpCost: 170, // 130 → 170
|
||||||
cooldownMs: 18000,
|
cooldownMs: 18000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'dynamic_analysis_buff',
|
id: 'dynamic_analysis_buff',
|
||||||
name: 'Dynamic',
|
name: 'Dynamic',
|
||||||
durationMs: 8000,
|
durationMs: 6000, // 8초 → 6초
|
||||||
criRateModifier: 0.15,
|
criRateModifier: 0.10, // 15% → 10%
|
||||||
evasionModifier: 0.15,
|
evasionModifier: 0.10, // 15% → 10%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -780,14 +802,14 @@ class SkillData {
|
|||||||
name: 'Reverse Engineer',
|
name: 'Reverse Engineer',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 150,
|
mpCost: 190, // 150 → 190
|
||||||
cooldownMs: 25000,
|
cooldownMs: 25000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'reverse_engineer_buff',
|
id: 'reverse_engineer_buff',
|
||||||
name: 'Reversed',
|
name: 'Reversed',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
atkModifier: 0.3,
|
atkModifier: 0.18, // 30% → 18%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -797,14 +819,14 @@ class SkillData {
|
|||||||
name: 'Cover Tracks',
|
name: 'Cover Tracks',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 100,
|
mpCost: 130, // 100 → 130
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'cover_tracks_buff',
|
id: 'cover_tracks_buff',
|
||||||
name: 'Covered',
|
name: 'Covered',
|
||||||
durationMs: 5000,
|
durationMs: 5000,
|
||||||
evasionModifier: 0.25,
|
evasionModifier: 0.15, // 25% → 15%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -814,14 +836,14 @@ class SkillData {
|
|||||||
name: 'Deploy',
|
name: 'Deploy',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 4,
|
tier: 4,
|
||||||
mpCost: 180,
|
mpCost: 220, // 180 → 220
|
||||||
cooldownMs: 30000,
|
cooldownMs: 30000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'deploy_buff',
|
id: 'deploy_buff',
|
||||||
name: 'Deployed',
|
name: 'Deployed',
|
||||||
durationMs: 12000,
|
durationMs: 10000, // 12초 → 10초
|
||||||
atkModifier: 0.35,
|
atkModifier: 0.22, // 35% → 22%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -831,14 +853,14 @@ class SkillData {
|
|||||||
name: 'Retry Logic',
|
name: 'Retry Logic',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 100,
|
mpCost: 130, // 100 → 130
|
||||||
cooldownMs: 15000,
|
cooldownMs: 15000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'retry_logic_buff',
|
id: 'retry_logic_buff',
|
||||||
name: 'Retrying',
|
name: 'Retrying',
|
||||||
durationMs: 10000,
|
durationMs: 8000, // 10초 → 8초
|
||||||
criRateModifier: 0.2,
|
criRateModifier: 0.12, // 20% → 12%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -848,15 +870,15 @@ class SkillData {
|
|||||||
name: 'State Machine',
|
name: 'State Machine',
|
||||||
type: SkillType.buff,
|
type: SkillType.buff,
|
||||||
tier: 2,
|
tier: 2,
|
||||||
mpCost: 150,
|
mpCost: 170, // 150 → 170
|
||||||
cooldownMs: 25000,
|
cooldownMs: 25000,
|
||||||
power: 0,
|
power: 0,
|
||||||
buff: BuffEffect(
|
buff: BuffEffect(
|
||||||
id: 'state_machine_buff',
|
id: 'state_machine_buff',
|
||||||
name: 'State Active',
|
name: 'State Active',
|
||||||
durationMs: 15000,
|
durationMs: 12000, // 15초 → 12초
|
||||||
atkModifier: 0.1,
|
atkModifier: 0.08, // 10% → 8%
|
||||||
defModifier: 0.1,
|
defModifier: 0.08, // 10% → 8%
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ class ActProgressionService {
|
|||||||
level: bossLevel,
|
level: bossLevel,
|
||||||
atk: (bossStats.atk * 1.5).round(), // Boss 보정 (1.5배)
|
atk: (bossStats.atk * 1.5).round(), // Boss 보정 (1.5배)
|
||||||
def: (bossStats.def * 1.5).round(),
|
def: (bossStats.def * 1.5).round(),
|
||||||
|
magDef: (bossStats.def * 1.8).round(), // 보스 마법 방어 (물리 대비 1.2배)
|
||||||
hpMax: (bossStats.hp * 2.0).round(), // HP는 2.0배 (보스다운 전투 시간)
|
hpMax: (bossStats.hp * 2.0).round(), // HP는 2.0배 (보스다운 전투 시간)
|
||||||
hpCurrent: (bossStats.hp * 2.0).round(),
|
hpCurrent: (bossStats.hp * 2.0).round(),
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ class ItemService {
|
|||||||
/// - 느린 무기 (1500ms): atk × 1.4
|
/// - 느린 무기 (1500ms): atk × 1.4
|
||||||
/// - 기본 무기 (1000ms): atk × 1.0
|
/// - 기본 무기 (1000ms): atk × 1.0
|
||||||
/// - 빠른 무기 (600ms): atk × 0.7
|
/// - 빠른 무기 (600ms): atk × 0.7
|
||||||
|
///
|
||||||
|
/// 마법 무기 확률: 30% (magAtk 부여)
|
||||||
ItemStats _generateWeaponStats(int baseValue, ItemRarity rarity) {
|
ItemStats _generateWeaponStats(int baseValue, ItemRarity rarity) {
|
||||||
final criBonus = rarity.index >= ItemRarity.rare.index
|
final criBonus = rarity.index >= ItemRarity.rare.index
|
||||||
? 0.02 + rarity.index * 0.01
|
? 0.02 + rarity.index * 0.01
|
||||||
@@ -115,39 +117,76 @@ class ItemService {
|
|||||||
: 0.0;
|
: 0.0;
|
||||||
|
|
||||||
// 공속 결정 (600ms ~ 1500ms 범위)
|
// 공속 결정 (600ms ~ 1500ms 범위)
|
||||||
// 희귀도가 높을수록 공속 변동 폭 증가
|
final speedVariance = 300 + rarity.index * 100;
|
||||||
final speedVariance =
|
|
||||||
300 + rarity.index * 100; // Common: 300, Legendary: 700
|
|
||||||
final speedOffset = rng.nextInt(speedVariance * 2) - speedVariance;
|
final speedOffset = rng.nextInt(speedVariance * 2) - speedVariance;
|
||||||
final attackSpeed = (1000 + speedOffset).clamp(600, 1500);
|
final attackSpeed = (1000 + speedOffset).clamp(600, 1500);
|
||||||
|
|
||||||
// 공속-데미지 역비례 계산
|
// 공속-데미지 역비례 계산
|
||||||
// 기준: 1000ms = 1.0x, 600ms = 0.7x, 1500ms = 1.4x
|
|
||||||
final speedMultiplier = 0.3 + (attackSpeed / 1000) * 0.7;
|
final speedMultiplier = 0.3 + (attackSpeed / 1000) * 0.7;
|
||||||
final adjustedAtk = (baseValue * speedMultiplier).round();
|
final adjustedAtk = (baseValue * speedMultiplier).round();
|
||||||
|
|
||||||
|
// 마법 무기 여부 (30% 확률)
|
||||||
|
final isMagicWeapon = rng.nextInt(100) < 30;
|
||||||
|
final magAtk = isMagicWeapon ? (adjustedAtk * 0.8).round() : 0;
|
||||||
|
|
||||||
|
// 능력치 보너스 (Rare 이상)
|
||||||
|
final strBonus = rarity.index >= ItemRarity.rare.index && !isMagicWeapon
|
||||||
|
? rarity.index
|
||||||
|
: 0;
|
||||||
|
final intBonus = rarity.index >= ItemRarity.rare.index && isMagicWeapon
|
||||||
|
? rarity.index
|
||||||
|
: 0;
|
||||||
|
|
||||||
return ItemStats(
|
return ItemStats(
|
||||||
atk: adjustedAtk,
|
atk: adjustedAtk,
|
||||||
|
magAtk: magAtk,
|
||||||
criRate: criBonus,
|
criRate: criBonus,
|
||||||
parryRate: parryBonus,
|
parryRate: parryBonus,
|
||||||
attackSpeed: attackSpeed,
|
attackSpeed: attackSpeed,
|
||||||
|
strBonus: strBonus,
|
||||||
|
intBonus: intBonus,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 방패 스탯 생성
|
/// 방패 스탯 생성
|
||||||
///
|
///
|
||||||
/// DEF 배율 조정 (v2): 방패 DEF를 0.15배로 축소
|
/// DEF 배율 조정 (v2): 방패 DEF를 0.15배로 축소
|
||||||
|
/// 마법 방어(magDef), CON 보너스 추가
|
||||||
ItemStats _generateShieldStats(int baseValue, ItemRarity rarity) {
|
ItemStats _generateShieldStats(int baseValue, ItemRarity rarity) {
|
||||||
final blockBonus = 0.05 + rarity.index * 0.02;
|
final blockBonus = 0.05 + rarity.index * 0.02;
|
||||||
final def = (baseValue * 0.15).round();
|
final def = (baseValue * 0.15).round();
|
||||||
|
|
||||||
return ItemStats(def: def, blockRate: blockBonus);
|
// 마법 방어 (50% 확률)
|
||||||
|
final hasMagDef = rng.nextInt(100) < 50;
|
||||||
|
final magDef = hasMagDef ? (def * 0.7).round() : 0;
|
||||||
|
|
||||||
|
// HP 보너스 (Uncommon 이상)
|
||||||
|
final hpBonus =
|
||||||
|
rarity.index >= ItemRarity.uncommon.index ? baseValue ~/ 3 : 0;
|
||||||
|
|
||||||
|
// CON 보너스 (Rare 이상)
|
||||||
|
final conBonus = rarity.index >= ItemRarity.rare.index ? rarity.index : 0;
|
||||||
|
|
||||||
|
return ItemStats(
|
||||||
|
def: def,
|
||||||
|
magDef: magDef,
|
||||||
|
blockRate: blockBonus,
|
||||||
|
hpBonus: hpBonus,
|
||||||
|
conBonus: conBonus,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 방어구 스탯 생성
|
/// 방어구 스탯 생성
|
||||||
///
|
///
|
||||||
/// DEF 배율 조정 (v2): 9개 방어구 합산 DEF ≈ 무기 ATK × 1.6
|
/// DEF 배율 조정 (v2): 9개 방어구 합산 DEF ≈ 무기 ATK × 1.6
|
||||||
/// 기존 배율(합계 8.0)에서 대폭 축소하여 일반 공격 데미지 정상화
|
/// 슬롯별 특화 보너스 추가:
|
||||||
|
/// - 투구(helm): INT, WIS 보너스
|
||||||
|
/// - 갑옷(hauberk): HP, CON 보너스
|
||||||
|
/// - 상완갑/전완갑(brassairts, vambraces): STR, DEX 보너스
|
||||||
|
/// - 건틀릿(gauntlets): STR, 크리티컬 보너스
|
||||||
|
/// - 갬비슨(gambeson): HP, MP 보너스
|
||||||
|
/// - 허벅지갑/정강이갑(cuisses, greaves): CON, 회피 보너스
|
||||||
|
/// - 철제부츠(sollerets): DEX, 회피 보너스
|
||||||
ItemStats _generateArmorStats(
|
ItemStats _generateArmorStats(
|
||||||
int baseValue,
|
int baseValue,
|
||||||
ItemRarity rarity,
|
ItemRarity rarity,
|
||||||
@@ -155,7 +194,7 @@ class ItemService {
|
|||||||
) {
|
) {
|
||||||
// 슬롯별 방어력 가중치 (총합 ~1.6으로 축소)
|
// 슬롯별 방어력 가중치 (총합 ~1.6으로 축소)
|
||||||
final defMultiplier = switch (slot) {
|
final defMultiplier = switch (slot) {
|
||||||
EquipmentSlot.hauberk => 0.30, // 갑옷류 최고
|
EquipmentSlot.hauberk => 0.30,
|
||||||
EquipmentSlot.helm => 0.25,
|
EquipmentSlot.helm => 0.25,
|
||||||
EquipmentSlot.gambeson => 0.20,
|
EquipmentSlot.gambeson => 0.20,
|
||||||
EquipmentSlot.cuisses => 0.18,
|
EquipmentSlot.cuisses => 0.18,
|
||||||
@@ -169,11 +208,88 @@ class ItemService {
|
|||||||
|
|
||||||
final def = (baseValue * defMultiplier).round();
|
final def = (baseValue * defMultiplier).round();
|
||||||
|
|
||||||
// 희귀도에 따른 추가 보너스
|
// 슬롯별 특화 보너스 계산
|
||||||
final hpBonus = rarity.index >= ItemRarity.rare.index ? baseValue ~/ 2 : 0;
|
return switch (slot) {
|
||||||
final evasionBonus = rarity.index >= ItemRarity.epic.index ? 0.01 : 0.0;
|
// 투구: 지능/지혜 보너스, 마법 방어
|
||||||
|
EquipmentSlot.helm => ItemStats(
|
||||||
|
def: def,
|
||||||
|
magDef: rarity.index >= ItemRarity.uncommon.index ? def ~/ 2 : 0,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 3 : 0,
|
||||||
|
intBonus: rarity.index >= ItemRarity.epic.index ? rarity.index : 0,
|
||||||
|
wisBonus: rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0,
|
||||||
|
),
|
||||||
|
|
||||||
return ItemStats(def: def, hpBonus: hpBonus, evasion: evasionBonus);
|
// 갑옷: HP, CON 보너스 (주력 방어구)
|
||||||
|
EquipmentSlot.hauberk => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.uncommon.index ? baseValue : 0,
|
||||||
|
conBonus: rarity.index >= ItemRarity.rare.index ? rarity.index : 0,
|
||||||
|
strBonus: rarity.index >= ItemRarity.epic.index ? rarity.index - 1 : 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 상완갑: STR 보너스
|
||||||
|
EquipmentSlot.brassairts => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 4 : 0,
|
||||||
|
strBonus: rarity.index >= ItemRarity.uncommon.index ? rarity.index : 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 전완갑: DEX 보너스
|
||||||
|
EquipmentSlot.vambraces => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 4 : 0,
|
||||||
|
dexBonus: rarity.index >= ItemRarity.uncommon.index ? rarity.index : 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 건틀릿: STR, 크리티컬 보너스
|
||||||
|
EquipmentSlot.gauntlets => ItemStats(
|
||||||
|
def: def,
|
||||||
|
criRate: rarity.index >= ItemRarity.rare.index
|
||||||
|
? 0.01 + rarity.index * 0.005
|
||||||
|
: 0.0,
|
||||||
|
strBonus: rarity.index >= ItemRarity.uncommon.index ? rarity.index : 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 갬비슨: HP, MP 보너스
|
||||||
|
EquipmentSlot.gambeson => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.uncommon.index ? baseValue ~/ 2 : 0,
|
||||||
|
mpBonus: rarity.index >= ItemRarity.uncommon.index ? baseValue ~/ 3 : 0,
|
||||||
|
wisBonus: rarity.index >= ItemRarity.epic.index ? rarity.index - 1 : 0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 허벅지갑: CON, 회피 보너스
|
||||||
|
EquipmentSlot.cuisses => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 3 : 0,
|
||||||
|
conBonus: rarity.index >= ItemRarity.uncommon.index ? rarity.index : 0,
|
||||||
|
evasion: rarity.index >= ItemRarity.epic.index ? 0.01 : 0.0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 정강이갑: CON, 회피 보너스
|
||||||
|
EquipmentSlot.greaves => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 3 : 0,
|
||||||
|
conBonus:
|
||||||
|
rarity.index >= ItemRarity.rare.index ? rarity.index - 1 : 0,
|
||||||
|
evasion: rarity.index >= ItemRarity.epic.index ? 0.01 : 0.0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 철제부츠: DEX, 회피 보너스
|
||||||
|
EquipmentSlot.sollerets => ItemStats(
|
||||||
|
def: def,
|
||||||
|
dexBonus: rarity.index >= ItemRarity.uncommon.index ? rarity.index : 0,
|
||||||
|
evasion: rarity.index >= ItemRarity.rare.index
|
||||||
|
? 0.01 + rarity.index * 0.005
|
||||||
|
: 0.0,
|
||||||
|
),
|
||||||
|
|
||||||
|
// 기본 (무기, 방패는 여기 안옴)
|
||||||
|
_ => ItemStats(
|
||||||
|
def: def,
|
||||||
|
hpBonus: rarity.index >= ItemRarity.rare.index ? baseValue ~/ 2 : 0,
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -970,6 +970,8 @@ class ProgressService {
|
|||||||
// 보스전 사망이 아닐 경우에만 장비 손실
|
// 보스전 사망이 아닐 경우에만 장비 손실
|
||||||
var newEquipment = state.equipment;
|
var newEquipment = state.equipment;
|
||||||
var lostCount = 0;
|
var lostCount = 0;
|
||||||
|
String? lostItemName;
|
||||||
|
EquipmentSlot? lostItemSlot;
|
||||||
|
|
||||||
if (!isBossDeath) {
|
if (!isBossDeath) {
|
||||||
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
// 무기(슬롯 0)를 제외한 장착된 장비 중 1개를 제물로 삭제
|
||||||
@@ -987,12 +989,16 @@ class ProgressService {
|
|||||||
equippedNonWeaponSlots[state.rng.nextInt(
|
equippedNonWeaponSlots[state.rng.nextInt(
|
||||||
equippedNonWeaponSlots.length,
|
equippedNonWeaponSlots.length,
|
||||||
)];
|
)];
|
||||||
final slot = EquipmentSlot.values[sacrificeIndex];
|
|
||||||
|
// 제물로 바칠 아이템 정보 저장
|
||||||
|
final lostItem = state.equipment.getItemByIndex(sacrificeIndex);
|
||||||
|
lostItemName = lostItem.name;
|
||||||
|
lostItemSlot = EquipmentSlot.values[sacrificeIndex];
|
||||||
|
|
||||||
// 해당 슬롯을 빈 장비로 교체
|
// 해당 슬롯을 빈 장비로 교체
|
||||||
newEquipment = newEquipment.setItemByIndex(
|
newEquipment = newEquipment.setItemByIndex(
|
||||||
sacrificeIndex,
|
sacrificeIndex,
|
||||||
EquipmentItem.empty(slot),
|
EquipmentItem.empty(lostItemSlot),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1002,6 +1008,8 @@ class ProgressService {
|
|||||||
cause: cause,
|
cause: cause,
|
||||||
killerName: killerName,
|
killerName: killerName,
|
||||||
lostEquipmentCount: lostCount,
|
lostEquipmentCount: lostCount,
|
||||||
|
lostItemName: lostItemName,
|
||||||
|
lostItemSlot: lostItemSlot,
|
||||||
goldAtDeath: state.inventory.gold,
|
goldAtDeath: state.inventory.gold,
|
||||||
levelAtDeath: state.traits.level,
|
levelAtDeath: state.traits.level,
|
||||||
timestamp: state.skillSystem.elapsedMs,
|
timestamp: state.skillSystem.elapsedMs,
|
||||||
|
|||||||
@@ -63,15 +63,22 @@ class SkillService {
|
|||||||
required MonsterCombatStats monster,
|
required MonsterCombatStats monster,
|
||||||
required SkillSystemState skillSystem,
|
required SkillSystemState skillSystem,
|
||||||
}) {
|
}) {
|
||||||
|
// 데미지 타입에 따른 공격력/방어력 선택
|
||||||
|
final (attackStat, defenseStat) = _getStatsByDamageType(
|
||||||
|
skill.damageType,
|
||||||
|
player,
|
||||||
|
monster,
|
||||||
|
);
|
||||||
|
|
||||||
// 기본 데미지 계산
|
// 기본 데미지 계산
|
||||||
final baseDamage = player.atk * skill.damageMultiplier;
|
final baseDamage = attackStat * skill.damageMultiplier;
|
||||||
|
|
||||||
// 버프 효과 적용
|
// 버프 효과 적용
|
||||||
final buffMods = skillSystem.totalBuffModifiers;
|
final buffMods = skillSystem.totalBuffModifiers;
|
||||||
final buffedDamage = baseDamage * (1 + buffMods.atkMod);
|
final buffedDamage = baseDamage * (1 + buffMods.atkMod);
|
||||||
|
|
||||||
// 적 방어력 감소 적용
|
// 적 방어력 감소 적용
|
||||||
final effectiveMonsterDef = monster.def * (1 - skill.targetDefReduction);
|
final effectiveMonsterDef = defenseStat * (1 - skill.targetDefReduction);
|
||||||
|
|
||||||
// 최종 데미지 계산 (방어력 감산 0.3)
|
// 최종 데미지 계산 (방어력 감산 0.3)
|
||||||
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
|
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
|
||||||
@@ -636,15 +643,22 @@ class SkillService {
|
|||||||
// 실제 MP 비용 계산
|
// 실제 MP 비용 계산
|
||||||
final actualMpCost = (skill.mpCost * mpMult).round();
|
final actualMpCost = (skill.mpCost * mpMult).round();
|
||||||
|
|
||||||
|
// 데미지 타입에 따른 공격력/방어력 선택
|
||||||
|
final (attackStat, defenseStat) = _getStatsByDamageType(
|
||||||
|
skill.damageType,
|
||||||
|
player,
|
||||||
|
monster,
|
||||||
|
);
|
||||||
|
|
||||||
// 기본 데미지 계산 (랭크 배율 적용)
|
// 기본 데미지 계산 (랭크 배율 적용)
|
||||||
final baseDamage = player.atk * skill.damageMultiplier * rankMult;
|
final baseDamage = attackStat * skill.damageMultiplier * rankMult;
|
||||||
|
|
||||||
// 버프 효과 적용
|
// 버프 효과 적용
|
||||||
final buffMods = skillSystem.totalBuffModifiers;
|
final buffMods = skillSystem.totalBuffModifiers;
|
||||||
final buffedDamage = baseDamage * (1 + buffMods.atkMod);
|
final buffedDamage = baseDamage * (1 + buffMods.atkMod);
|
||||||
|
|
||||||
// 적 방어력 감소 적용
|
// 적 방어력 감소 적용
|
||||||
final effectiveMonsterDef = monster.def * (1 - skill.targetDefReduction);
|
final effectiveMonsterDef = defenseStat * (1 - skill.targetDefReduction);
|
||||||
|
|
||||||
// 최종 데미지 계산 (방어력 감산 0.3)
|
// 최종 데미지 계산 (방어력 감산 0.3)
|
||||||
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
|
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.3)
|
||||||
@@ -708,4 +722,32 @@ class SkillService {
|
|||||||
|
|
||||||
return state.copyWith(skillStates: skillStates);
|
return state.copyWith(skillStates: skillStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 데미지 타입 헬퍼
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// 데미지 타입에 따른 공격력/방어력 스탯 반환
|
||||||
|
///
|
||||||
|
/// [damageType] 스킬의 데미지 타입
|
||||||
|
/// [player] 플레이어 전투 스탯
|
||||||
|
/// [monster] 몬스터 전투 스탯
|
||||||
|
/// Returns: (공격력, 방어력) 튜플
|
||||||
|
(double, double) _getStatsByDamageType(
|
||||||
|
DamageType damageType,
|
||||||
|
CombatStats player,
|
||||||
|
MonsterCombatStats monster,
|
||||||
|
) {
|
||||||
|
return switch (damageType) {
|
||||||
|
DamageType.physical => (player.atk.toDouble(), monster.def.toDouble()),
|
||||||
|
DamageType.magical => (
|
||||||
|
player.magAtk.toDouble(),
|
||||||
|
monster.magDef.toDouble(),
|
||||||
|
),
|
||||||
|
DamageType.hybrid => (
|
||||||
|
(player.atk + player.magAtk) / 2,
|
||||||
|
(monster.def + monster.magDef) / 2,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class DeathInfo {
|
|||||||
required this.levelAtDeath,
|
required this.levelAtDeath,
|
||||||
required this.timestamp,
|
required this.timestamp,
|
||||||
this.lostItemName,
|
this.lostItemName,
|
||||||
|
this.lostItemSlot,
|
||||||
this.lastCombatEvents = const [],
|
this.lastCombatEvents = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,6 +147,9 @@ class DeathInfo {
|
|||||||
/// 제물로 바친 아이템 이름 (null이면 없음)
|
/// 제물로 바친 아이템 이름 (null이면 없음)
|
||||||
final String? lostItemName;
|
final String? lostItemName;
|
||||||
|
|
||||||
|
/// 제물로 바친 아이템 슬롯 (null이면 없음)
|
||||||
|
final EquipmentSlot? lostItemSlot;
|
||||||
|
|
||||||
/// 사망 시점 골드
|
/// 사망 시점 골드
|
||||||
final int goldAtDeath;
|
final int goldAtDeath;
|
||||||
|
|
||||||
@@ -163,6 +167,7 @@ class DeathInfo {
|
|||||||
String? killerName,
|
String? killerName,
|
||||||
int? lostEquipmentCount,
|
int? lostEquipmentCount,
|
||||||
String? lostItemName,
|
String? lostItemName,
|
||||||
|
EquipmentSlot? lostItemSlot,
|
||||||
int? goldAtDeath,
|
int? goldAtDeath,
|
||||||
int? levelAtDeath,
|
int? levelAtDeath,
|
||||||
int? timestamp,
|
int? timestamp,
|
||||||
@@ -173,6 +178,7 @@ class DeathInfo {
|
|||||||
killerName: killerName ?? this.killerName,
|
killerName: killerName ?? this.killerName,
|
||||||
lostEquipmentCount: lostEquipmentCount ?? this.lostEquipmentCount,
|
lostEquipmentCount: lostEquipmentCount ?? this.lostEquipmentCount,
|
||||||
lostItemName: lostItemName ?? this.lostItemName,
|
lostItemName: lostItemName ?? this.lostItemName,
|
||||||
|
lostItemSlot: lostItemSlot ?? this.lostItemSlot,
|
||||||
goldAtDeath: goldAtDeath ?? this.goldAtDeath,
|
goldAtDeath: goldAtDeath ?? this.goldAtDeath,
|
||||||
levelAtDeath: levelAtDeath ?? this.levelAtDeath,
|
levelAtDeath: levelAtDeath ?? this.levelAtDeath,
|
||||||
timestamp: timestamp ?? this.timestamp,
|
timestamp: timestamp ?? this.timestamp,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class MonsterCombatStats {
|
|||||||
required this.level,
|
required this.level,
|
||||||
required this.atk,
|
required this.atk,
|
||||||
required this.def,
|
required this.def,
|
||||||
|
required this.magDef,
|
||||||
required this.hpMax,
|
required this.hpMax,
|
||||||
required this.hpCurrent,
|
required this.hpCurrent,
|
||||||
required this.criRate,
|
required this.criRate,
|
||||||
@@ -41,9 +42,12 @@ class MonsterCombatStats {
|
|||||||
/// 공격력
|
/// 공격력
|
||||||
final int atk;
|
final int atk;
|
||||||
|
|
||||||
/// 방어력
|
/// 물리 방어력
|
||||||
final int def;
|
final int def;
|
||||||
|
|
||||||
|
/// 마법 방어력
|
||||||
|
final int magDef;
|
||||||
|
|
||||||
/// 최대 HP
|
/// 최대 HP
|
||||||
final int hpMax;
|
final int hpMax;
|
||||||
|
|
||||||
@@ -96,6 +100,7 @@ class MonsterCombatStats {
|
|||||||
int? level,
|
int? level,
|
||||||
int? atk,
|
int? atk,
|
||||||
int? def,
|
int? def,
|
||||||
|
int? magDef,
|
||||||
int? hpMax,
|
int? hpMax,
|
||||||
int? hpCurrent,
|
int? hpCurrent,
|
||||||
double? criRate,
|
double? criRate,
|
||||||
@@ -110,6 +115,7 @@ class MonsterCombatStats {
|
|||||||
level: level ?? this.level,
|
level: level ?? this.level,
|
||||||
atk: atk ?? this.atk,
|
atk: atk ?? this.atk,
|
||||||
def: def ?? this.def,
|
def: def ?? this.def,
|
||||||
|
magDef: magDef ?? this.magDef,
|
||||||
hpMax: hpMax ?? this.hpMax,
|
hpMax: hpMax ?? this.hpMax,
|
||||||
hpCurrent: hpCurrent ?? this.hpCurrent,
|
hpCurrent: hpCurrent ?? this.hpCurrent,
|
||||||
criRate: criRate ?? this.criRate,
|
criRate: criRate ?? this.criRate,
|
||||||
@@ -173,11 +179,16 @@ class MonsterCombatStats {
|
|||||||
MonsterSpeedType.slow => 1400,
|
MonsterSpeedType.slow => 1400,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 마법 방어력: 물리 방어력의 70~130% (레벨에 따라 변동)
|
||||||
|
final magDefRatio = 0.7 + (level % 60) * 0.01; // 0.7 ~ 1.3
|
||||||
|
final magDef = (baseStats.def * magDefRatio).round();
|
||||||
|
|
||||||
return MonsterCombatStats(
|
return MonsterCombatStats(
|
||||||
name: name,
|
name: name,
|
||||||
level: level,
|
level: level,
|
||||||
atk: baseStats.atk,
|
atk: baseStats.atk,
|
||||||
def: baseStats.def,
|
def: baseStats.def,
|
||||||
|
magDef: magDef,
|
||||||
hpMax: adjustedHp,
|
hpMax: adjustedHp,
|
||||||
hpCurrent: adjustedHp,
|
hpCurrent: adjustedHp,
|
||||||
criRate: criRate,
|
criRate: criRate,
|
||||||
@@ -202,6 +213,7 @@ class MonsterCombatStats {
|
|||||||
level: bossLevel,
|
level: bossLevel,
|
||||||
atk: bossStats.atk,
|
atk: bossStats.atk,
|
||||||
def: bossStats.def,
|
def: bossStats.def,
|
||||||
|
magDef: (bossStats.def * 1.2).round(), // 보스는 마법 방어력 20% 증가
|
||||||
hpMax: bossStats.hp,
|
hpMax: bossStats.hp,
|
||||||
hpCurrent: bossStats.hp,
|
hpCurrent: bossStats.hp,
|
||||||
criRate: 0.25, // 보스 크리티컬 확률 25%
|
criRate: 0.25, // 보스 크리티컬 확률 25%
|
||||||
@@ -244,6 +256,7 @@ class MonsterCombatStats {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 8,
|
atk: 8,
|
||||||
def: 3,
|
def: 3,
|
||||||
|
magDef: 3,
|
||||||
hpMax: 35,
|
hpMax: 35,
|
||||||
hpCurrent: 35,
|
hpCurrent: 35,
|
||||||
criRate: 0.02,
|
criRate: 0.02,
|
||||||
@@ -264,6 +277,7 @@ class MonsterCombatStats {
|
|||||||
level: 0, // PvP에서는 레벨 페널티 없음
|
level: 0, // PvP에서는 레벨 페널티 없음
|
||||||
atk: stats.atk,
|
atk: stats.atk,
|
||||||
def: stats.def,
|
def: stats.def,
|
||||||
|
magDef: stats.magDef,
|
||||||
hpMax: stats.hpMax,
|
hpMax: stats.hpMax,
|
||||||
hpCurrent: stats.hpMax, // 풀 HP로 시작
|
hpCurrent: stats.hpMax, // 풀 HP로 시작
|
||||||
criRate: stats.criRate,
|
criRate: stats.criRate,
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
/// 스펠 랭크에 따른 스킬 배율 계산
|
/// 스펠 랭크에 따른 스킬 배율 계산
|
||||||
///
|
///
|
||||||
/// 랭크 1: 1.0x, 랭크 2: 1.15x, 랭크 3: 1.30x, ...
|
/// 랭크 1: 1.0x, 랭크 2: 1.08x, 랭크 3: 1.16x, ... 최대 1.72x (rank 10)
|
||||||
double getRankMultiplier(int rank) => 1.0 + (rank - 1) * 0.15;
|
/// Phase 3 밸런스: 0.15 → 0.08로 하향
|
||||||
|
double getRankMultiplier(int rank) => 1.0 + (rank - 1) * 0.08;
|
||||||
|
|
||||||
/// 랭크에 따른 쿨타임 감소율 계산
|
/// 랭크에 따른 쿨타임 감소율 계산
|
||||||
///
|
///
|
||||||
@@ -38,6 +39,18 @@ enum SkillType {
|
|||||||
debuff,
|
debuff,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 데미지 타입 (물리/마법 구분)
|
||||||
|
enum DamageType {
|
||||||
|
/// 물리 공격 - STR + atk 기반, 적 def로 방어
|
||||||
|
physical,
|
||||||
|
|
||||||
|
/// 마법 공격 - INT + magAtk 기반, 적 magDef로 방어
|
||||||
|
magical,
|
||||||
|
|
||||||
|
/// 하이브리드 - (atk + magAtk) / 2, (def + magDef) / 2
|
||||||
|
hybrid,
|
||||||
|
}
|
||||||
|
|
||||||
/// 스킬 속성 (하이브리드: 코드 + 시스템)
|
/// 스킬 속성 (하이브리드: 코드 + 시스템)
|
||||||
enum SkillElement {
|
enum SkillElement {
|
||||||
/// 논리 (Logic) - 순수 데미지
|
/// 논리 (Logic) - 순수 데미지
|
||||||
@@ -118,6 +131,7 @@ class Skill {
|
|||||||
required this.cooldownMs,
|
required this.cooldownMs,
|
||||||
required this.power,
|
required this.power,
|
||||||
this.tier = 1,
|
this.tier = 1,
|
||||||
|
this.damageType = DamageType.physical,
|
||||||
this.damageMultiplier = 1.0,
|
this.damageMultiplier = 1.0,
|
||||||
this.healAmount = 0,
|
this.healAmount = 0,
|
||||||
this.healPercent = 0.0,
|
this.healPercent = 0.0,
|
||||||
@@ -137,6 +151,9 @@ class Skill {
|
|||||||
/// 스킬 티어 (1~5, 높을수록 강함)
|
/// 스킬 티어 (1~5, 높을수록 강함)
|
||||||
final int tier;
|
final int tier;
|
||||||
|
|
||||||
|
/// 데미지 타입 (물리/마법/하이브리드)
|
||||||
|
final DamageType damageType;
|
||||||
|
|
||||||
/// 스킬 ID
|
/// 스킬 ID
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
||||||
import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
|
import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart';
|
||||||
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||||
|
|
||||||
@@ -293,6 +294,11 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
final expColor = RetroColors.expOf(context);
|
final expColor = RetroColors.expOf(context);
|
||||||
final gold = RetroColors.goldOf(context);
|
final gold = RetroColors.goldOf(context);
|
||||||
|
|
||||||
|
// 슬롯명 + 아이템명 조합
|
||||||
|
final lostItemDisplay = hasLostItem
|
||||||
|
? '[${_getSlotName(deathInfo.lostItemSlot)}] ${deathInfo.lostItemName}'
|
||||||
|
: null;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// 제물로 바친 아이템 표시
|
// 제물로 바친 아이템 표시
|
||||||
@@ -321,10 +327,10 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
deathInfo.lostItemName!,
|
lostItemDisplay!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'PressStart2P',
|
fontFamily: 'PressStart2P',
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
color: hpColor,
|
color: hpColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -671,4 +677,22 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 장비 슬롯 이름 반환
|
||||||
|
String _getSlotName(EquipmentSlot? slot) {
|
||||||
|
if (slot == null) return '';
|
||||||
|
return switch (slot) {
|
||||||
|
EquipmentSlot.weapon => l10n.slotWeapon,
|
||||||
|
EquipmentSlot.shield => l10n.slotShield,
|
||||||
|
EquipmentSlot.helm => l10n.slotHelm,
|
||||||
|
EquipmentSlot.hauberk => l10n.slotHauberk,
|
||||||
|
EquipmentSlot.brassairts => l10n.slotBrassairts,
|
||||||
|
EquipmentSlot.vambraces => l10n.slotVambraces,
|
||||||
|
EquipmentSlot.gauntlets => l10n.slotGauntlets,
|
||||||
|
EquipmentSlot.gambeson => l10n.slotGambeson,
|
||||||
|
EquipmentSlot.cuisses => l10n.slotCuisses,
|
||||||
|
EquipmentSlot.greaves => l10n.slotGreaves,
|
||||||
|
EquipmentSlot.sollerets => l10n.slotSollerets,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 컴팩트 HP 바
|
/// 컴팩트 HP 바 (숫자 오버레이)
|
||||||
Widget _buildCompactHpBar() {
|
Widget _buildCompactHpBar() {
|
||||||
final ratio = _currentHpMax > 0 ? _currentHp / _currentHpMax : 0.0;
|
final ratio = _currentHpMax > 0 ? _currentHp / _currentHpMax : 0.0;
|
||||||
final isLow = ratio < 0.2 && ratio > 0;
|
final isLow = ratio < 0.2 && ratio > 0;
|
||||||
@@ -320,9 +320,13 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 프로그레스
|
// 프로그레스 바 + 숫자 오버레이
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// 프로그레스 바
|
||||||
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.horizontal(
|
borderRadius: const BorderRadius.horizontal(
|
||||||
right: Radius.circular(3),
|
right: Radius.circular(3),
|
||||||
),
|
),
|
||||||
@@ -335,14 +339,23 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
minHeight: 20,
|
minHeight: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 숫자 오버레이 (바 중앙)
|
||||||
// 수치
|
Text(
|
||||||
Container(
|
|
||||||
width: 56,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
'$_currentHp/$_currentHpMax',
|
'$_currentHp/$_currentHpMax',
|
||||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.9),
|
||||||
|
blurRadius: 2,
|
||||||
|
),
|
||||||
|
const Shadow(color: Colors.black, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -352,7 +365,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
// 플로팅 변화량
|
// 플로팅 변화량
|
||||||
if (_hpChange != 0 && _hpFlashAnimation.value > 0.05)
|
if (_hpChange != 0 && _hpFlashAnimation.value > 0.05)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 50,
|
right: 20,
|
||||||
top: -8,
|
top: -8,
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(0, -10 * (1 - _hpFlashAnimation.value)),
|
offset: Offset(0, -10 * (1 - _hpFlashAnimation.value)),
|
||||||
@@ -378,7 +391,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 컴팩트 MP 바
|
/// 컴팩트 MP 바 (숫자 오버레이)
|
||||||
Widget _buildCompactMpBar() {
|
Widget _buildCompactMpBar() {
|
||||||
final ratio = _currentMpMax > 0 ? _currentMp / _currentMpMax : 0.0;
|
final ratio = _currentMpMax > 0 ? _currentMp / _currentMpMax : 0.0;
|
||||||
|
|
||||||
@@ -408,8 +421,13 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// 프로그레스 바 + 숫자 오버레이
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// 프로그레스 바
|
||||||
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.horizontal(
|
borderRadius: const BorderRadius.horizontal(
|
||||||
right: Radius.circular(3),
|
right: Radius.circular(3),
|
||||||
),
|
),
|
||||||
@@ -422,13 +440,23 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
minHeight: 20,
|
minHeight: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 숫자 오버레이 (바 중앙)
|
||||||
Container(
|
Text(
|
||||||
width: 56,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
'$_currentMp/$_currentMpMax',
|
'$_currentMp/$_currentMpMax',
|
||||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.9),
|
||||||
|
blurRadius: 2,
|
||||||
|
),
|
||||||
|
const Shadow(color: Colors.black, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -437,7 +465,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
|
|
||||||
if (_mpChange != 0 && _mpFlashAnimation.value > 0.05)
|
if (_mpChange != 0 && _mpFlashAnimation.value > 0.05)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 50,
|
right: 20,
|
||||||
top: -8,
|
top: -8,
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(0, -10 * (1 - _mpFlashAnimation.value)),
|
offset: Offset(0, -10 * (1 - _mpFlashAnimation.value)),
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 레트로 스타일 세그먼트 바
|
/// 레트로 스타일 세그먼트 바 (숫자 바 위 오버레이)
|
||||||
Widget _buildRetroBar({
|
Widget _buildRetroBar({
|
||||||
required String label,
|
required String label,
|
||||||
required int current,
|
required int current,
|
||||||
@@ -316,13 +316,18 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 세그먼트 바
|
// 세그먼트 바 (숫자 오버레이)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Stack(
|
||||||
height: 12,
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// 세그먼트 바
|
||||||
|
Container(
|
||||||
|
height: 14,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: emptyColor.withValues(alpha: 0.3),
|
color: emptyColor.withValues(alpha: 0.3),
|
||||||
border: Border.all(color: RetroColors.panelBorderOuter, width: 1),
|
border:
|
||||||
|
Border.all(color: RetroColors.panelBorderOuter, width: 1),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: List.generate(segmentCount, (index) {
|
children: List.generate(segmentCount, (index) {
|
||||||
@@ -336,7 +341,8 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
border: Border(
|
border: Border(
|
||||||
right: index < segmentCount - 1
|
right: index < segmentCount - 1
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
color: RetroColors.panelBorderOuter.withValues(
|
color:
|
||||||
|
RetroColors.panelBorderOuter.withValues(
|
||||||
alpha: 0.3,
|
alpha: 0.3,
|
||||||
),
|
),
|
||||||
width: 1,
|
width: 1,
|
||||||
@@ -349,20 +355,23 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// 숫자 오버레이 (바 중앙)
|
||||||
const SizedBox(width: 6),
|
Text(
|
||||||
// 수치 표시
|
|
||||||
SizedBox(
|
|
||||||
width: 60,
|
|
||||||
child: Text(
|
|
||||||
'$current/$max',
|
'$current/$max',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'PressStart2P',
|
fontFamily: 'PressStart2P',
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
color: RetroColors.textLight,
|
color: RetroColors.textLight.withValues(alpha: blinkOpacity),
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.9),
|
||||||
|
blurRadius: 2,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.right,
|
const Shadow(color: Colors.black, blurRadius: 4),
|
||||||
overflow: TextOverflow.ellipsis,
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 50, // DEF 50 → 50 * 0.4 = 20 감소
|
def: 50, // DEF 50 → 50 * 0.4 = 20 감소
|
||||||
|
magDef: 50,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -63,6 +64,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 0, // DEF 0
|
def: 0, // DEF 0
|
||||||
|
magDef: 0,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -100,6 +102,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -135,6 +138,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 100,
|
atk: 100,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.0, // 크리티컬 없음
|
criRate: 0.0, // 크리티컬 없음
|
||||||
@@ -174,6 +178,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 100,
|
atk: 100,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.0,
|
criRate: 0.0,
|
||||||
@@ -221,6 +226,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 10,
|
def: 10,
|
||||||
|
magDef: 10,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -255,6 +261,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 5,
|
def: 5,
|
||||||
|
magDef: 5,
|
||||||
hpMax: 50,
|
hpMax: 50,
|
||||||
hpCurrent: 50,
|
hpCurrent: 50,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -270,6 +277,7 @@ void main() {
|
|||||||
level: 10,
|
level: 10,
|
||||||
atk: 50,
|
atk: 50,
|
||||||
def: 20,
|
def: 20,
|
||||||
|
magDef: 20,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -314,6 +322,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 30,
|
atk: 30,
|
||||||
def: 10,
|
def: 10,
|
||||||
|
magDef: 10,
|
||||||
hpMax: 80,
|
hpMax: 80,
|
||||||
hpCurrent: 80,
|
hpCurrent: 80,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 5,
|
def: 5,
|
||||||
|
magDef: 5,
|
||||||
hpMax: 50,
|
hpMax: 50,
|
||||||
hpCurrent: 0, // 몬스터 사망
|
hpCurrent: 0, // 몬스터 사망
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ void main() {
|
|||||||
level: 10,
|
level: 10,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 5,
|
def: 5,
|
||||||
|
magDef: 5,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 0, // 몬스터 사망 상태
|
hpCurrent: 0, // 몬스터 사망 상태
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 50, // DEF 50 → 50 * 0.3 = 15 감소
|
def: 50, // DEF 50 → 50 * 0.3 = 15 감소
|
||||||
|
magDef: 50,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -124,7 +125,7 @@ void main() {
|
|||||||
// ATK 100 * 2.0 - DEF 50 * 0.3 = 200 - 15 = 185
|
// ATK 100 * 2.0 - DEF 50 * 0.3 = 200 - 15 = 185
|
||||||
expect(result.result.success, isTrue);
|
expect(result.result.success, isTrue);
|
||||||
expect(result.result.damage, equals(185));
|
expect(result.result.damage, equals(185));
|
||||||
expect(result.updatedPlayer.mpCurrent, equals(40)); // 50 - 10
|
expect(result.updatedPlayer.mpCurrent, equals(20)); // 50 - 30 (mpCost 30)
|
||||||
expect(result.updatedMonster.hpCurrent, equals(315)); // 500 - 185
|
expect(result.updatedMonster.hpCurrent, equals(315)); // 500 - 185
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,6 +144,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -196,6 +198,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -217,7 +220,7 @@ void main() {
|
|||||||
|
|
||||||
// 랭크 1: 1.0x → ATK 100 * 2.0 * 1.0 = 200
|
// 랭크 1: 1.0x → ATK 100 * 2.0 * 1.0 = 200
|
||||||
expect(result.result.damage, equals(200));
|
expect(result.result.damage, equals(200));
|
||||||
expect(result.updatedPlayer.mpCurrent, equals(40)); // MP 10 소모
|
expect(result.updatedPlayer.mpCurrent, equals(20)); // MP 30 소모
|
||||||
});
|
});
|
||||||
|
|
||||||
test('랭크 5 데미지 스케일링', () {
|
test('랭크 5 데미지 스케일링', () {
|
||||||
@@ -235,6 +238,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 0,
|
def: 0,
|
||||||
|
magDef: 0,
|
||||||
hpMax: 500,
|
hpMax: 500,
|
||||||
hpCurrent: 500,
|
hpCurrent: 500,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -254,12 +258,12 @@ void main() {
|
|||||||
rank: 5,
|
rank: 5,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 랭크 5: 1.6x (1.0 + 4 * 0.15)
|
// 랭크 5: 1.32x (1.0 + 4 * 0.08) - 랭크 배율 하향
|
||||||
// ATK 100 * 2.0 * 1.6 = 320
|
// ATK 100 * 2.0 * 1.32 = 264
|
||||||
expect(result.result.damage, equals(320));
|
expect(result.result.damage, equals(264));
|
||||||
|
|
||||||
// MP 비용: 10 * (1.0 - 4 * 0.03) = 10 * 0.88 = 9 (반올림)
|
// MP 비용: 30 * (1.0 - 4 * 0.03) = 30 * 0.88 = 26 (반올림)
|
||||||
expect(result.updatedPlayer.mpCurrent, equals(41)); // 50 - 9
|
expect(result.updatedPlayer.mpCurrent, equals(24)); // 50 - 26
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -268,12 +272,12 @@ void main() {
|
|||||||
final rng = DeterministicRandom(42);
|
final rng = DeterministicRandom(42);
|
||||||
final service = SkillService(rng: rng);
|
final service = SkillService(rng: rng);
|
||||||
|
|
||||||
const skill = SkillData.hotReload; // healAmount: 30
|
const skill = SkillData.hotReload; // healAmount: 30, mpCost: 80
|
||||||
final player = CombatStats.empty().copyWith(
|
final player = CombatStats.empty().copyWith(
|
||||||
hpMax: 200,
|
hpMax: 200,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
mpMax: 100,
|
mpMax: 200,
|
||||||
mpCurrent: 50,
|
mpCurrent: 150,
|
||||||
);
|
);
|
||||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||||
|
|
||||||
@@ -286,7 +290,7 @@ void main() {
|
|||||||
expect(result.result.success, isTrue);
|
expect(result.result.success, isTrue);
|
||||||
expect(result.result.healedAmount, equals(30));
|
expect(result.result.healedAmount, equals(30));
|
||||||
expect(result.updatedPlayer.hpCurrent, equals(130)); // 100 + 30
|
expect(result.updatedPlayer.hpCurrent, equals(130)); // 100 + 30
|
||||||
expect(result.updatedPlayer.mpCurrent, equals(35)); // 50 - 15
|
expect(result.updatedPlayer.mpCurrent, equals(70)); // 150 - 80
|
||||||
});
|
});
|
||||||
|
|
||||||
test('퍼센트 회복량', () {
|
test('퍼센트 회복량', () {
|
||||||
@@ -483,6 +487,7 @@ void main() {
|
|||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 10,
|
def: 10,
|
||||||
|
magDef: 10,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -512,14 +517,15 @@ void main() {
|
|||||||
final player = CombatStats.empty().copyWith(
|
final player = CombatStats.empty().copyWith(
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 20, // 20% HP
|
hpCurrent: 20, // 20% HP
|
||||||
mpMax: 100,
|
mpMax: 200,
|
||||||
mpCurrent: 80,
|
mpCurrent: 150, // 힐 스킬 사용 가능한 MP (garbageCollection: 130)
|
||||||
);
|
);
|
||||||
final monster = MonsterCombatStats(
|
final monster = MonsterCombatStats(
|
||||||
name: 'Test Monster',
|
name: 'Test Monster',
|
||||||
level: 1,
|
level: 1,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 10,
|
def: 10,
|
||||||
|
magDef: 10,
|
||||||
hpMax: 100,
|
hpMax: 100,
|
||||||
hpCurrent: 100,
|
hpCurrent: 100,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -548,10 +554,10 @@ void main() {
|
|||||||
final rng = DeterministicRandom(42);
|
final rng = DeterministicRandom(42);
|
||||||
final service = SkillService(rng: rng);
|
final service = SkillService(rng: rng);
|
||||||
|
|
||||||
const skill = SkillData.debugMode; // ATK +25% 버프
|
const skill = SkillData.debugMode; // ATK +25% 버프, mpCost: 100
|
||||||
final player = CombatStats.empty().copyWith(
|
final player = CombatStats.empty().copyWith(
|
||||||
mpMax: 100,
|
mpMax: 200,
|
||||||
mpCurrent: 50,
|
mpCurrent: 150,
|
||||||
);
|
);
|
||||||
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
final skillSystem = SkillSystemState.empty().copyWith(elapsedMs: 5000);
|
||||||
|
|
||||||
@@ -568,7 +574,7 @@ void main() {
|
|||||||
equals(0.25),
|
equals(0.25),
|
||||||
);
|
);
|
||||||
expect(result.updatedSkillSystem.activeBuffs.length, equals(1));
|
expect(result.updatedSkillSystem.activeBuffs.length, equals(1));
|
||||||
expect(result.updatedPlayer.mpCurrent, equals(30)); // 50 - 20
|
expect(result.updatedPlayer.mpCurrent, equals(50)); // 150 - 100
|
||||||
});
|
});
|
||||||
|
|
||||||
test('중복 버프 제거 후 새 버프 적용', () {
|
test('중복 버프 제거 후 새 버프 적용', () {
|
||||||
@@ -648,11 +654,12 @@ void main() {
|
|||||||
|
|
||||||
group('getRankMultiplier', () {
|
group('getRankMultiplier', () {
|
||||||
test('랭크별 배율 계산', () {
|
test('랭크별 배율 계산', () {
|
||||||
|
// 랭크 배율 하향: 0.15 → 0.08 per rank
|
||||||
expect(getRankMultiplier(1), equals(1.0));
|
expect(getRankMultiplier(1), equals(1.0));
|
||||||
expect(getRankMultiplier(2), closeTo(1.15, 0.001));
|
expect(getRankMultiplier(2), closeTo(1.08, 0.001));
|
||||||
expect(getRankMultiplier(3), closeTo(1.30, 0.001));
|
expect(getRankMultiplier(3), closeTo(1.16, 0.001));
|
||||||
expect(getRankMultiplier(5), closeTo(1.60, 0.001));
|
expect(getRankMultiplier(5), closeTo(1.32, 0.001));
|
||||||
expect(getRankMultiplier(10), closeTo(2.35, 0.001));
|
expect(getRankMultiplier(10), closeTo(1.72, 0.001));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class MockFactories {
|
|||||||
level: monsterLevel,
|
level: monsterLevel,
|
||||||
atk: 10,
|
atk: 10,
|
||||||
def: 5,
|
def: 5,
|
||||||
|
magDef: 5,
|
||||||
hpMax: monsterHpMax,
|
hpMax: monsterHpMax,
|
||||||
hpCurrent: monsterHpCurrent,
|
hpCurrent: monsterHpCurrent,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
@@ -146,6 +147,7 @@ class MockFactories {
|
|||||||
int level = 1,
|
int level = 1,
|
||||||
int atk = 10,
|
int atk = 10,
|
||||||
int def = 5,
|
int def = 5,
|
||||||
|
int magDef = 5,
|
||||||
int hpMax = 100,
|
int hpMax = 100,
|
||||||
int? hpCurrent,
|
int? hpCurrent,
|
||||||
double criRate = 0.05,
|
double criRate = 0.05,
|
||||||
@@ -160,6 +162,7 @@ class MockFactories {
|
|||||||
level: level,
|
level: level,
|
||||||
atk: atk,
|
atk: atk,
|
||||||
def: def,
|
def: def,
|
||||||
|
magDef: magDef,
|
||||||
hpMax: hpMax,
|
hpMax: hpMax,
|
||||||
hpCurrent: hpCurrent ?? hpMax,
|
hpCurrent: hpCurrent ?? hpMax,
|
||||||
criRate: criRate,
|
criRate: criRate,
|
||||||
@@ -182,6 +185,7 @@ class MockFactories {
|
|||||||
level: level,
|
level: level,
|
||||||
atk: base.atk,
|
atk: base.atk,
|
||||||
def: base.def,
|
def: base.def,
|
||||||
|
magDef: base.def, // 물리 방어와 동일
|
||||||
hpMax: base.hp,
|
hpMax: base.hp,
|
||||||
hpCurrent: base.hp,
|
hpCurrent: base.hp,
|
||||||
criRate: 0.05,
|
criRate: 0.05,
|
||||||
|
|||||||
Reference in New Issue
Block a user