feat(phase6): 밸런싱 상수 및 몬스터 스탯 스케일링 시스템 구현
- 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 매개변수 추가로 타입별 몬스터 지원
This commit is contained in:
419
lib/src/core/util/balance_constants.dart
Normal file
419
lib/src/core/util/balance_constants.dart
Normal file
@@ -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<BossAbilityType> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user