feat(combat): 디버프 시스템 추가
- CombatEventType.playerDebuff 추가 - CombatState에 activeDebuffs 목록 추가 - SkillService.useDebuffSkill() 구현 - 스킬 자동 선택에 디버프 우선순위 추가 - 밸런스 상수 업데이트
This commit is contained in:
@@ -984,12 +984,20 @@ class ProgressService {
|
||||
var updatedSkillSystem = skillSystem;
|
||||
var activeDoTs = [...combat.activeDoTs];
|
||||
var usedPotionTypes = {...combat.usedPotionTypes};
|
||||
var activeDebuffs = [...combat.activeDebuffs];
|
||||
PotionInventory? updatedPotionInventory;
|
||||
|
||||
// 새 전투 이벤트 수집
|
||||
final newEvents = <CombatEvent>[];
|
||||
final timestamp = updatedSkillSystem.elapsedMs;
|
||||
|
||||
// =========================================================================
|
||||
// 만료된 디버프 정리
|
||||
// =========================================================================
|
||||
activeDebuffs = activeDebuffs
|
||||
.where((debuff) => !debuff.isExpired(timestamp))
|
||||
.toList();
|
||||
|
||||
// =========================================================================
|
||||
// DOT 틱 처리
|
||||
// =========================================================================
|
||||
@@ -1090,6 +1098,7 @@ class ProgressService {
|
||||
skillSystem: updatedSkillSystem,
|
||||
availableSkillIds: availableSkillIds,
|
||||
activeDoTs: activeDoTs,
|
||||
activeDebuffs: activeDebuffs,
|
||||
);
|
||||
|
||||
if (selectedSkill != null && selectedSkill.isAttack) {
|
||||
@@ -1183,6 +1192,33 @@ class ProgressService {
|
||||
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 {
|
||||
// 일반 공격
|
||||
final attackResult = calculator.playerAttackMonster(
|
||||
@@ -1221,8 +1257,25 @@ class ProgressService {
|
||||
// 몬스터가 살아있으면 반격
|
||||
if (monsterStats.isAlive &&
|
||||
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(
|
||||
attacker: monsterStats,
|
||||
attacker: debuffedMonster,
|
||||
defender: playerStats,
|
||||
);
|
||||
playerStats = attackResult.updatedDefender;
|
||||
@@ -1288,6 +1341,7 @@ class ProgressService {
|
||||
recentEvents: recentEvents,
|
||||
activeDoTs: activeDoTs,
|
||||
usedPotionTypes: usedPotionTypes,
|
||||
activeDebuffs: activeDebuffs,
|
||||
),
|
||||
skillSystem: updatedSkillSystem,
|
||||
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 효과를 생성하여 반환. 호출자가 전투 상태의 activeDoTs에 추가해야 함.
|
||||
@@ -248,16 +298,19 @@ class SkillService {
|
||||
///
|
||||
/// 우선순위:
|
||||
/// 1. HP < 30% → 회복 스킬
|
||||
/// 2. 몬스터 HP > 50% & DOT 없음 → DOT 스킬 (장기전 유리)
|
||||
/// 3. 보스전 (레벨 차이 10 이상) → 가장 강력한 공격 스킬
|
||||
/// 4. 일반 전투 → MP 효율이 좋은 스킬
|
||||
/// 5. MP < 20% → null (일반 공격)
|
||||
/// 2. HP > 70% & MP > 50% → 버프 스킬 (안전할 때)
|
||||
/// 3. 몬스터 HP > 70% & 활성 디버프 없음 → 디버프 스킬
|
||||
/// 4. 몬스터 HP > 50% & DOT 없음 → DOT 스킬 (장기전 유리)
|
||||
/// 5. 보스전 (레벨 차이 10 이상) → 가장 강력한 공격 스킬
|
||||
/// 6. 일반 전투 → MP 효율이 좋은 스킬
|
||||
/// 7. MP < 20% → null (일반 공격)
|
||||
Skill? selectAutoSkill({
|
||||
required CombatStats player,
|
||||
required MonsterCombatStats monster,
|
||||
required SkillSystemState skillSystem,
|
||||
required List<String> availableSkillIds,
|
||||
List<DotEffect> activeDoTs = const [],
|
||||
List<ActiveBuff> activeDebuffs = const [],
|
||||
}) {
|
||||
final currentMp = player.mpCurrent;
|
||||
final mpRatio = player.mpRatio;
|
||||
@@ -289,6 +342,18 @@ class SkillService {
|
||||
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 스킬 사용
|
||||
if (monster.hpRatio > 0.5 && activeDoTs.isEmpty) {
|
||||
final dotSkill = _findBestDotSkill(availableSkills, currentMp);
|
||||
@@ -369,6 +434,52 @@ class SkillService {
|
||||
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 회복
|
||||
// ============================================================================
|
||||
|
||||
@@ -27,6 +27,9 @@ enum CombatEventType {
|
||||
/// 플레이어 버프
|
||||
playerBuff,
|
||||
|
||||
/// 플레이어 디버프 (적에게 적용)
|
||||
playerDebuff,
|
||||
|
||||
/// DOT 틱 데미지
|
||||
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 틱 이벤트 생성
|
||||
factory CombatEvent.dotTick({
|
||||
required int timestamp,
|
||||
|
||||
@@ -21,6 +21,7 @@ class CombatState {
|
||||
this.recentEvents = const [],
|
||||
this.activeDoTs = const [],
|
||||
this.usedPotionTypes = const {},
|
||||
this.activeDebuffs = const [],
|
||||
});
|
||||
|
||||
/// 플레이어 전투 스탯
|
||||
@@ -56,6 +57,9 @@ class CombatState {
|
||||
/// 이번 전투에서 사용한 물약 종류 (종류별 1회 제한)
|
||||
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({
|
||||
CombatStats? playerStats,
|
||||
MonsterCombatStats? monsterStats,
|
||||
@@ -100,6 +122,7 @@ class CombatState {
|
||||
List<CombatEvent>? recentEvents,
|
||||
List<DotEffect>? activeDoTs,
|
||||
Set<PotionType>? usedPotionTypes,
|
||||
List<ActiveBuff>? activeDebuffs,
|
||||
}) {
|
||||
return CombatState(
|
||||
playerStats: playerStats ?? this.playerStats,
|
||||
@@ -115,6 +138,7 @@ class CombatState {
|
||||
recentEvents: recentEvents ?? this.recentEvents,
|
||||
activeDoTs: activeDoTs ?? this.activeDoTs,
|
||||
usedPotionTypes: usedPotionTypes ?? this.usedPotionTypes,
|
||||
activeDebuffs: activeDebuffs ?? this.activeDebuffs,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,27 +10,42 @@ class 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();
|
||||
/// 레벨 구간별 경험치 증가율 (tiered growth rate)
|
||||
/// - 1-30: 1.10 (초반 빠른 진행)
|
||||
/// - 31-60: 1.12 (중반 적정 속도)
|
||||
/// - 61-100: 1.14 (후반 도전)
|
||||
static double _getGrowthRate(int level) {
|
||||
if (level <= 30) return 1.10;
|
||||
if (level <= 60) return 1.12;
|
||||
return 1.14;
|
||||
}
|
||||
|
||||
/// 효율적인 거듭제곱 계산
|
||||
static double _pow(double base, int exponent) {
|
||||
double result = 1.0;
|
||||
for (int i = 0; i < exponent; i++) {
|
||||
result *= base;
|
||||
/// 레벨업에 필요한 경험치 계산 (구간별 차등 적용)
|
||||
///
|
||||
/// 조정 후 예상:
|
||||
/// 레벨 10: ~259 exp
|
||||
/// 레벨 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,
|
||||
);
|
||||
|
||||
/// 정예: 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(
|
||||
hp: 2.0,
|
||||
atk: 1.3,
|
||||
def: 1.2,
|
||||
exp: 2.0,
|
||||
gold: 2.0,
|
||||
exp: 3.0, // 2.0 → 3.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(
|
||||
hp: 5.0,
|
||||
atk: 1.5,
|
||||
def: 1.5,
|
||||
exp: 5.0,
|
||||
gold: 5.0,
|
||||
exp: 8.0, // 5.0 → 8.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(
|
||||
hp: 10.0,
|
||||
atk: 2.0,
|
||||
def: 2.0,
|
||||
exp: 15.0,
|
||||
gold: 10.0,
|
||||
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 20배, ATK/DEF 2.5배, EXP 50배, GOLD 30배
|
||||
/// 최종 보스: HP 12배 (하향), ATK/DEF 2.2배 (하향), EXP 80배 (상향), GOLD 50배
|
||||
static const finalBoss = MonsterTypeMultiplier(
|
||||
hp: 20.0,
|
||||
atk: 2.5,
|
||||
def: 2.5,
|
||||
exp: 50.0,
|
||||
gold: 30.0,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -283,6 +298,8 @@ class BossStats extends MonsterBaseStats {
|
||||
}
|
||||
|
||||
/// 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(
|
||||
@@ -293,7 +310,7 @@ class BossStats extends MonsterBaseStats {
|
||||
gold: base.gold,
|
||||
phases: 3,
|
||||
enrageThreshold: 0.2,
|
||||
enrageMultiplier: 1.6,
|
||||
enrageMultiplier: 1.5, // 1.6 → 1.5 (분노 시 50% 스탯 증가)
|
||||
hasShield: true,
|
||||
shieldAmount: (base.hp * 0.2).round(),
|
||||
abilities: [BossAbilityType.stunAttack],
|
||||
@@ -301,6 +318,11 @@ class BossStats extends MonsterBaseStats {
|
||||
}
|
||||
|
||||
/// 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(
|
||||
@@ -310,10 +332,10 @@ class BossStats extends MonsterBaseStats {
|
||||
exp: base.exp,
|
||||
gold: base.gold,
|
||||
phases: 5,
|
||||
enrageThreshold: 0.1,
|
||||
enrageMultiplier: 2.0,
|
||||
enrageThreshold: 0.15, // 0.1 → 0.15 (15% HP에서 분노)
|
||||
enrageMultiplier: 1.7, // 2.0 → 1.7 (분노 시 70% 스탯 증가)
|
||||
hasShield: true,
|
||||
shieldAmount: (base.hp * 0.5).round(),
|
||||
shieldAmount: (base.hp * 0.35).round(), // 0.5 → 0.35 (보호막 30% 감소)
|
||||
abilities: [
|
||||
BossAbilityType.phaseShift,
|
||||
BossAbilityType.multiAttack,
|
||||
@@ -398,11 +420,17 @@ class LevelTierSettings {
|
||||
class PlayerScaling {
|
||||
PlayerScaling._();
|
||||
|
||||
/// 레벨당 HP 증가량
|
||||
static const int hpPerLevel = 10;
|
||||
/// 레벨당 HP 증가량 (10 → 12 상향)
|
||||
static const int hpPerLevel = 12;
|
||||
|
||||
/// 레벨당 MP 증가량
|
||||
static const int mpPerLevel = 5;
|
||||
/// 레벨당 MP 증가량 (5 → 6 상향)
|
||||
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 계산
|
||||
static ({int hpMax, int mpMax}) calculateResources({
|
||||
@@ -412,8 +440,17 @@ class PlayerScaling {
|
||||
required int conBonus,
|
||||
required int intBonus,
|
||||
}) {
|
||||
final hpMax = baseHp + (level - 1) * hpPerLevel + conBonus * 5;
|
||||
final mpMax = baseMp + (level - 1) * mpPerLevel + intBonus * 3;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user