feat(combat): 디버프 시스템 추가
- CombatEventType.playerDebuff 추가 - CombatState에 activeDebuffs 목록 추가 - SkillService.useDebuffSkill() 구현 - 스킬 자동 선택에 디버프 우선순위 추가 - 밸런스 상수 업데이트
This commit is contained in:
@@ -984,12 +984,20 @@ class ProgressService {
|
|||||||
var updatedSkillSystem = skillSystem;
|
var updatedSkillSystem = skillSystem;
|
||||||
var activeDoTs = [...combat.activeDoTs];
|
var activeDoTs = [...combat.activeDoTs];
|
||||||
var usedPotionTypes = {...combat.usedPotionTypes};
|
var usedPotionTypes = {...combat.usedPotionTypes};
|
||||||
|
var activeDebuffs = [...combat.activeDebuffs];
|
||||||
PotionInventory? updatedPotionInventory;
|
PotionInventory? updatedPotionInventory;
|
||||||
|
|
||||||
// 새 전투 이벤트 수집
|
// 새 전투 이벤트 수집
|
||||||
final newEvents = <CombatEvent>[];
|
final newEvents = <CombatEvent>[];
|
||||||
final timestamp = updatedSkillSystem.elapsedMs;
|
final timestamp = updatedSkillSystem.elapsedMs;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 만료된 디버프 정리
|
||||||
|
// =========================================================================
|
||||||
|
activeDebuffs = activeDebuffs
|
||||||
|
.where((debuff) => !debuff.isExpired(timestamp))
|
||||||
|
.toList();
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// DOT 틱 처리
|
// DOT 틱 처리
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -1090,6 +1098,7 @@ class ProgressService {
|
|||||||
skillSystem: updatedSkillSystem,
|
skillSystem: updatedSkillSystem,
|
||||||
availableSkillIds: availableSkillIds,
|
availableSkillIds: availableSkillIds,
|
||||||
activeDoTs: activeDoTs,
|
activeDoTs: activeDoTs,
|
||||||
|
activeDebuffs: activeDebuffs,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedSkill != null && selectedSkill.isAttack) {
|
if (selectedSkill != null && selectedSkill.isAttack) {
|
||||||
@@ -1183,6 +1192,33 @@ class ProgressService {
|
|||||||
skillName: selectedSkill.name,
|
skillName: selectedSkill.name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (selectedSkill != null && selectedSkill.isDebuff) {
|
||||||
|
// 디버프 스킬 사용
|
||||||
|
final skillResult = skillService.useDebuffSkill(
|
||||||
|
skill: selectedSkill,
|
||||||
|
player: playerStats,
|
||||||
|
skillSystem: updatedSkillSystem,
|
||||||
|
currentDebuffs: activeDebuffs,
|
||||||
|
);
|
||||||
|
playerStats = skillResult.updatedPlayer;
|
||||||
|
updatedSkillSystem = skillResult.updatedSkillSystem;
|
||||||
|
|
||||||
|
// 디버프 효과 추가 (기존 같은 디버프 제거 후)
|
||||||
|
if (skillResult.debuffEffect != null) {
|
||||||
|
activeDebuffs = activeDebuffs
|
||||||
|
.where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
|
||||||
|
.toList()
|
||||||
|
..add(skillResult.debuffEffect!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 디버프 이벤트 생성
|
||||||
|
newEvents.add(
|
||||||
|
CombatEvent.playerDebuff(
|
||||||
|
timestamp: timestamp,
|
||||||
|
skillName: selectedSkill.name,
|
||||||
|
targetName: monsterStats.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// 일반 공격
|
// 일반 공격
|
||||||
final attackResult = calculator.playerAttackMonster(
|
final attackResult = calculator.playerAttackMonster(
|
||||||
@@ -1221,8 +1257,25 @@ class ProgressService {
|
|||||||
// 몬스터가 살아있으면 반격
|
// 몬스터가 살아있으면 반격
|
||||||
if (monsterStats.isAlive &&
|
if (monsterStats.isAlive &&
|
||||||
monsterAccumulator >= monsterStats.attackDelayMs) {
|
monsterAccumulator >= monsterStats.attackDelayMs) {
|
||||||
|
// 디버프 효과 적용된 몬스터 스탯 계산
|
||||||
|
var debuffedMonster = monsterStats;
|
||||||
|
if (activeDebuffs.isNotEmpty) {
|
||||||
|
double atkMod = 0;
|
||||||
|
for (final debuff in activeDebuffs) {
|
||||||
|
if (!debuff.isExpired(timestamp)) {
|
||||||
|
atkMod += debuff.effect.atkModifier; // 음수 값
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ATK 감소 적용 (최소 10% ATK 유지)
|
||||||
|
final newAtk = (monsterStats.atk * (1 + atkMod)).round().clamp(
|
||||||
|
monsterStats.atk ~/ 10,
|
||||||
|
monsterStats.atk,
|
||||||
|
);
|
||||||
|
debuffedMonster = monsterStats.copyWith(atk: newAtk);
|
||||||
|
}
|
||||||
|
|
||||||
final attackResult = calculator.monsterAttackPlayer(
|
final attackResult = calculator.monsterAttackPlayer(
|
||||||
attacker: monsterStats,
|
attacker: debuffedMonster,
|
||||||
defender: playerStats,
|
defender: playerStats,
|
||||||
);
|
);
|
||||||
playerStats = attackResult.updatedDefender;
|
playerStats = attackResult.updatedDefender;
|
||||||
@@ -1288,6 +1341,7 @@ class ProgressService {
|
|||||||
recentEvents: recentEvents,
|
recentEvents: recentEvents,
|
||||||
activeDoTs: activeDoTs,
|
activeDoTs: activeDoTs,
|
||||||
usedPotionTypes: usedPotionTypes,
|
usedPotionTypes: usedPotionTypes,
|
||||||
|
activeDebuffs: activeDebuffs,
|
||||||
),
|
),
|
||||||
skillSystem: updatedSkillSystem,
|
skillSystem: updatedSkillSystem,
|
||||||
potionInventory: updatedPotionInventory,
|
potionInventory: updatedPotionInventory,
|
||||||
|
|||||||
@@ -185,6 +185,56 @@ class SkillService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 디버프 스킬 사용
|
||||||
|
///
|
||||||
|
/// 디버프 효과를 생성하여 반환. 호출자가 CombatState.activeDebuffs에 추가해야 함.
|
||||||
|
/// 디버프는 몬스터의 ATK/DEF를 감소시킴.
|
||||||
|
({
|
||||||
|
SkillUseResult result,
|
||||||
|
CombatStats updatedPlayer,
|
||||||
|
SkillSystemState updatedSkillSystem,
|
||||||
|
ActiveBuff? debuffEffect,
|
||||||
|
})
|
||||||
|
useDebuffSkill({
|
||||||
|
required Skill skill,
|
||||||
|
required CombatStats player,
|
||||||
|
required SkillSystemState skillSystem,
|
||||||
|
required List<ActiveBuff> currentDebuffs,
|
||||||
|
}) {
|
||||||
|
if (skill.buff == null) {
|
||||||
|
return (
|
||||||
|
result: SkillUseResult.failed(skill, SkillFailReason.invalidState),
|
||||||
|
updatedPlayer: player,
|
||||||
|
updatedSkillSystem: skillSystem,
|
||||||
|
debuffEffect: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 디버프 효과 생성
|
||||||
|
final newDebuff = ActiveBuff(
|
||||||
|
effect: skill.buff!,
|
||||||
|
startedMs: skillSystem.elapsedMs,
|
||||||
|
sourceSkillId: skill.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// MP 소모
|
||||||
|
var updatedPlayer = player.withMp(player.mpCurrent - skill.mpCost);
|
||||||
|
|
||||||
|
// 스킬 상태 업데이트 (쿨타임 시작)
|
||||||
|
final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
result: SkillUseResult(
|
||||||
|
skill: skill,
|
||||||
|
success: true,
|
||||||
|
appliedBuff: newDebuff,
|
||||||
|
),
|
||||||
|
updatedPlayer: updatedPlayer,
|
||||||
|
updatedSkillSystem: updatedSkillSystem,
|
||||||
|
debuffEffect: newDebuff,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// DOT 스킬 사용
|
/// DOT 스킬 사용
|
||||||
///
|
///
|
||||||
/// DOT 효과를 생성하여 반환. 호출자가 전투 상태의 activeDoTs에 추가해야 함.
|
/// DOT 효과를 생성하여 반환. 호출자가 전투 상태의 activeDoTs에 추가해야 함.
|
||||||
@@ -248,16 +298,19 @@ class SkillService {
|
|||||||
///
|
///
|
||||||
/// 우선순위:
|
/// 우선순위:
|
||||||
/// 1. HP < 30% → 회복 스킬
|
/// 1. HP < 30% → 회복 스킬
|
||||||
/// 2. 몬스터 HP > 50% & DOT 없음 → DOT 스킬 (장기전 유리)
|
/// 2. HP > 70% & MP > 50% → 버프 스킬 (안전할 때)
|
||||||
/// 3. 보스전 (레벨 차이 10 이상) → 가장 강력한 공격 스킬
|
/// 3. 몬스터 HP > 70% & 활성 디버프 없음 → 디버프 스킬
|
||||||
/// 4. 일반 전투 → MP 효율이 좋은 스킬
|
/// 4. 몬스터 HP > 50% & DOT 없음 → DOT 스킬 (장기전 유리)
|
||||||
/// 5. MP < 20% → null (일반 공격)
|
/// 5. 보스전 (레벨 차이 10 이상) → 가장 강력한 공격 스킬
|
||||||
|
/// 6. 일반 전투 → MP 효율이 좋은 스킬
|
||||||
|
/// 7. MP < 20% → null (일반 공격)
|
||||||
Skill? selectAutoSkill({
|
Skill? selectAutoSkill({
|
||||||
required CombatStats player,
|
required CombatStats player,
|
||||||
required MonsterCombatStats monster,
|
required MonsterCombatStats monster,
|
||||||
required SkillSystemState skillSystem,
|
required SkillSystemState skillSystem,
|
||||||
required List<String> availableSkillIds,
|
required List<String> availableSkillIds,
|
||||||
List<DotEffect> activeDoTs = const [],
|
List<DotEffect> activeDoTs = const [],
|
||||||
|
List<ActiveBuff> activeDebuffs = const [],
|
||||||
}) {
|
}) {
|
||||||
final currentMp = player.mpCurrent;
|
final currentMp = player.mpCurrent;
|
||||||
final mpRatio = player.mpRatio;
|
final mpRatio = player.mpRatio;
|
||||||
@@ -289,6 +342,18 @@ class SkillService {
|
|||||||
if (healSkill != null) return healSkill;
|
if (healSkill != null) return healSkill;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HP > 70% & MP > 50% → 버프 스킬 (안전할 때)
|
||||||
|
if (hpRatio > 0.7 && mpRatio > 0.5) {
|
||||||
|
final buffSkill = _findBestBuffSkill(availableSkills, currentMp);
|
||||||
|
if (buffSkill != null) return buffSkill;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 몬스터 HP > 70% & 활성 디버프 없음 → 디버프 스킬
|
||||||
|
if (monster.hpRatio > 0.7 && activeDebuffs.isEmpty) {
|
||||||
|
final debuffSkill = _findBestDebuffSkill(availableSkills, currentMp);
|
||||||
|
if (debuffSkill != null) return debuffSkill;
|
||||||
|
}
|
||||||
|
|
||||||
// 몬스터 HP > 50% & 활성 DOT 없음 → DOT 스킬 사용
|
// 몬스터 HP > 50% & 활성 DOT 없음 → DOT 스킬 사용
|
||||||
if (monster.hpRatio > 0.5 && activeDoTs.isEmpty) {
|
if (monster.hpRatio > 0.5 && activeDoTs.isEmpty) {
|
||||||
final dotSkill = _findBestDotSkill(availableSkills, currentMp);
|
final dotSkill = _findBestDotSkill(availableSkills, currentMp);
|
||||||
@@ -369,6 +434,52 @@ class SkillService {
|
|||||||
return attackSkills.first;
|
return attackSkills.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 가장 좋은 버프 스킬 찾기
|
||||||
|
///
|
||||||
|
/// ATK 증가 버프 우선, 그 다음 복합 버프
|
||||||
|
Skill? _findBestBuffSkill(List<Skill> skills, int currentMp) {
|
||||||
|
final buffSkills = skills
|
||||||
|
.where((s) => s.isBuff && s.mpCost <= currentMp && s.buff != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (buffSkills.isEmpty) return null;
|
||||||
|
|
||||||
|
// ATK 증가량 기준 정렬
|
||||||
|
buffSkills.sort((a, b) {
|
||||||
|
final aValue = (a.buff?.atkModifier ?? 0) +
|
||||||
|
(a.buff?.defModifier ?? 0) * 0.5 +
|
||||||
|
(a.buff?.criRateModifier ?? 0) * 0.3;
|
||||||
|
final bValue = (b.buff?.atkModifier ?? 0) +
|
||||||
|
(b.buff?.defModifier ?? 0) * 0.5 +
|
||||||
|
(b.buff?.criRateModifier ?? 0) * 0.3;
|
||||||
|
return bValue.compareTo(aValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return buffSkills.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 가장 좋은 디버프 스킬 찾기
|
||||||
|
///
|
||||||
|
/// 적 ATK 감소 디버프 우선
|
||||||
|
Skill? _findBestDebuffSkill(List<Skill> skills, int currentMp) {
|
||||||
|
final debuffSkills = skills
|
||||||
|
.where((s) => s.isDebuff && s.mpCost <= currentMp && s.buff != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (debuffSkills.isEmpty) return null;
|
||||||
|
|
||||||
|
// 디버프 효과 크기 기준 정렬 (음수 값이므로 절대값으로 비교)
|
||||||
|
debuffSkills.sort((a, b) {
|
||||||
|
final aValue = (a.buff?.atkModifier ?? 0).abs() +
|
||||||
|
(a.buff?.defModifier ?? 0).abs() * 0.5;
|
||||||
|
final bValue = (b.buff?.atkModifier ?? 0).abs() +
|
||||||
|
(b.buff?.defModifier ?? 0).abs() * 0.5;
|
||||||
|
return bValue.compareTo(aValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return debuffSkills.first;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// MP 회복
|
// MP 회복
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ enum CombatEventType {
|
|||||||
/// 플레이어 버프
|
/// 플레이어 버프
|
||||||
playerBuff,
|
playerBuff,
|
||||||
|
|
||||||
|
/// 플레이어 디버프 (적에게 적용)
|
||||||
|
playerDebuff,
|
||||||
|
|
||||||
/// DOT 틱 데미지
|
/// DOT 틱 데미지
|
||||||
dotTick,
|
dotTick,
|
||||||
|
|
||||||
@@ -209,6 +212,20 @@ class CombatEvent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 디버프 이벤트 생성 (적에게 디버프 적용)
|
||||||
|
factory CombatEvent.playerDebuff({
|
||||||
|
required int timestamp,
|
||||||
|
required String skillName,
|
||||||
|
required String targetName,
|
||||||
|
}) {
|
||||||
|
return CombatEvent(
|
||||||
|
type: CombatEventType.playerDebuff,
|
||||||
|
timestamp: timestamp,
|
||||||
|
skillName: skillName,
|
||||||
|
targetName: targetName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// DOT 틱 이벤트 생성
|
/// DOT 틱 이벤트 생성
|
||||||
factory CombatEvent.dotTick({
|
factory CombatEvent.dotTick({
|
||||||
required int timestamp,
|
required int timestamp,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class CombatState {
|
|||||||
this.recentEvents = const [],
|
this.recentEvents = const [],
|
||||||
this.activeDoTs = const [],
|
this.activeDoTs = const [],
|
||||||
this.usedPotionTypes = const {},
|
this.usedPotionTypes = const {},
|
||||||
|
this.activeDebuffs = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 플레이어 전투 스탯
|
/// 플레이어 전투 스탯
|
||||||
@@ -56,6 +57,9 @@ class CombatState {
|
|||||||
/// 이번 전투에서 사용한 물약 종류 (종류별 1회 제한)
|
/// 이번 전투에서 사용한 물약 종류 (종류별 1회 제한)
|
||||||
final Set<PotionType> usedPotionTypes;
|
final Set<PotionType> usedPotionTypes;
|
||||||
|
|
||||||
|
/// 몬스터에 적용된 활성 디버프 목록
|
||||||
|
final List<ActiveBuff> activeDebuffs;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 유틸리티
|
// 유틸리티
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -88,6 +92,24 @@ class CombatState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 활성 디버프 존재 여부
|
||||||
|
bool get hasActiveDebuffs => activeDebuffs.isNotEmpty;
|
||||||
|
|
||||||
|
/// 몬스터에 적용된 총 디버프 효과 계산
|
||||||
|
///
|
||||||
|
/// 디버프 효과는 몬스터 ATK/DEF에 부정적 배율로 적용됨
|
||||||
|
({double atkMod, double defMod}) get totalDebuffModifiers {
|
||||||
|
double atkMod = 0;
|
||||||
|
double defMod = 0;
|
||||||
|
|
||||||
|
for (final debuff in activeDebuffs) {
|
||||||
|
atkMod += debuff.effect.atkModifier;
|
||||||
|
defMod += debuff.effect.defModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (atkMod: atkMod, defMod: defMod);
|
||||||
|
}
|
||||||
|
|
||||||
CombatState copyWith({
|
CombatState copyWith({
|
||||||
CombatStats? playerStats,
|
CombatStats? playerStats,
|
||||||
MonsterCombatStats? monsterStats,
|
MonsterCombatStats? monsterStats,
|
||||||
@@ -100,6 +122,7 @@ class CombatState {
|
|||||||
List<CombatEvent>? recentEvents,
|
List<CombatEvent>? recentEvents,
|
||||||
List<DotEffect>? activeDoTs,
|
List<DotEffect>? activeDoTs,
|
||||||
Set<PotionType>? usedPotionTypes,
|
Set<PotionType>? usedPotionTypes,
|
||||||
|
List<ActiveBuff>? activeDebuffs,
|
||||||
}) {
|
}) {
|
||||||
return CombatState(
|
return CombatState(
|
||||||
playerStats: playerStats ?? this.playerStats,
|
playerStats: playerStats ?? this.playerStats,
|
||||||
@@ -115,6 +138,7 @@ class CombatState {
|
|||||||
recentEvents: recentEvents ?? this.recentEvents,
|
recentEvents: recentEvents ?? this.recentEvents,
|
||||||
activeDoTs: activeDoTs ?? this.activeDoTs,
|
activeDoTs: activeDoTs ?? this.activeDoTs,
|
||||||
usedPotionTypes: usedPotionTypes ?? this.usedPotionTypes,
|
usedPotionTypes: usedPotionTypes ?? this.usedPotionTypes,
|
||||||
|
activeDebuffs: activeDebuffs ?? this.activeDebuffs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,27 +10,42 @@ class ExpConstants {
|
|||||||
/// 기본 경험치 값
|
/// 기본 경험치 값
|
||||||
static const int baseExp = 100;
|
static const int baseExp = 100;
|
||||||
|
|
||||||
/// 레벨당 경험치 증가율 (1.15 = 15% 증가)
|
/// 레벨 구간별 경험치 증가율 (tiered growth rate)
|
||||||
static const double expGrowthRate = 1.15;
|
/// - 1-30: 1.10 (초반 빠른 진행)
|
||||||
|
/// - 31-60: 1.12 (중반 적정 속도)
|
||||||
/// 레벨업에 필요한 경험치 계산
|
/// - 61-100: 1.14 (후반 도전)
|
||||||
///
|
static double _getGrowthRate(int level) {
|
||||||
/// 공식: baseExp * (expGrowthRate ^ level)
|
if (level <= 30) return 1.10;
|
||||||
/// 레벨 10: ~405 exp
|
if (level <= 60) return 1.12;
|
||||||
/// 레벨 50: ~108,366 exp
|
return 1.14;
|
||||||
/// 레벨 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++) {
|
/// 레벨 10: ~259 exp
|
||||||
result *= base;
|
/// 레벨 30: ~1,744 exp
|
||||||
|
/// 레벨 50: ~9,705 exp
|
||||||
|
/// 레벨 80: ~133,860 exp
|
||||||
|
/// 레벨 100: ~636,840 exp
|
||||||
|
static int requiredExp(int level) {
|
||||||
|
if (level <= 0) return baseExp;
|
||||||
|
|
||||||
|
// 구간별 복합 성장 계산
|
||||||
|
double result = baseExp.toDouble();
|
||||||
|
for (int i = 1; i <= level; i++) {
|
||||||
|
result *= _getGrowthRate(i);
|
||||||
}
|
}
|
||||||
return result;
|
return result.round();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 총 누적 경험치 계산 (특정 레벨까지)
|
||||||
|
static int totalExpToLevel(int level) {
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 1; i < level; i++) {
|
||||||
|
total += requiredExp(i);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,40 +103,40 @@ class MonsterTypeMultiplier {
|
|||||||
gold: 1.0,
|
gold: 1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 정예: HP 2배, ATK 1.3배, DEF 1.2배, 보상 2배
|
/// 정예: HP 2배, ATK 1.3배, DEF 1.2배, EXP 3배 (상향), GOLD 2.5배
|
||||||
static const elite = MonsterTypeMultiplier(
|
static const elite = MonsterTypeMultiplier(
|
||||||
hp: 2.0,
|
hp: 2.0,
|
||||||
atk: 1.3,
|
atk: 1.3,
|
||||||
def: 1.2,
|
def: 1.2,
|
||||||
exp: 2.0,
|
exp: 3.0, // 2.0 → 3.0 상향
|
||||||
gold: 2.0,
|
gold: 2.5,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 미니보스: HP 5배, ATK/DEF 1.5배, 보상 5배
|
/// 미니보스: HP 5배, ATK/DEF 1.5배, EXP 8배 (상향), GOLD 6배
|
||||||
static const miniboss = MonsterTypeMultiplier(
|
static const miniboss = MonsterTypeMultiplier(
|
||||||
hp: 5.0,
|
hp: 5.0,
|
||||||
atk: 1.5,
|
atk: 1.5,
|
||||||
def: 1.5,
|
def: 1.5,
|
||||||
exp: 5.0,
|
exp: 8.0, // 5.0 → 8.0 상향
|
||||||
gold: 5.0,
|
gold: 6.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 보스: HP 10배, ATK/DEF 2배, EXP 15배, GOLD 10배
|
/// 보스: HP 8배 (하향), ATK/DEF 1.8배 (하향), EXP 25배 (상향), GOLD 15배
|
||||||
static const boss = MonsterTypeMultiplier(
|
static const boss = MonsterTypeMultiplier(
|
||||||
hp: 10.0,
|
hp: 8.0, // 10.0 → 8.0 하향 (플레이어 접근성 개선)
|
||||||
atk: 2.0,
|
atk: 1.8, // 2.0 → 1.8 하향
|
||||||
def: 2.0,
|
def: 1.8, // 2.0 → 1.8 하향
|
||||||
exp: 15.0,
|
exp: 25.0, // 15.0 → 25.0 상향
|
||||||
gold: 10.0,
|
gold: 15.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 최종 보스: HP 20배, ATK/DEF 2.5배, EXP 50배, GOLD 30배
|
/// 최종 보스: HP 12배 (하향), ATK/DEF 2.2배 (하향), EXP 80배 (상향), GOLD 50배
|
||||||
static const finalBoss = MonsterTypeMultiplier(
|
static const finalBoss = MonsterTypeMultiplier(
|
||||||
hp: 20.0,
|
hp: 12.0, // 20.0 → 12.0 대폭 하향 (클리어 가능성 확보)
|
||||||
atk: 2.5,
|
atk: 2.2, // 2.5 → 2.2 하향
|
||||||
def: 2.5,
|
def: 2.2, // 2.5 → 2.2 하향
|
||||||
exp: 50.0,
|
exp: 80.0, // 50.0 → 80.0 상향
|
||||||
gold: 30.0,
|
gold: 50.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +298,8 @@ class BossStats extends MonsterBaseStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Kernel Panic Archon (Act IV 보스, 레벨 80)
|
/// Kernel Panic Archon (Act IV 보스, 레벨 80)
|
||||||
|
///
|
||||||
|
/// Phase 6 밸런스 조정: enrageMultiplier 1.6 → 1.5
|
||||||
static BossStats kernelPanicArchon(int baseLevel) {
|
static BossStats kernelPanicArchon(int baseLevel) {
|
||||||
final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss);
|
final base = MonsterBaseStats.generate(baseLevel, MonsterType.boss);
|
||||||
return BossStats(
|
return BossStats(
|
||||||
@@ -293,7 +310,7 @@ class BossStats extends MonsterBaseStats {
|
|||||||
gold: base.gold,
|
gold: base.gold,
|
||||||
phases: 3,
|
phases: 3,
|
||||||
enrageThreshold: 0.2,
|
enrageThreshold: 0.2,
|
||||||
enrageMultiplier: 1.6,
|
enrageMultiplier: 1.5, // 1.6 → 1.5 (분노 시 50% 스탯 증가)
|
||||||
hasShield: true,
|
hasShield: true,
|
||||||
shieldAmount: (base.hp * 0.2).round(),
|
shieldAmount: (base.hp * 0.2).round(),
|
||||||
abilities: [BossAbilityType.stunAttack],
|
abilities: [BossAbilityType.stunAttack],
|
||||||
@@ -301,6 +318,11 @@ class BossStats extends MonsterBaseStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Glitch God (최종 보스, 레벨 100)
|
/// Glitch God (최종 보스, 레벨 100)
|
||||||
|
///
|
||||||
|
/// Phase 6 밸런스 조정:
|
||||||
|
/// - enrageThreshold: 0.1 → 0.15 (분노 발동 시점 완화)
|
||||||
|
/// - enrageMultiplier: 2.0 → 1.7 (분노 시 스탯 증가 완화)
|
||||||
|
/// - shieldAmount: 50% → 35% (보호막 감소)
|
||||||
static BossStats glitchGod(int baseLevel) {
|
static BossStats glitchGod(int baseLevel) {
|
||||||
final base = MonsterBaseStats.generate(baseLevel, MonsterType.finalBoss);
|
final base = MonsterBaseStats.generate(baseLevel, MonsterType.finalBoss);
|
||||||
return BossStats(
|
return BossStats(
|
||||||
@@ -310,10 +332,10 @@ class BossStats extends MonsterBaseStats {
|
|||||||
exp: base.exp,
|
exp: base.exp,
|
||||||
gold: base.gold,
|
gold: base.gold,
|
||||||
phases: 5,
|
phases: 5,
|
||||||
enrageThreshold: 0.1,
|
enrageThreshold: 0.15, // 0.1 → 0.15 (15% HP에서 분노)
|
||||||
enrageMultiplier: 2.0,
|
enrageMultiplier: 1.7, // 2.0 → 1.7 (분노 시 70% 스탯 증가)
|
||||||
hasShield: true,
|
hasShield: true,
|
||||||
shieldAmount: (base.hp * 0.5).round(),
|
shieldAmount: (base.hp * 0.35).round(), // 0.5 → 0.35 (보호막 30% 감소)
|
||||||
abilities: [
|
abilities: [
|
||||||
BossAbilityType.phaseShift,
|
BossAbilityType.phaseShift,
|
||||||
BossAbilityType.multiAttack,
|
BossAbilityType.multiAttack,
|
||||||
@@ -398,11 +420,17 @@ class LevelTierSettings {
|
|||||||
class PlayerScaling {
|
class PlayerScaling {
|
||||||
PlayerScaling._();
|
PlayerScaling._();
|
||||||
|
|
||||||
/// 레벨당 HP 증가량
|
/// 레벨당 HP 증가량 (10 → 12 상향)
|
||||||
static const int hpPerLevel = 10;
|
static const int hpPerLevel = 12;
|
||||||
|
|
||||||
/// 레벨당 MP 증가량
|
/// 레벨당 MP 증가량 (5 → 6 상향)
|
||||||
static const int mpPerLevel = 5;
|
static const int mpPerLevel = 6;
|
||||||
|
|
||||||
|
/// CON당 HP 보너스 (5 → 6 상향)
|
||||||
|
static const int hpPerCon = 6;
|
||||||
|
|
||||||
|
/// INT당 MP 보너스 (3 → 4 상향)
|
||||||
|
static const int mpPerInt = 4;
|
||||||
|
|
||||||
/// 레벨업 시 HP/MP 계산
|
/// 레벨업 시 HP/MP 계산
|
||||||
static ({int hpMax, int mpMax}) calculateResources({
|
static ({int hpMax, int mpMax}) calculateResources({
|
||||||
@@ -412,8 +440,17 @@ class PlayerScaling {
|
|||||||
required int conBonus,
|
required int conBonus,
|
||||||
required int intBonus,
|
required int intBonus,
|
||||||
}) {
|
}) {
|
||||||
final hpMax = baseHp + (level - 1) * hpPerLevel + conBonus * 5;
|
final hpMax = baseHp + (level - 1) * hpPerLevel + conBonus * hpPerCon;
|
||||||
final mpMax = baseMp + (level - 1) * mpPerLevel + intBonus * 3;
|
final mpMax = baseMp + (level - 1) * mpPerLevel + intBonus * mpPerInt;
|
||||||
return (hpMax: hpMax, mpMax: mpMax);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user