feat(combat): 디버프 시스템 추가

- CombatEventType.playerDebuff 추가
- CombatState에 activeDebuffs 목록 추가
- SkillService.useDebuffSkill() 구현
- 스킬 자동 선택에 디버프 우선순위 추가
- 밸런스 상수 업데이트
This commit is contained in:
JiWoong Sul
2025-12-30 15:58:03 +09:00
parent bdd3b45329
commit 80b6cd63e3
5 changed files with 294 additions and 51 deletions

View File

@@ -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,

View File

@@ -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 회복
// ============================================================================