492 lines
13 KiB
Dart
492 lines
13 KiB
Dart
/// 밸런스 상수 정의 (balance constants)
|
|
///
|
|
/// Phase 6: 레벨 1-100 구간의 난이도 곡선 및 스케일링 상수
|
|
library;
|
|
|
|
/// 경험치 관련 상수 (experience constants)
|
|
class ExpConstants {
|
|
ExpConstants._();
|
|
|
|
/// 레벨업에 필요한 경험치 계산 (몬스터 기반)
|
|
///
|
|
/// 공식: (10 + level * 5) * (25 + level / 3)
|
|
/// - 몬스터 경험치와 동기화 (MonsterBaseStats.exp = 10 + level * 5)
|
|
/// - 레벨당 약 25~58마리 처치 필요
|
|
///
|
|
/// 예상:
|
|
/// 레벨 1: 15 * 25 = 375 exp (~25마리)
|
|
/// 레벨 10: 60 * 28 = 1,680 exp (~28마리)
|
|
/// 레벨 30: 160 * 35 = 5,600 exp (~35마리)
|
|
/// 레벨 50: 260 * 42 = 10,920 exp (~42마리)
|
|
/// 레벨 80: 410 * 52 = 21,320 exp (~52마리)
|
|
/// 레벨 100: 510 * 58 = 29,580 exp (~58마리)
|
|
static int requiredExp(int level) {
|
|
if (level <= 0) return 375;
|
|
|
|
// 해당 레벨 몬스터 경험치 = 10 + level * 5
|
|
final monsterExp = 10 + level * 5;
|
|
// 필요 킬 수 = 25 + level / 3 (레벨이 올라갈수록 약간 더 많이)
|
|
final killsRequired = 25 + level ~/ 3;
|
|
return monsterExp * killsRequired;
|
|
}
|
|
|
|
/// 총 누적 경험치 계산 (특정 레벨까지)
|
|
static int totalExpToLevel(int level) {
|
|
int total = 0;
|
|
for (int i = 1; i < level; i++) {
|
|
total += requiredExp(i);
|
|
}
|
|
return total;
|
|
}
|
|
}
|
|
|
|
/// 몬스터 타입 (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배, EXP 3배 (상향), GOLD 2.5배
|
|
static const elite = MonsterTypeMultiplier(
|
|
hp: 2.0,
|
|
atk: 1.3,
|
|
def: 1.2,
|
|
exp: 3.0, // 2.0 → 3.0 상향
|
|
gold: 2.5,
|
|
);
|
|
|
|
/// 미니보스: HP 5배, ATK/DEF 1.5배, EXP 8배 (상향), GOLD 6배
|
|
static const miniboss = MonsterTypeMultiplier(
|
|
hp: 5.0,
|
|
atk: 1.5,
|
|
def: 1.5,
|
|
exp: 8.0, // 5.0 → 8.0 상향
|
|
gold: 6.0,
|
|
);
|
|
|
|
/// 보스: HP 8배 (하향), ATK/DEF 1.8배 (하향), EXP 25배 (상향), GOLD 15배
|
|
static const boss = MonsterTypeMultiplier(
|
|
hp: 8.0, // 10.0 → 8.0 하향 (플레이어 접근성 개선)
|
|
atk: 1.8, // 2.0 → 1.8 하향
|
|
def: 1.8, // 2.0 → 1.8 하향
|
|
exp: 25.0, // 15.0 → 25.0 상향
|
|
gold: 15.0,
|
|
);
|
|
|
|
/// 최종 보스: HP 12배 (하향), ATK/DEF 2.2배 (하향), EXP 80배 (상향), GOLD 50배
|
|
static const finalBoss = MonsterTypeMultiplier(
|
|
hp: 12.0, // 20.0 → 12.0 대폭 하향 (클리어 가능성 확보)
|
|
atk: 2.2, // 2.5 → 2.2 하향
|
|
def: 2.2, // 2.5 → 2.2 하향
|
|
exp: 80.0, // 50.0 → 80.0 상향
|
|
gold: 50.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: 레벨별 차등 적용
|
|
/// - 레벨 1~5: 2 + level * 3 (초반 난이도 완화)
|
|
/// - 레벨 6+: 3 + level * 4 (기존 공식)
|
|
/// - DEF 감산율 0.5와 연동하여 밸런스 조정
|
|
/// - 플레이어가 5~10회 공격 생존 가능하도록 설계
|
|
/// DEF: 2 + level * 2
|
|
/// EXP: 10 + level * 5
|
|
/// GOLD: 5 + level * 3
|
|
factory MonsterBaseStats.forLevel(int level) {
|
|
// 레벨 1~5: 초반 난이도 완화 (2 + level * 3)
|
|
// 레벨 1=5, 2=8, 3=11, 4=14, 5=17
|
|
// 레벨 6+: 기존 공식 (3 + level * 4)
|
|
// 레벨 6=27, 10=43, 20=83, 50=203, 100=403
|
|
final atk = level <= 5 ? 2 + level * 3 : 3 + level * 4;
|
|
|
|
return MonsterBaseStats(
|
|
hp: 50 + level * 20 + (level * level ~/ 5),
|
|
atk: atk,
|
|
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)
|
|
///
|
|
/// Phase 6 밸런스 조정: enrageMultiplier 1.6 → 1.5
|
|
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.5, // 1.6 → 1.5 (분노 시 50% 스탯 증가)
|
|
hasShield: true,
|
|
shieldAmount: (base.hp * 0.2).round(),
|
|
abilities: [BossAbilityType.stunAttack],
|
|
);
|
|
}
|
|
|
|
/// Glitch God (최종 보스, 레벨 100)
|
|
///
|
|
/// Phase 6 밸런스 조정:
|
|
/// - enrageThreshold: 0.1 → 0.15 (분노 발동 시점 완화)
|
|
/// - enrageMultiplier: 2.0 → 1.7 (분노 시 스탯 증가 완화)
|
|
/// - shieldAmount: 50% → 35% (보호막 감소)
|
|
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.15, // 0.1 → 0.15 (15% HP에서 분노)
|
|
enrageMultiplier: 1.7, // 2.0 → 1.7 (분노 시 70% 스탯 증가)
|
|
hasShield: true,
|
|
shieldAmount: (base.hp * 0.35).round(), // 0.5 → 0.35 (보호막 30% 감소)
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// Act별 최소 몬스터 레벨 (act minimum monster level)
|
|
///
|
|
/// 플레이어 레벨과 무관하게 각 Act에서 등장하는 몬스터의 최소 레벨.
|
|
/// 저레벨 플레이어가 고Act에 도달해도 적절한 난이도 유지.
|
|
class ActMonsterLevel {
|
|
ActMonsterLevel._();
|
|
|
|
/// plotStageCount 기준 최소 몬스터 레벨
|
|
/// - 1: Prologue → 1
|
|
/// - 2: Act I → 3
|
|
/// - 3: Act II → 22
|
|
/// - 4: Act III → 45
|
|
/// - 5: Act IV → 72
|
|
/// - 6: Act V → 88
|
|
static const List<int> _minimumLevels = [
|
|
1, // index 0: unused (plotStageCount starts at 1)
|
|
1, // Prologue (plotStageCount = 1)
|
|
3, // Act I (plotStageCount = 2)
|
|
22, // Act II (plotStageCount = 3)
|
|
45, // Act III (plotStageCount = 4)
|
|
72, // Act IV (plotStageCount = 5)
|
|
88, // Act V (plotStageCount = 6)
|
|
];
|
|
|
|
/// plotStageCount에 해당하는 최소 몬스터 레벨 반환
|
|
static int forPlotStage(int plotStageCount) {
|
|
if (plotStageCount < 1) return 1;
|
|
if (plotStageCount >= _minimumLevels.length) {
|
|
return _minimumLevels.last;
|
|
}
|
|
return _minimumLevels[plotStageCount];
|
|
}
|
|
}
|
|
|
|
/// 플레이어 스탯 스케일링 (player stat scaling)
|
|
class PlayerScaling {
|
|
PlayerScaling._();
|
|
|
|
/// 레벨당 HP 증가량 (12 → 18 상향, 생존율 개선)
|
|
static const int hpPerLevel = 18;
|
|
|
|
/// 레벨당 MP 증가량 (5 → 6 상향)
|
|
static const int mpPerLevel = 6;
|
|
|
|
/// CON당 HP 보너스 (6 → 10 상향, 체력 투자 효율 개선)
|
|
static const int hpPerCon = 10;
|
|
|
|
/// INT당 MP 보너스 (3 → 4 상향)
|
|
static const int mpPerInt = 4;
|
|
|
|
/// 레벨업 시 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 * hpPerCon;
|
|
final mpMax = baseMp + (level - 1) * mpPerLevel + intBonus * mpPerInt;
|
|
return (hpMax: hpMax, mpMax: mpMax);
|
|
}
|
|
|
|
/// 레벨 구간별 ATK 보너스 (후반 DPS 보조)
|
|
/// - 레벨 60+: +1 ATK per level
|
|
/// - 레벨 80+: +2 ATK per level
|
|
static int bonusAtk(int level) {
|
|
if (level >= 80) return (level - 80) * 2 + 20;
|
|
if (level >= 60) return level - 60;
|
|
return 0;
|
|
}
|
|
}
|