From 97d9875e00f0f6b791ebb5b0c4fa978e913ff2a0 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Wed, 17 Dec 2025 17:48:46 +0900 Subject: [PATCH] =?UTF-8?q?feat(phase6):=20=EB=B0=B8=EB=9F=B0=EC=8B=B1=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EB=B0=8F=20=EB=AA=AC=EC=8A=A4=ED=84=B0=20?= =?UTF-8?q?=EC=8A=A4=ED=83=AF=20=EC=8A=A4=EC=BC=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - balance_constants.dart 생성 - ExpConstants: 경험치 곡선 (baseExp * 1.15^level) - MonsterType: normal, elite, miniboss, boss, finalBoss - MonsterTypeMultiplier: 타입별 HP/ATK/DEF/EXP/GOLD 배율 - MonsterBaseStats: 레벨 기반 몬스터 스탯 생성 - BossStats: 5개 보스 정의 (Syntax Error Dragon ~ Glitch God) - LevelTierSettings: 구간별 난이도 설정 (초반/중반/후반/엔드게임) - PlayerScaling: 플레이어 레벨업 시 HP/MP 스케일링 - MonsterCombatStats 통합 - MonsterBaseStats.generate() 사용하여 스탯 생성 - monsterType 매개변수 추가로 타입별 몬스터 지원 --- lib/src/core/model/monster_combat_stats.dart | 30 +- lib/src/core/util/balance_constants.dart | 419 +++++++++++++++++++ 2 files changed, 430 insertions(+), 19 deletions(-) create mode 100644 lib/src/core/util/balance_constants.dart diff --git a/lib/src/core/model/monster_combat_stats.dart b/lib/src/core/model/monster_combat_stats.dart index 98efd29..7efa0b9 100644 --- a/lib/src/core/model/monster_combat_stats.dart +++ b/lib/src/core/model/monster_combat_stats.dart @@ -1,3 +1,5 @@ +import 'package:askiineverdie/src/core/util/balance_constants.dart'; + /// 몬스터 공격 속도 타입 enum MonsterSpeedType { /// 빠름 (600ms) @@ -127,22 +129,15 @@ class MonsterCombatStats { /// [name] 몬스터 표시 이름 /// [level] 몬스터 레벨 (원본 데이터 기준) /// [speedType] 공격 속도 타입 (기본: normal) + /// [monsterType] 몬스터 타입 (기본: normal) factory MonsterCombatStats.fromLevel({ required String name, required int level, MonsterSpeedType speedType = MonsterSpeedType.normal, + MonsterType monsterType = MonsterType.normal, }) { - // 레벨 기반 스탯 스케일링 - // 레벨 1 기준으로 선형/비선형 증가 - - // HP: 레벨 * 15 + 기본값 20 - final hpMax = 20 + level * 15; - - // 공격력: 레벨 * 3 + 기본값 5 - final atk = 5 + level * 3; - - // 방어력: 레벨 * 1.5 + 기본값 2 - final def = 2 + (level * 1.5).round(); + // balance_constants.dart의 MonsterBaseStats 사용 + final baseStats = MonsterBaseStats.generate(level, monsterType); // 크리티컬 확률: 레벨에 따라 천천히 증가 (0.02 ~ 0.3) final criRate = (0.02 + level * 0.003).clamp(0.02, 0.3); @@ -163,22 +158,19 @@ class MonsterCombatStats { MonsterSpeedType.slow => 1400, }; - // 경험치 보상: 레벨 기반 - final expReward = 10 + level * 5; - return MonsterCombatStats( name: name, level: level, - atk: atk, - def: def, - hpMax: hpMax, - hpCurrent: hpMax, + atk: baseStats.atk, + def: baseStats.def, + hpMax: baseStats.hp, + hpCurrent: baseStats.hp, criRate: criRate, criDamage: criDamage, evasion: evasion, accuracy: accuracy, attackDelayMs: attackDelayMs, - expReward: expReward, + expReward: baseStats.exp, ); } diff --git a/lib/src/core/util/balance_constants.dart b/lib/src/core/util/balance_constants.dart new file mode 100644 index 0000000..0b86ff3 --- /dev/null +++ b/lib/src/core/util/balance_constants.dart @@ -0,0 +1,419 @@ +/// 밸런스 상수 정의 (balance constants) +/// +/// Phase 6: 레벨 1-100 구간의 난이도 곡선 및 스케일링 상수 +library; + +/// 경험치 관련 상수 (experience constants) +class ExpConstants { + ExpConstants._(); + + /// 기본 경험치 값 + static const int baseExp = 100; + + /// 레벨당 경험치 증가율 (1.15 = 15% 증가) + static const double expGrowthRate = 1.15; + + /// 레벨업에 필요한 경험치 계산 + /// + /// 공식: baseExp * (expGrowthRate ^ level) + /// 레벨 10: ~405 exp + /// 레벨 50: ~108,366 exp + /// 레벨 100: ~11,739,085 exp + static int requiredExp(int level) { + if (level <= 0) return baseExp; + return (baseExp * _pow(expGrowthRate, level)).round(); + } + + /// 효율적인 거듭제곱 계산 + static double _pow(double base, int exponent) { + double result = 1.0; + for (int i = 0; i < exponent; i++) { + result *= base; + } + return result; + } +} + +/// 몬스터 타입 (monster type) +enum MonsterType { + /// 일반 몬스터 + normal, + + /// 정예 몬스터 (Elite) + elite, + + /// 미니보스 + miniboss, + + /// 보스 + boss, + + /// 최종 보스 + finalBoss, +} + +/// 몬스터 타입별 배율 (monster type multipliers) +class MonsterTypeMultiplier { + const MonsterTypeMultiplier({ + required this.hp, + required this.atk, + required this.def, + required this.exp, + required this.gold, + }); + + final double hp; + final double atk; + final double def; + final double exp; + final double gold; + + /// 타입별 배율 가져오기 + static MonsterTypeMultiplier forType(MonsterType type) { + return switch (type) { + MonsterType.normal => normal, + MonsterType.elite => elite, + MonsterType.miniboss => miniboss, + MonsterType.boss => boss, + MonsterType.finalBoss => finalBoss, + }; + } + + /// 일반: 모든 스탯 1.0배 + static const normal = MonsterTypeMultiplier( + hp: 1.0, + atk: 1.0, + def: 1.0, + exp: 1.0, + gold: 1.0, + ); + + /// 정예: HP 2배, ATK 1.3배, DEF 1.2배, 보상 2배 + static const elite = MonsterTypeMultiplier( + hp: 2.0, + atk: 1.3, + def: 1.2, + exp: 2.0, + gold: 2.0, + ); + + /// 미니보스: HP 5배, ATK/DEF 1.5배, 보상 5배 + static const miniboss = MonsterTypeMultiplier( + hp: 5.0, + atk: 1.5, + def: 1.5, + exp: 5.0, + gold: 5.0, + ); + + /// 보스: HP 10배, ATK/DEF 2배, EXP 15배, GOLD 10배 + static const boss = MonsterTypeMultiplier( + hp: 10.0, + atk: 2.0, + def: 2.0, + exp: 15.0, + gold: 10.0, + ); + + /// 최종 보스: HP 20배, ATK/DEF 2.5배, EXP 50배, GOLD 30배 + static const finalBoss = MonsterTypeMultiplier( + hp: 20.0, + atk: 2.5, + def: 2.5, + exp: 50.0, + gold: 30.0, + ); +} + +/// 몬스터 기본 스탯 (monster base stats) +class MonsterBaseStats { + const MonsterBaseStats({ + required this.hp, + required this.atk, + required this.def, + required this.exp, + required this.gold, + }); + + final int hp; + final int atk; + final int def; + final int exp; + final int gold; + + /// 레벨 기반 기본 스탯 생성 + /// + /// HP: 50 + level * 20 + (level^2 / 5) + /// ATK: 5 + level * 3 + /// DEF: 2 + level * 2 + /// EXP: 10 + level * 5 + /// GOLD: 5 + level * 3 + factory MonsterBaseStats.forLevel(int level) { + return MonsterBaseStats( + hp: 50 + level * 20 + (level * level ~/ 5), + atk: 5 + level * 3, + def: 2 + level * 2, + exp: 10 + level * 5, + gold: 5 + level * 3, + ); + } + + /// 타입 배율 적용 + MonsterBaseStats applyType(MonsterType type) { + final mult = MonsterTypeMultiplier.forType(type); + return MonsterBaseStats( + hp: (hp * mult.hp).round(), + atk: (atk * mult.atk).round(), + def: (def * mult.def).round(), + exp: (exp * mult.exp).round(), + gold: (gold * mult.gold).round(), + ); + } + + /// 레벨과 타입을 기반으로 몬스터 스탯 생성 + static MonsterBaseStats generate(int level, MonsterType type) { + return MonsterBaseStats.forLevel(level).applyType(type); + } +} + +/// 보스 특수 능력 타입 (boss ability type) +enum BossAbilityType { + /// 연속 공격 (Syntax Error Dragon) + multiAttack, + + /// HP 회복 (Memory Leak Hydra) + regeneration, + + /// 보호막 (Buffer Overflow Titan) + shield, + + /// 스턴 공격 (Kernel Panic Archon) + stunAttack, + + /// 페이즈 전환 (Glitch God) + phaseShift, +} + +/// 보스 스탯 (boss stats) +class BossStats extends MonsterBaseStats { + const BossStats({ + required super.hp, + required super.atk, + required super.def, + required super.exp, + required super.gold, + required this.phases, + required this.enrageThreshold, + required this.enrageMultiplier, + required this.hasShield, + required this.shieldAmount, + required this.abilities, + }); + + /// 페이즈 수 (글리치 신은 5페이즈) + final int phases; + + /// 분노 발동 HP % (0.3 = 30%) + final double enrageThreshold; + + /// 분노 시 스탯 배율 + final double enrageMultiplier; + + /// 보호막 보유 여부 + final bool hasShield; + + /// 보호막 수치 + final int shieldAmount; + + /// 특수 능력 목록 + final List abilities; + + /// Syntax Error Dragon (Act I 보스, 레벨 20) + static BossStats syntaxErrorDragon(int baseLevel) { + final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss); + return BossStats( + hp: base.hp, + atk: base.atk, + def: base.def, + exp: base.exp, + gold: base.gold, + phases: 3, + enrageThreshold: 0.2, + enrageMultiplier: 1.5, + hasShield: false, + shieldAmount: 0, + abilities: [BossAbilityType.multiAttack], + ); + } + + /// Memory Leak Hydra (Act II 보스, 레벨 40) + static BossStats memoryLeakHydra(int baseLevel) { + final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss); + return BossStats( + hp: base.hp, + atk: base.atk, + def: base.def, + exp: base.exp, + gold: base.gold, + phases: 2, + enrageThreshold: 0.3, + enrageMultiplier: 1.3, + hasShield: false, + shieldAmount: 0, + abilities: [BossAbilityType.regeneration], + ); + } + + /// Buffer Overflow Titan (Act III 보스, 레벨 60) + static BossStats bufferOverflowTitan(int baseLevel) { + final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss); + return BossStats( + hp: base.hp, + atk: base.atk, + def: base.def, + exp: base.exp, + gold: base.gold, + phases: 2, + enrageThreshold: 0.25, + enrageMultiplier: 1.4, + hasShield: true, + shieldAmount: (base.hp * 0.3).round(), + abilities: [BossAbilityType.shield], + ); + } + + /// Kernel Panic Archon (Act IV 보스, 레벨 80) + static BossStats kernelPanicArchon(int baseLevel) { + final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss); + return BossStats( + hp: base.hp, + atk: base.atk, + def: base.def, + exp: base.exp, + gold: base.gold, + phases: 3, + enrageThreshold: 0.2, + enrageMultiplier: 1.6, + hasShield: true, + shieldAmount: (base.hp * 0.2).round(), + abilities: [BossAbilityType.stunAttack], + ); + } + + /// Glitch God (최종 보스, 레벨 100) + static BossStats glitchGod(int baseLevel) { + final base = MonsterBaseStats.generate(baseLevel, MonsterType.finalBoss); + return BossStats( + hp: base.hp, + atk: base.atk, + def: base.def, + exp: base.exp, + gold: base.gold, + phases: 5, + enrageThreshold: 0.1, + enrageMultiplier: 2.0, + hasShield: true, + shieldAmount: (base.hp * 0.5).round(), + abilities: [ + BossAbilityType.phaseShift, + BossAbilityType.multiAttack, + BossAbilityType.regeneration, + BossAbilityType.stunAttack, + ], + ); + } +} + +/// 레벨 구간별 설정 (level tier settings) +class LevelTierSettings { + const LevelTierSettings({ + required this.minLevel, + required this.maxLevel, + required this.name, + required this.targetDeathRate, + required this.estimatedPlayTimeMinutes, + }); + + final int minLevel; + final int maxLevel; + final String name; + + /// 목표 사망 확률 (전투당, 0.01 = 1%) + final double targetDeathRate; + + /// 예상 플레이 시간 (분) + final int estimatedPlayTimeMinutes; + + /// 초반 (레벨 1-20): 튜토리얼 + static const early = LevelTierSettings( + minLevel: 1, + maxLevel: 20, + name: '초반', + targetDeathRate: 0.02, // 1-3% + estimatedPlayTimeMinutes: 90, // 1-2시간 + ); + + /// 중반 (레벨 21-50): 본격적인 성장 + static const mid = LevelTierSettings( + minLevel: 21, + maxLevel: 50, + name: '중반', + targetDeathRate: 0.04, // 3-5% + estimatedPlayTimeMinutes: 240, // 3-5시간 + ); + + /// 후반 (레벨 51-80): 고급 장비 + static const late = LevelTierSettings( + minLevel: 51, + maxLevel: 80, + name: '후반', + targetDeathRate: 0.075, // 5-10% + estimatedPlayTimeMinutes: 390, // 5-8시간 + ); + + /// 엔드게임 (레벨 81-100): 최종 보스 + static const endgame = LevelTierSettings( + minLevel: 81, + maxLevel: 100, + name: '엔드게임', + targetDeathRate: 0.15, // 10-20% + estimatedPlayTimeMinutes: 240, // 3-5시간 + ); + + /// 모든 레벨 구간 + static const all = [early, mid, late, endgame]; + + /// 레벨로 구간 찾기 + static LevelTierSettings forLevel(int level) { + for (final tier in all) { + if (level >= tier.minLevel && level <= tier.maxLevel) { + return tier; + } + } + return endgame; + } +} + +/// 플레이어 스탯 스케일링 (player stat scaling) +class PlayerScaling { + PlayerScaling._(); + + /// 레벨당 HP 증가량 + static const int hpPerLevel = 10; + + /// 레벨당 MP 증가량 + static const int mpPerLevel = 5; + + /// 레벨업 시 HP/MP 계산 + static ({int hpMax, int mpMax}) calculateResources({ + required int level, + required int baseHp, + required int baseMp, + required int conBonus, + required int intBonus, + }) { + final hpMax = baseHp + (level - 1) * hpPerLevel + conBonus * 5; + final mpMax = baseMp + (level - 1) * mpPerLevel + intBonus * 3; + return (hpMax: hpMax, mpMax: mpMax); + } +}